-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathParser.js
379 lines (361 loc) · 15.6 KB
/
Parser.js
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
// Parser.js
// (c) 2009 B. Crowell and M. Khafateh, GPL 2 license
//
// This file provides a constructor, com.lightandmatter.Parser.
//
// Design:
// two types of operators: infix binary, prefix unary
// prefix unary (functions) have highest priority
// unary minus is detected based on the token to its left, and is converted to 0-x
// binary operators are left-associative by default
// assignment operator returns lhs, and has side-effect of carrying out assignment
// to do:
// implement inverse trig functions for complex and LC args
// make some operators for operating on an LC number z:
// lc_lambda z = lambda "order of magnitude" operator
// array z = list of lists of q's and a_q's.
// These would be useful in the test suite.
// It's awkward and wrong that the code for evaluating the parse tree is in Parser. It should be in its own module.
var com;
if (!com) {com = {};}
if (!com.lightandmatter) {com.lightandmatter = {};}
com.lightandmatter.Parser =
function () {
this.nn = com.lightandmatter.Num;
// in order from lowest to highest precedence:
this.binop = [
{'name':';','nopromote':true},
{'name':'=','nopromote':true},
{'name':',','nopromote':true},
{'name':'<'},
{'name':'>'},
{'name':'=='},
{'name':'+'},
{'name':'-','unary':false},
{'name':'*'},
{'name':'/'},
{'name':'-','unary':true}, // handles -3^2
{'name':'^','assoc':'right'},
{'name':'-','unary':true} // handles 3^-1
];
// In the following, func points to the function to use on the native javascript number type, cfunc is the one to use for everything else.
// Some functions are implemented only in LeviCivita, not in Complex, so we promote and use the LC function; these are marked with c_as_l.
// Similarly if it's not implemented in Math for native numbers, set func to null.
this.unop = [
{'name':'sin','func':Math.sin,'cfunc':'sin','c_as_l':true},
{'name':'cos','func':Math.cos,'cfunc':'cos','c_as_l':true},
{'name':'tan','func':Math.tan,'cfunc':'tan','c_as_l':true},
{'name':'asin','func':Math.asin},
{'name':'acos','func':Math.acos},
{'name':'atan','func':Math.atan},
{'name':'sinh','func':null,'cfunc':'sinh','c_as_l':true},
{'name':'cosh','func':null,'cfunc':'cosh','c_as_l':true},
{'name':'tanh','func':null,'cfunc':'tanh','c_as_l':true},
{'name':'sqrt','func':Math.sqrt,'cfunc':'sqrt'},
{'name':'abs','func':Math.abs,'cfunc':'abs'},
{'name':'exp','func':Math.exp,'cfunc':'exp'},
{'name':'ln','func':Math.log,'cfunc':'ln'},
{'name':'floor','func':Math.floor,'cfunc':'floor'},
{'name':'ceil','func':Math.ceil,'cfunc':'ceil'},
{'name':'array','cfunc':'to_array'}
];
this.sym = {
'pi':Math.PI,
'i':com.lightandmatter.Complex(0.0,1.0),
'd':com.lightandmatter.LeviCivita(1.0,1.0,[[0,1]])
};
// get and set the variable by calling this function rather that by looking something up in the symbol table:
this.sym_side_effect = {
'levi_civita_n':com.lightandmatter.LeviCivita.change_n,
'precision':function (p) {com.lightandmatter.Num.precision = p;}
};
this.builtin_constants = {}; // They're not allowed to overwrite these.
for (var builtin in this.sym) {
this.builtin_constants[builtin] = true;
}
this.parse = function(tokens,props) {
this.errs = [];
this.tokens = tokens; // used only for error reporting
this.props = props;
if (tokens!==null) {this.tree = this.parse_part(tokens,0,tokens.length-1);}
};
this.parse_part = function(tokens,start,end) {
if (start>=end) {
var p = this.props[start];
if (p!==undefined) {
if (p.name===null && p.num===null && tokens[start]!=='-' && tokens[start]!=='') {this.errs.push(['illegal characters',start,end]);}//final test on - is to allow for unary minus
if (p.num!==null) {
// Lexer will let through malformed stuff like 1.2.3. Check for that.
if (tokens[start].match(/\..*\./)) {this.errs.push(['malformed number with more than one decimal point',start,end]);}
}
}
return ['leaf',tokens[start],p];
}
for (var i=0; i<this.binop.length; i++) {
var b = this.binop[i];
var name = b.name;
var assoc = b.assoc;
var j1 = end;
var step = -1;
if (assoc==='right') {
j1 = start;
step = 1;
}
var paren_depth = 0;
for (var k=0; k<=(end-start); k++) {
var j = j1+k*step;
var tl = null; // token to left
if (j!=start) {tl = tokens[j-1];}
if (tokens[j]==name && paren_depth===0) {
var is_unary_minus = name=='-' && ((j==start) || (/[=\+\-\*\/\^\(]/.test(tl))); // the kludgy regex is needed for, e.g., 2*-3
var match = (name!='-') || (is_unary_minus==b.unary);
var function_def = ''; // ='f' for function definition f x : x^2
var bound_var = '';
var clobbered; // normally stays undefined; if defining f x, x may also be a defined variable, so save its value
if (name=='=' && j==start+2) {
function_def=tokens[start];
bound_var=tokens[start+1];
this.delete_function(function_def);
this.unop.unshift({'name':function_def});
clobbered = this.sym.bound_var;
this.sym.bound_var = null; // null marks it as bound var
}
var lhs = this.parse_part(tokens,start,j-1);
var rhs = this.parse_part(tokens,j+1,end);
if (function_def!=='') {
if (clobbered!==undefined) {this.sym.bound_var=clobbered;}
lhs = ['leaf',function_def,{'name':function_def}];
rhs = this.lambdafy(bound_var,rhs);
}
if (match) {
var skip = false;
if (is_unary_minus) {
if (j==start) { // 2*-3
lhs=['leaf','0',{'name':null,'num':0}];
}
else { // 3^-1
skip = true;
}
}
if (! skip) {return ['binop',name,lhs,rhs,start,end,j];}
}
}
if ((step==1 && this.is_left_paren(tokens[j])) || (step==-1 && this.is_right_paren(tokens[j]))) {paren_depth++;}
if ((step==1 && this.is_right_paren(tokens[j])) || (step==-1 && this.is_left_paren(tokens[j])) ) {paren_depth--;}
}
if (paren_depth !== 0) {this.errs.push(['unbalanced parentheses',start,end]); return null;}
}// end of loop over binary operators
if (this.is_left_paren(tokens[start]) && this.is_right_paren(tokens[end])) {
if (this.paren_style(tokens[start])!=this.paren_style(tokens[end])) {this.errs.push(['mismatched style of parentheses'],start,end);}
var inside = this.parse_part(tokens,start+1,end-1);
if (inside[0]=='binop' && inside[1]==',') {if (inside[7]===undefined) {inside[7]={};} inside[7].closed_array=true;}
return inside;
}
for (var i=0; i<this.unop.length; i++) {
var u = this.unop[i];
var name = u.name;
if (tokens[start]==name) {return ['unop',name,this.parse_part(tokens,start+1,end),start,end];}
}
this.errs.push(['syntax error',start,end]);
return null;
};
this.to_debug_string = function() {
return this.tokens.join(',')+' -- '+this.tree_to_debug_string(this.tree);
};
this.tree_to_debug_string = function(tree) {
var what = tree[0];
if (what==='leaf') {return 'l('+tree[1]+','+this.props_to_string(tree[2])+')';}
if (what==='error') {return 'error in substring from token '+tree[1]+' to token '+tree[2];}
if (what==='binop') {return 'b('+tree[1]+','+this.tree_to_debug_string(tree[2])+','+this.tree_to_debug_string(tree[3])+')';}
if (what==='unop') {return 'u('+tree[1]+','+this.tree_to_debug_string(tree[2])+')';}
return 'unrecognized:'+tree+'?';
};
this.toString = function() {
var s = this.tree_to_string(this.tree); // evaluates it, and may also cause errors
if (this.errs.length>0) {
var e = '';
for (var i in this.errs) {
var ee = this.errs[i];
var t = '';
if (ee[1]!==undefined && ee[2]!==undefined) {t = ': "' +this.tokens.slice(ee[1],ee[2]+1).join(' ')+'"';}
e = e + '<p>Error: ' + ee[0] + t +'</p>';
}
return e;
}
if (s===null) {s='';}
if (typeof(s)=='object' && s instanceof Array) {
// Intervene to keep it from flattening arrays of arrays, which is what toString() normally does on arrays.
s = this.array_to_string(s);
}
return com.lightandmatter.Num.convert_to_string(s);
};
this.array_to_string = function(a) {
if (typeof(a)=='object' && a instanceof Array) {
var b = [];
for (var i in a) {
b.push(this.array_to_string(a[i]));
}
return '['+b.join(',')+']';
}
else {
return a.toString();
}
};
this.tree_to_string = function(tree) { // badly named, doesn't really compute string
if (tree===null || tree===undefined) {return null;}
var what = tree[0];
if (what==='leaf') {
var props = tree[2];
if (props!==undefined && props.num!==null) {return props.num*1;}
if (props!==undefined && props.name!==null) {
var name = props.name;
var value = this.sym[name];
if (this.sym_side_effect[name]!==undefined) {value=this.sym_side_effect[name]();}
if (value===undefined) {this.errs.push(["undefined variable: \""+name+'"']); return null;}
return value;
}
return null;
}
if (what==='error') {return null;}
if (what==='binop') {
var op = tree[1];
var lhs = tree[2];
var rhs = tree[3];
var start = tree[4]; // for error messages, if necessary
var end = tree[5];
var ca = false;
if (lhs[7]!==undefined && lhs[7].closed_array===true) {ca=true;}
var function_def = op=='=' && rhs[0]=='lambda';
var a;
var b;
if (!function_def) {
// Order of evaluation can be important:
// In an assignment, don't even try to evaluate the left-hand side; even if we tried to evaluate it after the rhs, it would cause errors if
// if was a function definition, because the dummy var is undefined.
// In a ; operator, we need to evaluate the lhs first, because it may have side-effects such as assignment.
if (op!='=') {a = this.tree_to_string(lhs);}
b = this.tree_to_string(rhs);
if (b===null) {this.errs.push(["Nothing is on the right-hand side of the operator "+op+" in the expression ",start,end]); return null;}
}
if (op=='=') {
if (lhs[0] != 'leaf' || lhs[2].name===null) {
this.errs.push(["The left-hand side of the assignment statement is not a valid name for a variable or function."],start,end);
return null;
}
var n = lhs[2].name;
if (this.builtin_constants[n]===true) {
this.errs.push(["Illegal assignment into built-in constant "+n,start,end]);
return null;
}
if (!function_def) {
if (this.sym_side_effect[n]!==undefined) {this.sym_side_effect[n](b);}
this.sym[n] = b;
return b;
}
else {
this.delete_function(n); // delete the placeholder that only had a name field but no userfunc
this.unop.unshift({'name':n,'userfunc':rhs}); // ...and replace it with a full entry
return null;
}
}// endif :
// If it's not : operator, we fall through to here.
return this.nn.binop(op,a,b,{'nopromote':this.find_binop(op).nopromote,'closed_array':ca});
}
if (what==='unop') {
var f = tree[1];
var start = tree[3]; // for error messages, if necessary
var end = tree[4];
var x = this.tree_to_string(tree[2]);
var t = this.nn.num_type(x);
if (x===null) {return null;}
for (var i in this.unop) {
if (this.unop[i].name==f) {
var ff;
if (this.unop[i].userfunc!==undefined) {
ff = this.unop[i].userfunc;
var b = ff[1]; // bound var, will be set to x; has a collision-avoiding name like 'bound_var___'
var e = ff[2]; // defining expression
var clobber = this.sym[b]; // may already have a value, if functions are nested
this.sym[b] = x;
var y = this.tree_to_string(e);
delete this.sym.b;
if (clobber!==undefined) {this.sym[b]=clobber;}
return y;
}
// Some functions are implemented only in LeviCivita, so promote and use the LC function:
var as_lc = ((this.unop[i].c_as_l===true && t=='c') || (t=='r' && this.unop[i].func===null));
if (as_lc) {x = com.lightandmatter.LeviCivita(x,0,[[0,1]]); t='l';}
if (t=='r' && (isNaN(x) || !isFinite(x))) {return NaN;}
if (t=='r') {
ff = this.unop[i].func;
}
else {
ff = x[this.unop[i].cfunc];
}
var dt = this.nn.describe_type(t);
if (ff===undefined) {this.errs.push(["The function "+f+" is not implemented for variables of type "+dt,start,end]); return null;}
var y;
try {
y = ff(x);
}
catch (foo) {}
if (y!==undefined && y!==null && !(typeof(y)=='number' && (isNaN(y) || !isFinite(y)))) {
return y;
}
else {
if (t=='r') { // happens, e.g., ln(-1) or sqrt(-1) or asin(3)
x = com.lightandmatter.Complex(x,0.0);
ff = x[this.unop[i].cfunc];
try {return ff(x);} catch(foo) {this.errs.push(["Error evaluating function "+f+" for type "+dt],start,end);}
}
else {
return NaN;
}
}
}
}
}
return null;
};
this.is_left_paren = function (c) { return c=='(' || c=='[' || c=='{'; };
this.is_right_paren = function (c) { return c==')' || c==']' || c=='}'; };
this.paren_style = function(c) {
if (c=='(' || c==')') {return 1;}
if (c=='[' || c==']') {return 2;}
if (c=='{' || c=='}') {return 3;}
return null;
};
this.find_binop = function (op) {
for (var i in this.binop) {
if (this.binop[i].name==op) {return this.binop[i];}
}
return undefined;
};
this.props_to_string = function(props) {
if (props===undefined) {return null;}
return 'name='+props.name+', num='+props.num;
};
this.delete_function = function(f) {
for (var i in this.unop) {
if (this.unop[i].name==f) {this.unop.splice(i,i); return;}
}
};
this.lambdafy = function(x,tree) {
var b = 'bound_var___';
return ['lambda',b,this.rename_var(tree,x,b)];
};
this.rename_var = function(tree,x,y) {
var what = tree[0];
if (what==='leaf') {
if (tree[1]!=x) {return tree;}
return [tree[0],y,{'name':y,'num':null}];
}
if (what==='error') {return tree;}
if (what==='binop') {return [tree[0],tree[1],this.rename_var(tree[2],x,y),this.rename_var(tree[3],x,y)];}
if (what==='unop') {return [tree[0],tree[1],this.rename_var(tree[2],x,y)];}
return undefined;
};
var debug = function(s) {
document.getElementById("debug").innerHTML=document.getElementById("debug").innerHTML+' '+s+' ';
};
};