1 import re, strutils, sequtils, types
4 tokenRE = re"""[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)"""
8 Blank* = object of Exception
14 proc next(r: var Reader): string =
15 if r.position >= r.tokens.len:
18 result = r.tokens[r.position]
21 proc peek(r: Reader): string =
22 if r.position >= r.tokens.len: nil
23 else: r.tokens[r.position]
25 proc tokenize(str: string): seq[string] =
29 var matches: array[2, string]
30 var len = str.findBounds(tokenRE, matches, pos)
31 if len.first != -1 and len.last != -1:
33 if matches[0][0] != ';':
38 proc read_form(r: var Reader): MalType
40 proc read_seq(r: var Reader, fr, to: string): seq[MalType] =
43 if t != fr: raise newException(ValueError, "expected '" & fr & "'")
47 if t == nil: raise newException(ValueError, "expected '" & to & "', got EOF")
48 result.add r.read_form
52 proc read_list(r: var Reader): MalType =
53 result = list r.read_seq("(", ")")
55 proc read_vector(r: var Reader): MalType =
56 result = vector r.read_seq("[", "]")
58 proc read_hash_map(r: var Reader): MalType =
59 result = hash_map r.read_seq("{", "}")
61 proc read_atom(r: var Reader): MalType =
63 if t.match(intRE): number t.parseInt
64 elif t[0] == '"': str t[1 .. <t.high].replace("\\\"", "\"").replace("\\n", "\n").replace("\\\\", "\\")
65 elif t[0] == ':': keyword t[1 .. t.high]
66 elif t == "nil": nilObj
67 elif t == "true": trueObj
68 elif t == "false": falseObj
71 proc read_form(r: var Reader): MalType =
78 result = list(symbol "quote", r.read_form)
81 result = list(symbol "quasiquote", r.read_form)
84 result = list(symbol "unquote", r.read_form)
87 result = list(symbol "splice-unquote", r.read_form)
90 let meta = r.read_form
91 result = list(symbol "with-meta", r.read_form, meta)
94 result = list(symbol "deref", r.read_form)
97 of "(": result = r.read_list
98 of ")": raise newException(ValueError, "unexpected ')'")
101 of "[": result = r.read_vector
102 of "]": raise newException(ValueError, "unexpected ']'")
105 of "{": result = r.read_hash_map
106 of "}": raise newException(ValueError, "unexpected '}'")
109 else: result = r.read_atom
111 proc read_str*(str: string): MalType =
112 var r = Reader(tokens: str.tokenize)
113 if r.tokens.len == 0:
114 raise newException(Blank, "Blank line")