b94acce6 |
1 | import nre, optional_t, strutils, types |
2 | |
2800f318 |
3 | let |
b94acce6 |
4 | tokenRE = re"""[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)""" |
5 | intRE = re"-?[0-9]+$" |
6 | |
3603af96 |
7 | type |
8 | Blank* = object of Exception |
9 | |
10 | Reader = object |
11 | tokens: seq[string] |
12 | position: int |
b94acce6 |
13 | |
14 | proc next(r: var Reader): string = |
15 | if r.position >= r.tokens.len: |
16 | result = nil |
17 | else: |
18 | result = r.tokens[r.position] |
19 | inc r.position |
20 | |
21 | proc peek(r: Reader): string = |
22 | if r.position >= r.tokens.len: nil |
23 | else: r.tokens[r.position] |
24 | |
25 | proc tokenize(str: string): seq[string] = |
26 | result = @[] |
27 | for match in str.findIter(tokenRE): |
3603af96 |
28 | if match.captures[0][0] != ';': |
29 | result.add match.captures[0] |
b94acce6 |
30 | |
31 | proc read_form(r: var Reader): MalType |
32 | |
33 | proc read_seq(r: var Reader, fr, to: string): seq[MalType] = |
34 | result = @[] |
35 | var t = r.next |
36 | if t != fr: raise newException(ValueError, "expected '" & fr & "'") |
37 | |
38 | t = r.peek |
39 | while t != to: |
40 | if t == nil: raise newException(ValueError, "expected '" & to & "', got EOF") |
41 | result.add r.read_form |
42 | t = r.peek |
43 | discard r.next |
44 | |
45 | proc read_list(r: var Reader): MalType = |
46 | result = list r.read_seq("(", ")") |
47 | |
48 | proc read_vector(r: var Reader): MalType = |
49 | result = vector r.read_seq("[", "]") |
50 | |
51 | proc read_hash_map(r: var Reader): MalType = |
52 | result = hash_map r.read_seq("{", "}") |
53 | |
54 | proc read_atom(r: var Reader): MalType = |
55 | let t = r.next |
56 | if t.match(intRE): number t.parseInt |
819bd786 |
57 | elif t[0] == '"': str t[1 .. <t.high].replace("\\\"", "\"") |
58 | elif t[0] == ':': keyword t[1 .. t.high] |
59 | elif t == "nil": nilObj |
60 | elif t == "true": trueObj |
61 | elif t == "false": falseObj |
b94acce6 |
62 | else: symbol t |
63 | |
64 | proc read_form(r: var Reader): MalType = |
65 | if r.peek[0] == ';': |
66 | discard r.next |
67 | return nilObj |
68 | case r.peek |
69 | of "'": |
70 | discard r.next |
71 | result = list(symbol "quote", r.read_form) |
72 | of "`": |
73 | discard r.next |
74 | result = list(symbol "quasiquote", r.read_form) |
75 | of "~": |
76 | discard r.next |
77 | result = list(symbol "unquote", r.read_form) |
78 | of "~@": |
79 | discard r.next |
80 | result = list(symbol "splice-unquote", r.read_form) |
81 | of "^": |
82 | discard r.next |
83 | let meta = r.read_form |
84 | result = list(symbol "with-meta", r.read_form, meta) |
85 | of "@": |
86 | discard r.next |
87 | result = list(symbol "deref", r.read_form) |
88 | |
89 | # list |
90 | of "(": result = r.read_list |
91 | of ")": raise newException(ValueError, "unexpected ')'") |
92 | |
93 | # vector |
94 | of "[": result = r.read_vector |
95 | of "]": raise newException(ValueError, "unexpected ']'") |
96 | |
97 | # hash-map |
98 | of "{": result = r.read_hash_map |
99 | of "}": raise newException(ValueError, "unexpected '}'") |
100 | |
101 | # atom |
102 | else: result = r.read_atom |
103 | |
104 | proc read_str*(str: string): MalType = |
105 | var r = Reader(tokens: str.tokenize) |
3603af96 |
106 | if r.tokens.len == 0: |
107 | raise newException(Blank, "Blank line") |
b94acce6 |
108 | r.read_form |