bbc-basic: Slight tweak to heap size.
[jackhill/mal.git] / livescript / reader.ls
1 readline = require 'readline'
2 {id, map, pairs-to-obj} = require 'prelude-ls'
3 {list-to-pairs} = require './utils'
4
5 export class OnlyComment
6
7 parse-error = (msg) -> throw new Error msg
8
9 class Reader
10 (tokens) ->
11 @tokens = tokens
12 @pos = 0
13
14 # returns the token at the current position
15 # and increments position.
16 next: ->
17 result = @peek!
18 if result? then @pos += 1
19 result
20
21 # just returns the token at the current position.
22 peek: ->
23 if @pos < @tokens.length
24 @tokens[@pos]
25
26
27 eof-or-comment = (reader) ->
28 token = reader.peek!
29 if token? and not token.startsWith ';'
30 then parse-error "expected EOF, got '#{token}'"
31
32
33 export read_str = (str) ->
34 str
35 |> tokenizer
36 |> (tokens) -> new Reader tokens
37 |> (reader) ->
38 result = read_form reader
39 if token? then parse-error "expected EOF, got '#{token}'"
40 result
41
42
43 # This function will take a single string and return an array/list
44 # of all the tokens (strings) in it.
45 tokenizer = (str) ->
46 re = //
47 [\s,]* # whitespace or commas
48 ( ~@ # special two-char ~@
49 | [\[\]{}()'`~^@] # special single char one of []{}'`~^@
50 | "(?:\\.| [^\\"])*"? # double-quoted string
51 | ;.* # any seq of chars starting ;
52 | [^\s\[\]{}('"`,;)]+ # seq of non-special chars: symbols, numbers,
53 ) # "true", "false" and "nil".
54 //y
55
56 tokens = []
57 while re.lastIndex < str.length
58 idx = re.lastIndex
59 m = re.exec str
60 if not m
61 # Allow whitespace or commas at the end of the input.
62 break if /[\s,]+/.exec str.substring idx
63 parse-error "parse error at character #{idx}"
64
65 tok = m[1]
66 # Ignore comments.
67 if tok[0] != ';' then tokens.push m[1]
68
69 tokens
70
71 read_form = (reader) ->
72 switch reader.peek!
73 | '(' => read_list reader, ')'
74 | '[' => read_list reader, ']'
75 | '{' => read_list reader, '}'
76 | '\'' => read-macro 'quote', reader
77 | '\`' => read-macro 'quasiquote', reader
78 | '~' => read-macro 'unquote', reader
79 | '~@' => read-macro 'splice-unquote', reader
80 | '@' => read-macro 'deref', reader # todo only symbol?
81 | '^' => read-with-meta reader
82 | otherwise =>
83 if that? then read_atom reader
84 else parse-error 'expected a form, got EOF'
85
86
87 read_list = (reader, end) ->
88 list = []
89 reader.next! # accept '(', '[' or '{'
90 loop
91 token = reader.peek!
92 if not token?
93 parse-error "expected '#{end}', got EOF"
94 else if token == end
95 reader.next!
96 break
97
98 list.push read_form reader
99
100 switch end
101 | ')' => {type: \list, value: list}
102 | ']' => {type: \vector, value: list}
103 | '}' => list-to-map list
104
105
106 special_chars = '[]{}\'`~^@'
107 constants = [\true \false \nil]
108
109
110 read_atom = (reader) ->
111 token = reader.peek!
112 if token in constants
113 {type: \const, value: reader.next!}
114 else if token.match /^"(?:\\.|[^\\"])*"$/
115 {type: \string, value: decode-string reader.next!}
116 else if token[0] == '"'
117 parse-error "expected '\"', got EOF"
118 else if token.match /^-?\d+$/
119 {type: \int, value: parseInt reader.next!}
120 else if token != '~@' and token not in special_chars
121 if token.startsWith ':'
122 {type: \keyword, value: reader.next!}
123 else
124 {type: \symbol, value: reader.next!}
125 else
126 parse-error "expected an atom, got #{token}"
127
128
129 decode-string = (str) ->
130 str |> (.slice 1, -1)
131 |> (.replace /\\[\"\\n]/g,
132 (esc) -> switch esc
133 | '\\n' => '\n'
134 | '\\"' => '"'
135 | '\\\\' => '\\')
136
137
138 export keyword-prefix = '\u029e'
139
140 export map-keyword = (key) ->
141 switch key.type
142 | \string => key.value
143 | \keyword => keyword-prefix + key.value
144 | otherwise =>
145 parse-error "#{key.type} can't be a map key"
146
147 export list-to-map = (list) ->
148 if list.length % 2 != 0
149 parse-error "map should have an even number
150 of elements, got #{list.length}"
151
152 list-to-pairs list
153 |> map ([key, value]) -> [(map-keyword key), value]
154 |> pairs-to-obj
155 |> (obj) -> {type: \map, value: obj}
156
157
158 read-macro = (symbol, reader) ->
159 reader.next! # accept macro start token
160
161 do
162 type: \list
163 value:
164 * {type: \symbol, value: symbol}
165 * read_form reader
166
167
168 read-with-meta = (reader) ->
169 reader.next! # accept ^
170 if reader.peek! != '{'
171 parse-error "expected a map after with-meta reader macro '^'"
172
173 meta = read_list reader, '}'
174 form = read_form reader
175
176 do
177 type: \list
178 value:
179 * {type: \symbol, value: 'with-meta'}
180 * form
181 * meta