Refactor towards a working QuickJS version
dkl9

dkl9 commited on 2023-196 12:46:40
Showing 10 changed files, with 242 additions and 41 deletions.


Code is now organised in files in a module-ish format, reducing
circular and unclear dependencies. You can run `qjs qjs_rtensor.js`
to get RTensor in the terminal. Things are probably still very buggy.
... ...
@@ -1,16 +1,27 @@
1 1
 "use strict";
2 2
 
3
+import {fts, OPS} from "./rtensor_common.js";
4
+import {} from "./types/bigint.js";
5
+import {Complex} from "./types/complex.js";
6
+import {MathsFunc} from "./types/mathsfunc.js";
7
+import {} from "./types/real.js";
8
+
3 9
 const isArray = x => (typeof x == "object" && x.constructor == Array);
4 10
 const NUMERIC_TYPES = {
5 11
     integer: BigInt,
6 12
     complex: Complex,
7 13
     real: Number,
8 14
 };
9
-const nti = (new URLSearchParams(window.location.search)).get("nt") || "a";
10
-const NumericType = NUMERIC_TYPES[nti] || Number;
15
+let nti;
16
+try {
17
+    nti = (new URLSearchParams(window.location.search)).get("nt") || "a";
18
+} catch (_e) {
19
+    nti = scriptArgs[1] || "a";
20
+}
21
+export const NumericType = NUMERIC_TYPES[nti] || Number;
11 22
 
12 23
 // a: tensor, b: tensor, o: binary function of numbers
