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