Merge pull request #380 from bjh21/bjh21-bbc-basic
[jackhill/mal.git] / io / MalReader.io
1 MalReader := Object clone do (
2
3 Reader := Object clone do (
4 pos ::= 0
5 tokens ::= list()
6
7 with := method(theTokens,
8 self clone setTokens(theTokens)
9 )
10
11 peek := method(tokens at(pos))
12
13 next := method(
14 pos = pos + 1
15 tokens at(pos - 1)
16 )
17 )
18
19 tokenizerRegex := Regex with("[\\s ,]*(~@|[\\[\\]{}()'`~@]|\"(?:[\\\\].|[^\\\\\"])*\"?|;.*|[^\\s \\[\\]{}()'\"`~@,;]*)")
20
21 tokenize := method(str,
22 tokenizerRegex matchesIn(str) \
23 map(m, m at(1) asMutable strip) \
24 select(t, t size > 0) \
25 select(t, t exSlice(0, 1) != ";")
26 )
27
28 numberRegex := Regex with("^-?[0-9]+$")
29
30 read_string := method(token,
31 (token endsWithSeq("\"")) ifFalse(Exception raise("expected '\"', got EOF"))
32 placeholder := 127 asCharacter
33 token exSlice(1, -1) replaceSeq("\\\\", placeholder) replaceSeq("\\\"", "\"") replaceSeq("\\n", "\n") replaceSeq(placeholder, "\\")
34 )
35
36 read_atom := method(rdr,
37 token := rdr next
38 (token hasMatchOfRegex(numberRegex)) ifTrue(return(token asNumber))
39 (token == "true") ifTrue(return(true))
40 (token == "false") ifTrue(return(false))
41 (token == "nil") ifTrue(return(nil))
42 (token beginsWithSeq(":")) ifTrue(return(MalKeyword with(token exSlice(1))))
43 (token beginsWithSeq("\"")) ifTrue(return(read_string(token)))
44 MalSymbol with(token)
45 )
46
47 read_list := method(rdr, start, end,
48 token := rdr next
49 if(token != start, Exception raise("expected '" .. start .. "'"))
50 ast := list()
51 token = rdr peek
52 while(token != end,
53 if(token isNil, Exception raise("expected '" .. end .. "', got EOF"))
54 ast push(read_form(rdr))
55 token = rdr peek
56 )
57 rdr next
58 ast
59 )
60
61 reader_macro := method(symbol, rdr,
62 rdr next
63 MalList with(list(MalSymbol with(symbol), read_form(rdr)))
64 )
65
66 read_form := method(rdr,
67 token := rdr peek
68 (token == "'") ifTrue(return(reader_macro("quote", rdr)))
69 (token == "`") ifTrue(return(reader_macro("quasiquote", rdr)))
70 (token == "~") ifTrue(return(reader_macro("unquote", rdr)))
71 (token == "~@") ifTrue(return(reader_macro("splice-unquote", rdr)))
72 (token == "^") ifTrue(
73 rdr next
74 meta := read_form(rdr)
75 return(MalList with(list(MalSymbol with("with-meta"), read_form(rdr), meta)))
76 )
77 (token == "@") ifTrue(return(reader_macro("deref", rdr)))
78 (token == "(") ifTrue(return(MalList with(read_list(rdr, "(", ")"))))
79 (token == ")") ifTrue(Exception raise("unexepcted ')'"))
80 (token == "[") ifTrue(return(MalVector with(read_list(rdr, "[", "]"))))
81 (token == "]") ifTrue(Exception raise("unexepcted ']'"))
82 (token == "{") ifTrue(return(MalMap withList(read_list(rdr, "{", "}"))))
83 (token == "}") ifTrue(Exception raise("unexepcted '}'"))
84 read_atom(rdr)
85 )
86
87 read_str := method(str,
88 tokens := tokenize(str)
89 if(tokens isEmpty, nil, read_form(Reader with(tokens)))
90 )
91 )