Haxe: step7-A, hash-maps, metadata, self-hosting.
[jackhill/mal.git] / haxe / reader / Reader.hx
1 package reader;
2
3 import types.Types.MalType;
4 import types.Types.*;
5
6 class Reader {
7 // Reader class implementation
8 var tokens:Array<String>;
9 var position:Int = 0;
10
11 public function new(toks:Array<String>) {
12 tokens = toks;
13 }
14
15 public function next() {
16 return tokens[position++];
17 }
18
19 public function peek() {
20 if (tokens.length > position) {
21 return tokens[position];
22 } else {
23 return null;
24 }
25 }
26
27
28 // Static functions grouped with Reader class
29 static function tokenize(str:String) {
30 var re = ~/[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/g;
31 var tokens = new Array<String>();
32 var pos = 0;
33 while (re.matchSub(str, pos)) {
34 var t = re.matched(1);
35 if (t == "") { break; }
36 var pos_len = re.matchedPos();
37 pos = pos_len.pos + pos_len.len;
38 if (t.charAt(0) == ";") { continue; }
39 tokens.push(t);
40
41 }
42 return tokens;
43 }
44
45 static function read_atom(rdr:Reader) {
46 var re_int = ~/^[0-9]*$/;
47 var re_str = ~/^".*"$/;
48 var token = rdr.next();
49 return switch (token) {
50 case "nil":
51 MalNil;
52 case "true":
53 MalTrue;
54 case "false":
55 MalFalse;
56 case _ if (token.charAt(0) == ":"):
57 MalString("\x7f" + token.substr(1));
58 case _ if (re_int.match(token)):
59 MalInt(Std.parseInt(token));
60 case _ if (re_str.match(token)):
61 var re1 = ~/\\"/g,
62 re2 = ~/\\n/g,
63 re3 = ~/\\\\/g,
64 s = token.substr(1, token.length-2);
65 MalString(re3.replace(
66 re2.replace(
67 re1.replace(s, "\""),
68 "\n"),
69 "\\"));
70 case _:
71 MalSymbol(token);
72 }
73 }
74
75 static function read_seq(rdr:Reader, start, end) {
76 var lst = [];
77 var token = rdr.next();
78 if (token != start) {
79 throw 'expected \'${start}\'';
80 }
81 while ((token = rdr.peek()) != end) {
82 if (token == null) {
83 throw 'expected \'${end}\', got EOF';
84 }
85 lst.push(read_form(rdr));
86 }
87 rdr.next();
88 return lst;
89 }
90
91 static function read_form(rdr:Reader):MalType {
92 var token = rdr.peek();
93 return switch (token) {
94 // reader macros/transforms
95 case "'": rdr.next();
96 MalList([MalSymbol("quote"), read_form(rdr)]);
97 case "`": rdr.next();
98 MalList([MalSymbol("quasiquote"), read_form(rdr)]);
99 case "~": rdr.next();
100 MalList([MalSymbol("unquote"), read_form(rdr)]);
101 case "~@": rdr.next();
102 MalList([MalSymbol("splice-unquote"), read_form(rdr)]);
103 case "^": rdr.next();
104 var meta = read_form(rdr);
105 MalList([MalSymbol("with-meta"), read_form(rdr), meta]);
106 case "@": rdr.next();
107 MalList([MalSymbol("deref"), read_form(rdr)]);
108
109 // list
110 case ")": throw("unexpected ')'");
111 case "(": MalList(read_seq(rdr, '(', ')'));
112
113 // vector
114 case "]": throw("unexpected ']'");
115 case "[": MalVector(read_seq(rdr, '[', ']'));
116
117 // hashmap
118 case "}": throw("unexpected '}'");
119 case "{": hash_map(read_seq(rdr, '{', '}'));
120 case _: read_atom(rdr);
121 }
122 }
123
124 public static function read_str(str:String):MalType {
125 var tokens = tokenize(str);
126 if (tokens.length == 0) { throw(new BlankLine()); }
127 return read_form(new Reader(tokens));
128 }
129 }