DISABLE FDs (REMOVE ME).
[jackhill/mal.git] / coffee / reader.coffee
CommitLineData
891c3f3b 1types = require "./types.coffee"
b8ee29b2 2_symbol = types._symbol
891c3f3b
JM
3
4
5class Reader
6 constructor: (@tokens) -> @position = 0
7 next: -> @tokens[@position++]
8 peek: -> @tokens[@position]
9 skip: ->
10 @position++
11 @
12
13tokenize = (str) ->
970935da 14 re = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)/g
891c3f3b
JM
15 results = []
16 while (match = re.exec(str)[1]) != ""
17 continue if match[0] == ';'
18 results.push(match)
19 results
20
21read_atom = (rdr) ->
22 token = rdr.next()
23 if token.match /^-?[0-9]+$/ then parseInt token,10
24 else if token.match /^-?[0-9][0-9.]*$/ then parseFloat token,10
0a32f367 25 else if token.match /^"(?:\\.|[^\\"])*"$/
891c3f3b 26 token.slice(1, token.length-1)
a821cd72 27 .replace(/\\(.)/g, (_, c) -> if c == 'n' then '\n' else c)
0a32f367
JM
28 else if token[0] == '"'
29 throw new Error "expected '\"', got EOF"
b8ee29b2 30 else if token[0] == ':' then types._keyword(token[1..])
891c3f3b
JM
31 else if token == "nil" then null
32 else if token == "true" then true
33 else if token == "false" then false
34 else _symbol(token)
35
36read_list = (rdr, start='(', end=')') ->
37 ast = []
38 token = rdr.next()
39 throw new Error "expected '" + start + "'" if token != start
40 while (token = rdr.peek()) != end
41 throw new Error "expected '" + end + "', got EOF" if !token
42 ast.push read_form rdr
43 rdr.next()
44 ast
45
46read_vector = (rdr) ->
b8ee29b2 47 types._vector(read_list(rdr, '[', ']')...)
891c3f3b
JM
48
49read_hash_map = (rdr) ->
b8ee29b2 50 types._hash_map(read_list(rdr, '{', '}')...)
891c3f3b
JM
51
52read_form = (rdr) ->
53 token = rdr.peek()
54 switch token
55 when '\'' then [_symbol('quote'), read_form(rdr.skip())]
56 when '`' then [_symbol('quasiquote'), read_form(rdr.skip())]
57 when '~' then [_symbol('unquote'), read_form(rdr.skip())]
58 when '~@' then [_symbol('splice-unquote'), read_form(rdr.skip())]
59 when '^'
60 meta = read_form(rdr.skip())
61 [_symbol('with-meta'), read_form(rdr), meta]
62 when '@' then [_symbol('deref'), read_form(rdr.skip())]
63
64 # list
65 when ')' then throw new Error "unexpected ')'"
66 when '(' then read_list(rdr)
67 # vector
68 when ']' then throw new Error "unexpected ']'"
69 when '[' then read_vector(rdr)
70 # hash-map
71 when '}' then throw new Error "unexpected '}'"
72 when '{' then read_hash_map(rdr)
73 # atom
74 else read_atom(rdr)
75
76
77exports.BlankException = BlankException = (msg) -> null
78
79exports.read_str = read_str = (str) ->
80 tokens = tokenize(str)
81 throw new BlankException() if tokens.length == 0
82 read_form(new Reader(tokens))
83
84#console.log read_str "(1 \"two\" three)"
85#console.log read_str "[1 2 3]"
86#console.log read_str '{"abc" 123 "def" 456}'
87
88# vim: ts=2:sw=2