Merge pull request #370 from asarhaddon/hide-gensym-counter
[jackhill/mal.git] / crystal / reader.cr
CommitLineData
fdf28b76 1require "./types"
2c76c2ff 2require "./error"
fdf28b76 3
4class Reader
492144ce 5 def initialize(@tokens : Array(String))
fdf28b76 6 @pos = 0
7 end
8
448769e3 9 def current_token
fdf28b76 10 @tokens[@pos] rescue nil
11 end
12
448769e3 13 def peek
14 t = current_token
15
16 if t && t[0] == ';'
17 @pos += 1
18 peek
19 else
20 t
21 end
22 end
23
fdf28b76 24 def next
448769e3 25 peek
fdf28b76 26 ensure
27 @pos += 1
28 end
fdf28b76 29
66f3d7a4 30 def read_sequence(init, open, close)
31 token = self.next
32 parse_error "expected '#{open}', got EOF" unless token
5185c56e 33 parse_error "expected '#{open}', got #{token}" unless token[0] == open
fdf28b76 34
66f3d7a4 35 loop do
36 token = peek
37 parse_error "expected '#{close}', got EOF" unless token
38 break if token[0] == close
fdf28b76 39
66f3d7a4 40 init << read_form
41 peek
42 end
fdf28b76 43
66f3d7a4 44 self.next
45 init
fdf28b76 46 end
47
66f3d7a4 48 def read_list
7fe6282e 49 Mal::Type.new read_sequence(Mal::List.new, '(', ')')
66f3d7a4 50 end
448769e3 51
66f3d7a4 52 def read_vector
7fe6282e 53 Mal::Type.new read_sequence(Mal::Vector.new, '[', ']')
66f3d7a4 54 end
448769e3 55
66f3d7a4 56 def read_hashmap
57 types = read_sequence([] of Mal::Type, '{', '}')
fdf28b76 58
66f3d7a4 59 parse_error "odd number of elements for hash-map: #{types.size}" if types.size.odd?
60 map = Mal::HashMap.new
afc3a8d5 61
66f3d7a4 62 types.each_slice(2) do |kv|
9d627bcc 63 k, v = kv[0].unwrap, kv[1]
66f3d7a4 64 case k
65 when String
66 map[k] = v
67 else
9d627bcc 68 parse_error("key of hash-map must be string or keyword")
66f3d7a4 69 end
70 end
afc3a8d5 71
7fe6282e 72 Mal::Type.new map
66f3d7a4 73 end
74
75 def read_atom
76 token = self.next
77 parse_error "expected Atom but got EOF" unless token
78
7fe6282e 79 Mal::Type.new case
7546ae18 80 when token =~ /^-?\d+$/ then token.to_i64
66f3d7a4 81 when token == "true" then true
82 when token == "false" then false
83 when token == "nil" then nil
4aa0ebdf
JM
84 when token[0] == '"'
85 parse_error "expected '\"', got EOF" if token[-1] != '"'
86 token[1..-2].gsub(/\\(.)/, {"\\\"" => "\"",
87 "\\n" => "\n",
88 "\\\\" => "\\"})
5185c56e
OR
89 when token[0] == ':' then "\u029e#{token[1..-1]}"
90 else Mal::Symbol.new token
afc3a8d5 91 end
92 end
93
66f3d7a4 94 def list_of(symname)
ce0696d5 95 Mal::List.new << gen_type(Mal::Symbol, symname) << read_form
66f3d7a4 96 end
afc3a8d5 97
66f3d7a4 98 def read_form
99 token = peek
100
101 parse_error "unexpected EOF" unless token
102 parse_error "unexpected comment" if token[0] == ';'
103
7fe6282e 104 Mal::Type.new case token
66f3d7a4 105 when "(" then read_list
106 when ")" then parse_error "unexpected ')'"
107 when "[" then read_vector
108 when "]" then parse_error "unexpected ']'"
109 when "{" then read_hashmap
110 when "}" then parse_error "unexpected '}'"
111 when "'" then self.next; list_of("quote")
112 when "`" then self.next; list_of("quasiquote")
113 when "~" then self.next; list_of("unquote")
114 when "~@" then self.next; list_of("splice-unquote")
115 when "@" then self.next; list_of("deref")
116 when "^"
117 self.next
118 meta = read_form
119 list_of("with-meta") << meta
120 else read_atom
121 end
fdf28b76 122 end
123end
124
66f3d7a4 125def tokenize(str)
4aa0ebdf 126 regex = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)/
5185c56e 127 str.scan(regex).map { |m| m[1] }.reject(&.empty?)
448769e3 128end
129
66f3d7a4 130def read_str(str)
131 r = Reader.new(tokenize(str))
132 begin
133 r.read_form
134 ensure
135 unless r.peek.nil?
136 raise Mal::ParseException.new "expected EOF, got #{r.peek.to_s}"
137 end
fdf28b76 138 end
139end