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,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) {
|