Commit | Line | Data |
---|---|---|
a7081401 HÖS |
1 | unit module reader; |
2 | use types; | |
3 | ||
4 | class Reader { | |
5 | has @.tokens; | |
6 | has $!position = 0; | |
7 | method peek { @.tokens[$!position] } | |
8 | method next { @.tokens[$!position++] } | |
9 | } | |
10 | ||
11 | sub read_form ($rdr) { | |
12 | given $rdr.peek { | |
13 | when "'" { $rdr.next; MalList([MalSymbol('quote'), read_form($rdr)]) } | |
14 | when '`' { $rdr.next; MalList([MalSymbol('quasiquote'), read_form($rdr)]) } | |
15 | when '~' { $rdr.next; MalList([MalSymbol('unquote'), read_form($rdr)]) } | |
16 | when '~@' { $rdr.next; MalList([MalSymbol('splice-unquote'), read_form($rdr)]) } | |
17 | when '@' { $rdr.next; MalList([MalSymbol('deref'), read_form($rdr)]) } | |
18 | when '^' { | |
19 | $rdr.next; | |
20 | my $meta = read_form($rdr); | |
21 | MalList([MalSymbol('with-meta'), read_form($rdr), $meta]); | |
22 | } | |
23 | when ')'|']'|'}' { die X::MalUnexpected.new(token => $_) } | |
24 | when '(' { MalList(read_list($rdr, ')')) } | |
25 | when '[' { MalVector(read_list($rdr, ']')) } | |
26 | when '{' { MalHashMap(read_list($rdr, '}').map({ $^a.val => $^b }).Hash) } | |
27 | default { read_atom($rdr) } | |
28 | } | |
29 | } | |
30 | ||
31 | sub read_list ($rdr, $end) { | |
32 | my @list; | |
33 | my $token = $rdr.next; | |
34 | ||
68d56be6 HÖS |
35 | loop { |
36 | $token = $rdr.peek; | |
a7081401 | 37 | die X::MalIncomplete.new(end => $end) if !$token.defined; |
68d56be6 | 38 | last if $token eq $end; |
a7081401 HÖS |
39 | @list.push(read_form($rdr)); |
40 | } | |
41 | $rdr.next; | |
42 | ||
43 | return @list; | |
44 | } | |
45 | ||
46 | sub read_atom ($rdr) { | |
47 | my $atom = $rdr.next; | |
48 | given $atom { | |
49 | when /^\"/ { | |
50 | die X::MalIncomplete.new(end => '"') if $atom !~~ /\"$/; | |
51 | s:g/^\"|\"$//; | |
52 | MalString(.trans(/\\\"/ => '"', /\\n/ => "\n", /\\\\/ => '\\')); | |
53 | } | |
54 | when /^\:(.*)/ { MalString("\x29E$0") } | |
55 | when /^'-'? <[0..9]>+$/ { MalNumber($_) } | |
56 | when 'nil' { $NIL } | |
57 | when 'true' { $TRUE } | |
58 | when 'false' { $FALSE } | |
59 | default { MalSymbol($_) } | |
60 | } | |
61 | } | |
62 | ||
63 | my regex mal { | |
64 | [ | |
65 | <[\s,]>* # whitespace/commas | |
66 | $<token>=( | |
67 | || '~@' # ~@ | |
68 | || <[\[\]{}()'`~^@]> # special single-char tokens | |
69 | || '"' [ \\. || <-[\"\\]> ]* '"'? # double-quoted strings | |
70 | || ';'<-[\n]>* # comments | |
71 | || <-[\s\[\]{}('"`,;)]>+ # symbols | |
72 | ) | |
73 | ]+ | |
74 | } | |
75 | ||
76 | sub tokenizer ($str) { | |
77 | return [] if !$str.match(/^<mal>/); | |
78 | return grep { ! /^\;/ }, $<mal><token>.map({~$_}); | |
79 | } | |
80 | ||
81 | sub read_str ($str) is export { | |
82 | my @tokens = tokenizer($str); | |
83 | die X::MalNoTokens.new if !@tokens; | |
84 | return read_form(Reader.new(tokens => @tokens)); | |
85 | } |