Mercurial > repos > tabletprog
comparison parser.js @ 8:04ae32e91598
Move compiler and test page related code out of parser.js
author | Mike Pavone <pavone@retrodev.com> |
---|---|
date | Wed, 21 Mar 2012 20:33:39 -0700 |
parents | 8af72f11714e |
children | 132c7756860e |
comparison
equal
deleted
inserted
replaced
7:8af72f11714e | 8:04ae32e91598 |
---|---|
1 var mainModule; | |
2 function toobj(val) | |
3 { | |
4 switch(typeof val) | |
5 { | |
6 case 'boolean': | |
7 if(val) { | |
8 return mainModule.strue; | |
9 } else { | |
10 return mainModule.sfalse; | |
11 } | |
12 case 'number': | |
13 return mainModule.snumber(val); | |
14 } | |
15 throw new Error("can't make val into object"); | |
16 } | |
17 | |
18 function indent(str) | |
19 { | |
20 return str.split('\n').join('\n\t'); | |
21 } | |
22 | |
23 function osymbols(parent) | |
24 { | |
25 this.parent = parent; | |
26 this.names = {}; | |
27 this.lastname = null; | |
28 } | |
29 osymbols.prototype.find = function(name) { | |
30 if (name in this.names) { | |
31 if (this.names[name] instanceof funcall && this.names[name].name == 'foreign:') { | |
32 return { | |
33 type: 'foreign', | |
34 def: this.names[name] | |
35 }; | |
36 } | |
37 return { | |
38 type: 'self', | |
39 def: this.names[name], | |
40 }; | |
41 } else if(this.parent) { | |
42 var ret = this.parent.find(name); | |
43 if (ret) { | |
44 if(ret.type == 'self') { | |
45 ret.type = 'parent'; | |
46 ret.depth = 1; | |
47 } else if(ret.type == 'parent') { | |
48 ret.depth++; | |
49 } | |
50 } | |
51 return ret; | |
52 } | |
53 return null; | |
54 }; | |
55 osymbols.prototype.defineMsg = function(name, def) { | |
56 this.lastname = name; | |
57 this.names[name] = def; | |
58 } | |
59 osymbols.prototype.parentObject = function() { | |
60 if (!this.parent) { | |
61 return 'null'; | |
62 } | |
63 return 'this'; | |
64 } | |
65 | |
66 function lsymbols(parent) | |
67 { | |
68 this.parent = parent; | |
69 this.names = {}; | |
70 this.lastname = null; | |
71 this.needsSelfVar = false; | |
72 } | |
73 lsymbols.prototype.find = function(name) { | |
74 if (name in this.names) { | |
75 if (this.names[name] instanceof funcall && this.names[name].name == 'foreign:') { | |
76 return { | |
77 type: 'foreign', | |
78 def: this.names[name] | |
79 }; | |
80 } | |
81 return { | |
82 type: 'local', | |
83 def: this.names[name] | |
84 }; | |
85 } else if(this.parent) { | |
86 var ret = this.parent.find(name); | |
87 if (ret && ret.type == 'local') { | |
88 ret.type = 'upvar'; | |
89 } | |
90 return ret; | |
91 } | |
92 return null; | |
93 }; | |
94 lsymbols.prototype.defineVar = function(name, def) { | |
95 this.lastname = name; | |
96 this.names[name] = def; | |
97 }; | |
98 lsymbols.prototype.selfVar = function() { | |
99 if (this.parent && this.parent instanceof lsymbols) { | |
100 this.parent.needsSelf(); | |
101 return 'self'; | |
102 } else { | |
103 return 'this'; | |
104 } | |
105 }; | |
106 lsymbols.prototype.needsSelf = function() { | |
107 if (this.parent && this.parent instanceof lsymbols) { | |
108 this.parent.needsSelf(); | |
109 } else { | |
110 this.needsSelfVar = true; | |
111 } | |
112 }; | |
113 | 1 |
114 function op(left, op, right) | 2 function op(left, op, right) |
115 { | 3 { |
116 this.left = left; | 4 this.left = left; |
117 this.op = op; | 5 this.op = op; |
118 this.right = right; | 6 this.right = right; |
119 } | 7 } |
120 op.prototype.toJS = function(symbols, isReceiver) { | |
121 var ret = '(' + this.left.toJS(symbols) +' '+ (this.op == '=' ? '==' : this.op) +' '+ this.right.toJS(symbols) + ')'; | |
122 if (isReceiver) { | |
123 ret = 'toobj' + ret; | |
124 } | |
125 return ret; | |
126 }; | |
127 | 8 |
128 function symbol(name) | 9 function symbol(name) |
129 { | 10 { |
130 this.name = name; | 11 this.name = name; |
131 } | |
132 symbol.prototype.toJS = function(symbols) { | |
133 var name = this.cleanName(); | |
134 if (name == 'self') { | |
135 return symbols.selfVar(); | |
136 } | |
137 name = name.replace("_", "UN_").replace(":", "CN_").replace("!", "EX_").replace('?', 'QS_').replace('@', 'AT_'); | |
138 var reserved = {'true': true, 'false': true, 'this': true, 'if': true, 'else': true, 'NaN': true}; | |
139 if (name in reserved) { | |
140 name = 's' + name; | |
141 } | |
142 return name; | |
143 } | 12 } |
144 symbol.prototype.cleanName = function() { | 13 symbol.prototype.cleanName = function() { |
145 return this.name[0] == ':' ? this.name.substr(1) : this.name; | 14 return this.name[0] == ':' ? this.name.substr(1) : this.name; |
146 } | 15 } |
147 | 16 |
148 function intlit(val) | 17 function intlit(val) |
149 { | 18 { |
150 this.val = val; | 19 this.val = val; |
151 } | 20 } |
152 intlit.prototype.toJS = function(symbols) { | |
153 return this.val.toString(); | |
154 } | |
155 | 21 |
156 function floatlit(val) | 22 function floatlit(val) |
157 { | 23 { |
158 this.val = val; | 24 this.val = val; |
159 } | 25 } |
160 floatlit.prototype.toJS = function(symbols) { | |
161 return this.val.toString(); | |
162 } | |
163 | 26 |
164 function strlit(val) | 27 function strlit(val) |
165 { | 28 { |
166 this.val = val; | 29 this.val = val; |
167 } | |
168 strlit.prototype.toJS = function(symbols) { | |
169 console.log('string:', this.val); | |
170 return '"' + this.val.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\r', '\\r') + '"'; | |
171 } | 30 } |
172 | 31 |
173 function funcall(name, args) | 32 function funcall(name, args) |
174 { | 33 { |
175 this.name = name; | 34 this.name = name; |
176 this.args = args; | 35 this.args = args; |
177 this.receiver = null; | 36 this.receiver = null; |
178 } | 37 } |
179 funcall.prototype.toJS = function(symbols) { | |
180 var name = this.name[this.name.length-1] == ':' ? this.name.substr(0, this.name.length-1) : this.name; | |
181 var args = this.args.slice(0, this.args.length); | |
182 if (this.receiver) { | |
183 args.splice(0, 0, this.receiver); | |
184 } | |
185 var funinfo = symbols.find(name); | |
186 if (!funinfo) { | |
187 var receiver = args[0]; | |
188 args.splice(0, 1); | |
189 for (var i in args) { | |
190 args[i] = args[i].toJS(symbols); | |
191 } | |
192 return receiver.toJS(symbols, true) + '.' + (new symbol(name)).toJS(symbols) + '(' + args.join(', ') + ')'; | |
193 } | |
194 switch(funinfo.type) | |
195 { | |
196 case 'self': | |
197 if (args.length < funinfo.def.args.length || funinfo.def.args[0].name != 'self') { | |
198 var receiver = new symbol('self'); | |
199 } else { | |
200 var receiver = args[0]; | |
201 args.splice(0, 1); | |
202 } | |
203 for (var i in args) { | |
204 args[i] = args[i].toJS(symbols); | |
205 } | |
206 return receiver.toJS(symbols, true) + '.' + (new symbol(name)).toJS(symbols) + '(' + args.join(', ') + ')'; | |
207 case 'parent': | |
208 var ret = 'this'; | |
209 for (var i = 0; i < funinfo.depth; ++i) { | |
210 ret += '.parent'; | |
211 } | |
212 for (var i in args) { | |
213 args[i] = args[i].toJS(symbols); | |
214 } | |
215 ret += (new symbol(name)).toJS(symbols) + '(' + args.join(', ') + ')'; | |
216 return ret; | |
217 case 'local': | |
218 case 'upvar': | |
219 case 'foreign': | |
220 for (var i in args) { | |
221 args[i] = args[i].toJS(symbols); | |
222 } | |
223 return (new symbol(name)).toJS(symbols) + '(' + args.join(', ') + ')'; | |
224 } | |
225 } | |
226 | 38 |
227 function object(messages) | 39 function object(messages) |
228 { | 40 { |
229 this.messages = messages; | 41 this.messages = messages; |
230 } | |
231 object.prototype.toJS = function(symbols) { | |
232 var messages = this.messages; | |
233 symbols = new osymbols(symbols); | |
234 var compiled = [] | |
235 for (var i in messages) { | |
236 var js = messages[i].toJSObject(symbols); | |
237 if (js) { | |
238 compiled.push(indent(js)); | |
239 } | |
240 } | |
241 return '{\n\tparent: ' + symbols.parentObject() + ',\n\t' + compiled.join(',\n\t') + '\n}'; | |
242 } | |
243 | |
244 object.prototype.toJSModule = function() { | |
245 return '(function () {\n\tvar module = ' + indent(this.toJS(null)) + ';\n\treturn module;\n})' | |
246 } | 42 } |
247 | 43 |
248 function lambda(args, expressions) | 44 function lambda(args, expressions) |
249 { | 45 { |
250 this.args = args; | 46 this.args = args; |
251 this.expressions = expressions; | 47 this.expressions = expressions; |
252 } | 48 } |
253 lambda.prototype.toJS = function(symbols) { | |
254 var args = this.args ? this.args.slice(0, this.args.length) : []; | |
255 if (args.length && args[0].cleanName() == 'self') { | |
256 args.splice(0, 1); | |
257 } | |
258 var exprs = this.expressions; | |
259 symbols = new lsymbols(symbols); | |
260 for (var i in args) { | |
261 symbols.defineVar(args[i].cleanName(), null); | |
262 args[i] = args[i].toJS(symbols); | |
263 } | |
264 var compiled = [] | |
265 for (var i in exprs) { | |
266 var js = exprs[i].toJS(symbols); | |
267 if (js) { | |
268 compiled.push(indent(js)); | |
269 } | |
270 } | |
271 exprs = compiled; | |
272 if (exprs.length) { | |
273 exprs[exprs.length-1] = 'return ' + exprs[exprs.length-1] + ';'; | |
274 } | |
275 return 'function (' + args.join(', ') + ') {\n\t' + (symbols.needsSelfVar ? 'var self = this;\n\t' : '') + exprs.join(';\n\t') + '\n}' | |
276 }; | |
277 lambda.prototype.toJSModule = lambda.prototype.toJS | |
278 | 49 |
279 function assignment(sym, expr) | 50 function assignment(sym, expr) |
280 { | 51 { |
281 this.symbol = sym; | 52 this.symbol = sym; |
282 this.expression = expr; | 53 this.expression = expr; |
283 } | 54 } |
284 assignment.prototype.toJS = function(symbols) { | |
285 var existing = symbols.find(this.symbol.name); | |
286 var prefix = ''; | |
287 if (!existing) { | |
288 symbols.defineVar(this.symbol.name, this.expression); | |
289 prefix = 'var '; | |
290 } else { | |
291 switch (existing.type) | |
292 { | |
293 case 'self': | |
294 prefix = 'this.'; | |
295 break; | |
296 case 'parent': | |
297 prefix = 'this.'; | |
298 for (var i = 0; i < existing.depth; ++i) { | |
299 prefix += 'parent.'; | |
300 } | |
301 break; | |
302 } | |
303 } | |
304 if (this.expression instanceof funcall && this.expression.name == 'foreign:') { | |
305 return null; | |
306 } | |
307 return prefix + this.symbol.toJS(symbols) + ' = ' + this.expression.toJS(symbols); | |
308 }; | |
309 assignment.prototype.toJSObject = function(symbols) { | |
310 symbols.defineMsg(this.symbol.name, this.expression); | |
311 if (this.expression instanceof funcall && this.expression.name == 'foreign:') { | |
312 return null; | |
313 } | |
314 return this.symbol.toJS(symbols) + ': ' + this.expression.toJS(symbols); | |
315 }; | |
316 | 55 |
317 var grammar = | 56 var grammar = |
318 'start = ws module:(object / lambda) ws { return module; };' + | 57 'start = ws module:(object / lambda) ws { return module; };' + |
319 'ws = ([ \\t\\n\\r] / "//" [^\\n]* "\\n")*;' + | 58 'ws = ([ \\t\\n\\r] / "//" [^\\n]* "\\n")*;' + |
320 'hws = ([ \\t] / "/*" ([^*] / "*" ! "/")* "*/" )*;' + | 59 'hws = ([ \\t] / "/*" ([^*] / "*" ! "/")* "*/" )*;' + |
337 'methcall = receiver:opexpr hws info:methcallrest { info.receiver = receiver; return info; };' + | 76 'methcall = receiver:opexpr hws info:methcallrest { info.receiver = receiver; return info; };' + |
338 'methcallrest = funcall / unarymeth;' + | 77 'methcallrest = funcall / unarymeth;' + |
339 'unarymeth = name:symbol { return new funcall(name.name, []); };'; | 78 'unarymeth = name:symbol { return new funcall(name.name, []); };'; |
340 var parser = PEG.buildParser(grammar); | 79 var parser = PEG.buildParser(grammar); |
341 | 80 |
342 //var parser = PEG.buildParser('start = expr; expr = int; int = digits:[0-9]+ { return parseInt(digits.join(""), 10); }'); | |
343 onReady(function() { | |
344 q('#parse').onclick = function() { | |
345 var text = q('textarea').value; | |
346 try { | |
347 var parsed = parser.parse(text); | |
348 q('pre').innerHTML = text + "\n\n" + JSON.stringify(parsed); | |
349 console.log(parsed); | |
350 } catch(e) { | |
351 q('pre').innerHTML = e.message + '\nLine: ' + e.line + '\nCol: ' + e.column; | |
352 } | |
353 } | |
354 q('#tojs').onclick = function() { | |
355 var text = q('textarea').value; | |
356 //try { | |
357 var parsed = parser.parse(text); | |
358 var js = parsed.toJSModule(); | |
359 q('pre').innerHTML = js; | |
360 console.log(parsed); | |
361 /*} catch(e) { | |
362 q('pre').innerHTML = e.message + '\nLine: ' + e.line + '\nCol: ' + e.column; | |
363 }*/ | |
364 } | |
365 q('#run').onclick = function() { | |
366 var text = q('textarea').value; | |
367 //try { | |
368 var parsed = parser.parse(text); | |
369 var js = parsed.toJSModule(); | |
370 mainModule = eval(js)(); | |
371 q('pre').innerHTML = mainModule.main(); | |
372 /*} catch(e) { | |
373 q('pre').innerHTML = e.message + '\nLine: ' + e.line + '\nCol: ' + e.column; | |
374 }*/ | |
375 } | |
376 }); | |
377 | 81 |