Merge pull request #361 from asarhaddon/exercise-native-implementations
[jackhill/mal.git] / perl6 / reader.pm
CommitLineData
a7081401
HÖS
1unit module reader;
2use types;
3
4class Reader {
5 has @.tokens;
6 has $!position = 0;
7 method peek { @.tokens[$!position] }
8 method next { @.tokens[$!position++] }
9}
10
11sub 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
31sub 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
46sub 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
63my 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
76sub tokenizer ($str) {
77 return [] if !$str.match(/^<mal>/);
78 return grep { ! /^\;/ }, $<mal><token>.map({~$_});
79}
80
81sub 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}