Crystal: subcommand is changed from 'build' to 'compile'
[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
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
66f3d7a4 80 when token =~ /^-?\d+$/ then token.to_i
81 when token == "true" then true
82 when token == "false" then false
83 when token == "nil" then nil
84 when token[0] == '"' then token[1..-2].gsub(/\\"/, "\"")
8d78bc26
JM
85 .gsub(/\\n/, "\n")
86 .gsub(/\\\\/, "\\")
66f3d7a4 87 when token[0] == ':' then "\u029e#{token[1..-1]}"
88 else Mal::Symbol.new token
afc3a8d5 89 end
90 end
91
66f3d7a4 92 def list_of(symname)
ce0696d5 93 Mal::List.new << gen_type(Mal::Symbol, symname) << read_form
66f3d7a4 94 end
afc3a8d5 95
66f3d7a4 96 def read_form
97 token = peek
98
99 parse_error "unexpected EOF" unless token
100 parse_error "unexpected comment" if token[0] == ';'
101
7fe6282e 102 Mal::Type.new case token
66f3d7a4 103 when "(" then read_list
104 when ")" then parse_error "unexpected ')'"
105 when "[" then read_vector
106 when "]" then parse_error "unexpected ']'"
107 when "{" then read_hashmap
108 when "}" then parse_error "unexpected '}'"
109 when "'" then self.next; list_of("quote")
110 when "`" then self.next; list_of("quasiquote")
111 when "~" then self.next; list_of("unquote")
112 when "~@" then self.next; list_of("splice-unquote")
113 when "@" then self.next; list_of("deref")
114 when "^"
115 self.next
116 meta = read_form
117 list_of("with-meta") << meta
118 else read_atom
119 end
fdf28b76 120 end
66f3d7a4 121
fdf28b76 122end
123
66f3d7a4 124def tokenize(str)
125 regex = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/
126 str.scan(regex).map{|m| m[1]}.reject(&.empty?)
448769e3 127end
128
66f3d7a4 129def read_str(str)
130 r = Reader.new(tokenize(str))
131 begin
132 r.read_form
133 ensure
134 unless r.peek.nil?
135 raise Mal::ParseException.new "expected EOF, got #{r.peek.to_s}"
136 end
fdf28b76 137 end
138end
139