Commit | Line | Data |
---|---|---|
b5dedee0 | 1 | package reader; |
b5dedee0 | 2 | use strict; |
1d834731 | 3 | use warnings; |
01c97316 | 4 | no if $] >= 5.018, warnings => "experimental::smartmatch"; |
29702ab6 BH |
5 | use feature qw(switch); |
6 | ||
b5dedee0 JM |
7 | use Exporter 'import'; |
8 | our @EXPORT_OK = qw( read_str ); | |
9 | ||
b8ee29b2 | 10 | use types qw($nil $true $false _keyword _hash_map); |
b5dedee0 JM |
11 | |
12 | use Data::Dumper; | |
13 | ||
14 | { | |
6708078b | 15 | package Mal::Reader; |
b5dedee0 JM |
16 | sub new { |
17 | my $class = shift; | |
18 | bless { position => 0, tokens => shift } => $class | |
19 | } | |
20 | sub next { my $self = shift; return $self->{tokens}[$self->{position}++] } | |
21 | sub peek { my $self = shift; return $self->{tokens}[$self->{position}] } | |
22 | } | |
23 | ||
24 | sub tokenize { | |
25 | my($str) = @_; | |
5f80c83f | 26 | my @tokens = $str =~ /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)/g; |
a3b0621d | 27 | return grep {! /^;|^$/} @tokens; |
b5dedee0 JM |
28 | } |
29 | ||
30 | sub read_atom { | |
31 | my($rdr) = @_; | |
32 | my $token = $rdr->next(); | |
33 | given ($token) { | |
6708078b | 34 | when(/^-?[0-9]+$/) { return Mal::Integer->new($token) } |
16309256 | 35 | when(/^"(?:\\.|[^\\"])*"$/) { |
0794206d | 36 | my %escaped_chars = ( "\\\\" => "\\", "\\\"" => "\"", "\\n" => "\n" ); |
a5a66058 | 37 | my $str = substr $token, 1, -1; |
0794206d | 38 | $str =~ s/\\./$escaped_chars{$&}/ge; |
6708078b | 39 | return Mal::String->new($str) |
a5a66058 | 40 | } |
16309256 | 41 | when(/^"/) { |
5f80c83f JM |
42 | die "expected '\"', got EOF"; |
43 | } | |
b8ee29b2 | 44 | when(/^:/) { return _keyword(substr($token,1)) } |
1753f4a9 BH |
45 | when('nil') { return $nil } |
46 | when('true') { return $true } | |
47 | when('false') { return $false } | |
6708078b | 48 | default { return Mal::Symbol->new($token) } |
b5dedee0 JM |
49 | } |
50 | } | |
51 | ||
52 | sub read_list { | |
53 | my($rdr,$class,$start,$end) = @_; | |
74d12000 BH |
54 | $start = $start // '('; |
55 | $end = $end // ')'; | |
b5dedee0 JM |
56 | |
57 | my $token = $rdr->next(); | |
58 | my @lst = (); | |
59 | if ($token ne $start) { | |
60 | die "expected '$start'"; | |
61 | } | |
5f80c83f JM |
62 | while (1) { |
63 | $token = $rdr->peek(); | |
64 | if (! defined($token)) { | |
b5dedee0 JM |
65 | die "expected '$end', got EOF"; |
66 | } | |
5f80c83f | 67 | last if ($token eq $end); |
b5dedee0 JM |
68 | push(@lst, read_form($rdr)); |
69 | } | |
70 | $rdr->next(); | |
6708078b BH |
71 | if ($class eq 'Mal::List') { |
72 | return Mal::List->new(\@lst); | |
73 | } elsif ($class eq 'Mal::Vector') { | |
74 | return Mal::Vector->new(\@lst); | |
b5dedee0 | 75 | } else { |
16354bb4 | 76 | return _hash_map(@lst); |
b5dedee0 JM |
77 | } |
78 | } | |
79 | ||
80 | sub read_form { | |
81 | my($rdr) = @_; | |
82 | my $token = $rdr->peek(); | |
83 | given ($token) { | |
6708078b BH |
84 | when("'") { $rdr->next(); Mal::List->new([Mal::Symbol->new('quote'), |
85 | read_form($rdr)]) } | |
86 | when('`') { $rdr->next(); | |
87 | Mal::List->new([Mal::Symbol->new('quasiquote'), | |
88 | read_form($rdr)]) } | |
89 | when('~') { $rdr->next(); Mal::List->new([Mal::Symbol->new('unquote'), | |
90 | read_form($rdr)]) } | |
91 | when('~@') { $rdr->next(); | |
92 | Mal::List->new([Mal::Symbol->new('splice-unquote'), | |
93 | read_form($rdr)]) } | |
89bd4de1 | 94 | when('^') { $rdr->next(); my $meta = read_form($rdr); |
6708078b BH |
95 | Mal::List->new([Mal::Symbol->new('with-meta'), |
96 | read_form($rdr), $meta]) } | |
97 | when('@') { $rdr->next(); Mal::List->new([Mal::Symbol->new('deref'), | |
98 | read_form($rdr)]) } | |
89bd4de1 | 99 | |
fd637e03 | 100 | when(')') { die "unexpected ')'" } |
6708078b | 101 | when('(') { return read_list($rdr, 'Mal::List') } |
fd637e03 | 102 | when(']') { die "unexpected ']'" } |
6708078b | 103 | when('[') { return read_list($rdr, 'Mal::Vector', '[', ']') } |
fd637e03 | 104 | when('}') { die "unexpected '}'" } |
6708078b | 105 | when('{') { return read_list($rdr, 'Mal::HashMap', '{', '}') } |
fd637e03 | 106 | default { return read_atom($rdr) } |
b5dedee0 JM |
107 | } |
108 | } | |
109 | ||
110 | sub read_str { | |
111 | my($str) = @_; | |
112 | my @tokens = tokenize($str); | |
89bd4de1 | 113 | #print "tokens: " . Dumper(\@tokens); |
6708078b BH |
114 | if (scalar(@tokens) == 0) { die Mal::BlankException->new(); } |
115 | return read_form(Mal::Reader->new(\@tokens)); | |
b5dedee0 JM |
116 | } |
117 | ||
118 | #print Dumper(read_str("123")); | |
119 | #print Dumper(read_str("+")); | |
120 | #print Dumper(read_str("\"abc\"")); | |
121 | #print Dumper(read_str("nil")); | |
122 | #print Dumper(read_str("true")); | |
123 | #print Dumper(read_str("false")); | |
124 | #print Dumper(read_str("(+ 2 3)")); | |
125 | #print Dumper(read_str("(foo 2 (3 4))")); | |
126 | ||
127 | 1; |