DISABLE FDs (REMOVE ME).
[jackhill/mal.git] / nim / reader.nim
CommitLineData
41558f01 1import options, re, strutils, types
b94acce6 2
2800f318 3let
4aa0ebdf 4 tokenRE = re"""[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)"""
b94acce6 5 intRE = re"-?[0-9]+$"
48901f14 6 strRE = re"""^"(?:\\.|[^\\"])*"$"""
b94acce6 7
3603af96 8type
9 Blank* = object of Exception
10
11 Reader = object
12 tokens: seq[string]
13 position: int
b94acce6 14
41558f01
DF
15proc 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
20proc peek(r: Reader): Option[string] =
21 if r.position < r.tokens.len: return r.tokens[r.position].some
b94acce6 22
23proc 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
36proc read_form(r: var Reader): MalType
37
38proc 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
50proc read_list(r: var Reader): MalType =
51 result = list r.read_seq("(", ")")
52
53proc read_vector(r: var Reader): MalType =
54 result = vector r.read_seq("[", "]")
55
56proc read_hash_map(r: var Reader): MalType =
57 result = hash_map r.read_seq("{", "}")
58
59proc 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
72proc 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
112proc 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