13
-function vecOp(a, b, o) {
24
+export function vecOp(a, b, o) {
14 25
     const tra = tensorRank(a);
15 26
     const trb = tensorRank(b);
16 27
     if (tra == trb && tra >= 1 && a.length == b.length) {
... ...
@@ -25,7 +36,7 @@ function vecOp(a, b, o) {
25 36
 }
26 37
 
27 38
 // true iff any elements of v is null or NaN
28
-function anyNaN(v) {
39
+export function anyNaN(v) {
29 40
     return v.map ?
30 41
         v.map(x => anyNaN(x)).reduce((a, b) => (a || b), false) :
31 42
         (v == null || (typeof v == "number" && isNaN(v)) || (v.invalid && v.invalid()));
... ...
@@ -38,7 +49,7 @@ function tensorRank(t) {
38 49
 }
39 50
 
40 51
 // node in an abstract syntax tree, as produced by NewParser
41
-class AST {
52
+export class AST {
42 53
     constructor(ts, l, m, r, len) {
43 54
         switch (ts) {
44 55
             // identifier: l == Token (tt == 1), m, r == undefined
... ...
@@ -1,6 +1,9 @@
1 1
 "use strict";
2 2
 // maths parser, by dkl9, 2021-05 to 2021-07, used as part of rtensor
3 3
 
4
+import {OPS} from "./rtensor_common.js";
5
+import {AST} from "./maths_ast.js";
6
+
4 7
 // whitespace characters
5 8
 const WSPACE = [" ", "\t", "\n"];
6 9
 // characters for starting identifiers
... ...
@@ -21,13 +24,6 @@ const LCHAR = [];
21 24
 for (let c = 0; c <= 9; c++) {
22 25
     LCHAR.push(String.fromCodePoint(c + 48));
23 26
 }
24
-// operator text
25
-const OPS = [
26
-    "+",  "-",  "*",  "/",
27
-    "^",  "\\", "==", "-/",
28
-    ",",  "<",  "=>", "=",
29
-    "..",
30
-];
31 27
 // precedence of binary operators (greater == lower)
32 28
 const PREC = [
33 29
     4,  4,  3,  3,
... ...
@@ -79,7 +75,7 @@ const normIntLit = s => (s.replace(/^0+/, "") || "0");
79 75
 // 8 (right square bracket)
80 76
 // value depends on tt: 1 => ident text, 2 => int value, 3 => operator id
81 77
 // 4, 5, 6, 7, 8 => undefined
82
-class Token {
78
+export class Token {
83 79
     constructor(tt, text) {
84 80
         this.tt = tt;
85 81
         switch (this.tt) {
... ...
@@ -111,7 +107,7 @@ class Token {
111 107
 }
112 108
 
113 109
 // source text surrounded by an interface to provide tokens from it on demand
114
-class TokenStream {
110
+export class TokenStream {
115 111
     constructor(source) {
116 112
         this.toks = [];
117 113
         this.tind = -1;
... ...
@@ -226,7 +222,7 @@ class TokenStream {
226 222
 // each function parses a particular structure,
227 223
 // returning an AST (with .len, measured in tokens) if successful
228 224
 // or null if failed
229
-class NewParser {
225
+export class NewParser {
230 226
     // statement -> expr[9]
231 227
     static statement(stream) {
232 228
         let tc = 0;
... ...
@@ -0,0 +1,158 @@
1
+import {fts} from "./rtensor_common.js";
2
+import {AST} from "./maths_ast.js";
3
+import {TokenStream, NewParser} from "./maths_parser.js";
4
+import {BUILTIN_FUNCS} from "./rtensor_lib.js";
5
+import * as std from "std";
6
+import * as os from "os";
7
+
8
+const calcVars = Object.fromEntries(Object.entries(BUILTIN_FUNCS));
9
+calcVars["_"] = [];
10
+const pastInps = [];
11
+var histPos = 0;
12
+
13
+function evalInp(inpText) {
14
+    let disp;
15
+    let perr = false;
16
+    const st = inpText.split(";");
17
+    for (let i = 0; i < st.length; i++) {
18
+        const wt = st[i];
19
+        const lexer = new TokenStream(wt);
20
+        const ast = NewParser.statement(lexer);
21
+        if (ast === null) {
22
+            disp = "syntax error";
23
+            break;
24
+        } else {
25
+            perr = perr || (lexer.tind != lexer.toks.length - 1);
26
+            try {
27
+                const res = ast.evaluate(calcVars, false);
28
+                if (res != null) { calcVars["_"] = res; }
29
+                const svn = "_ans_" + pastInps.length.toString();
30
+                calcVars[svn] = res;
31
+                disp = (res == null) ? "evaluation error" : (svn + " = " + fts(res));
32
+            } catch (err) {
33
+                disp = err;
34
+            }
35
+        }
36
+    }
37
+    return disp + (perr ? "\npossible syntax error" : "");
38
+}
39
+
40
+function getEscSeq(fhs) {
41
+    let r = "";
42
+    while (/[A-H]$/.exec(r) == null) {
43
+        r += String.fromCharCode(fhs.getByte());
44
+    }
45
+    return r;
46
+}
47
+
48
+// returns Array [newt, newwi]
49
+function histFetch(oldt) {
50
+    const t = pastInps[histPos] || oldt;
51
+    const wi = t.length;
52
+    std.out.puts("\r\x1b[" + PRLEN + "C\x1b[K" + t +
53
+        "\r\x1b[" + (wi + PRLEN) + "C");
54
+    return [t, wi];
55
+}
56
+
57
+function wf(t) {
58
+    std.out.puts(t);
59
+    std.out.flush();
60
+}
61
+
62
+if (os.isatty(std.in)) {
63
+    os.ttySetRaw(std.in);
64
+}
65
+
66
+const PROMPT = "> ";
67
+const PRLEN = PROMPT.length;
68
+
69
+let t;
70
+while (true) {
71
+    if (os.isatty(std.in)) {
72
+        wf(PROMPT);
73
+        t = "";
74
+        let wi = 0;
75
+        let nmi = true;
76
+        while (nmi) {
77
+            const nc = std.in.getByte();
78
+            switch (nc) {
79
+                // ^D
80
+                case 4:
81
+                    t = "quit";
82
+                    nmi = false;
83
+                    break;
84
+                // escape
85
+                case 27:
86
+                    const es = getEscSeq(std.in);
87
+                    // up arrow
88
+                    if (es == "[A") {
89
+                        histPos--;
90
+                        const ra = histFetch(t);
91
+                        t = ra[0]; wi = ra[1];
92
+                        wf("\r\x1b[" + PRLEN + "C" + t);
93
+                    // down arrow
94
+                    } else if (es == "[B") {
95
+                        histPos++;
96
+                        const ra = histFetch(t);
97
+                        t = ra[0]; wi = ra[1];
98
+                    // right arrow
99
+                    } else if (es == "[C") {
100
+                        wi = (wi == t.length) ? wi : (wi + 1);
101
+                        wf("\x1b[C");
102
+                    // left arrow
103
+                    } else if (es == "[D") {
104
+                        wi = (wi == 0) ? wi : (wi - 1);
105
+                        wf("\x1b[D");
106
+                    // ctrl-right arrow
107
+                    } else if (es == "[1;5C") {
108
+                        //
109
+                    // ctrl-left arrow
110
+                    } else if (es == "[1;5D") {
111
+                        //
112
+                    // home
113
+                    } else if (es == "[H") {
114
+                        wi = 0;
115
+                        wf("\r\x1b[" + PRLEN + "C");
116
+                    // end
117
+                    } else if (es == "[F") {
118
+                        wi = t.length;
119
+                        wf("\r\x1b[" + (wi + PRLEN) + "C");
120
+                    }
121
+                    break;
122
+                // backspace
123
+                case 8:
124
+                case 127:
125
+                    if (wi > 0) {
126
+                        wi--;
127
+                        t = t.slice(0, wi) + t.slice(wi + 1);
128
+                        wf("\x1b[D\x1b[K" +
129
+                            (t.slice(wi) ? (t.slice(wi) + "\x1b[" + t.slice(wi).length + "D") : ""));
130
+                    } else {
131
+                        wf("\x07");
132
+                    }
133
+                    break;
134
+                // newline
135
+                case 13:
136
+                    nmi = false;
137
+                    wf("\n");
138
+                    break;
139
+                // printable?
140
+                default:
141
+                    wf(String.fromCharCode(nc));
142
+                    t = t.slice(0, wi) + String.fromCharCode(nc) + t.slice(wi);
143
+                    wi++;
144
+            }
145
+        }
146
+    } else {
147
+        t = std.in.getline();
148
+    }
149
+    pastInps.push(t);
150
+    histPos = pastInps.length;
151
+    if (t == "quit" || t == null) {
152
+        break;
153
+    }
154
+    if (t == "") {
155
+        continue;
156
+    }
157
+    console.log(evalInp(t));
158
+}
... ...
@@ -0,0 +1,29 @@
1
+"use strict";
2
+
3
+// operator text
4
+export const OPS = [
5
+    "+",  "-",  "*",  "/",
6
+    "^",  "\\", "==", "-/",
7
+    ",",  "<",  "=>", "=",
8
+    "..",
9
+];
10
+
11
+// stringify a vecalc value for presentation
12
+export function fts(v) {
13
+    if (v == null) {
14
+        return "nonexistent";
15
+    } else if (v.constructor === Array) {
16
+        switch (v.length) {
17
+            case 0:
18
+                return "[ ]";
19
+            default:
20
+                return "[" + v.map(x => fts(x)).join(", ") + "]";
21
+        }
22
+    } else if (v.constructor.name === "MathsFunc" || v.constructor === Function) {
23
+        return "function";
24
+    } else if (v.constructor === null) {
25
+        return "null";
26
+    } else {
27
+        return v.toString();
28
+    }
29
+}
... ...
@@ -1,6 +1,12 @@
1 1
 "use strict";
2 2
 
3
-const IDENT_AST = new AST(1, new Token(1, ""));
3
+import {fts} from "./rtensor_common.js";
4
+import {Complex} from "./types/complex.js";
5
+import {MathsFunc} from "./types/mathsfunc.js";
6
+import {AST, vecOp, NumericType, anyNaN} from "./maths_ast.js";
7
+import {Token} from "./maths_parser.js";
8
+
9
+export const IDENT_AST = new AST(1, new Token(1, ""));
4 10
 
5 11
 function elemwiseFunc(f) {
6 12
     const ref = (args, de) =>
... ...
@@ -11,7 +17,7 @@ function elemwiseFunc(f) {
11 17
     return new MathsFunc([[[IDENT_AST], ref]], {});
12 18
 }
13 19
 
14
-const BUILTIN_FUNCS = {
20
+export const BUILTIN_FUNCS = {
15 21
     // numeric
16 22
     "abs": elemwiseFunc(x => x.mag()),
17 23
     "floor": elemwiseFunc(x => x.floor()),
... ...
@@ -314,24 +320,4 @@ function dl(v) {
314 320
     return null;
315 321
 }
316 322
 
317
-window.onerror = (m, s, l, c, e) => dl(`${s}:${l}:${c}: ${m}`);
318
-
319
-// stringify a vecalc value for presentation
320
-function fts(v) {
321
-    if (v == null) {
322
-        return "nonexistent";
323
-    } else if (v.constructor === Array) {
324
-        switch (v.length) {
325
-            case 0:
326
-                return "[ ]";
327
-            default:
328
-                return "[" + v.map(x => fts(x)).join(", ") + "]";
329
-        }
330
-    } else if (v.constructor === MathsFunc || v.constructor === Function) {
331
-        return "function";
332
-    } else if (v.constructor === null) {
333
-        return "null";
334
-    } else {
335
-        return v.toString();
336
-    }
337
-}
323
+try { window.onerror = (m, s, l, c, e) => dl(`${s}:${l}:${c}: ${m}`); } catch (_e) {}
... ...
@@ -1,6 +1,6 @@
1 1
 "use strict";
2 2
 
3
-class BasicNumber {
3
+export class BasicNumber {
4 4
     constructor() {}
5 5
 
6 6
     sub(z) {
... ...
@@ -36,3 +36,5 @@ class BasicNumber {
36 36
 
37 37
 // because the prototype fields are nonenumerable argh
38 38
 BasicNumber.keys = ["sub", "div", "pow", "log", "isNeg", "rangeTo", "dir"];
39
+
40
+["zero", "one", "add", "neg", "mul", "recip", "exp", "ln", "sqrt", "sin", "cos", "asin", "acos", "atan", "eq", "lt", "mag", "floor"].forEach(o => BasicNumber.prototype[o] = function() { throw `${o} not defined for current number type`; });
... ...
@@ -1,5 +1,7 @@
1 1
 "use strict";
2 2
 
3
+import {BasicNumber} from "./basic.js";
4
+
3 5
 BasicNumber.keys.forEach(k => (BigInt.prototype[k] = BasicNumber.prototype[k]));
4 6
 
5 7
 BigInt.ify = function(x) {
... ...
@@ -1,6 +1,8 @@
1 1
 "use strict";
2 2
 
3
-class Complex extends BasicNumber {
3
+import {BasicNumber} from "./basic.js";
4
+
5
+export class Complex extends BasicNumber {
4 6
     constructor(x, y) {
5 7
         super();
6 8
         this.re = x;
... ...
@@ -89,6 +91,16 @@ class Complex extends BasicNumber {
89 91
         return this.re == z.re && this.im == z.im;
90 92
     }
91 93
 
94
+    lt(z) {
95
+        if ((((this.re < z.re) && (this.im <= z.im)) || ((this.re <= z.re) && (this.im < z.im))) && (this.mag() < z.mag())) {
96
+            return true;
97
+        } else if ((((this.re > z.re) && (this.im >= z.im)) || ((this.re >= z.re) && (this.im > z.im))) && (this.mag() > z.mag())) {
98
+            return false;
99
+        } else {
100
+            throw `cannot unambiguously compare complex values ${this} and ${z}`;
101
+        }
102
+    }
103
+
92 104
     sqrt() {
93 105
         return this.pow(new Complex(0.5, 0));
94 106
     }
... ...
@@ -1,5 +1,8 @@
1 1
 "use strict";
2 2
 
3
+import {fts} from "../rtensor_common.js";
4
+import {BasicNumber} from "./basic.js";
5
+
3 6
 // check if vectors (Arrays of numbers) a and b are completely equal
4 7
 const vecEq = (a, b) => (a?.eq && b?.eq) ?
5 8
     (a.eq(b)) :
... ...
@@ -11,7 +14,7 @@ const vecEq = (a, b) => (a?.eq && b?.eq) ?
11 14
 // each case is a list of zero or more identifiers or constant values
12 15
 // (in the form of ASTs)
13 16
 // each expr is an expression (wow); more precisely, a function of the variables
14
-class MathsFunc extends BasicNumber {
17
+export class MathsFunc extends BasicNumber {
15 18
     // cems is an Array of Array-pairs of Array of AST and functions
16 19
     // de is an evaluation input in accordance with AST.evaluate
17 20
     constructor(cems, de) {
... ...
@@ -1,5 +1,7 @@
1 1
 "use strict";
2 2
 
3
+import {BasicNumber} from "./basic.js";
4
+
3 5
 BasicNumber.keys.forEach(k => (Number.prototype[k] = BasicNumber.prototype[k]));
4 6
 
5 7
 Number.ify = function(x) {
6 8