266 lines
6.9 KiB
JavaScript
266 lines
6.9 KiB
JavaScript
{{
|
|
function toFixed(x) {
|
|
if (Math.abs(x) < 1.0) {
|
|
var e = parseInt(x.toString().split('e-')[1]);
|
|
if (e) {
|
|
x *= Math.pow(10,e-1);
|
|
x = '0.' + (new Array(e)).join('0') + x.toString().substring(2);
|
|
}
|
|
} else {
|
|
var e = parseInt(x.toString().split('+')[1]);
|
|
if (e > 20) {
|
|
e -= 20;
|
|
x /= Math.pow(10,e);
|
|
x += (new Array(e+1)).join('0');
|
|
}
|
|
}
|
|
x = x.toString();
|
|
if(!x.includes(".")) {
|
|
x += ".";
|
|
}
|
|
return x;
|
|
}
|
|
|
|
class OpAdd {
|
|
constructor(left, right) {
|
|
this.left = left;
|
|
this.right = right;
|
|
}
|
|
gen(ctx) {
|
|
return "(" + this.left.gen(ctx) + "+" + this.right.gen(ctx) + ")";
|
|
}
|
|
reduce() {
|
|
const left = this.left.reduce();
|
|
const right = this.right.reduce();
|
|
if(left !== null && right !== null) {
|
|
return new Vec2(left.re + right.re, left.im + right.im);
|
|
} else {
|
|
if(left !== null) {
|
|
this.left = left;
|
|
} else if(right !== null) {
|
|
this.right = right;
|
|
}
|
|
return null
|
|
}
|
|
}
|
|
}
|
|
class OpSub {
|
|
constructor(left, right) {
|
|
this.left = left;
|
|
this.right = right;
|
|
}
|
|
gen(ctx) {
|
|
return "(" + this.left.gen(ctx) + "-" + this.right.gen(ctx) + ")";
|
|
}
|
|
reduce() {
|
|
const left = this.left.reduce();
|
|
const right = this.right.reduce();
|
|
if(left !== null && right !== null) {
|
|
return new Vec2(left.re - right.re, left.im - right.im);
|
|
} else {
|
|
if(left !== null) {
|
|
this.left = left;
|
|
} else if(right !== null) {
|
|
this.right = right;
|
|
}
|
|
return null
|
|
}
|
|
}
|
|
}
|
|
class OpNeg {
|
|
constructor(arg) {
|
|
this.arg = arg;
|
|
}
|
|
gen(ctx) {
|
|
return "(-(" + this.arg.gen(ctx) + "))";
|
|
}
|
|
reduce() {
|
|
const arg = this.arg.reduce();
|
|
if(arg !== null) {
|
|
return new Vec2(-arg.re, -arg.im);
|
|
} else {
|
|
return null
|
|
}
|
|
}
|
|
}
|
|
class Fn {
|
|
constructor(name, args, argoff) {
|
|
this.name = name;
|
|
this.args = args;
|
|
this.argoff = argoff;
|
|
}
|
|
gen(ctx) {
|
|
let name = this.name + "$" + this.args.length;
|
|
if(ctx[name] === undefined) {
|
|
throw "Error: the function " + this.name + " does not exist or has the wrong number of arguments";
|
|
}
|
|
if(this.argoff != null) {
|
|
name += "_o";
|
|
}
|
|
if(ctx[name] === undefined) {
|
|
throw "Error: the function " + this.name + " cannot take an argument offset";
|
|
}
|
|
let s = ctx[name] + "(";
|
|
if(this.argoff != null) {
|
|
s += toFixed(this.argoff) + ",";
|
|
}
|
|
for(const a of this.args) {
|
|
s += a.gen(ctx) + ",";
|
|
}
|
|
s = s.slice(0,-1) + ")";
|
|
return s;
|
|
}
|
|
reduce() {
|
|
for(const i in this.args) {
|
|
const a = this.args[i];
|
|
const r = a.reduce();
|
|
if(r !== null) {
|
|
this.args[i] = r;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
class Variable {
|
|
constructor(name) {
|
|
this.name = name;
|
|
}
|
|
gen(ctx) {
|
|
if(ctx[this.name] === undefined) {
|
|
throw "Error: " + this.name + " is not a variable";
|
|
}
|
|
return ctx[this.name];
|
|
}
|
|
reduce() {
|
|
return null;
|
|
}
|
|
}
|
|
class Vec2 {
|
|
constructor(re, im) {
|
|
this.re = re;
|
|
this.im = im;
|
|
}
|
|
gen(ctx) {
|
|
return "vec2(" + toFixed(this.re) + "," + toFixed(this.im) + ")";
|
|
}
|
|
reduce() {
|
|
return this;
|
|
}
|
|
}
|
|
class Conditional {
|
|
constructor(cond) {
|
|
this.cond = cond;
|
|
}
|
|
gen(ctx) {
|
|
let res = "";
|
|
for(const part of this.cond) {
|
|
if(part.length == 2) {
|
|
res += "(" + part[0].gen(ctx) + ").x>0.0 ? (" + part[1].gen(ctx) + ") : ";
|
|
} else {
|
|
res += part[0].gen(ctx);
|
|
}
|
|
}
|
|
return "(" + res + ")";
|
|
}
|
|
reduce() {
|
|
return null;
|
|
}
|
|
}
|
|
}}
|
|
|
|
Level0
|
|
= head:Level1 tail:(_ ("+" / "-") _ Level1)* {
|
|
return tail.reduce(function(result, element) {
|
|
if (element[1] === "+") { return new OpAdd(result, element[3]); }
|
|
if (element[1] === "-") { return new OpSub(result, element[3]); }
|
|
}, head);
|
|
}
|
|
|
|
Level1
|
|
= head:Level2 tail:(_ ("*" / "/") _ Level2)* {
|
|
return tail.reduce(function(result, element) {
|
|
if (element[1] === "*") { return new Fn("*", [result, element[3]]); }
|
|
if (element[1] === "/") { return new Fn("/", [result, element[3]]); }
|
|
}, head);
|
|
}
|
|
|
|
Level2
|
|
= head:(Level3 _ "^" _)* tail:Level3 {
|
|
return head.reverse().reduce(function(result, element) {
|
|
return new Fn("^", [element[0], result]);
|
|
}, tail);
|
|
}
|
|
|
|
Level3
|
|
= _ "(" _ expr:Level0 _ ")" { return expr; }
|
|
/ _ "{" _ cond:ConditionalInner _ "}" { return new Conditional(cond); }
|
|
/ _ name:Name _ argoff:("[" _ ArgOffset _ "]" _)? "(" args:ParameterList ")" {
|
|
if(typeof(name) == "string") {
|
|
return new Fn(name, args.reverse(), argoff == null ? null : argoff[2]);
|
|
} else {
|
|
throw "Error: i is not a function";
|
|
}
|
|
}
|
|
/ _ name:Name {
|
|
if(typeof(name) == "string") {
|
|
return new Variable(name);
|
|
} else {
|
|
return name;
|
|
}
|
|
}
|
|
/ Imag / Real
|
|
/ _ "-" e:Level1 { return new OpNeg(e); }
|
|
|
|
ArgOffset
|
|
= n:Real _ ("tau" / "τ" / "t") {
|
|
console.log("AAA", n.re);
|
|
return n.re * 6.283185307179586;
|
|
}
|
|
/ n:Real _ ("pi" / "π" / "p") {
|
|
return n.re * 3.141592653589793;
|
|
}
|
|
/ n:Real {
|
|
return n.re;
|
|
}
|
|
|
|
ParameterList
|
|
= _ arg:Level0 _ "," lst:ParameterList {
|
|
lst.push(arg);
|
|
return lst;
|
|
}
|
|
/ _ arg:Level0 _ { return [arg]; }
|
|
|
|
ConditionalInner
|
|
= _ cond:Level0 _ ":" _ expr:Level0 _ "," _ rest:ConditionalInner _ { return [].concat([[cond, expr]],rest); }
|
|
/ _ expr:Level0 _ { return [[expr]]; }
|
|
|
|
Name "varname"
|
|
= [a-zA-Z_√Γπτγ]+ {
|
|
if(text() === "i") {
|
|
return new Vec2(0,1);
|
|
} else {
|
|
return text();
|
|
}
|
|
}
|
|
|
|
Real "real"
|
|
= _ Number {
|
|
return new Vec2(parseFloat(text()),0);
|
|
}
|
|
|
|
Imag "imag"
|
|
= _ Number "i" {
|
|
return new Vec2(0,parseFloat(text()));
|
|
}
|
|
|
|
Number "number"
|
|
= [+-]? Digits "." Digits
|
|
/ [+-]? Digits "."
|
|
/ [+-]? "." Digits
|
|
/ [+-]? Digits
|
|
|
|
Digits "digits"
|
|
= [0-9]+
|
|
|
|
_ "whitespace"
|
|
= [ \t\n\r]*
|