Commit | Line | Data |
---|---|---|
891c3f3b | 1 | types = require "./types.coffee" |
b8ee29b2 | 2 | _symbol = types._symbol |
891c3f3b JM |
3 | |
4 | ||
5 | class Reader | |
6 | constructor: (@tokens) -> @position = 0 | |
7 | next: -> @tokens[@position++] | |
8 | peek: -> @tokens[@position] | |
9 | skip: -> | |
10 | @position++ | |
11 | @ | |
12 | ||
13 | tokenize = (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 | ||
21 | read_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 | ||
36 | read_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 | ||
46 | read_vector = (rdr) -> | |
b8ee29b2 | 47 | types._vector(read_list(rdr, '[', ']')...) |
891c3f3b JM |
48 | |
49 | read_hash_map = (rdr) -> | |
b8ee29b2 | 50 | types._hash_map(read_list(rdr, '{', '}')...) |
891c3f3b JM |
51 | |
52 | read_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 | ||
77 | exports.BlankException = BlankException = (msg) -> null | |
78 | ||
79 | exports.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 |