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