Commit | Line | Data |
---|---|---|
213a3288 ST |
1 | class Mal.Reader : GLib.Object { |
2 | static Regex tok_re; | |
3 | static Regex tok_num; | |
4 | ||
5 | int origlen; | |
6 | string data; | |
7 | int pos; | |
8 | ||
9 | string next_token; | |
10 | ||
11 | static construct { | |
12 | tok_re = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;[^\n]*|[^\s\[\]{}('"`,;)]*)/; // comment to unconfuse emacs vala-mode "]); | |
13 | tok_num = /^-?[0-9]/; | |
14 | } | |
15 | ||
16 | private string poserr(string fmt, ...) { | |
17 | return "char %d: %s".printf(origlen - data.length, | |
18 | fmt.vprintf(va_list())); | |
19 | } | |
20 | ||
21 | private void advance() throws Error { | |
22 | do { | |
23 | MatchInfo info; | |
24 | if (!tok_re.match(data, 0, out info)) | |
25 | throw new Error.BAD_TOKEN(poserr("bad token")); | |
26 | ||
27 | next_token = info.fetch(1); | |
28 | int tokenend; | |
29 | info.fetch_pos(1, null, out tokenend); | |
30 | data = data[tokenend:data.length]; | |
31 | } while (next_token.has_prefix(";")); | |
32 | } | |
33 | ||
34 | public Reader(string str) throws Error { | |
35 | data = str; | |
36 | origlen = data.length; | |
37 | pos = 0; | |
38 | advance(); | |
39 | } | |
40 | ||
41 | public string peek() throws Error { | |
42 | return next_token; | |
43 | } | |
44 | ||
45 | public string next() throws Error { | |
46 | advance(); | |
47 | return peek(); | |
48 | } | |
49 | ||
50 | public static Mal.Val? read_str(string str) throws Error { | |
51 | var rdr = new Reader(str); | |
52 | if (rdr.peek() == "") | |
53 | return null; | |
54 | var toret = rdr.read_form(); | |
55 | if (rdr.peek() != "") | |
56 | throw new Mal.Error.PARSE_ERROR( | |
57 | rdr.poserr("trailing junk after expression")); | |
58 | return toret; | |
59 | } | |
60 | ||
61 | public Mal.Val read_form() throws Error { | |
62 | string token = peek(); | |
63 | if (token == "(") { | |
64 | next(); // eat ( | |
65 | return new Mal.List(read_list(")")); | |
66 | } else { | |
67 | return read_atom(); | |
68 | } | |
69 | } | |
70 | ||
71 | public GLib.List<Mal.Val> read_list(string endtok) throws Error { | |
72 | var list = new GLib.List<Mal.Val>(); | |
73 | string token; | |
74 | while (true) { | |
75 | token = peek(); | |
76 | if (token == "") | |
77 | throw new Mal.Error.PARSE_ERROR(poserr("unbalanced parens")); | |
78 | if (token == endtok) { | |
79 | next(); // eat end token | |
80 | return list; | |
81 | } | |
82 | ||
83 | list.append(read_form()); | |
84 | } | |
85 | } | |
86 | ||
87 | public Mal.Hashmap read_hashmap() throws Error { | |
88 | var map = new Mal.Hashmap(); | |
89 | string token; | |
90 | while (true) { | |
91 | Mal.Val vals[2]; | |
92 | for (int i = 0; i < 2; i++) { | |
93 | token = peek(); | |
94 | if (token == "") | |
95 | throw new Mal.Error.PARSE_ERROR( | |
96 | poserr("unbalanced braces")); | |
97 | if (token == "}") { | |
98 | if (i != 0) | |
99 | throw new Mal.Error.PARSE_ERROR( | |
100 | poserr("odd number of elements in hashmap")); | |
101 | ||
102 | next(); // eat end token | |
103 | return map; | |
104 | } | |
105 | ||
106 | vals[i] = read_form(); | |
107 | } | |
108 | map.insert(vals[0], vals[1]); | |
109 | } | |
110 | } | |
111 | ||
112 | public Mal.Val read_atom() throws Error { | |
113 | string token = peek(); | |
114 | next(); | |
115 | if (tok_num.match(token)) | |
116 | return new Mal.Num(int64.parse(token)); | |
117 | if (token.has_prefix(":")) | |
118 | return new Mal.Keyword(token[1:token.length]); | |
119 | if (token.has_prefix("\"")) { | |
f59776b1 | 120 | if (token.length < 2 || !token.has_suffix("\"")) |
213a3288 ST |
121 | throw new Mal.Error.BAD_TOKEN( |
122 | poserr("end of input in mid-string")); | |
123 | ||
124 | token = token[1:token.length-1]; | |
125 | ||
126 | int end = 0; | |
127 | int pos = 0; | |
128 | string strval = ""; | |
129 | ||
130 | while ((pos = token.index_of ("\\", end)) != -1) { | |
131 | strval += token[end:pos]; | |
f59776b1 ST |
132 | if (token.length - pos < 2) |
133 | throw new Mal.Error.BAD_TOKEN( | |
134 | poserr("end of input in mid-string")); | |
213a3288 ST |
135 | switch (token[pos:pos+2]) { |
136 | case "\\\\": | |
137 | strval += "\\"; break; | |
138 | case "\\\"": | |
139 | strval += "\""; break; | |
140 | case "\\n": | |
141 | strval += "\n"; break; | |
142 | } | |
143 | end = pos+2; | |
144 | } | |
145 | strval += token[end:token.length]; | |
146 | return new Mal.String(strval); | |
147 | } | |
148 | switch (token) { | |
149 | case "nil": | |
150 | return new Mal.Nil(); | |
151 | case "true": | |
152 | return new Mal.Bool(true); | |
153 | case "false": | |
154 | return new Mal.Bool(false); | |
155 | case "[": | |
156 | return new Mal.Vector.from_list(read_list("]")); | |
157 | case "{": | |
158 | return read_hashmap(); | |
159 | case "'": | |
160 | case "`": | |
161 | case "~": | |
162 | case "~@": | |
163 | case "@": | |
164 | var list = new GLib.List<Mal.Val>(); | |
165 | list.append(new Mal.Sym( | |
166 | token == "'" ? "quote" : | |
167 | token == "`" ? "quasiquote" : | |
168 | token == "~" ? "unquote" : | |
169 | token == "~@" ? "splice-unquote" : "deref")); | |
170 | list.append(read_form()); | |
171 | return new Mal.List(list); | |
172 | case "^": | |
173 | var list = new GLib.List<Mal.Val>(); | |
174 | list.append(new Mal.Sym("with-meta")); | |
175 | var metadata = read_form(); | |
176 | list.append(read_form()); | |
177 | list.append(metadata); | |
178 | return new Mal.List(list); | |
179 | default: | |
180 | return new Mal.Sym(token); | |
181 | } | |
182 | } | |
183 | } |