Livescript: update Dockerfile to support Travis.
[jackhill/mal.git] / livescript / reader.ls
CommitLineData
18ed1641 1readline = require 'readline'
2ff2d84b
JB
2{id, map, pairs-to-obj} = require 'prelude-ls'
3{list-to-pairs} = require './utils'
4
5export class OnlyComment
6
7parse-error = (msg) -> throw new Error msg
18ed1641
JB
8
9class 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
2ff2d84b
JB
27eof-or-comment = (reader) ->
28 token = reader.peek!
29 if token? and not token.startsWith ';'
30 then parse-error "expected EOF, got '#{token}'"
31
32
18ed1641
JB
33export read_str = (str) ->
34 str
35 |> tokenizer
36 |> (tokens) -> new Reader tokens
cb86911f
JB
37 |> (reader) ->
38 result = read_form reader
25bb14c9 39 if token? then parse-error "expected EOF, got '#{token}'"
cb86911f
JB
40 result
41
18ed1641
JB
42
43# This function will take a single string and return an array/list
44# of all the tokens (strings) in it.
45tokenizer = (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
18ed1641
JB
60 if not m
61 # Allow whitespace or commas at the end of the input.
62 break if /[\s,]+/.exec str.substring idx
2ff2d84b 63 parse-error "parse error at character #{idx}"
18ed1641 64
25bb14c9
JB
65 tok = m[1]
66 # Ignore comments.
67 if tok[0] != ';' then tokens.push m[1]
18ed1641
JB
68
69 tokens
70
71read_form = (reader) ->
2ff2d84b
JB
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
18ed1641 86
cb86911f 87read_list = (reader, end) ->
18ed1641 88 list = []
2ff2d84b 89 reader.next! # accept '(', '[' or '{'
18ed1641
JB
90 loop
91 token = reader.peek!
92 if not token?
2ff2d84b 93 parse-error "expected '#{end}', got EOF"
cb86911f 94 else if token == end
18ed1641
JB
95 reader.next!
96 break
97
98 list.push read_form reader
2ff2d84b
JB
99
100 switch end
101 | ')' => {type: \list, value: list}
102 | ']' => {type: \vector, value: list}
103 | '}' => list-to-map list
104
18ed1641
JB
105
106special_chars = '[]{}\'`~^@'
3181c695 107constants = [\true \false \nil]
18ed1641 108
2ff2d84b 109
18ed1641
JB
110read_atom = (reader) ->
111 token = reader.peek!
3181c695
JB
112 if token in constants
113 {type: \const, value: reader.next!}
114 else if token[0] == '"'
2ff2d84b 115 {type: \string, value: decode-string reader.next!}
18ed1641 116 else if token.match /^-?\d+$/
cb86911f 117 {type: \int, value: parseInt reader.next!}
18ed1641 118 else if token != '~@' and token not in special_chars
2ff2d84b
JB
119 if token.startsWith ':'
120 {type: \keyword, value: reader.next!}
121 else
122 {type: \symbol, value: reader.next!}
18ed1641 123 else
2ff2d84b
JB
124 parse-error "expected an atom, got #{token}"
125
126
127decode-string = (str) ->
128 str |> (.slice 1, -1)
129 |> (.replace /\\[\"\\n]/g,
130 (esc) -> switch esc
131 | '\\n' => '\n'
132 | '\\"' => '"'
133 | '\\\\' => '\\')
134
135
136export keyword-prefix = '\u029e'
137
894f5ce8
JB
138export map-keyword = (key) ->
139 switch key.type
140 | \string => key.value
141 | \keyword => keyword-prefix + key.value
142 | otherwise =>
143 parse-error "#{key.type} can't be a map key"
2ff2d84b 144
894f5ce8 145export list-to-map = (list) ->
2ff2d84b
JB
146 if list.length % 2 != 0
147 parse-error "map should have an even number
148 of elements, got #{list.length}"
149
150 list-to-pairs list
894f5ce8 151 |> map ([key, value]) -> [(map-keyword key), value]
2ff2d84b
JB
152 |> pairs-to-obj
153 |> (obj) -> {type: \map, value: obj}
154
155
156read-macro = (symbol, reader) ->
157 reader.next! # accept macro start token
158
159 do
160 type: \list
161 value:
162 * {type: \symbol, value: symbol}
163 * read_form reader
164
165
166read-with-meta = (reader) ->
167 reader.next! # accept ^
168 if reader.peek! != '{'
169 parse-error "expected a map after with-meta reader macro '^'"
170
171 meta = read_list reader, '}'
172 form = read_form reader
173
174 do
175 type: \list
176 value:
177 * {type: \symbol, value: 'with-meta'}
178 * form
179 * meta