Import v2.3-dev from a demonic ritual
dkl9

dkl9 commited on 2023-184 18:25:57
Showing 18 changed files, with 3629 additions and 0 deletions.

... ...
@@ -0,0 +1,19 @@
1
+RTensor (v2.3-dev) is (C) 2021-2022 dkl9 and licensed as below:
2
+
3
+Permission is hereby granted, free of charge, to any person obtaining a copy
4
+of this software and associated documentation files (the "Software"), to deal
5
+in the Software without restriction, including without limitation the rights
6
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+copies of the Software, and to permit persons to whom the Software is
8
+furnished to do so, subject to the following conditions:
9
+
10
+The above copyright notice and this permission notice shall be included in all
11
+copies or substantial portions of the Software.
12
+
13
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+SOFTWARE.
... ...
@@ -0,0 +1,149 @@
1
+# RTensor/VeCalc/maths_parser changelog
2
+
3
+## 0.1
4
+
5
+* make five basic binary operators
6
+* make lexer
7
+* attempt left-recursive parser. fail miserably.
8
+* remove left-recursion
9
+* make right-recursive parser. succeed (but it's ugly).
10
+* add support for radix-pointed numeric literals
11
+* add method for handling associativity
12
+* make parsetree to ast converter
13
+* implement ast evaluation
14
+
15
+## 0.2
16
+
17
+* add parentheticals
18
+* add assignment
19
+* add postfix output
20
+* add unary operationss
21
+* improve vecalc independently of parser (semicolons, error handling, etc)
22
+* add more operators (`-/` and `,`)
23
+
24
+## 1.0
25
+
26
+* rewrite parser, eliminating parsetree step
27
+* add vector component access syntax ( `(a, b, c)[2] == b` )
28
+* add vector component assignment ( `v[2] = a` ) (lvalue generalisation)
29
+* add function call syntax ( `f()`, `f(x)`, `f(x, y)` )
30
+* add function objects
31
+* add builtin functions in vecalc
32
+
33
+## 1.1
34
+
35
+* change unary operator parsing
36
+* fix index-suffix parsing bug
37
+* fix vector-element-assignment bug
38
+* add `<` operator
39
+* add functions `floor`, `len`, `any`, and `all` (vecalc, not in parser)
40
+* add functions `if` and `map` (vecalc, not in parser)
41
+
42
+## 1.2
43
+
44
+* add functions `filter`, `reduce`, and `tail` (vecalc, not in parser)
45
+* add constants `pi` and `phi` (vecalc, not in parser)
46
+* fix vector-indexing bug
47
+* fix other wacky bugs
48
+
49
+## 1.3 2021-09-01
50
+
51
+* improve empty list handling
52
+* add functions `random`, `zero`, and `clear` (vecalc, not in parser)
53
+* add constants `true` and `false` (vecalc, not in parser)
54
+* add function `for` (vecalc, not in parser)
55
+
56
+## 1.4 2021-09-05
57
+
58
+* improve evaluation error messages
59
+* further improve empty list handling
60
+* fix bug in `random` function
61
+* demonstrate that vecalc is now sufficiently powerful as to implement quicksort
62
+* make some examples clickable
63
+* try and fail to add an indication that the expression is being evaluated but is not finished
64
+
65
+## 1.5 2021-10-01
66
+
67
+* finally add a favicon, wow
68
+* warning on closing page
69
+* more examples
70
+
71
+## 1.6 2021-11-23
72
+
73
+* fix bug in `map` function
74
+* improve `filter` function
75
+* abbreviate saved-answer identifiers
76
+* improve error messages for argument patterns
77
+
78
+## 1.7 2021-12-28
79
+
80
+* add operator `=>` (maplet, anonymous function)
81
+* enhance function call syntax
82
+* fix bug in function call parsing
83
+* correct inconsistency in `zero` function
84
+* more examples
85
+* improve examples
86
+* fix warning on closing page
87
+* replace PNG file favicon with inline SVG (saves 2.3 kB)
88
+
89
+## 1.8 2022-010
90
+
91
+* improve error messages for library functions and token errors
92
+* catch long `for` loops
93
+* add functions `trim`, `tostring`, `render`, and `html`
94
+* deprecate `gi` parameter
95
+* split `AST` class from `maths_parser.js` to `maths_ast.js`
96
+* implement syntactic function mechanism
97
+* fix bug in history navigation
98
+* more examples
99
+
100
+## 2.0 2022-035
101
+
102
+* produce direct characters (rather than references) in `toHTML`
103
+* improvements to `toHTML`
104
+* make `MathsFunc.apply()` API match that of `Function`
105
+* syntax changes, theoretically non-breaking to users (no distinct assignment statements or lvalues, just `=` operator)
106
+* syntax changes, probably breaking (unified `v[i]` syntax with `f(x)` syntax to make `v(i)`, removing `v[i]`)
107
+* update examples to match syntax changes
108
+* add constant `null`
109
+* make function call/vector index syntax more sugary (`f(a)(b)` instead of `(f(a))(b)`)
110
+* properly distinguish scalars from vectors
111
+* add operator `..` (tensor concatenation and ranges)
112
+* allow `'` (prime/apostrophe) in identifiers
113
+* implement multi-argument anonymous functions
114
+* rename from VeCalc to RTensor
115
+
116
+## 2.1 2022-081
117
+
118
+* split rendering to HTML from `maths_ast.js` to `rtensor_to_html.js`
119
+* split `MathsFunc` definition from `maths_parser.js` to `mathsfunc.js`
120
+* fix `AST.toString()` to invert parsing
121
+* switch to XHTML
122
+* write a separate documentation file (`documentation.md`)
123
+* render comma operations in `toHTML`
124
+* fix bug in parsing numbers with only a fractional component
125
+* make `toHTML` consider derived functions (`sec`, `csc`, `cot`, etc) to be of similar status to actual builtins
126
+* fix parenthesisation of base of exponents in `toHTML`
127
+
128
+## 2.2 2022-230
129
+
130
+* fix associativity bugs
131
+* allow identifiers starting with `'` (apostrophe)
132
+* allow square brackets for indexing and functions
133
+* treat square-bracket function calls as syntactic
134
+* remove the syntactic function distinction, making all functions call-by-value with an option for the value to be an AST
135
+* add functions `lhs`, `rhs`, and `operator` to access parts of ASTs
136
+* add syntactic-input handler for `if` function
137
+* implement binary operations on ASTs (returns new ASTs)
138
+* add functions `sum` and `product` (expecting syntactic input)
139
+
140
+## 2.3-dev 2022-294
141
+
142
+* implement complex numbers
143
+* unify numeric types (reals also supported) into query-string-controlled list of types
144
+* implement bigints
145
+* implement arithmetic operations on functions
146
+* fix bug with `sum` and `product` applied to tensors
147
+* add code-loading textbox
148
+* implement syntactic version of `for`
149
+* write library files `linalg.rt` and `polynomial.rt`
... ...
@@ -0,0 +1,660 @@
1
+# RTensor documentation (for v2.1)
2
+
3
+*RTensor: the sixth-best formula calculator in the world*
4
+
5
+RTensor (previously known as VeCalc, and originally as maths_parser) refers to a set of programs:
6
+1. a parser for a maths-oriented programming language (called "RTensor", or "the RTensor language")
7
+2. an interpreter for the RTensor language
8
+3. an HTML-targeting maths typesetter, using the RTensor language as an input syntax
9
+4. a [browser application](#interface) enabling one to run programs in the RTensor language
10
+
11
+dkl9 wrote RTensor, starting in 2021-05, and writes this documentation.
12
+For more history, see `./changelog.md`.
13
+
14
+# Concepts
15
+
16
+## AST
17
+
18
+All valid in code in the RTensor language gets parsed into an AST &mdash; an **a**bstract **s**yntax **t**ree.
19
+Internally, the `AST` class (defined in `./maths_ast.js`) represents such an AST.
20
+Each node of the AST either references subnodes (branch nodes, corresponding to [operations](#operators), [function calls](#function-calls), etc) or does not (leaf nodes, corresponding to [primitives](#primitives)).
21
+
22
+## Tensor
23
+
24
+Consider the following progression:
25
+- **scalar**: a single number (rank 0)
26
+- **vector**: a list of numbers (rank 1)
27
+- **matrix**: a list of lists of numbers (rank 2)
28
+
29
+All items in that progression qualify as **tensors** of various **ranks**;
30
+ranks of tensors continue beyond 2 as far as one wants, though ranks from 0 to 2 occur most commonly.
31
+
32
+# Interface
33
+
34
+Item 4 in the set of RTensor programs more specifically means `./rtensor.xhtml`, which consists of some documentative content, followed by a space for alternating lines of input (text fields, all but the most recent disabled) and output.
35
+
36
+## Running code
37
+
38
+To run a line of RTensor code, type (or copy, as appropriate) it into the currently available input line and press `Enter`.
39
+You can also use the up and down arrow keys to navigate through the current session's history of previously entered lines.
40
+Whilst not in the RTensor language, you can combine multiple lines of code into one input by joining them with semicolons (`;`).
41
+
42
+## Successful output
43
+
44
+When no error occurs whilst processing the input code, RTensor will write as output the return value of the code.
45
+Preceding this return value, RTensor indicates a [variable name](#identifiers) (an underscore followed by a serial number) to which it saved that return value.
46
+If the input code consists of multiple semicolon-separated statements, RTensor will evaluate all of the statements, but will only present the return value of the last one, and the serial variable will only receive the return value of the final statement.
47
+
48
+RTensor also silently saves the final return value to the variable `_` (underscore), but leaves its previous value if an error occurred.
49
+
50
+## Extra output
51
+
52
+Some code (such as [syntactic functions](#syntactic-functions)) produces extra output, in varying forms, before the main output.
53
+
54
+## Error messages
55
+
56
+When an error occurs whilst parsing or evaluating the input code, RTensor will display a relevant error message.
57
+In some cases, this error message comes directly from JavaScript, and therefore starts with a PascalCase error type &mdash; this qualifies as a bug.
58
+
59
+If the error occurs during evaluation, RTensor will skip execution of code after the part that triggered the error;
60
+errors propagate up through a program.
61
+
62
+For some code (such as `3+`), RTensor successfully parses and evaluates it, but notices that not all of the code corresponds to part of a valid AST, and so in addition to [typical output](#successful-output), RTensor leaves the message "possible syntax error", which *almost always* means "actual syntax error".
63
+
64
+# Primitives
65
+
66
+## Numeric literals
67
+
68
+A numeric literal directly represents a real number (a [scalar](#tensor)), and consists of one or both of an integer component and a fractional component;
69
+Each component consists of digits (in base ten).
70
+The fractional component, if present, must begin with a dot (`.`).
71
+Valid numeric literals include `0`, `389`, `3.14159`, and `.367`.
72
+RTensor currently accepts `.` (just a dot) to mean `NaN`, but you should not rely on this always remaining true.
73
+
74
+## Identifiers
75
+
76
+An identifier consists of a sequence of letters, digits, underscores, or (unlike most other languages) apostrophes (akin to prime symbols), and does not start with a digit.
77
+When interpreted for code-evaluation, an identifier references a variable, which can hold a value of any type.
78
+Shadowing/[function scope](#evaluation-process) may affect what variable an identifier references.
79
+
80
+# Operators
81
+
82
+**Operators** combine one or two subexpressions (making the operation **unary** or **binary**, respectively) and express a computation based on the subexpressions.
83
+When the operator combines two subexpressions, it goes between the subexpressions;
84
+when the operator modifies one subexpression, it goes before that subexpression.
85
+
86
+Each operator has a **precedence**, which may group it more tightly or loosely than other operators, and each precedence has an **associativity**.
87
+Unary operators have tighter precedence than binary operators, regardless of their relative precedence as binary operators.
88
+You can use parentheses to group operations differently than what their precedence and associativity would ordinarily require.
89
+
90
+In descriptions of operators, `a` and `b` represent subexpressions and their results.
91
+
92
+## Basic operators
93
+
94
+When used on a scalar and a tensor, the result comes from the tensor, with each element replaced by the result of the operation between the scalar and each scalar in the tensor.
95
+For example, `3 * ((7, 2), (-1, 3), (-8, -2))` returns `[[21, 6], [-3, 9], [-24, -6]]`.
96
+
97
+When used on two tensors, the result comes from the elementwise combination of the two tensors, or an error if the tensors have different structures.
98
+
99
+### Addition (`+`)
100
+
101
+`a + b`, the sum of `a` and `b`, or `+a`, an identity operation.
102
+Addition has precedence between multiplication and equality, and associates to the left.
103
+
104
+### Subtraction (`-`)
105
+
106
+`a - b`, the difference of `a` and `b`, or `-a`, the negative of `a`.
107
+Subtraction has the same precedence as addition, and thus the same associativity.
108
+
109
+### Multiplication (`*`)
110
+
111
+`a * b`, the product of `a` and `b`.
112
+`*a` will result in an error as meaningless.
113
+Multiplication has precedence between exponentiation and addition, and associates to the left.
114
+
115
+### Division (`/`)
116
+
117
+`a / b`, the quotient of `a` and `b`, or `/a`, the reciprocal of `a`.
118
+Division has the same precedence as multiplication.
119
+
120
+### Exponentiation (`^`)
121
+
122
+`a ^ b`, `a` to the power of `b`, or `^a`, the natural exponential function of `a`.
123
+Exponentiation has precedence between logarithms and multiplication, and associates to the right.
124
+
125
+### Logarithms (`\`)
126
+
127
+`a \ b`, the base-`a` logarithm of `b`, or `\a`, the natural logarithm of `a`.
128
+Logarithms have the tightest precedence of all operators, and associate to the right.
129
+
130
+### Equality (`==`)
131
+
132
+`a == b`, a boolean value indicating whether or not `a` equals `b`, or `== a`, equivalent to `0 == a`.
133
+Equality has precedence between addition and appending, and associates to the left.
134
+
135
+### Radicals (`-/`)
136
+
137
+`a -/b`, the `a`-th root of `b`, or `-/a`, the square root of `a`.
138
+Radicals have the same precedence as exponentiation.
139
+
140
+Swapping the characters of a radical turns it into `a /-b`, equivalent to `-(a / b)`, or `/-a`, equivalent to `- / a`.
141
+
142
+### Comparison (`<`)
143
+
144
+`a < b`, a boolean value indicating whether or not `b` exceeds `a`, or `< a`, equivalent to `a < 0`.
145
+Comparison has the same precedence as equality.
146
+
147
+You cannot chain comparisons, as in `a < b < c`;
148
+RTensor will interpret that as `(a < b) < c`, which reduces to either `true < c` or `false < c`.
149
+
150
+Notice the absence of a `>` operator;
151
+one may implement it by swapping the operands.
152
+
153
+## Tensor operators
154
+
155
+### Appending (`,`)
156
+
157
+The output of `a, b` depends on the relative [rank](#tensor) of `a` and `b`:
158
+- if `a` has greater rank, RTensor appends `b` to the end of `a`, returning a new tensor
159
+- otherwise, RTensor creates a two-element tensor, consisting of `a` and `b` as its elements
160
+
161
+These semantics allow one to chain the appending operator (as in `a, b, c, d`) to construct a many-element tensor.
162
+
163
+Appending has precedence between equality and the maplet, and associates to the left.
164
+
165
+### Concatenation (`..`)
166
+
167
+The output of `a .. b` depends on the [ranks](#tensor) of `a` and `b`:
168
+- if `a` and `b` both have ranks exceeding zero, RTensor concatenates `b` to the end of `a`, returning a new tensor
169
+- if `a` and `b` both have ranks which equal zero, RTensor generates a vector by iterating through the numbers from `a` to `b` (inclusive) with a step size of 1
170
+- if one of `a` or `b` has zero rank, and the other does not, `a .. b` results in an error as meaningless
171
+
172
+`.. a` encapsulates `a` as a one-element tensor, effectively incrementing the rank without adding any new data.
173
+
174
+When using the concatenation operator, carefully distinguish it from a decimal point;
175
+e.g. `3..5` will result in an error, unlike the valid options `3.5` and `3 .. 5`.
176
+
177
+Concatenation has the same precedence as addition.
178
+
179
+## Unusual operators
180
+
181
+### Maplet (`=>`)
182
+
183
+The maplet operation, `a => b`, constructs an [anonymous function](#anonymous-functions).
184
+`a` may consist of a comma-separated list, each of which corresponds to a separate argument for the function;
185
+it gets tranlated into a [pattern](#pattern-matching), but syntactically forms as an expression.
186
+`b` must consist of some expression, possibly using variables defined in `a`.
187
+
188
+The maplet has precedence between appending and assignment, and associates to the right.
189
+
190
+### Assignment (`=`)
191
+
192
+The assignment operation, `a = b`, saves the value from `b` into the location described by `a`, and returns the new value of `a` (or something closely related to `a`, with semantics that remain undocumented).
193
+`a` may consist of an [identifier](#identifiers) &mdash; in which case, `b` replaces the value already stored in that variable.
194
+`a` may have a [function call](#function-calls) or [index](#indexing) suffix (the two have the same form):
195
+- if `a` already references a [function](#function-definitions), the assignment adds a new case (the [pattern](#pattern-matching) consisting of the argument list, the expression coming from `b`) to that function (at top priority)
196
+- if `a` already references a [tensor](#tensor), the assignment replaces the element at the given index with the value of `b`, or [causes an error](#error-messages) if `a` does not already contain an element at that index
197
+- if `a` does not reference anything, or references a scalar, the assignment replaces the value with a new function, adding a single case as if `a` already referenced a function
198
+
199
+Assignment has the loosest precedence of all operators, and associates to the right.
200
+
201
+# Indexing
202
+
203
+An expression suffixed with a parenthesised expression (`v(i)`) forms an **indexing expression** (or a [function call](#function-calls), depending on `v`);
204
+if `v` references a tensor, the index expression returns the `i`th element of `v` (a tensor of [rank](#tensor) at most one less than that of `v`).
205
+Indexing in RTensor starts at 1 and not 0.
206
+Attempts to access `v(i)` where `i` exceeds the length of `v`, `i` does not equal an integer, or `i` subceeds 1 will [cause an error](#error-messages).
207
+
208
+The `i` component of the indexing expression can include extra comma-separated indices, but RTensor currently ignores all indices after the first.
209
+
210
+# Function calls
211
+
212
+An expression suffixed with a parenthesised expression-list (`f(a, b, c, ...)`) forms a **function call** (or an [indexing expression](#indexing), depending on `f`);
213
+if `f` references a [function](#function-definitions), the function call returns the value `f` provides when evaluated with the given argument list.
214
+The argument list may contain any number of arguments, including 0.
215
+
216
+Whilst [`,` normally acts as an operator](#appending-), the argument-list syntax suppresses this behaviour;
217
+when in an argument list, `,` only acts as an operator if it occurs in parentheses (as in the first argument of `f((a, b), c)`), and operators with precedence looser than that of `,` will not work at all unless parenthesised.
218
+
219
+## Evaluation process
220
+
221
+When evaluating a function, RTensor first evaluates all the arguments, then searches for a [pattern](#pattern-matching) (going in descending priority) that matches the arguments.
222
+If RTensor could not find a matching pattern, an [error occurs](#error-messages).
223
+Upon finding a matching pattern, RTensor evaluates the expression or internal function corresponding to that pattern in the function's [definition](#function-definitions), using the argument-name correspondences given from the pattern to create local variables for that expression.
224
+
225
+# Function definitions
226
+
227
+**Functions**, as objects in RTensor (or **function definitions**), consist of a list (arranged by priority) of [pattern](#pattern-matching)-expression pairs, or **cases**.
228
+Some functions ([those predefined by the code which sets up RTensor](#built-in-functions)) have, in place of an expression defined from RTensor, a function in the language implementing RTensor.
229
+
230
+## Pattern matching
231
+
232
+A **pattern** consists of a list of zero or more **parameters**.
233
+A parameter consists of either an [identifier](#identifiers) or some other expression:
234
+- in the former case, that parameter matches any value, and will require RTensor to create a local variable with that name to store that value, *even if a variable defined in a scope further out already exists with that name*
235
+- in the latter case, RTensor evaluates the expression (at the time of case-creation), using only variables defined in a scope further out than the [function evaluation](#evaluation-process);
236
+    that parameter matches only values considered equal to the result of the expression
237
+
238
+When checking if a pattern matches a list of arguments, the arguments get aligned to the parameters;
239
+if the number of arguments differs from the number of parameters, the pattern does not match without any further checks.
240
+
241
+To make a parameter that matches only values which equal an existing variable, wrap the variable identifier in an identity (such as `x + 0` or `x * 1`).
242
+
243
+RTensor currently does not provide pattern-matching mechanisms more complex than equality checks against constant values;
244
+you must do so yourself with [explicit checks](#control-flow).
245
+
246
+## Anonymous functions
247
+
248
+RTensor provides both an [assignment-based syntax](#assignment-) and an anonymous [maplet-based syntax](#maplet-) for defining functions.
249
+The former works with (named) variables, but the latter constructs a function without saving it to a variable (anonymous).
250
+A maplet operation takes its left operand as a [pattern](#pattern-matching) and its right operand as an unevaluated sub-expression, constructing and returning a single-[case](#function-definitions) function, usable in the same contexts as any other function.
251
+
252
+In that single case's expression, one can use variables defined in the scope which defines the anonymous function, creating a sort of closure.
253
+
254
+If the left operand has [commas](#appending-), RTensor will break it down into a list of patterns, allowing for more than one parameter, rather than a single pattern.
255
+
256
+### Currying
257
+
258
+Using closures, one can make multi-argument anonymous functions a different way: make a function that returns another function, as a partially-evaluated form of the desired multi-argument function &mdash; for example, `x => (y => x + y)` instead of `x, y => x + y`.
259
+RTensor does not have a special syntax to facilitate the requisite chained method calls, so one would use the curried version not as `f(a, b)` but `f(a)(b)`.
260
+
261
+# Built-in functions
262
+
263
+The RTensor [browser application](#interface) includes `rtensor_lib.js`, which predefines a set of functions.
264
+
265
+In descriptions of functions, identifiers [in parentheses](#function-calls) correspond to arguments for valid ways to call a function.
266
+The exact identifier used indicates the expected type:
267
+- `a`, `b`, `c`: number (in some contexts, specifically integer)
268
+- `f`, `g`: function
269
+- `k`, `m`, `n`: integer
270
+- `t`: tensor (any rank above 0)
271
+- `v`: vector ([rank 1 tensor](#tensor))
272
+- `x`, `y`: number ([rank 0 tensor](#tensor))
273
+- `w`, `z`: any type
274
+
275
+Some functions work on a tensor, but only look one rank deep, using the tensor in place of a vector.
276
+In this case, the parameter uses `t` for the identifier.
277
+
278
+## Numeric/trigonometric functions
279
+
280
+All functions in this list that accept a real number (so not `random`) will accept, in place of that number, a tensor, and will perform the same operation on each number in the tensor, returning an equivalently-structured tensor.
281
+
282
+### `abs`
283
+
284
+`abs(x)`, the absolute value of `x`.
285
+
286
+### `floor`
287
+
288
+`floor(x)`, the integer rounded down (towards negative infinity) from `x`.
289
+
290
+### `random`
291
+
292
+`random()`, a random number in `[0, 1)`, or `random(m, n)`, a random integer in `[m, n]`.
293
+
294
+### `sin`
295
+
296
+`sin(x)`, the trigonometric sine of `x`.
297
+
298
+### `cos`
299
+
300
+`cos(x)`, the trigonometric cosine of `x`.
301
+
302
+### `arcsin`
303
+
304
+`arcsin(y)`, the trigonometric inverse sine of `y`.
305
+
306
+### `arccos`
307
+
308
+`arccos(y)`, the trigonometric inverse cosine of `y`.
309
+
310
+### `arctan`
311
+
312
+`arctan(y)`, the trigonometric inverse tangent of `y`, or `arctan(y, x)`, the plane angle between `(0, 0)` and `(x, y)`.
313
+
314
+The corresponding `tan(x)` does not exist predefined in RTensor, but one can implement it as `tan(x) = sin(x) / cos(x)`.
315
+
316
+## Tensor functions
317
+
318
+### `zero`
319
+
320
+`zero(n)`, an `n`-element vector of zeroes.
321
+`zero` accepts `0` as an argument, producing, in that case, a zero-element vector.
322
+
323
+### `len`
324
+
325
+`len(t)`, the number of elements in the top level of `t`.
326
+This does not count the number of numbers in `t`, but merely the number of [tensors of lesser rank](#tensor) immediately composing `t`.
327
+For example, `len(((4, 7), (2, 3), (8, 5)))` return 3 rather than 6.
328
+
329
+### `any`
330
+
331
+`any(v)`, the first nonzero value in `v`.
332
+If `v` consists of [`true`/`false` values](#booleans-true-and-false), this functions as an inclusive OR.
333
+
334
+### `all`
335
+
336
+`any(v)`, the first zero-equivalent value in `v`.
337
+If `v` consists of [`true`/`false` values](#booleans-true-and-false), this functions as an AND.
338
+
339
+### `map`
340
+
341
+`map(t, f)`, a new [tensor (same rank as `t`, greater than 0)](#tensor) with each element `x` from `t` (in the top level) replaced [with `f(x)`](#function-calls).
342
+If `f` accepts two arguments (binary function), `map` will replace `x` with `f(x, i)`, where `i` matches the index of `x` from `t`.
343
+If `f` does not accept any arguments (nullary function), `map` will replace `x` with `f()`.
344
+
345
+Notice that `map` *only* acts on top-level elements;
346
+e.g. `map(((4, 7), (2, 3), (8, 5)), f)` applies `f` to `(4, 7)`, `(2, 3)`, and `(8, 5)`, not `4`, `7`, `2`, `3`, `8`, `5`.
347
+To apply a function to replace *every number* in a tensor, [use `deepmap`](#deepmap).
348
+
349
+### `filter`
350
+
351
+`filter(t, f)`, a new [tensor (same rank as `t`, greater than 0)](#tensor) containing all elements `x` from `t` (in the top level) [for which `f(x)`](#function-calls) returns a value equivalent to [`true`](#booleans-true-and-false).
352
+If `f` accepts two arguments (binary function), `filter` will copy `x` for which `f(x, i)` returns a value equivalent to `true`, where `i` matches the index of `x` from `t`.
353
+
354
+As with [`map`](#map), `filter` *only* acts on top-level elements.
355
+RTensor currently does not have any `deepfilter` to check `f(x)` for every number in a tensor.
356
+
357
+### `reduce`
358
+
359
+`reduce(t, f)`, a value computed by repeatedly applying `x = f(x, y)` for each element `y` of `t` (at the top level).
360
+The initial value of `x` comes from the first element of `t`;
361
+`reduce` does not apply `f(x, y)` using that initial value for `y`.
362
+
363
+As with [`map`](#map), `reduce` *only* acts on top-level elements.
364
+RTensor currently does not have any `deepreduce` to combine all individual numbers in a tensor.
365
+
366
+### `tail`
367
+
368
+`tail(t)`, a new tensor consisting of all the elements of `t`, in the same order, except for the first.
369
+
370
+### `trim`
371
+
372
+`trim(t)`, a new tensor consisting of all the elements of `t`, in the same order, except for the last.
373
+
374
+### `deepmap`
375
+
376
+`deepmap(t, f)`, a new tensor with the same structure as `t`, but with every number `x` (down through all ranks) replaced with `f(x)`.
377
+
378
+## Control flow
379
+
380
+### `if`
381
+
382
+`if(c, w, z)`, a selection between `w` and `z` depending on `c`, selecting `w` iff `c` [equates to `true`](#booleans-true-and-false).
383
+`if` accepts up to two extra arguments to automatically apply to the selected value as arguments (assuming that value [has callable behaviour](#function-calls)).
384
+For more than two arguments (and recommended in general, as we may deprecate that feature), we suggest directly calling the result instead, as in `if(c, w, z)(a, b, x, y)`.
385
+
386
+### `for`
387
+
388
+`for(init, cond, step)`, the final value of `x` &mdash; which starts as `x = init` &mdash; after repeatedly applying `x = step(x)`, until `cond(x)` returns a value [equivalent to `false`](#booleans-true-and-false).
389
+
390
+## Other built-in functions
391
+
392
+### `clear`
393
+
394
+`clear()`, which clears away all the content in the input/output area (but keeps variables and input history), returning 0.
395
+
396
+# Other built-ins
397
+
398
+RTensor predefines some values other than functions in `rtensor_lib.js`.
399
+These include:
400
+- `pi`, Archimedes' constant, approximately 3.14
401
+- `phi`, the golden ratio, approximately 1.62
402
+- `true`, the affirmative boolean value (distinct from, but usually equivalent to, 1)
403
+- `false`, the negative boolean value (distinct from, but usually equivalent to, 0)
404
+- `null`, a unique null value
405
+
406
+This set does *not* include Euler's number `e` (approximately 2.72);
407
+one can already [get it as `^1`](#exponentiation-).
408
+
409
+# Typesetting/rendering
410
+
411
+Item 3 in the set of RTensor programs &mdash; the HTML-targeting maths typesetter &mdash; consists of a set of functions that convert [valid RTensor ASTs](#ast) into HTML text, intended to use styling from `maths.css`.
412
+
413
+## RTensor typesetting as a library
414
+
415
+An outside HTML/JavaScript program (such as mdprocess) that includes the right files:
416
+- `maths_parser.js`
417
+- `maths_ast.js`
418
+- `mathsfunc.js`
419
+- `rtensor_lib.js`
420
+- `rtensor_to_html.js`
421
+- `maths.css`
422
+
423
+... can get typeset HTML through `ast.toHTML()` for an `AST` object `ast`.
424
+For example:
425
+```js
426
+const p = document.createElement("p");
427
+const l = new TokenStream("x = (-b + pm(-/(b^2 - 4*a*c))) / (2*a)");
428
+const a = NewParser.statement(l);
429
+p.innerHTML = a ? a.toHTML() : "syntax error";
430
+document.body.appendChild(p);
431
+```
432
+
433
+## RTensor typesetting in the interface
434
+
435
+The [RTensor browser interface](#interface) provides two built-in functions in `rtensor_lib.js` to access this typesetting feature.
436
+Both functions [act on syntax](#syntactic-functions).
437
+
438
+### `html`
439
+
440
+`html(e)`, which places HTML text depicting `e` provided by `ast.toHTML()` into the output area (as plaintext), returning 0.
441
+
442
+### `render`
443
+
444
+`render(e)`, which places a rendering of `e` provided by `ast.toHTML()` into the output area, returning 0.
445
+
446
+# Syntactic functions
447
+
448
+Some functions act on syntax rather than values;
449
+i.e. they interpret each argument as an [AST](#ast) without evaluating it, rather than a [tensor](#tensor) or similar ordinary value.
450
+
451
+Some operators (specifically, [appending](#appending) and all looser operators) have looser precedence than the parentheses and commas [of function call syntax](#function-calls), so syntactic arguments using such operators must have delimiting parentheses.
452
+
453
+## `tostring`
454
+
455
+`tostring(e)`, which places an unambiguous form of `e` into the output area, returning 0.
456
+
457
+# Examples
458
+
459
+You can paste any of these examples into the [RTensor application](#interface) (line-by-line) to test them.
460
+
461
+## Numerical error
462
+
463
+```rtensor
464
+(-/10)^2 == 10
465
+```
466
+
467
+In the current implementation, this will return `false`, because RTensor &mdash; based upon JavaScript &mdash; uses the imperfect 64-bit floating point system.
468
+
469
+## Local variables
470
+
471
+```rtensor
472
+f(x) = (\x)^2 + \x
473
+f(x) = (a => a^2 + a)(\x)
474
+f(x) = (a = \x) + a^2
475
+f(x) = (a = \x)*0 + (a^2 + a)
476
+```
477
+
478
+All of these functions give the same output:
479
+- the first computes `\x` twice
480
+- the second caches `\x` into a local variable by calling an inner function
481
+- the third stores `\x` into a local variable without a new function
482
+- the fourth discards the result of the assignment, demonstrating a method that works in more general cases
483
+
484
+## Factorial, several ways
485
+
486
+```rtensor
487
+fact(n) = n * fact(n - 1)
488
+fact(1) = 1
489
+
490
+fact(n) = n * if(n == 1, (_x => 1), fact)(n - 1)
491
+
492
+((f => (n => (if(n==1,(_=>1),f(f)))(n-1) * n))(
493
+    (f => (n => (if(n==1,(_=>1),f(f)))(n-1) * n)))
494
+)(6)
495
+```
496
+
497
+The first pair of assignments creates a function, `fact`, which returns the factorial of a positive integer by recursion.
498
+Swapping the assignments would make the function incorrect;
499
+functions try cases [in reverse order of their definition](#evaluation-process), stopping at the first one that works.
500
+
501
+The second assignment implements `fact` recursively, using [`if`](#if) instead of [pattern matching](#pattern-matching).
502
+
503
+The final expression uses a Y combinator-like construct and [anonymous functions](#anonymous-functions) to evaluate a factorial recursively without assigning to a variable.
504
+
505
+## Ackermann function
506
+
507
+```rtensor
508
+A(m, n) = A(m - 1, A(m, n - 1))
509
+A(m, 0) = A(m - 1, 1)
510
+A(0, n) = n + 1
511
+```
512
+
513
+Beware that with arguments greater than `A(3, 3)`, this will run very slowly.
514
+Observe that with RTensor's [pattern matching](#pattern-matching), the implementation of the function looks almost identical to [the theoretical definition](https://en.wikipedia.org/wiki/Ackermann_function).
515
+
516
+## `Rtensor:Psi` bump function
517
+
518
+```rtensor
519
+psi(x) = if(all((-1 < x, x < 1)), ^/-(1 - x^2), 0)
520
+```
521
+
522
+An implementation of the [psi bump function](https://en.wikipedia.org/wiki/Bump_function#Examples), again remarkably close to the theoretical definition, and even closer when [presented with `render`](#render).
523
+
524
+## Vector dot product
525
+
526
+```rtensor
527
+dot(u, v) = u(1) * v(1) + dot(tail(u), tail(v))
528
+dot(zero(0), v) = 0
529
+dot(u, zero(0)) = 0
530
+```
531
+
532
+## List of prime numbers
533
+
534
+```rtensor
535
+div(a, b) = floor(a / b) == a / b
536
+fp(n, l) = ==len(filter(l, (k => div(n, k))))
537
+last(v) = v(len(v))
538
+fnp(n) = trim(for(
539
+    .. 2,
540
+    (v => len(v) < n + 1),
541
+    (v => if(fp(last(v), trim(v)), (v, last(v)), (trim(v), last(v) + 1)) )
542
+))
543
+fnp(30)
544
+```
545
+
546
+`div(a, b)` checks if `b` divides `a`.
547
+
548
+`fp(n, l)` checks if `n` has no divisors in `l`.
549
+
550
+`last(v)` returns the last element of `v`.
551
+
552
+`fnp(n)` uses a [`for` loop](#for) to increment a potential prime and append any actual primes to a list, until the list has the desired length `n`.
553
+
554
+## Integer factorisation
555
+
556
+```rtensor
557
+div(a, b) = floor(a / b) == a / b
558
+factor(n, g) = if(div(n, g), ..g, zero(0)) ..
559
+    factor(if(div(n, g), n / g, n), if(div(n, g), g, g + 1))
560
+factor(1, g) = zero(0)
561
+factor(n) = factor(floor(n), 2)
562
+```
563
+
564
+The `factor(n, g)` function recursively builds up a vector of prime factors of `n`, based on the principle that if `n` has the factor `g`, the factorisation of `n` matches that of `n / g`, except for the addition of `g`.
565
+
566
+## Fibonacci sequence
567
+
568
+```rtensor
569
+cond(state) = len(state) < 20
570
+next(st) = st, (st(len(st)) + st(len(st) - 1))
571
+for((1, 1), cond, next)
572
+```
573
+
574
+## Quicksort
575
+
576
+```rtensor
577
+qs(v) = qs(filter(tail(v), (x => x < v(1)))) ..
578
+    (.. v(1)) ..
579
+    qs(filter(tail(v), (x => 1 - (x < v(1)))))
580
+qs(zero(0)) = zero(0)
581
+tv = map(zero(30), (_x => random(1, 100)))
582
+qs(tv)
583
+```
584
+
585
+Quicksort sorts a list by recursively collecting elements less than and greater than a pivot into sublists on each side of the pivot, and quicksorting each such sublist, until the process reaches lists of length 0.
586
+
587
+This implementation consistently uses the first element of a list as its pivot.
588
+
589
+## Hofstadter Q-sequence
590
+
591
+```rtensor
592
+a(n) = a(n-a(n-1)) + a(n-a(n-2))
593
+a(1) = 1
594
+a(2) = 1
595
+
596
+Qs(v) = v, (v(len(v) + 1 - v(len(v))) + v(len(v) + 1 - v(len(v) - 1)))
597
+Qc(v) = len(v) < 100
598
+for((1, 1), Qc, Qs)
599
+```
600
+
601
+The first implementation provides a recursive function to compute any term of the Q-sequence.
602
+This runs very slowly.
603
+
604
+The second implementation iteratively generates a list of Q-sequence terms, running much faster.
605
+
606
+## Polynomial operations
607
+
608
+Each function here works with a polynomial as a little-endian vector of coefficients;
609
+e.g. `(-2, 5, 6)` represents `Rtensor:6*x^2 + 5*x - 2`.
610
+
611
+Evaluate a polynomial at an input value by multiplying each coefficient with the corresponding power of the input and summing the results:
612
+
613
+```rtensor
614
+pe(x, p) = reduce(map(p, (t, i => t * x^(i - 1))), (a, b => a + b))
615
+```
616
+
617
+Differentiate a polynomial (into another polynomial) by multiplying each coefficient by its exponent and shifting all elements down by one:
618
+
619
+```rtensor
620
+pd(p) = tail(map(p, (x, i => (i - 1) * x)))
621
+```
622
+
623
+Find a root of a polynomial near a given value with Newton's method by subtracting function values divided by derivatives until the approximation matches a zero:
624
+
625
+```rtensor
626
+rnr(v) = 1 - ==pe(v(1), v(2))
627
+nrs(v) = (v(1) - pe(v(1), v(2)) / pe(v(1), pd(v(2)))), v(2)
628
+pr(x, p) = (for((x, p), rnr, nrs))(1)
629
+```
630
+
631
+## Digits of an integer
632
+
633
+```rtensor
634
+digits(n, b) = digits(floor(n / b), b), (n - b * floor(n / b))
635
+digits(0, b) = zero(0)
636
+```
637
+
638
+As with [the factoring algorithm](#integer-factorisation), this function works by recursively building up a vector, acting on reduced versions of the input until the input reaches zero.
639
+
640
+## Summation
641
+
642
+RTensor currently does not provide a built-in summation function, but that doesn't matter;
643
+we can implement it.
644
+
645
+```rtensor
646
+sum(a, b, f) = sum(0, b - a, (x => f(x + a)))
647
+sum(0, b, f) = f(b) + sum(0, b - 1, f)
648
+sum(0, 0, f) = f(0)
649
+```
650
+
651
+## Euclid's algorithm (GCD)
652
+
653
+RTensor currently does not provide a built-in, modulo function, but that doesn't matter;
654
+we can implement it.
655
+
656
+```rtensor
657
+mod(a,b) = a - b * floor(a / b)
658
+gcd(m, n) = gcd(n, mod(m, n))
659
+gcd(m, 0) = m
660
+```
... ...
@@ -0,0 +1,40 @@
1
+# functions for integer calculations and number theory
2
+# use this by pasting file contents into the code-area
3
+
4
+# factorial of n
5
+fact(n) = if[n == 0, 1, n * fact(n - 1)]
6
+
7
+# is n prime?
8
+prime(n) = for[(g = 2), floor(n / g) * g < n, \
9
+    (g = if(<(n - g^2 - 1), n, g + 1)), g] == n
10
+
11
+# digits of n in base b (big-endian)
12
+digits(n, b) = digits(floor(n / b), b), (n - b * floor(n / b))
13
+digits(0, b) = zero(0)
14
+
15
+# a modulo b
16
+mod(a,b) = a - b * floor(a / b)
17
+
18
+# greatest common divisor of m and n
19
+gcd(m, n) = gcd(n, mod(m, n))
20
+gcd(m, 0) = m
21
+
22
+# factorial-base digits of n (little-endian)
23
+fd(n, k) = if[n < fact(k - 1), \
24
+    zero(0), \
25
+    (.. mod(n / fact(k - 1), k)) .. fd(n, k + 1)]
26
+fd(n) = fd(n, 1)
27
+
28
+# internal helper
29
+except(v, i) = if[all((len(v) == 2, i == 1)), \
30
+    .. v[2], \
31
+    if[len(v) == i, \
32
+        trim(v), \
33
+        (except(trim(v), i), v[len(v)])]]
34
+pad(v, n) = if[len(v) == n, v, pad((v, 0), n)]
35
+
36
+# permutation indexed by k, of order n
37
+permi(n, v, u) = (.. u[v[len(v)] + 1]) .. \
38
+    perm(n, trim(v), except(u, v[len(v)] + 1))
39
+permi(n, zero(0), u) = zero(0)
40
+perm(n, k) = permi(n, pad(fd(k), n), 1 .. n)
... ...
@@ -0,0 +1,79 @@
1
+# functions useful in linear algebra (vectors, matrices, and such)
2
+# use this by pasting file contents into the code-area
3
+# matrices are assumed to be row-major
4
+
5
+# dot product of u and v
6
+dot(u, v) = if[any((==len(u), ==len(v))), \
7
+    0, \
8
+    u[1] * v[1] + dot(tail(u), tail(v))]
9
+
10
+# determinant of matrix m
11
+det(m) = if[len(m) == 1, \
12
+    m[1][1], \
13
+    sum[(k = 1), len(m), \
14
+        (-1)^(k + 1) * m[1][k] * det(minor(m, 1, k))]]
15
+
16
+# minor of a m, removing row i and column j
17
+minor(m, i, j) = map(except(m, i), (r => except(r, j)))
18
+except(v, i) = if[all((len(v) == 2, i == 1)), \
19
+    .. v[2], \
20
+    if[len(v) == i, \
21
+        trim(v), \
22
+        (except(trim(v), i), v[len(v)])]]
23
+
24
+# cross product of 3D vectors u and v
25
+cross(u, v) = map(1 .. 3, (k => \
26
+    (-1)^(k+1) * det(minor(((0, 0, 0), u, v), 1, k))))
27
+
28
+# cross product of 4D vectors u, v, and w
29
+cross(u, v, w) = map(1 .. 4, (k => \
30
+    (-1)^(k+1) * det(minor(((0, 0, 0, 0), u, v, w), 1, k))))
31
+
32
+# product of matrix M and vector v
33
+mvm(M, v) = map(v, (_x, i => dot(v, M[i])))
34
+
35
+# product of matrices a and b
36
+mm(a, b) = map(1 .. len(a), (i => map(1 .. len(b[1]), (j => \
37
+    dot(a[i], col(b, j))))))
38
+
39
+# column i of matrix m
40
+col(m, i) = map(m, (r => r[i]))
41
+
42
+# transpose of matrix m
43
+trans(m) = map(1 .. len(m[1]), (k => col(m, k)))
44
+
45
+# n-by-n identity matrix
46
+idm(n) = map(1 .. n, (k => zero(k - 1) .. (.. 1) .. zero(n - k)))
47
+
48
+# matrix m to the (natural number) power n
49
+mpow(m, n) = mm(m, mpow(m, n - 1))
50
+mpow(m, 0) = idm(len(m))
51
+
52
+# matrix m, but in row-echelon form
53
+ref(m) = for[(c = 1), c < len(m[1]), \
54
+    (m = map(m, ((r, i) => if[i < c + 1, \
55
+        r, \
56
+        r - m[c] * r[c] / m[c][c]]))) + (c = c + 1), \
57
+    m]
58
+
59
+# internal helper function
60
+nz(x) = if[x == 0, 1, x]
61
+
62
+# row-echelon matrix m (answers are probably wrong)
63
+rref(m) = for[(c = 2), c < len(m[1]), \
64
+    (m = map(m, ((r, i) => if[c + 1 < i, \
65
+        r, \
66
+        r - m[c] * r[c] / nz(m[c][c]])))) + (c = c + 1), \
67
+    m]
68
+
69
+# invert upper triangular matrix m by recursive partitioning
70
+invut(m) = if[all((len(m) == len(m[1]), \
71
+    all(tail(col(m, 1)) == zero(len(m) - 1)))), \
72
+    if[len(m) == 1, \
73
+        .. .. /m[1][1], \
74
+        (ai, di => \
75
+            ((.. ((.. ai) .. -mm(mm(.. .. ai, .. tail(m[1])), di)[1]))) .. \
76
+            map(tail(m), ((_r, i) => (.. 0) .. di[i])))( \
77
+            /m[1][1], \
78
+            invut(map(tail(m), (r => tail(r)))))], \
79
+    0/0]
... ...
@@ -0,0 +1,64 @@
1
+# functions for manipulating polynomials
2
+# use this by pasting file contents into the code-area
3
+# polynomials are assumed to be little-endian vectors of coefficients
4
+
5
+# evaluate polynomial p at input x
6
+pe(x, p) = sum[(i = 1), len(p), p[i] * x^(i - 1)]
7
+
8
+# derivative of polynomial p (a new polynomial)
9
+pd(p) = tail(map(p, (x, i => (i - 1) * x)))
10
+
11
+# root of polynomial p near x (by Newton's method)
12
+pr(p, x) = for[(dp = pd(p)), \
13
+    10^-14 < abs(pe(x, p)), \
14
+    (x = x - pe(x, p) / pe(x, dp)), \
15
+    x]
16
+
17
+# integral (antiderivative) of polynomial p (a new polynomial)
18
+pa(p) = (.. 0) .. map(p, (x, i => /i * x))
19
+
20
+# definite integral of polynomial p between bounds
21
+pdi(p, a, b) = (q => pe(b, q) - pe(a, q))(pa(p))
22
+
23
+# divide p by the trivial linear polynomial (-a, 1), ignoring remainder
24
+dtl(p, a) = if[len(p) == 1, \
25
+    zero(0), \
26
+    (dtl((trim(trim(p)), p[len(p) - 1] + p[len(p)] * a), a), p[len(p)])]
27
+
28
+# internal helper
29
+discard(x) = 0
30
+
31
+# all roots of polynomial p
32
+pr(p) = if[len(p) < 2, \
33
+    zero(0), \
34
+    (r => (pr(dtl(p, r)), r))(pr(p, 0))]
35
+
36
+# multiply polynomials p and q
37
+pm(p, q) = sum[(k = 1), len(p), \
38
+    (zero(k - 1) .. (q * p[k]) .. zero(len(p) - k))]
39
+
40
+# inner product of polynomials over integration interval
41
+ip(p, q, a, b) = pdi(pm(p, q), a, b)
42
+
43
+# internal helper
44
+chebygen(f) = n => pm((0, 2), f(n - 1)) - (f(n - 2), 0, 0)
45
+
46
+# n-th Chebyshev polnomial of the first kind
47
+chebycos(n) = chebygen(chebycos)(n)
48
+chebycos(1) = (0, 1)
49
+chebycos(0) = .. 1
50
+
51
+# n-th Chebyshev polynomial of the second kind
52
+chebysin(n) = chebygen(chebysin)(n)
53
+chebysin(1) = (0, 2)
54
+chebysin(0) = .. 1
55
+
56
+# polynomial to an (integer) power
57
+pp(p, n) = if[n / 2 == floor(n / 2), \
58
+    (q => pm(q, q))(pp(p, n / 2)), \
59
+    pm(p, pp(p, n - 1))]
60
+pp(p, 0) = .. 1
61
+
62
+# total of list of polynomials (of possible varying degree)
63
+pt(l, n) = reduce(map(l, (p => p .. zero(n - len(p)))), (a, b => a + b))
64
+pt(l) = pt(l, reduce(map(l, (p => len(p))), (a, b => if[a < b, b, a])))
... ...
@@ -0,0 +1,151 @@
1
+/* general */
2
+.maths {
3
+    font-family: "Garamond", "Times New Roman", serif;
4
+    font-weight: 600;
5
+}
6
+
7
+sup {
8
+    font-size: 0.7em;
9
+}
10
+
11
+.big {
12
+    vertical-align: middle;
13
+    font-size: 2em;
14
+    font-weight: normal;
15
+}
16
+
17
+.greek {
18
+    font-family: serif;
19
+    font-weight: normal;
20
+    font-style: normal;
21
+}
22
+
23
+.spaced {
24
+    margin-left: 0.2em;
25
+    margin-right: 0.2em;
26
+}
27
+
28
+.builtin {
29
+    font-style: normal;
30
+    margin-left: 0.2em;
31
+}
32
+
33
+/* fractions */
34
+.frac, .rboth, .pwf, .sum, .intbs, .ebounds, .limls {
35
+    display: inline-block;
36
+    vertical-align: middle;
37
+}
38
+
39
+.frac td, .rboth td, .sum td, .ebounds td, .limls td {
40
+    margin: 0;
41
+    padding: 0;
42
+    border: none;
43
+}
44
+
45
+.numer > td {
46
+    text-align: center;
47
+    border-bottom: 2px solid black;
48
+}
49
+
50
+.denom > td {
51
+    text-align: center;
52
+}
53
+
54
+/* roots */
55
+.rboth {
56
+    margin: 0;
57
+}
58
+
59
+.rord {
60
+    font-size: 1.2em;
61
+}
62
+
63
+.rcal {
64
+    margin-left: -0.3em;
65
+    font-size: 1.6em;
66
+}
67
+
68
+.rcand {
69
+    vertical-align: middle;
70
+    padding: 0.3em 0.1em 0;
71
+    border-top: 2px solid black;
72
+}
73
+
74
+/* piecewise */
75
+.pwf {
76
+    margin: 0;
77
+}
78
+
79
+.pwf > tr > td {
80
+    margin: 0;
81
+    padding: 0 0.5em 0 0.5em;
82
+    border: none;
83
+}
84
+
85
+/* summation */
86
+.sum tr:first-of-type {
87
+    line-height: 0.5em;
88
+}
89
+
90
+.sum td {
91
+    text-align: center;
92
+}
93
+
94
+/* integration */
95
+.intbs {
96
+    margin-left: -0.5em;
97
+    margin-right: -0.3em;
98
+}
99
+
100
+.intb > td {
101
+    margin: 0;
102
+    padding: 0;
103
+    border: none;
104
+}
105
+
106
+.intb > .ub {
107
+    padding-left: 0.5em;
108
+}
109
+
110
+.intsym {
111
+    font-size: 2em;
112
+    font-style: oblique;
113
+    font-family: serif;
114
+    font-weight: normal;
115
+    margin-left: 0.2em;
116
+}
117
+
118
+/* evaluation bar */
119
+.ebounds {
120
+    margin-left: -0.3em;
121
+}
122
+
123
+.ebar {
124
+    font-size: 2.5em;
125
+    margin-left: -0.2em;
126
+}
127
+
128
+/* limits */
129
+.limls td {
130
+    text-align: center;
131
+}
132
+
133
+/* matrices */
134
+.mat {
135
+    display: inline-block;
136
+    vertical-align: middle;
137
+    margin: 0.2em;
138
+    border-width: 0px 2px;
139
+    border-style: solid;
140
+    border-color: black;
141
+    border-radius: 12px;
142
+}
143
+
144
+.mat > tr > td {
145
+    padding: 0.2em;
146
+    border: none;
147
+}
148
+
149
+.mat + sup {
150
+    vertical-align: top;
151
+}
... ...
@@ -0,0 +1,421 @@
1
+"use strict";
2
+
3
+const isArray = x => (typeof x == "object" && x.constructor == Array);
4
+const NUMERIC_TYPES = {
5
+    integer: BigInt,
6
+    complex: Complex,
7
+    real: Number,
8
+};
9
+const nti = (new URLSearchParams(window.location.search)).get("nt") || "a";
10
+const NumericType = NUMERIC_TYPES[nti] || Number;
11
+
12
+// a: tensor, b: tensor, o: binary function of numbers
13
+function vecOp(a, b, o) {
14
+    const tra = tensorRank(a);
15
+    const trb = tensorRank(b);
16
+    if (tra == trb && tra >= 1 && a.length == b.length) {
17
+        return a.map((x, i) => vecOp(x, b[i], o));
18
+    } else if (tra > trb) {
19
+        return a.map(x => vecOp(x, b, o));
20
+    } else if (trb > tra) {
21
+        return b.map(x => vecOp(a, x, o));
22
+    } else if (tra == trb && tra == 0) {
23
+        return o(a, b);
24
+    }
25
+}
26
+
27
+// true iff any elements of v is null or NaN
28
+function anyNaN(v) {
29
+    return v.map ?
30
+        v.map(x => anyNaN(x)).reduce((a, b) => (a || b), false) :
31
+        (v == null || (typeof v == "number" && isNaN(v)) || (v.invalid && v.invalid()));
32
+}
33
+
34
+// the rank of t -- 0 for scalars, 1 for vectors, 2 for matrices, etc
35
+// see https://mathworld.wolfram.com/TensorRank.html
36
+function tensorRank(t) {
37
+    return (typeof t == "object" && t.constructor == Array) ? 1 + t.map(st => tensorRank(st)).reduce((a, b) => Math.max(a, b), 0) : 0;
38
+}
39
+
40
+// node in an abstract syntax tree, as produced by NewParser
41
+class AST {
42
+    constructor(ts, l, m, r, len) {
43
+        switch (ts) {
44
+            // identifier: l == Token (tt == 1), m, r == undefined
45
+            case 1: this.v = l.v; break;
46
+            // literal: l == Token (tt == 2)
47
+            case 2: this.v = l.v; break;
48
+            // operation: l == AST, m == Token (tt == 3), r == AST
49
+            case 3: this.l = l; this.m = m.v; this.r = r; break;
50
+            // statement: l == bool
51
+            // l true: m == AST, r == AST
52
+            // l false: m == AST, r == undefined
53
+            case 4: this.l = l; this.m = m; this.r = r; break;
54
+            // unary operation: l == Token (tt == 3), m == AST, r == undefined
55
+            case 5: this.l = l.v; this.m = m; break;
56
+            // indexing operation: l == AST, m == AST
57
+            case 6: this.l = l; this.m = m; break;
58
+            // function call (lvalue): l == AST, m == Array of AST, r == bool (square brackets?)
59
+            case 7: this.l = l; this.m = m; this.r = r; break;
60
+            // function call (non-lvalue): l == AST, m == Array of AST, r == bool (square brackets?)
61
+            case 8: this.l = l; this.m = m; this.r = r; break;
62
+            default: dl("error: invalid AST node type " + ts);
63
+        }
64
+        this.ts = ts;
65
+        this.len = len;
66
+    }
67
+    
68
+    // de is an object for variables
69
+    // wv is the AST for the value to write
70
+    // returns the evaluated value saved on success or null on failure
71
+    lvalueWrite(de, wv) {
72
+        switch (this.ts) {
73
+            // identifier: write to variable
74
+            case 1:
75
+                const ewv = wv.evaluate(de);
76
+                de[this.v] = ewv;
77
+                return ewv;
78
+            // literal, binary operation, statement, unary operator: invalid
79
+            case 2:
80
+            case 3:
81
+            case 4:
82
+            case 5:
83
+                return null;
84
+            // indexing operation: write to indexed position
85
+            case 6:
86
+                let oldv, ind;
87
+                try {
88
+                    oldv = this.l.evaluate(de);
89
+                    ind = this.m.evaluate(de);
90
+                } catch (err) {
91
+                    throw err;
92
+                }
93
+                if (isArray(oldv) && oldv[ind - 1] != null) {
94
+                    const er = wv.evaluate(de);
95
+                    ind = ind.mag();
96
+                    if (er.length == 1) {
97
+                        oldv[ind - 1] = er[0];
98
+                        return this.l.lvalueWrite(de, { "evaluate": () => oldv });
99
+                    } else {
100
+                        throw `cannot insert vector ${fts(er)} into ${fts(oldv)} as a scalar`;
101
+                    }
102
+                } else {
103
+                    throw `index ${fts(ind + 1)} out of range for vector ${fts(oldv)}`;
104
+                }
105
+                break;
106
+            // function call: add case to corresponding function
107
+            case 7:
108
+            case 8:
109
+                let oldf;
110
+                try {
111
+                    oldf = this.l.evaluate(de);
112
+                } catch (err) {
113
+                    oldf = null;
114
+                }
115
+                if (oldf && oldf.constructor === MathsFunc) {
116
+                    oldf.addCase([this.m, function(args, ide) {
117
+                        const tde = Object.fromEntries(Object.entries(ide));
118
+                        for (const ak in args) {
119
+                            tde[ak] = args[ak];
120
+                        }
121
+                        return wv.evaluate(tde);
122
+                    }], de, false);
123
+                    return oldf;
124
+                // don't know what to do in this case, but it's a possibility
125
+                } else if (oldf && oldf.constructor == Function) {
126
+                } else if (oldf && oldf.constructor == Array) {
127
+                    let ind;
128
+                    try {
129
+                        // TODO: handle multi-indexing
130
+                        ind = this.m[0].evaluate(de);
131
+                    } catch (err) {
132
+                        throw err;
133
+                    }
134
+                    if (oldf[ind - 1] != null) {
135
+                        const er = wv.evaluate(de);
136
+                        ind = ind.mag();
137
+                        oldf[ind - 1] = er;
138
+                        return this.l.lvalueWrite(de, { "evaluate": () => oldf });
139
+                    } else {
140
+                        throw `index ${fts(ind + 1)} out of range for vector ${fts(oldf)}`;
141
+                    }
142
+                } else {
143
+                    const confn = false ?
144
+                        new MathsFunc([[this.m, function(args, ide) {
145
+                            const wvc = wv.clone();
146
+                            for (const ak in args) {
147
+                                wvc.substitute(ak, args[ak]);
148
+                            }
149
+                            const tde = Object.fromEntries(Object.entries(ide));
150
+                            return wvc.evaluate(tde);
151
+                        }]], de, true) :
152
+                        new MathsFunc([[this.m, function(args, ide) {
153
+                            const tde = Object.fromEntries(Object.entries(ide));
154
+                            for (const ak in args) {
155
+                                tde[ak] = args[ak];
156
+                            }
157
+                            return wv.evaluate(tde);
158
+                        }]], de, false);
159
+                    return this.l.lvalueWrite(de, { "evaluate": () => confn });
160
+                }
161
+                break;
162
+            default:
163
+                return null;
164
+        }
165
+    }
166
+    
167
+    // de is an object for variables
168
+    evaluate(de) {
169
+        let ret;
170
+        switch (this.ts) {
171
+            // identifier: get variable value
172
+            case 1:
173
+                const dev = de[this.v];
174
+                if (dev != null) {
175
+                    return dev;
176
+                } else {
177
+                    throw `variable ${this.v} not found`;
178
+                }
179
+                break;
180
+            // literal: return scalar
181
+            case 2:
182
+                const pf = parseFloat(this.v);
183
+                return NumericType.ify(pf);
184
+            // binary operation: evaluate operands and combine
185
+            case 3:
186
+                // maplet is anonymous function
187
+                if (this.m == 10) {
188
+                    let csat = this.l;
189
+                    const csal = [];
190
+                    while (csat.ts == 3 && csat.m == 8) {
191
+                        csal.unshift(csat.r);
192
+                        csat = csat.l;
193
+                    }
194
+                    csal.unshift(csat);
195
+                    const mfn = new MathsFunc([[csal, (args, ide) => {
196
+                        const tde = Object.fromEntries(Object.entries(ide));
197
+                        for (const ek in de) {
198
+                            tde[ek] = de[ek];
199
+                        }
200
+                        for (const ak in args) {
201
+                            tde[ak] = args[ak];
202
+                        }
203
+                        return this.r.evaluate(tde);
204
+                    }]], de, false);
205
+                    return mfn;
206
+                }
207
+                // equal sign is assignment
208
+                if (this.m == 11) {
209
+                    return this.l.lvalueWrite(de, this.r);
210
+                }
211
+                let a;
212
+                let b;
213
+                try {
214
+                    a = this.l.evaluate(de);
215
+                    b = this.r.evaluate(de);
216
+                } catch (err) {
217
+                    throw err;
218
+                }
219
+                if (a.constructor == AST || b.constructor == AST) {
220
+                    return new AST(3, a, { v: this.m }, b);
221
+                }
222
+                switch (this.m) {
223
+                    case 0: ret = vecOp(a, b, (x, y) => NumericType.ify(x).add(NumericType.ify(y))); break;
224
+                    case 1: ret = vecOp(a, b, (x, y) => NumericType.ify(x).sub(NumericType.ify(y))); break;
225
+                    case 2: ret = vecOp(a, b, (x, y) => NumericType.ify(x).mul(NumericType.ify(y))); break;
226
+                    case 3: ret = vecOp(a, b, (x, y) => NumericType.ify(x).div(NumericType.ify(y))); break;
227
+                    // exponent
228
+                    case 4: ret = vecOp(a, b, (x, y) => NumericType.ify(x).pow(NumericType.ify(y))); break;
229
+                    // base x logarithm
230
+                    case 5: ret = vecOp(a, b, (x, y) => NumericType.ify(y).log(NumericType.ify(x))); break;
231
+                    case 6: ret = vecOp(a, b, (x, y) => NumericType.ify(x).eq(NumericType.ify(y))); break;
232
+                    // yth root
233
+                    case 7: ret = vecOp(a, b, (x, y) => NumericType.ify(y).pow(NumericType.ify(x).recip())); break;
234
+                    // comma of vector joining
235
+                    case 8:
236
+                        ret = (tensorRank(b) + 1 <= tensorRank(a)) ?
237
+                            a.concat([b]) : [a, b];
238
+                        break;
239
+                    case 9: ret = vecOp(a, b, (x, y) => NumericType.ify(x).lt(NumericType.ify(y))); break;
240
+                    case 12:
241
+                        if (a.rangeTo && b.rangeTo) {
242
+                            ret = a.rangeTo(b);
243
+                        } else if (isArray(a) && isArray(b)) {
244
+                            ret = a.concat(b);
245
+                        } else {
246
+                            ret = null;
247
+                        }
248
+                        break;
249
+                    default: ret = dl("error: unrecognised operator id " + this.m);
250
+                }
251
+                if (ret == null || anyNaN(ret)) {
252
+                    throw `operation ${OPS[this.m]} between ${fts(a)} and ${fts(b)} failed`;
253
+                } else {
254
+                    return ret;
255
+                }
256
+                break;
257
+            // statement: assign after evaluation or just evaluate
258
+            case 4:
259
+                return this.l ?
260
+                    this.m.lvalueWrite(de, this.r) :
261
+                    this.m.evaluate(de);
262
+            // unary operator: evaluate operand and modify
263
+            case 5:
264
+                let c;
265
+                try {
266
+                    c = this.m.evaluate(de);
267
+                } catch (err) {
268
+                    throw err;
269
+                }
270
+                switch (this.l) {
271
+                    case 0: ret = c; break;
272
+                    case 1: ret = vecOp(c, 0, (x, _y) => NumericType.ify(x).neg()); break;
273
+                    // unary * is undefined
274
+                    case 2: ret = null; break;
275
+                    case 3: ret = vecOp(c, 0, (x, _y) => NumericType.ify(x).recip()); break;
276
+                    case 4: ret = vecOp(c, 0, (x, _y) => NumericType.ify(x).exp()); break;
277
+                    case 5: ret = vecOp(c, 0, (x, _y) => NumericType.ify(x).ln(x)); break;
278
+                    case 6: ret = vecOp(c, 0, (x, _y) => NumericType.ify(x).eq(NumericType.zero())); break;
279
+                    case 7: ret = vecOp(c, 0, (x, _y) => NumericType.ify(x).sqrt()); break;
280
+                    // unary , is undefined
281
+                    case 8: ret = null; break;
282
+                    case 9: ret = vecOp(c, 0, (x, _y) => NumericType.ify(x).isNeg()); break;
283
+                    // unary => is undefined
284
+                    case 10: ret = null; break;
285
+                    // unary = is undefined
286
+                    case 11: ret = null; break;
287
+                    case 12: ret = [c]; break;
288
+                    default: ret = dl("error: unrecognised operator id " + this.l);
289
+                }
290
+                if (ret == null || anyNaN(ret)) {
291
+                    throw `operation ${OPS[this.l]} on ${fts(c)} failed`;
292
+                } else {
293
+                    return ret;
294
+                }
295
+                break;
296
+            // indexing: evaluate vector and index, access vector element
297
+            case 6:
298
+                let v, i;
299
+                try {
300
+                    v = this.l.evaluate(de);
301
+                    i = this.m.evaluate(de);
302
+                } catch (err) {
303
+                    throw err;
304
+                }
305
+                i = i.mag();
306
+                if (!isArray(v)) {
307
+                    throw `${fts(v)} cannot be indexed`;
308
+                } else if (v[i - 1] == null) {
309
+                    throw `index ${fts(i)} out of range for vector ${fts(v)}`;
310
+                } else {
311
+                    return v[i - 1];
312
+                }
313
+                break;
314
+            // function call: call function on arguments
315
+            case 7:
316
+            case 8:
317
+                const f = this.l.evaluate(de);
318
+                if (f && f.apply) {
319
+                    const args = this.m.map(x => (this.r ? x : x.evaluate(de)));
320
+                    return f.apply(f, [args, de]);
321
+                } else if (f && f.constructor == Array) {
322
+                    // TODO: handle multi-indexing
323
+                    const args = this.m.map(x => x.evaluate(de));
324
+                    const i = args[0].mag ? args[0].mag() - 1 : null;
325
+                    if (f[i] == null) {
326
+                        throw `index ${fts(i + 1)} out of range for vector ${fts(f)}`;
327
+                    } else {
328
+                        return f[i];
329
+                    }
330
+                } else {
331
+                    throw f ? `${fts(f)} is not a function or vector` : "function not found";
332
+                }
333
+                break;
334
+            default:
335
+                return null;
336
+        }
337
+    }
338
+    
339
+    // deep-copy this AST
340
+    clone() {
341
+        let cl, cm, cr;
342
+        switch (this.ts) {
343
+            case 1: case 2: cl = { v: this.v }; break;
344
+            case 3: cl = this.l.clone(); cm = { v: this.m }; cr = this.r.clone(); break;
345
+            case 4: cl = this.l; cm = this.m.clone(); cr = this.r && this.r.clone(); break;
346
+            case 5: cl = { v: this.v }; cm = this.m; break;
347
+            case 6: cl = this.l.clone(); cm = this.m.clone(); break;
348
+            // function call (lvalue): l == AST, m == Array of AST, r == bool (square brackets?)
349
+            case 7: case 8: cl= this.l.clone(); cm = this.m.map(a => a.clone()); cr = this.r; break;
350
+            default: dl("error: invalid AST node type " + ts);
351
+        }
352
+        const ra = new AST(this.ts, cl, cm, cr, this.len);
353
+        return ra;
354
+    }
355
+    
356
+    // replace all instances of variable name x with AST expression y
357
+    substitute(x, y) {
358
+        switch (this.ts) {
359
+            // identifier
360
+            case 1:
361
+                if (this.v == x) {
362
+                    this.ts = y.ts;
363
+                    this.v = y.v;
364
+                    this.l = y.l;
365
+                    this.m = y.m;
366
+                    this.r = y.r;
367
+                }
368
+                break;
369
+            // literal
370
+            case 2: break;
371
+            // operation
372
+            case 3: this.l.substitute(x, y); this.r.substitute(x, y); break;
373
+            // statement
374
+            case 4: this.m.substitute(x, y); this.l && this.r.substitute(x, y); break;
375
+            // unary operation
376
+            case 5: this.m.substitute(x, y); break;
377
+            // indexing operation
378
+            case 6: this.l.substitute(x, y); this.m.substitute(x, y); break;
379
+            // function call (lvalue)
380
+            case 7: case 8: this.l.substitute(x, y); this.m.forEach(a => a.substitute(x, y)); break;
381
+            default: dl("error: invalid AST node type " + this.ts);
382
+        }
383
+    }
384
+    
385
+    // convert this AST to postfix expression notation
386
+    // not used in vecalc, and out of date wrt new syntactical structures
387
+    toPostfix() {
388
+        switch (this.ts) {
389
+            case 1:
390
+            case 2:
391
+                return "" + this.v;
392
+            case 3:
393
+                return this.l.toPostfix() + " " +
394
+                    this.r.toPostfix() + " " + OPS[this.m];
395
+            case 4:
396
+                return this.l ?
397
+                    (this.m.toPostfix() + " " + this.r.toPostfix() + " =") :
398
+                    this.m.toPostfix();
399
+        }
400
+    }
401
+    
402
+    // string representation of the AST -- the inverse of parsing
403
+    toString() {
404
+        switch (this.ts) {
405
+            case 1:
406
+            case 2:
407
+                return `${this.v}`;
408
+            case 3:
409
+                return `(${this.l} ${OPS[this.m]} ${this.r})`;
410
+            case 4:
411
+                return this.l ? `${this.m} = ${this.r}` : `${this.m}`;
412
+            case 5:
413
+                return `${OPS[this.l]}(${this.m})`;
414
+            case 6:
415
+                return `${this.l}[${this.m}]`;
416
+            case 7:
417
+            case 8:
418
+                return `${this.l}(${this.m.map(x => x.toString()).join(", ")})`;
419
+        }
420
+    }
421
+}
... ...
@@ -0,0 +1,485 @@
1
+"use strict";
2
+// maths parser, by dkl9, 2021-05 to 2021-07, used as part of rtensor
3
+
4
+// whitespace characters
5
+const WSPACE = [" ", "\t", "\n"];
6
+// characters for starting identifiers
7
+const ISTART = ["_", "'"];
8
+// characters in identifiers after the start
9
+const ICHAR = ["_", "'"];
10
+for (let c = 1; c <= 26; c++) {
11
+    ISTART.push(String.fromCodePoint(c + 64));
12
+    ISTART.push(String.fromCodePoint(c + 96));
13
+    ICHAR.push(String.fromCodePoint(c + 64));
14
+    ICHAR.push(String.fromCodePoint(c + 96));
15
+}
16
+for (let c = 0; c <= 9; c++) {
17
+    ICHAR.push(String.fromCodePoint(c + 48));
18
+}
19
+// characters in numeric literals
20
+const LCHAR = [];
21
+for (let c = 0; c <= 9; c++) {
22
+    LCHAR.push(String.fromCodePoint(c + 48));
23
+}
24
+// operator text
25
+const OPS = [
26
+    "+",  "-",  "*",  "/",
27
+    "^",  "\\", "==", "-/",
28
+    ",",  "<",  "=>", "=",
29
+    "..",
30
+];
31
+// precedence of binary operators (greater == lower)
32
+const PREC = [
33
+    4,  4,  3,  3,
34
+    2,  1,  5,  2,
35
+    6,  5,  7,  8,
36
+    4,
37
+];
38
+// associativity of precedence levels (1 == left, 2 == right)
39
+const ASSOC = [0, 0, 2, 2, 1, 1, 1, 1, 2, 2];
40
+// prefix-substrings of operators
41
+const OPB = OPS.flatMap(function(s) {
42
+    const r = [];
43
+    for (let i = 0; i <= s.length; i++) {
44
+        r.push(s.substring(0, i));
45
+    }
46
+    return r;
47
+});
48
+// names of token types
49
+const TTN = [
50
+    "BAD", "identifier", "literal", "boperator",
51
+    "lparen", "rparen", "assign", "lsqbr",
52
+    "rsqbr"
53
+];
54
+
55
+/*
56
+operators
57
++ add or identity
58
+- subtract or negate
59
+* multiply or error
60
+/ divide or reciprocal
61
+^ exponent or power-of-e
62
+\ logarithm or natural logarithm
63
+== equal or equals-zero
64
+-/ nth-root or square root
65
+, vector join or error
66
+< less than or negative
67
+=> anonymous function or error
68
+= assignment or error
69
+.. range/concatenation or encapsulation
70
+*/
71
+
72
+// normalise integer literal:
73
+// strip all zeroes from the beginning, unless it is just "0"
74
+const normIntLit = s => (s.replace(/^0+/, "") || "0");
75
+
76
+// a token, having a token type (this.tt) and a value (this.v)
77
+// token type may be one of 1 (ident), 2 (int literal), 3 (operator)
78
+// 4 (lparen), 5 (rparen), 6 (assign), 7 (left square bracket),
79
+// 8 (right square bracket)
80
+// value depends on tt: 1 => ident text, 2 => int value, 3 => operator id
81
+// 4, 5, 6, 7, 8 => undefined
82
+class Token {
83
+    constructor(tt, text) {
84
+        this.tt = tt;
85
+        switch (this.tt) {
86
+            case 1:
87
+                this.v = text;
88
+                break;
89
+            case 2:
90
+                this.v = parseFloat(normIntLit(text));
91
+                break;
92
+            case 3:
93
+                this.v = OPS.indexOf(text);
94
+                break;
95
+            case 4:
96
+            case 5:
97
+            case 6:
98
+            case 7:
99
+            case 8:
100
+                break;
101
+            default:
102
+                dl("error: unrecognised token type " + tt);
103
+        }
104
+    }
105
+
106
+    // textual representation of the token
107
+    // for debugging purposes -- would not be parsed back into the same token
108
+    toString() {
109
+        return TTN[this.tt] + " " + this.v;
110
+    }
111
+}
112
+
113
+// source text surrounded by an interface to provide tokens from it on demand
114
+class TokenStream {
115
+    constructor(source) {
116
+        this.toks = [];
117
+        this.tind = -1;
118
+        this.src = source;
119
+        this.sind = 0;
120
+    }
121
+    
122
+    // returns boolean indicating if progress through the source text
123
+    // "overran" the source text's length
124
+    orun() {
125
+        return this.sind >= this.src.length;
126
+    }
127
+    
128
+    // get previous token
129
+    prev(n) {
130
+        const tn = n || 1;
131
+        if (this.tind < tn - 1) {
132
+            return null;
133
+        } else {
134
+            this.tind -= tn;
135
+            return this.toks[this.tind];
136
+        }
137
+    }
138
+
139
+    // get next token, generating the next one if necessary
140
+    next() {
141
+        if (this.tind >= (this.toks.length - 1) && (!this.gen())) {
142
+            return null;
143
+        } else {
144
+            this.tind++;
145
+            return this.toks[this.tind];
146
+        }
147
+    }
148
+    
149
+    // generate next token, returning a boolean indicating success or failure
150
+    gen() {
151
+        // already at the end of the source text? fail
152
+        if (this.orun()) {
153
+            return false;
154
+        }
155
+        // go past any whitespace
156
+        while (WSPACE.includes(this.src[this.sind])) {
157
+            this.sind++;
158
+            if (this.orun()) {
159
+                return false;
160
+            }
161
+        }
162
+        // check if character starts identifier
163
+        if (ISTART.includes(this.src[this.sind])) {
164
+            // collect next characters into identifier token
165
+            let ntok = "";
166
+            while (!this.orun() && ICHAR.includes(this.src[this.sind])) {
167
+                ntok += this.src[this.sind];
168
+                this.sind++;
169
+            }
170
+            this.toks.push(new Token(1, ntok));
171
+        // check if character is in numeric literal
172
+        } else if (LCHAR.includes(this.src[this.sind]) ||
173
+            (this.src[this.sind] == "." && this.src[this.sind + 1] != ".")) {
174
+            // collect next characters into numeric literal token
175
+            let ntok = "";
176
+            let df = false;
177
+            while (!this.orun() && (LCHAR.includes(this.src[this.sind]) || this.src[this.sind] == ".")) {
178
+                if (this.src[this.sind] == ".") {
179
+                    if (df) {
180
+                        break;
181
+                    }
182
+                    df = true;
183
+                }
184
+                ntok += this.src[this.sind];
185
+                this.sind++;
186
+            }
187
+            this.toks.push(new Token(2, ntok));
188
+        // check if character is an operator beginner
189
+        } else if (OPB.includes(this.src[this.sind])) {
190
+            let ntok = "";
191
+            while (!this.orun() && OPB.includes(ntok)) {
192
+                ntok += this.src[this.sind];
193
+                this.sind++;
194
+            }
195
+            ntok = ntok.substring(0, ntok.length - 1);
196
+            this.sind--;
197
+            this.toks.push(new Token(3, ntok));
198
+        /*
199
+        // equal sign of assignment
200
+        } else if (this.src[this.sind] == "=" &&
201
+            this.src[this.sind + 1] != "=" &&
202
+            this.src[this.sind + 1] != ">") {
203
+            this.sind++;
204
+            this.toks.push(new Token(6, "="));
205
+        */
206
+        // left and right square brackets
207
+        } else if (["[", "]"].includes(this.src[this.sind])) {
208
+            const pt = (this.src[this.sind] == "[" ? 7 : 8);
209
+            this.sind++;
210
+            this.toks.push(new Token(pt));
211
+        // left and right parens
212
+        } else if (["(", ")"].includes(this.src[this.sind])) {
213
+            const pt = (this.src[this.sind] == "(" ? 4 : 5);
214
+            this.sind++;
215
+            this.toks.push(new Token(pt));
216
+        // anything else is invalid
217
+        } else {
218
+            throw `invalid character ${this.src[this.sind]}`;
219
+            return false;
220
+        }
221
+        return true;
222
+    }
223
+}
224
+
225
+// hopefully, this will be less dumb (than the old Parser class, now deleted)
226
+// each function parses a particular structure,
227
+// returning an AST (with .len, measured in tokens) if successful
228
+// or null if failed
229
+class NewParser {
230
+    // statement -> expr[9]
231
+    static statement(stream) {
232
+        let tc = 0;
233
+        /*const a = NewParser.lvalue(stream, 9);
234
+        if (a) {
235
+            tc += a.len;
236
+            const b = stream.next() || {};
237
+            tc += !!b.tt;
238
+            if (b.tt == 6) {
239
+                const c = NewParser.expr(stream, 9);
240
+                if (c) {
241
+                    tc += c.len;
242
+                    return new AST(4, true, a, c, tc);
243
+                }
244
+            }
245
+        }
246
+        stream.prev(tc);
247
+        tc = 0;*/
248
+        const d = NewParser.expr(stream, 9);
249
+        if (d) {
250
+            tc += d.len;
251
+            return new AST(4, false, d, undefined, tc);
252
+        }
253
+        return null;
254
+    }
255
+
256
+    // BAD expr[0] -> lvalue[9]
257
+    // expr[0] -> identifier
258
+    // expr[0] -> literal
259
+    // expr[0] -> lparen expr[9] rparen
260
+    // expr[1] -> expr[0] argSuff*
261
+    // expr[n > 1] -> expr[n - 1] (operator[n - 1] expr[n - 1])*
262
+    // expr[n] -> operator[9] expr[1] argSuff?
263
+    static expr(stream, n) {
264
+        let tc = 0;
265
+        /*if (n == 0) {
266
+            // expr[0] -> lvalue[9]
267
+            const a = NewParser.lvalue(stream, 9);
268
+            if (a) {
269
+                return a;
270
+            }
271
+        }*/
272
+        let b = stream.next() || {};
273
+        tc += !!b.tt;
274
+        if (n == 0) {
275
+            // expr[0] -> identifier
276
+            if (b.tt == 1) {
277
+                return new AST(1, b, undefined, undefined, 1);
278
+            }
279
+            // expr[0] -> literal
280
+            if (b.tt == 2) {
281
+                return new AST(2, b, undefined, undefined, 1);
282
+            }
283
+            // expr[0] -> lparen expr[9] rparen
284
+            if (b.tt == 4) {
285
+                const c = NewParser.expr(stream, 9);
286
+                if (c) {
287
+                    tc += c.len;
288
+                    const d = stream.next() || {};
289
+                    tc += !!d.tt;
290
+                    if (d.tt == 5) {
291
+                        c.len += 2;
292
+                        return c;
293
+                    }
294
+                }
295
+                stream.prev(tc - 1);
296
+            }
297
+            tc = !!b.tt;
298
+        }
299
+        stream.prev();
300
+        tc = 0;
301
+        // expr[1] -> expr[0] argSuff*
302
+        if (n == 1) {
303
+            let c = NewParser.expr(stream, 0);
304
+            if (c) {
305
+                tc += c.len;
306
+                let d;
307
+                while ((d = NewParser.argSuff(stream))) {
308
+                    tc += d.len;
309
+                    c = new AST(8, c, d, d.sqbr, tc);
310
+                }
311
+                return c;
312
+            }
313
+            tc && stream.prev(tc);
314
+            tc = 0;
315
+        }
316
+        // expr[n > 1] -> expr[n - 1] (operator[n - 1] expr[n - 1])*
317
+        if (n > 1) {
318
+            const eol = [NewParser.expr(stream, n - 1)];
319
+            if (!(eol[0])) { return null; }
320
+            tc += eol.len;
321
+            // collect (operator[n - 1] expr[n - 1]) pairs
322
+            while (true) {
323
+                const no = stream.next() || {};
324
+                if (!no.tt) { break; }
325
+                if (no.tt != 3 || PREC[no.v] != n - 1) { stream.prev(); break; }
326
+                const nse = NewParser.expr(stream, n - 1);
327
+                if (!nse) { stream.prev(); break; }
328
+                eol.push(no); eol.push(nse);
329
+                tc += (!!no.tt) + nse.len;
330
+            }
331
+            // collapse expression to tree based on associativity
332
+            let d; let e; let f;
333
+            while (eol.length >= 3) {
334
+                switch (ASSOC[n]) {
335
+                    case 1:
336
+                        d = eol.shift(); e = eol.shift(); f = eol.shift();
337
+                        eol.unshift(new AST(3, d, e, f, d.len + (!!e.tt) + f.len));
338
+                        break;
339
+                    case 2:
340
+                        d = eol.pop(); e = eol.pop(); f = eol.pop();
341
+                        eol.push(new AST(3, f, e, d, f.len + (!!e.tt) + d.len));
342
+                        break;
343
+                    default:
344
+                        dl("error: unreachable");
345
+                        break;
346
+                }
347
+            }
348
+            return eol[0];
349
+        }
350
+        b = stream.next() || {};
351
+        tc += !!b.tt;
352
+        // expr[n] -> operator[9] expr[1]
353
+        if (b.tt == 3) {
354
+            const c = NewParser.expr(stream, 1);
355
+            if (c) {
356
+                tc += c.len;
357
+                return new AST(5, b, c, undefined, tc);
358
+            }
359
+            stream.prev(tc - 1);
360
+        }
361
+        stream.prev();
362
+        tc = 0;
363
+        return null;
364
+    }
365
+    
366
+    // lvalue[0] -> identifier
367
+    // lvalue[1] -> lvalue[0] indSuff?
368
+    // lvalue[2] -> lvalue[1] argSuff?
369
+    // lvalue[n > 2] -> lvalue[n - 1]
370
+    static lvalue(stream, n) {
371
+        // lvalue[0] -> identifier
372
+        if (n == 0) {
373
+            const a = stream.next() || {};
374
+            if (a.tt == 1) {
375
+                return new AST(1, a, undefined, undefined, 1);
376
+            } else {
377
+                a.tt && stream.prev();
378
+                return null;
379
+            }
380
+        }
381
+        let tc = 0;
382
+        // lvalue[1] -> lvalue[0] indSuff?
383
+        if (n == 1) {
384
+            const a = NewParser.lvalue(stream, 0);
385
+            if (a) {
386
+                tc += a.len;
387
+                const b = NewParser.indSuff(stream);
388
+                if (b) {
389
+                    tc += b.len;
390
+                    return new AST(6, a, b, undefined, tc);
391
+                } else {
392
+                    return a;
393
+                }
394
+            } else {
395
+                return null;
396
+            }
397
+        }
398
+        // lvalue[2] -> lvalue[1] argSuff?
399
+        if (n == 2) {
400
+            const a = NewParser.lvalue(stream, 1);
401
+            if (a) {
402
+                tc += a.len;
403
+                const b = NewParser.argSuff(stream);
404
+                if (b) {
405
+                    tc += b.len;
406
+                    return new AST(7, a, b, b.sqbr, tc);
407
+                } else {
408
+                    return a;
409
+                }
410
+            } else {
411
+                return null;
412
+            }
413
+        }
414
+        // lvalue[n > 2] -> lvalue[n - 1]
415
+        if (n > 2) {
416
+            const a = NewParser.lvalue(stream, n - 1);
417
+            if (a) {
418
+                return a;
419
+            } else {
420
+                return null;
421
+            }
422
+        }
423
+        return null;
424
+    }
425
+    
426
+    // indSuff -> lsqbr expr[9] rsqbr
427
+    static indSuff(stream) {
428
+        const lb = stream.next() || {};
429
+        if (!lb.tt) { return null; }
430
+        if (lb.tt != 7) { stream.prev(); return null; }
431
+        const iexc = NewParser.expr(stream, 9);
432
+        if (!iexc) { stream.prev(); return null; }
433
+        const rb = stream.next() || {};
434
+        if (!rb.tt) { stream.prev(1 + iexc.len); return null; }
435
+        if (rb.tt != 8) { stream.prev(1 + iexc.len + 1); return null; }
436
+        iexc.len += 2;
437
+        return iexc;
438
+    }
439
+
440
+    // will return Array of AST (with properties .len, .sqbr) instead of AST on success
441
+    // precedence rank 6 is used to avoid confusing comma-as-operator
442
+    // with comma-as-argument-separator
443
+    // argSuff -> lparen (expr[6] (comma expr[6])*)? rparen
444
+    // argSuff -> lsqbr (expr[6] (comma expr[6])*)? lsqbr
445
+    static argSuff(stream) {
446
+        const a = stream.next() || {};
447
+        if (a.tt == 4 || a.tt == 7) {
448
+            const rl = [];
449
+            rl.len = 1;
450
+            rl.sqbr = a.tt == 7;
451
+            const b = NewParser.expr(stream, 6);
452
+            if (b) {
453
+                rl.len += b.len;
454
+                rl.push(b);
455
+                let nct; let nae;
456
+                // collect (comma expr[5]) pairs
457
+                while (true) {
458
+                    nct = stream.next() || {};
459
+                    if (nct.tt != 3 || nct.v != 8) {
460
+                        nct.tt && stream.prev();
461
+                        break;
462
+                    }
463
+                    nae = NewParser.expr(stream, 6);
464
+                    if (!nae) {
465
+                        stream.prev();
466
+                    }
467
+                    rl.len += 1 + nae.len;
468
+                    rl.push(nae);
469
+                }
470
+            }
471
+            const c = stream.next() || {};
472
+            rl.len += !!c.tt;
473
+            if (!rl.sqbr && c.tt == 5) {
474
+                return rl;
475
+            }
476
+            if (rl.sqbr && c.tt == 8) {
477
+                return rl;
478
+            }
479
+            stream.prev(rl.len);
480
+        } else if (a.tt) {
481
+            stream.prev();
482
+        }
483
+        return null;
484
+    }
485
+}
... ...
@@ -0,0 +1,77 @@
1
+<!doctype html>
2
+<html>
3
+    <head>
4
+        <title>Parser</title>
5
+        <style>
6
+            input[type=text] {
7
+                width: 50vw;
8
+            }
9
+        </style>
10
+    </head>
11
+    <body>
12
+        <p>
13
+            <input id="ttp" type="text" />
14
+            <button id="bpb">parse</button>
15
+            <button id="eeb">eval</button>
16
+            <button id="pfn">postfix</button>
17
+        </p>
18
+        <p><label><input id="doo" type="checkbox" checked /> enable debug output (much faster when off)</label></p>
19
+        <p id="mop">parse first</p>
20
+        <details open><summary>debugging log</summary><p id="log"></p></details>
21
+        <script src="maths_parser.js"></script>
22
+        <script>
23
+            function dl(t) {
24
+                // when i was first testing the parser, i was getting parse times in the tens or hundreds of milliseconds
25
+                // so i was wondering, is it really that slow?
26
+                // no -- it's mostly because of all the debug output
27
+                // if we stub this out, it goes down to 0-5 ms
28
+                if (doe) {
29
+                    document.getElementById("log").innerHTML += t + "<br />";
30
+                }
31
+                return null;
32
+            }
33
+            function udd(ev) {
34
+                doe = ev.target.checked;
35
+            }
36
+            window.onerror = function(ev) {
37
+                dl("ERROR: " + JSON.stringify(ev));
38
+            }
39
+            const oldlog = console.log;
40
+            console.error = console.debug = console.info = console.log = function(v) {
41
+                oldlog(v);
42
+                dl(v);
43
+            };
44
+            let ast;
45
+            let doe;
46
+            udd({ "target": document.getElementById("doo") });
47
+            document.getElementById("bpb").onclick = function() {
48
+                document.getElementById("log").innerText = "";
49
+                const lexer = new TokenStream(document.getElementById("ttp").value);
50
+                dl("about to parse!");
51
+                const start = Date.now();
52
+                ast = NewParser.statement(lexer);
53
+                dl(`received statement ${ast.toString()}`);
54
+                const end = Date.now();
55
+                dl("parsed " + ast);
56
+                document.getElementById("mop").innerText = `parsed in ${end - start} ms (token index ${lexer.tind})\n${ast.toString()}`;
57
+            };
58
+            document.getElementById("eeb").onclick = function() {
59
+                if (ast) {
60
+                    const er = ast.evaluate({}, true);
61
+                    dl(`evaluation result: ${er}`);
62
+                    document.getElementById("mop").innerText = er || "evaluation error";
63
+                } else {
64
+                    document.getElementById("mop").innerText += "!";
65
+                }
66
+            };
67
+            document.getElementById("pfn").onclick = function() {
68
+                if (ast) {
69
+                    document.getElementById("mop").innerText = ast.toPostfix();
70
+                } else {
71
+                    document.getElementById("mop").innerText += "!";
72
+                }
73
+            };
74
+            document.getElementById("doo").onchange = udd;
75
+        </script>
76
+    </body>
77
+</html>
0 78
\ No newline at end of file
... ...
@@ -0,0 +1,456 @@
1
+<?xml version="1.0" encoding="UTF-8" ?>
2
+<!DOCTYPE html>
3
+<html xmlns="http://www.w3.org/1999/xhtml">
4
+    <head>
5
+        <meta charset="utf-8" />
6
+        <title>RTensor</title>
7
+        <link rel="stylesheet" href="maths.css" />
8
+        <style>
9
+            /*
10
+            look at this -- 27 lines of CSS!
11
+            that's all you need!
12
+            what's wrong with the web developers these days?!
13
+            */
14
+            body {
15
+                font-size: 1.2em;
16
+            }
17
+            input[type=text] {
18
+                font-family: serif;
19
+                font-size: 1em;
20
+                width: calc(100% - 3ch);
21
+            }
22
+            p, td, tr, table {
23
+                margin: 0;
24
+            }
25
+            td {
26
+                padding: 0.5vh;
27
+                border: 1px solid black;
28
+            }
29
+            .bad {
30
+                background-color: lightcoral;
31
+            }
32
+            .good {
33
+                background-color: lightgreen;
34
+            }
35
+            .excode {
36
+                cursor: pointer;
37
+            }
38
+            #title, #vnum {
39
+                margin: 0 2em 0 0;
40
+                display: inline-block;
41
+            }
42
+        </style>
43
+        <link rel="icon" href='data:image/svg+xml,&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"&gt;&lt;style&gt;text { font: italic bold 40px "Garamond", serif; }&lt;/style&gt;&lt;text fill="none" stroke="white" stroke-width="16px" y="40" x="8" textLength="56px"&gt;f(x)&lt;/text&gt;&lt;text y="40" x="8" textLength="56px"&gt;f(x)&lt;/text&gt;&lt;/svg&gt;' />
44
+    </head>
45
+    <body>
46
+        <noscript>do you really expect a usable calculator without javascript?</noscript>
47
+        <h1 id="title">RTensor</h1>
48
+        <h2 id="vnum"><a href="changelog.md">version 2.3-dev</a></h2>
49
+        <br />
50
+        <a href="./documentation_v2.1.pdf">documentation (prose, partially outdated)</a>
51
+        <details>
52
+            <summary>documentation (example table)</summary>
53
+            RTensor == (R)ecursive (Tensor)<br />
54
+            if, when pressing enter, nothing happens, it's probably an uncaught evaluation error, or a very slow computation<br />
55
+            you can use up/down arrow keys to navigate history<br />
56
+            some examples can be double-clicked on to run them
57
+            <table cellspacing="0">
58
+                <tr><td>example</td><td>explanation</td></tr>
59
+                <tr><td>123</td><td>integer literal (scalar)</td></tr>
60
+                <tr><td>3.14</td><td>non-integer literal (scalar)</td></tr>
61
+                <tr><td>.. 3.14</td><td>single-element vector</td></tr>
62
+                <tr><td>abc</td><td>identifier</td></tr>
63
+                <tr><td>f'</td><td>prime symbols in identifiers</td></tr>
64
+                <tr><td>pi; phi; true; false; null</td><td>built-in special constants</td></tr>
65
+                <tr><td>a = expr</td><td>save value to identifier</td></tr>
66
+                <tr><td>_</td><td>most recent result</td></tr>
67
+                <tr><td>a + b; a - b; a * b; a / b</td><td>basic operations</td></tr>
68
+                <tr><td>a ^ b</td><td>exponents</td></tr>
69
+                <tr><td>b \ x</td><td>base-b logarithm of x</td></tr>
70
+                <tr><td>a == b</td><td>check equality</td></tr>
71
+                <tr class="bad"><td class="excode">(-/10)^2 == 10</td><td>equality is sensitive to numerical error</td></tr>
72
+                <tr><td>a &lt; b</td><td>comparison</td></tr>
73
+                <tr class="bad"><td>a &lt; x &lt; b</td><td>can't chain comparison</td></tr>
74
+                <tr><td>n -/ x</td><td>nth-root of x</td></tr>
75
+                <tr><td>a, b, c</td><td>tensor [a, b, c]</td></tr>
76
+                <tr class="good"><td>(a, b, c)[2]</td><td>tensor indexing; returns b</td></tr>
77
+                <tr class="bad"><td>(a, b, c)[4]</td><td>tensor index out of range</td></tr>
78
+                <tr><td>v[i] = a</td><td>assign to item in vector</td></tr>
79
+                <tr class="good"><td class="excode">(3, 4, 5) * 0.5</td><td>vector-scalar operation</td></tr>
80
+                <tr class="good"><td class="excode">(1, 2) + (3, 4)</td><td>vector-vector operation</td></tr>
81
+                <tr class="bad"><td class="excode">(6, 2, 3) + (4, 4)</td><td>incompatible dimensions</td></tr>
82
+                <tr><td class="excode">(3, 4, 5), (6, 7, 8)</td><td>matrix</td></tr>
83
+                <tr><td class="excode">(3, 4, 5) .. (6, 7, 8)</td><td>vector concatenation</td></tr>
84
+                <tr class="good"><td class="excode">1 .. 10</td><td>range of integers</td></tr>
85
+                <tr class="bad"><td class="excode">1..10</td><td>spacing matters here</td></tr>
86
+                <tr class="bad"><td class="excode">1 .. (5, 10)</td><td>invalid</td></tr>
87
+                <tr class="good"><td>b^2 - 4*a*c</td><td>quadratic discriminant</td></tr>
88
+                <tr class="good"><td>b ^ 2 -4 *a*c</td><td>spacing (usually) doesn't matter</td></tr>
89
+                <tr><td>-x; /x; ^x; \x</td><td>negative; reciprocal; e<sup>x</sup>; ln(x)</td></tr>
90
+                <tr><td>== x</td><td>is x zero?</td></tr>
91
+                <tr><td>&lt; x</td><td>is x negative?</td></tr>
92
+                <tr><td>-/x</td><td>square root</td></tr>
93
+                <tr><td>/-x</td><td>negative reciprocal</td></tr>
94
+                <tr><td class="excode">a = 3; sin(a)^2 + cos(a)^2</td><td>semicolon-separated statements</td></tr>
95
+                <tr><td class="excode">cos(x)</td><td>function evaluation</td></tr>
96
+                <tr><td class="excode">arctan(3, 2)</td><td>two-argument function</td></tr>
97
+                <tr><td class="excode">cos((0, 1))</td><td>function of a vector</td></tr>
98
+                <tr><td class="excode">f(x) = x^2</td><td>function definition</td></tr>
99
+                <tr><td class="excode">f = x =&gt; x^2</td><td>anonymous function (saved to variable here)</td></tr>
100
+                <tr><td class="excode">f = x =&gt; y =&gt; x + y; f(3)(5)</td><td>two-argument function by currying</td></tr>
101
+                <tr><td class="excode">f(x) = y =&gt; x + y; f(3)(5)</td><td>mixed currying syntax</td></tr>
102
+                <tr><td class="excode">f = x, y =&gt; x + y; f(3, 5)</td><td>two-argument function</td></tr>
103
+                <tr><td class="excode">f(x) = (a =&gt; a^2 + a)(\x); f(3)</td><td>hacky local variables</td></tr>
104
+                <tr><td class="excode">f(x) = (a = \x)*0 + (a^2 + a); f(3)</td><td>less hacky local variables</td></tr>
105
+                <tr>
106
+                    <td>abs(x); floor(x); clear()<br />len(v); any(v); all(v); tail(v); trim(v)<br />sin(x); cos(x)<br />arcsin(y); arccos(y); arctan(y)</td>
107
+                    <td>various built-in functions</td>
108
+                </tr>
109
+                <tr><td>zero(n)</td><td>n-element vector of zeroes</td></tr>
110
+                <tr><td class="excode">tail(..0); trim(..0); zero(0); 1 .. 0</td><td>empty list (four ways)</td></tr>
111
+                <tr class="good"><td class="excode">random()</td><td>random number, 0 to 1</td></tr>
112
+                <tr class="bad"><td>random(a)</td><td>invalid</td></tr>
113
+                <tr class="good"><td>random(a, b)</td><td>random integer, a to b (inclusive)</td></tr>
114
+                <tr class="good">
115
+                    <td class="excode">fact(n) = n * fact(n - 1)<br />fact(0) = 1</td>
116
+                    <td>recursive functions</td>
117
+                </tr>
118
+                <tr class="bad">
119
+                    <td>fact(0) = 1<br />fact(n) = n * fact(n - 1)</td>
120
+                    <td>always define base cases last</td>
121
+                </tr>
122
+                <tr>
123
+                    <td class="excode">A(m, n) = A(m - 1, A(m, n - 1))<br />A(m, 0) = A(m - 1, 1)<br />A(0, n) = n + 1</td>
124
+                    <td>the Ackermann function,<br />right out of Wikipedia<br />(beware: very recursive and slow)</td>
125
+                </tr>
126
+                <tr>
127
+                    <td class="excode">psi(x) = if(all((-1 &lt; x, x &lt; 1)), ^/-(1 - x^2), 0)</td>
128
+                    <td>the &#936; bump function, right out of Wikipedia</td>
129
+                </tr>
130
+                <tr>
131
+                    <td class="excode">fact(n) = if[n == 0, 1, n * fact(n - 1)]</td>
132
+                    <td>recursive function with a conditional</td>
133
+                </tr>
134
+                <tr>
135
+                    <td class="excode">map(1 .. 5, fact)</td>
136
+                    <td>first few factorials<br />(assuming fact is already defined)</td>
137
+                </tr>
138
+                <tr>
139
+                    <td class="excode">((f =&gt; (n =&gt; (if(n==1,(_=&gt;1),f(f)))(n-1) * n))((f =&gt; (n =&gt; (if(n==1,(_=&gt;1),f(f)))(n-1) * n))))(6)</td>
140
+                    <td>factorial without named functions</td>
141
+                </tr>
142
+                <tr><td>filter(v, (x =&gt; 1 - &lt;x))</td><td>remove negative items from vector</td></tr>
143
+                <tr><td>reduce(v, (a, b =&gt; a + b))</td><td>sum of the items of a vector</td></tr>
144
+                <tr>
145
+                    <td class="excode">aloz(v, i) = if[len(v) &lt; i, 0, v[i]]</td>
146
+                    <td>access list at index, or 0 if index beyond length</td>
147
+                </tr>
148
+                <tr>
149
+                    <td class="excode">dot(u, v) = if[any((==len(u), ==len(v))), 0, u[1] * v[1] + dot(tail(u), tail(v))]</td>
150
+                    <td>dot product of vectors</td>
151
+                </tr>
152
+                <tr>
153
+                    <td class="excode">
154
+                        prime(n) = for[(g = 2), floor(n / g) &lt; n / g, (g = if(&lt;(n - g^2 - 1), n, g + 1)), g] == n
155
+                    </td>
156
+                    <td>sieve-based primality checker</td>
157
+                </tr>
158
+                <tr>
159
+                    <td class="excode">
160
+                        div(a, b) = floor(a / b) == a / b<br />
161
+                        fp(n, l) = 1 - any(map(l, (k =&gt; div(n, k))))<br />
162
+                        last(v) = v(len(v))<br />
163
+                        fnp(n) = for[(v = zero(0)) + (p = 2), len(v) &lt; n, if[fp(p, v), (v = v, p), (p = p + 1)], v]
164
+                    </td>
165
+                    <td>first n primes by accelerated sieve</td>
166
+                </tr>
167
+                <tr>
168
+                    <td class="excode">
169
+                        dv(a, b) = (x =&gt; x == floor(x))(b / a)<br />
170
+                        factor(n, g) = if(dv(g, n), zero(1) + g, zero(0)) .. factor(if(dv(g, n), n / g, n), if(dv(g, n), g, g + 1))<br />
171
+                        factor(1, g) = zero(0)<br />
172
+                        factor(n) = factor(floor(n), 2)<br />
173
+                    </td>
174
+                    <td>integer factorisation</td>
175
+                </tr>
176
+                <tr>
177
+                    <td class="excode">
178
+                        cond(v) = len(v) &lt; 20<br />
179
+                        next(v) = v, (v[len(v)] + v[len(v) - 1])<br />
180
+                        for[(v = 1, 1), cond(v), (v = next(v)), v]
181
+                    </td>
182
+                    <td>list of Fibonacci numbers</td>
183
+                </tr>
184
+                <tr>
185
+                    <td class="excode">
186
+                        qs(v) = qs(filter(tail(v), (x =&gt; x &lt; v[1]))) .. (.. v[1]) .. qs(filter(tail(v), (x =&gt; 1 - (x &lt; v[1]))))<br />
187
+                        qs(zero(0)) = zero(0)<br />
188
+                        tv = map(zero(30), (_x =&gt; random(1, 100)))<br />
189
+                        qs(tv)
190
+                    </td>
191
+                    <td>quicksort algorithm implementation</td>
192
+                </tr>
193
+                <tr>
194
+                    <td class="excode">
195
+                        a(n) = a(n-a(n-1)) + a(n-a(n-2))<br />
196
+                        a(1) = 1<br />
197
+                        a(2) = 1
198
+                    </td>
199
+                    <td>the Hofstadter Q-sequence,<br />right out of the OEIS<br />(beware: very recursive and slow)</td>
200
+                </tr>
201
+                <tr>
202
+                    <td class="excode">
203
+                        Qs(v) = v, (v(len(v) + 1 - v(len(v))) + v(len(v) + 1 - v(len(v) - 1)))<br />
204
+                        Qc(v) = len(v) &lt; 100<br />
205
+                        for((1, 1), Qc, Qs)
206
+                    </td>
207
+                    <td>the Hofstadter Q-sequence,<br />computed iteratively (much faster)</td>
208
+                </tr>
209
+                <tr>
210
+                    <td class="excode">
211
+                        pe(x, p) = sum[(i = 1), len(p), p[i] * x^(i - 1)]<br />
212
+                        pe(4, (-2, 5, 6))
213
+                    </td>
214
+                    <td>polynomial evaluation<br />6x<sup>2</sup> + 5x - 2 at x = 4</td>
215
+                </tr>
216
+                <tr>
217
+                    <td class="excode">
218
+                        pd(p) = tail(map(p, (x, i =&gt; (i - 1) * x)))<br />
219
+                        pd((2, 1, 4, 1))
220
+                    </td>
221
+                    <td>polynomial derivative: x<sup>3</sup> + 4x<sup>2</sup> + x + 2</td>
222
+                </tr>
223
+                <tr>
224
+                    <td class="excode">
225
+                        rnr(v) = 1 - ==pe(v(1), v(2))<br />
226
+                        nrs(v) = (v(1) - pe(v(1), v(2)) / pe(v(1), pd(v(2)))), v(2)<br />
227
+                        pr(x, p) = (for((x, p), rnr, nrs))(1)<br />
228
+                        pr(0, (2, 1, 4, 1))
229
+                    </td>
230
+                    <td>
231
+                        polynomial rootfinding by Newton's method<br />
232
+                        (assuming pe and pd are already defined)<br />
233
+                        root of x<sup>3</sup> + 4x<sup>2</sup> + x + 2 near 0<br />
234
+                        (may loop or error when unable to find root)
235
+                    </td>
236
+                </tr>
237
+                <tr>
238
+                    <td class="excode">
239
+                        digits(n, b) = digits(floor(n / b), b), (n - b * floor(n / b))<br />
240
+                        digits(0, b) = zero(0)
241
+                    </td>
242
+                    <td>
243
+                        vector of digits of number in base<br />
244
+                        (errors on negative inputs)
245
+                    </td>
246
+                </tr>
247
+                <tr>
248
+                    <td class="excode">T(n) = sum[(k = 1), n, k]</td>
249
+                    <td>the triangle numbers,<br />right out of Wolfram MathWorld</td>
250
+                </tr>
251
+                <tr>
252
+                    <td class="excode">render[(x = (-b + pm(-/(b^2 - 4*a*c))) / (2*a))]</td>
253
+                    <td>render quadratic formula</td>
254
+                </tr>
255
+                <tr>
256
+                    <td class="excode">html[(x = (-b + pm(-/(b^2 - 4*a*c))) / (2*a))]</td>
257
+                    <td>HTML behind the quadratic formula</td>
258
+                </tr>
259
+                <tr>
260
+                    <td class="excode">render[(sum((k=1), n, k^2) = (n + 1) * (2*n + 1) * n / 6)]</td>
261
+                    <td>render sum-of-squares formula</td>
262
+                </tr>
263
+                <tr>
264
+                    <td class="excode">render[(pi/4 = int(0, 1, -/(1 - x^2)*dx))]</td>
265
+                    <td>render an integral for &#960; &#247; 4</td>
266
+                </tr>
267
+                <tr>
268
+                    <td class="excode">
269
+                        mod(a,b) = a - b * floor(a / b)<br />
270
+                        gcd(m, n) = gcd(n, mod(m, n))<br />
271
+                        gcd(m, 0) = m
272
+                    </td>
273
+                    <td>Euclid's GCD algorithm</td>
274
+                </tr>
275
+                <tr>
276
+                    <td class="excode">int(a, b, f, n) = sum[(k = 1), n, (b - a) / n * f(a + (b - a) * ((k - /2) / n))]</td>
277
+                    <td>numerical integration by midpoint sum</td>
278
+                </tr>
279
+                <tr>
280
+                    <td class="excode">
281
+                        cint(a, b, u, f, n) = sum[(k = 1), n, (u(a + (b - a) * ((k + 1) / n)) - u(a + (b - a) * (k / n))) * f(u(a + (b - a) * ((k - /2) / n)))]
282
+                    </td>
283
+                    <td>numerical contour integration<br />(with parametric curve function u)</td>
284
+                </tr>
285
+                <tr>
286
+                    <td class="excode">fact2(n) = fact(2 * n)<br />succ(n) = n + 1<br />cat = fact2 / fact^2 / succ</td>
287
+                    <td>Catalan numbers by functional arithmetic<br />(assuming fact is already defined)</td>
288
+                </tr>
289
+            </table>
290
+        </details>
291
+        <textarea id="bci" cols="120" rows="8"># Throw in some code ...</textarea><br />
292
+        <span>use alternative numeric type:</span>
293
+        <div id="iol"></div>
294
+        <script src="types/basic.js"></script>
295
+        <script src="types/bigint.js"></script>
296
+        <script src="types/complex.js"></script>
297
+        <script src="types/mathsfunc.js"></script>
298
+        <script src="types/real.js"></script>
299
+        <script src="maths_ast.js"></script>
300
+        <script src="maths_parser.js"></script>
301
+        <script src="rtensor_to_html.js"></script>
302
+        <script src="rtensor_lib.js"></script>
303
+        <script>
304
+            /*<![CDATA[*/
305
+            const calcVars = Object.fromEntries(Object.entries(BUILTIN_FUNCS));
306
+            calcVars["_"] = calcVars["null"];
307
+            const pastInps = [];
308
+            const iol = document.getElementById("iol");
309
+            const bci = document.getElementById("bci");
310
+            var histPos = 0;
311
+
312
+            // handle keydown events in the latest input line
313
+            // for history navigation and input submission
314
+            function inpKeyHandler(ev) {
315
+                let nd = false;
316
+                switch (ev.key) {
317
+                    case "ArrowUp":
318
+                        if (pastInps.length <= 1) {
319
+                            break;
320
+                        }
321
+                        histPos--;
322
+                        if (histPos < 1) {
323
+                            histPos = 1;
324
+                        }
325
+                        ev.target.value = pastInps[histPos].value;
326
+                        nd = true;
327
+                        break;
328
+                    case "ArrowDown":
329
+                        histPos++;
330
+                        if (histPos > pastInps.length) {
331
+                            histPos = pastInps.length;
332
+                        }
333
+                        ev.target.value = (pastInps[histPos] || { value: "" }).value;
334
+                        nd = true;
335
+                        break;
336
+                    case "Enter":
337
+                        if (ev.target.value.trim() != "") {
338
+                            evalInp();
339
+                            nextInp();
340
+                        }
341
+                        nd = true;
342
+                        break;
343
+                    default:
344
+                        break;
345
+                }
346
+                if (nd) {
347
+                    ev.preventDefault();
348
+                }
349
+            }
350
+
351
+            function ioLog(t) {
352
+                const outp = document.createElement("p");
353
+                outp.innerText = t;
354
+                iol.append(outp);
355
+                return outp;
356
+            }
357
+
358
+            function evalInp() {
359
+                let disp;
360
+                let perr = false;
361
+                const ci = document.getElementById("cil" + pastInps.length.toString());
362
+                const st = ci.value.split(";");
363
+                for (let i = 0; i < st.length; i++) {
364
+                    const wt = st[i];
365
+                    const lexer = new TokenStream(wt);
366
+                    try {
367
+                        const ast = NewParser.statement(lexer);
368
+                        if (ast === null) {
369
+                            disp = "syntax error";
370
+                            break;
371
+                        } else {
372
+                            perr = perr || (lexer.tind != lexer.toks.length - 1);
373
+                            document.body.style.cursor = "wait";
374
+                            try {
375
+                                const res = ast.evaluate(calcVars, false);
376
+                                if (res != null) { calcVars["_"] = res; }
377
+                                const svn = "_" + pastInps.length.toString();
378
+                                calcVars[svn] = res;
379
+                                disp = (res == null) ? "evaluation error" : (svn + " = " + fts(res));
380
+                            } catch (err) {
381
+                                disp = err;
382
+                            }
383
+                            document.body.style.cursor = "auto";
384
+                        }
385
+                    } catch (err) {
386
+                        disp = err;
387
+                    }
388
+                }
389
+                ioLog(disp);
390
+                if (perr) { ioLog("possible syntax error"); }
391
+            }
392
+
393
+            // disable the current input line, add its contents to history,
394
+            // and add a new input line
395
+            function nextInp() {
396
+                const prev = (document.getElementById("cil" + pastInps.length.toString()) || {});
397
+                prev.onkeydown = null;
398
+                prev.disabled = true;
399
+                pastInps.push(prev);
400
+                const newi = document.createElement("input");
401
+                newi.id = "cil" + pastInps.length.toString();
402
+                newi.onkeydown = inpKeyHandler;
403
+                newi.type = "text";
404
+                iol.append("> ");
405
+                iol.appendChild(newi);
406
+                document.getElementById("cil" + pastInps.length.toString()).focus();
407
+                histPos = pastInps.length;
408
+            }
409
+            
410
+            function vce(t) {
411
+                t.split("\n").forEach(l => {
412
+                    if (l.trim() != "") {
413
+                        iol.lastElementChild.value = l;
414
+                        evalInp();
415
+                        nextInp();
416
+                    }
417
+                });
418
+            }
419
+            
420
+            const ech = document.getElementsByClassName("excode");
421
+            for (let i = 0; i < ech.length; i++) {
422
+                ech[i].ondblclick = ev => vce(ev.target.innerText);
423
+            }
424
+            
425
+            window.onbeforeunload = ev => (pastInps.length > 1 ? "Quit RTensor?" : undefined);
426
+
427
+            // create a link for a version of the calculator using each numeric type
428
+            Object.keys(NUMERIC_TYPES).forEach(ntn => {
429
+                const le = document.createElement("a");
430
+                le.innerText = " " + ntn;
431
+                le.href = "?nt=" + ntn;
432
+                document.body.insertBefore(le, iol);
433
+            });
434
+            document.body.insertBefore(document.createElement("br"), iol);
435
+
436
+            bci.onchange = ev => {
437
+                // split by lines, except with backslash continuation
438
+                ev.target.value.split(/(?<!\\)\n/g).
439
+                    // remove backslash continuation
440
+                    map(l => l.replace(/\\\n/g, "\n")).
441
+                    // remove comments and blank lines
442
+                    filter(l => l.replace(/#.*/, "").trim()).
443
+                    forEach(l => {
444
+                        const ci = document.getElementById("cil" + pastInps.length.toString());
445
+                        ci.value = l;
446
+                        evalInp();
447
+                        nextInp();
448
+                    });
449
+                ev.target.value = "";
450
+            };
451
+
452
+            nextInp();
453
+            /*]]>*/
454
+        </script>
455
+    </body>
456
+</html>
0 457
\ No newline at end of file
... ...
@@ -0,0 +1,337 @@
1
+"use strict";
2
+
3
+const IDENT_AST = new AST(1, new Token(1, ""));
4
+
5
+function elemwiseFunc(f) {
6
+    const ref = (args, de) =>
7
+        (typeof args["0"] == "number" || typeof args["0"] == "bigint" ||
8
+            (typeof args["0"] == "object" && args["0"].map == null)) ?
9
+        f(args["0"]) :
10
+        args["0"].map(x => ref({ "0": x }, de));
11
+    return new MathsFunc([[[IDENT_AST], ref]], {});
12
+}
13
+
14
+const BUILTIN_FUNCS = {
15
+    // numeric
16
+    "abs": elemwiseFunc(x => x.mag()),
17
+    "floor": elemwiseFunc(x => x.floor()),
18
+    "random": new MathsFunc([[[], function(args, de) {
19
+        return NumericType.ify(Math.random());
20
+    }], [[IDENT_AST, IDENT_AST], function(args, de) {
21
+        const ub = args["1"];
22
+        const lb = args["0"];
23
+        return lb.add(NumericType.ify(Math.ceil(ub.sub(lb).add(ub.constructor.one()).mag() * Math.random()))).sub(lb.constructor.one());
24
+    }]], {}),
25
+    // complex
26
+    "re": elemwiseFunc(z => z.re),
27
+    "im": elemwiseFunc(z => z.im),
28
+    "conj": elemwiseFunc(z => new Complex(z.re, -z.im)),
29
+    // trigonometric
30
+    "sin": elemwiseFunc(x => x.sin()),
31
+    "cos": elemwiseFunc(x => x.cos()),
32
+    "arcsin": elemwiseFunc(x => x.asin()),
33
+    "arccos": elemwiseFunc(x => x.acos()),
34
+    "arctan": new MathsFunc([[[IDENT_AST, IDENT_AST], function(args, de) {
35
+        return vecOp(args["0"], args["1"], (x, y) => y.atan(x));
36
+    }], [[IDENT_AST], function(args, de) {
37
+        return vecOp(args["0"], 0, ((x, _y) => x.atan()));
38
+    }]], {}),
39
+    // vector
40
+    "zero": new MathsFunc([[[IDENT_AST], function(args, de) {
41
+        const l = args["0"].mag();
42
+        if (l >= 0 && Math.floor(l) == l) {
43
+            return (new Array(l)).fill(NumericType.zero());
44
+        } else {
45
+            throw `cannot make ${fts(l)}-element vector`;
46
+        }
47
+    }]], {}),
48
+    "len": new MathsFunc([[[IDENT_AST], function(args, de) {
49
+        const a0l = args["0"].length;
50
+        if (a0l != null) {
51
+            return NumericType.ify(a0l);
52
+        } else {
53
+            throw `${fts(args["0"])} has no length`;
54
+        }
55
+    }]], {}),
56
+    "any": new MathsFunc([[[IDENT_AST], function(args, de) {
57
+        return args["0"].
58
+            map(a => (typeof a == "boolean") ? a : !a.eq(a.constructor.zero())).
59
+            reduce((a, b) => a || b, 0);
60
+    }]], {}),
61
+    "all": new MathsFunc([[[IDENT_AST], function(args, de) {
62
+        return args["0"].
63
+            map(a => (typeof a == "boolean") ? a : !a.eq(a.constructor.zero())).
64
+            reduce((a, b) => a && b, 1);
65
+    }]], {}),
66
+    "map": new MathsFunc([[[IDENT_AST, IDENT_AST], function(args, de) {
67
+        if (!args["1"].apply) {
68
+            throw `${fts(args["1"])} is not a function`;
69
+        }
70
+        return args["0"].map((x, i) => {
71
+            if (anyNaN(x)) {
72
+                return null;
73
+            } else {
74
+                // TODO: if the function handles the given argument structure,
75
+                // but breaks down in evaluation,
76
+                // it skips to the next argument structure and messes stuff up
77
+                try {
78
+                    return args["1"].apply(args["1"], [[x, i + 1], de]);
79
+                } catch (err) {
80
+                    try {
81
+                        return args["1"].apply(args["1"], [[x], de]);
82
+                    } catch (err) {
83
+                        return args["1"].apply(args["1"], [[], de]);
84
+                    }
85
+                }
86
+            }
87
+        });
88
+    }]], {}),
89
+    "filter": new MathsFunc([[[IDENT_AST, IDENT_AST], function(args, de) {
90
+        return args["0"].filter((x, i) => {
91
+            if (anyNaN(x)) {
92
+                return false;
93
+            } else {
94
+                let er;
95
+                try {
96
+                    er = args["1"].apply(args["1"], [[x, i + 1], de]);
97
+                } catch (err) {
98
+                    er = args["1"].apply(args["1"], [[x], de]);
99
+                }
100
+                return (er === true) || ((er !== false) && !er.eq(er.constructor.zero()));
101
+            }
102
+        });
103
+    }]], {}),
104
+    "reduce": new MathsFunc([[[IDENT_AST, IDENT_AST], function(args, de) {
105
+        return args["0"].reduce((x, y) =>
106
+            anyNaN(x) ?
107
+            null :
108
+            (anyNaN(y) ?
109
+            x :
110
+            args["1"].apply(args["1"], [[x, y], de])));
111
+    }]], {}),
112
+    "tail": new MathsFunc([[[IDENT_AST], function(args, de) {
113
+        if (args["0"].slice) {
114
+            return args["0"].slice(1);
115
+        } else {
116
+            throw `${args["0"]} has no tail`;
117
+        }
118
+    }]], {}),
119
+    "trim": new MathsFunc([[[IDENT_AST], function(args, de) {
120
+        if (args["0"].slice) {
121
+            return args["0"].slice(0, args["0"].length - 1);
122
+        } else {
123
+            throw `cannot trim ${args["0"]}`;
124
+        }
125
+    }]], {}),
126
+    "deepmap": new MathsFunc([[[IDENT_AST, IDENT_AST], function(args, de) {
127
+        return vecOp(args["0"], 0, (x, _y) => args["1"].apply(args["1"], [[x], de]));
128
+    }]], {}),
129
+    // non-functions
130
+    "i": new Complex(0, 1),
131
+    "pi": Math.PI,
132
+    "phi": (1 + Math.sqrt(5)) / 2,
133
+    "true": true,
134
+    "false": false,
135
+    "null": { constructor: null },
136
+    // other
137
+    "if": new MathsFunc([[[IDENT_AST, IDENT_AST, IDENT_AST], function(args, de) {
138
+        if (args["0"].constructor == AST) {
139
+            const cer = args["0"].evaluate(de);
140
+            return (typeof cer == "boolean" ? cer : !cer.eq(cer.constructor.zero())) ? args["1"].evaluate(de) : args["2"].evaluate(de);
141
+        } else {
142
+            return (args["0"][0] || args["0"]) ? args["1"] : args["2"];
143
+        }
144
+    }], [[IDENT_AST, IDENT_AST, IDENT_AST, IDENT_AST], function(args, de) {
145
+        const sa = args["0"][0] ? args["1"] : args["2"];
146
+        const ar = sa.apply(sa, [[args["3"]], de]);
147
+        return ar;
148
+    }], [[IDENT_AST, IDENT_AST, IDENT_AST, IDENT_AST, IDENT_AST], function(args, de) {
149
+        const sa = args["0"][0] ? args["1"] : args["2"];
150
+        const ar = sa.apply(sa, [[args["3"], args["4"]], de]);
151
+        return ar;
152
+    }]], {}),
153
+    "for": new MathsFunc([[[IDENT_AST, IDENT_AST, IDENT_AST], function(args, de) {
154
+        let state;
155
+        let ic = 0;
156
+        const it = Date.now();
157
+        for (
158
+            state = args["0"];
159
+            (x => x[0] == null ? x : x[0])(args["1"].apply(args["1"], [[state], de]));
160
+            state = args["2"].apply(args["2"], [[state], de])
161
+        ) {
162
+            if (ic > 10000 || ic % 100 == 0 && Date.now() - it > 10000) {
163
+                throw `suspected infinite for() loop`;
164
+            }
165
+            ic++;
166
+        }
167
+        return state;
168
+    }], [[IDENT_AST, IDENT_AST, IDENT_AST, IDENT_AST], function(args, de) {
169
+        const ide = Object.fromEntries(Object.entries(de));
170
+        let ic = 0;
171
+        const it = Date.now();
172
+        for (args["0"].evaluate(ide); args["1"].evaluate(ide); args["2"].evaluate(ide)) {
173
+            if (ic > 10000 || ic % 100 == 0 && Date.now() - it > 10000) {
174
+                throw `suspected infinite for() loop`;
175
+            }
176
+            ic++;
177
+        }
178
+        return args["3"].evaluate(ide);
179
+    }]], {}),
180
+    "clear": new MathsFunc([[[], function(args, de) {
181
+        iol.innerHTML = "";
182
+        return 0;
183
+    }]], {}),
184
+    "tostring": new MathsFunc([[[IDENT_AST], function(args, de) {
185
+        const oe = document.createElement("p");
186
+        oe.innerText = args["0"].toString();
187
+        iol.append(oe);
188
+        return 0;
189
+    }]], {}),
190
+    "render": new MathsFunc([[[IDENT_AST], function(args, de) {
191
+        const oe = document.createElement("p");
192
+        oe.innerHTML = `<span class="maths">${args["0"].toHTML()}</span>`;
193
+        iol.append(oe);
194
+        return 0;
195
+    }]], {}),
196
+    "html": new MathsFunc([[[IDENT_AST], function(args, de) {
197
+        const oe = document.createElement("pre");
198
+        oe.innerText = `<span class="maths"><table></table>${args["0"].toHTML()}</span>`;
199
+        iol.append(oe);
200
+        return 0;
201
+    }]], {}),
202
+    "lhs": new MathsFunc([[[IDENT_AST], function(args, de) {
203
+        const ce = args["0"];
204
+        switch (ce.ts) {
205
+            case 1:
206
+            case 2:
207
+                return de.null;
208
+            case 3:
209
+                return ce.l;
210
+            case 4:
211
+                return ce.l ? ce.m : de.null;
212
+            case 5:
213
+                return de.null;
214
+            case 6:
215
+                return ce.l;
216
+            case 7:
217
+            case 8:
218
+                return ce.l;
219
+            default:
220
+                dl("error: invalid AST node type " + ce.ts);
221
+        }
222
+    }]], {}),
223
+    "rhs": new MathsFunc([[[IDENT_AST], function(args, de) {
224
+        const ce = args["0"];
225
+        switch (ce.ts) {
226
+            case 1:
227
+            case 2:
228
+                return de.null;
229
+            case 3:
230
+                return ce.r;
231
+            case 4:
232
+                return ce.l ? ce.r : ce.m;
233
+            case 5:
234
+                return ce.m;
235
+            case 6:
236
+                return ce.m;
237
+            case 7:
238
+            case 8:
239
+                return ce.m;
240
+            default:
241
+                dl("error: invalid AST node type " + ts);
242
+        }
243
+    }]], {}),
244
+    "operator": new MathsFunc([[[IDENT_AST], function(args, de) {
245
+        const ce = args["0"];
246
+        switch (ce.ts) {
247
+            case 1:
248
+                return -1;
249
+            case 2:
250
+                return -2;
251
+            case 3:
252
+                return ce.m;
253
+            case 4:
254
+                return -4;
255
+            case 5:
256
+                return ce.l;
257
+            case 6:
258
+                return -6;
259
+            case 7:
260
+            case 8:
261
+                return -7;
262
+            default:
263
+                dl("error: invalid AST node type " + ts);
264
+        }
265
+    }]], {}),
266
+    "sum": new MathsFunc([[[IDENT_AST, IDENT_AST, IDENT_AST], function(args, de) {
267
+        const ide = Object.fromEntries(Object.entries(de));
268
+        const ias = args["0"];
269
+        // the initialisation statement must be a simple assignment
270
+        if (!(ias.ts == 3 && ias.m == 11 && ias.l.ts == 1)) {
271
+            throw `cannot use ${ias} to initialise summation`;
272
+        }
273
+        const vn = ias.l.v;
274
+        const ul = args["1"].evaluate(ide);
275
+        let ret;
276
+        let start = true;
277
+        for (ias.evaluate(ide); ide[vn].lt(ul) || ide[vn].eq(ul); ide[vn] = ide[vn].add(ide[vn].constructor.one())) {
278
+            if (start) {
279
+                ret = args["2"].evaluate(ide);
280
+                start = false;
281
+            } else {
282
+                const rhs = args["2"].evaluate(ide);
283
+                ret = vecOp(ret, rhs, (x, y) => x.add(x.constructor.ify(y)));
284
+            }
285
+        }
286
+        return ret || NumericType.zero();
287
+    }]], {}),
288
+    "product": new MathsFunc([[[IDENT_AST, IDENT_AST, IDENT_AST], function(args, de) {
289
+        const ide = Object.fromEntries(Object.entries(de));
290
+        const ias = args["0"];
291
+        // the initialisation statement must be a simple assignment
292
+        if (!(ias.ts == 3 && ias.m == 11 && ias.l.ts == 1)) {
293
+            throw `cannot use ${ias} to initialise product`;
294
+        }
295
+        const vn = ias.l.v;
296
+        const ul = args["1"].evaluate(ide);
297
+        let ret;
298
+        let start = true;
299
+        for (ias.evaluate(ide); ide[vn] <= ul; ide[vn]++) {
300
+            if (start) {
301
+                ret = args["2"].evaluate(ide);
302
+                start = false;
303
+            } else {
304
+                ret = vecOp(ret, args["2"].evaluate(ide), (x, y) => x.mul(y));
305
+            }
306
+        }
307
+        return ret || NumericType.one();
308
+    }]], {}),
309
+};
310
+
311
+function dl(v) {
312
+    // required by maths_parser, but doesn't actually have to do anything
313
+    ioLog(v);
314
+    return null;
315
+}
316
+
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
+}
... ...
@@ -0,0 +1,329 @@
1
+"use strict";
2
+
3
+// maths symbols for which the intuitive name matches the HTML entity (mostly Greek)
4
+const ENT_SYM = [
5
+    "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta",
6
+    "lambda", "mu", "nu", "xi", "pi", "rho", "sigma", "tau", "phi", "psi",
7
+    "omega", "Gamma", "Delta", "Theta", "Lambda", "Pi", "Sigma", "Phi", "Psi",
8
+    "Omega", "Del", "part", "complexes", "naturals", "rationals", "reals",
9
+    "integers",
10
+];
11
+
12
+const EXTRA_BUILTINS = new Set(["tan", "csc", "sec", "cot"]);
13
+
14
+// HTML named character references
15
+const NCR = {
16
+    "&alpha;": { "codepoints":[945], "characters":"α" },
17
+    "&beta;":{"codepoints":[946],"characters":"β"},
18
+    "&gamma;":{"codepoints":[947],"characters":"γ"},
19
+    "&delta;":{"codepoints":[948],"characters":"δ"},
20
+    "&epsilon;":{"codepoints":[949],"characters":"ε"},
21
+    "&zeta;":{"codepoints":[950],"characters":"ζ"},
22
+    "&eta;":{"codepoints":[951],"characters":"η"},
23
+    "&theta;":{"codepoints":[952],"characters":"θ"},
24
+    "&lambda;":{"codepoints":[955],"characters":"λ"},
25
+    "&mu;":{"codepoints":[956],"characters":"μ"},
26
+    "&nu;":{"codepoints":[957],"characters":"ν"},
27
+    "&xi;":{"codepoints":[958],"characters":"ξ"},
28
+    "&pi;":{"codepoints":[960],"characters":"π"},
29
+    "&rho;":{"codepoints":[961],"characters":"ρ"},
30
+    "&sigma;":{"codepoints":[963],"characters":"σ"},
31
+    "&tau;":{"codepoints":[964],"characters":"τ"},
32
+    "&phi;":{"codepoints":[966],"characters":"φ"},
33
+    "&psi;":{"codepoints":[968],"characters":"ψ"},
34
+    "&omega;":{"codepoints":[969],"characters":"ω"},
35
+    "&Gamma;":{"codepoints":[915],"characters":"Γ"},
36
+    "&Delta;":{"codepoints":[916],"characters":"Δ"},
37
+    "&Theta;":{"codepoints":[920],"characters":"Θ"},
38
+    "&Lambda;":{"codepoints":[923],"characters":"Λ"},
39
+    "&Pi;":{"codepoints":[928],"characters":"Π"},
40
+    "&Sigma;":{"codepoints":[931],"characters":"Σ"},
41
+    "&Phi;":{"codepoints":[934],"characters":"Φ"},
42
+    "&prime;": { "codepoints": [8242], "characters": "\u2032" },
43
+    "&Psi;":{"codepoints":[936],"characters":"Ψ"},
44
+    "&Omega;":{"codepoints":[937],"characters":"Ω"},
45
+    "&Del;": { "codepoints": [8711], "characters": "\u2207" },
46
+    "&part;": { "codepoints": [8706], "characters": "\u2202" },
47
+    "&complexes;": { "codepoints": [8450], "characters": "\u2102" },
48
+    "&naturals;": { "codepoints": [8469], "characters": "\u2115" },
49
+    "&rationals;": { "codepoints": [8474], "characters": "\u211A" },
50
+    "&reals;": { "codepoints": [8477], "characters": "\u211D" },
51
+    "&integers;": { "codepoints": [8484], "characters": "\u2124" },
52
+    // above: "Greek", below: operations
53
+    "&and;": { "codepoints": [8743], "characters": "\u2227" },
54
+    "&approx;": { "codepoints": [8776], "characters": "\u2248" },
55
+    "&Cross;": { "codepoints": [10799], "characters": "\u2A2F" },
56
+    "&in;": { "codepoints": [8712], "characters": "\u2208" },
57
+    "&infin;": { "codepoints": [8734], "characters": "\u221E" },
58
+    "&int;": { "codepoints": [8747], "characters": "\u222B" },
59
+    "&Intersection;": { "codepoints": [8898], "characters": "\u22C2" },
60
+    "&lceil;": { "codepoints": [8968], "characters": "\u2308" },
61
+    "&lfloor;": { "codepoints": [8970], "characters": "\u230A" },
62
+    "&minus;": { "codepoints": [8722], "characters": "\u2212" },
63
+    "&or;": { "codepoints": [8744], "characters": "\u2228" },
64
+    "&plusmn;": { "codepoints": [177], "characters": "\u00B1" },
65
+    "&Product;": { "codepoints": [8719], "characters": "\u220F" },
66
+    "&radic;": { "codepoints": [8730], "characters": "\u221A" },
67
+    "&rceil;": { "codepoints": [8969], "characters": "\u2309" },
68
+    "&rfloor;": { "codepoints": [8971], "characters": "\u230B" },
69
+    "&rightarrow;": { "codepoints": [8594], "characters": "\u2192" },
70
+    "&sdot;": { "codepoints": [8901], "characters": "\u22C5" },
71
+    "&sum;": { "codepoints": [8721], "characters": "\u2211" },
72
+    "&Union;": { "codepoints": [8899], "characters": "\u22C3" },
73
+};
74
+
75
+const ucFromNcr = s => NCR[`&${s};`].characters;
76
+
77
+const RTensorRender = {
78
+    // special character from identifier, along with "greek" boolean
79
+    unicodify(ident) {
80
+        if (ident == "infty" || ident == "inf" || ident == "infinity") {
81
+            return [ucFromNcr("infin"), false];
82
+        } else if (ident == "star") {
83
+            return ["*", false];
84
+        } else if (ident == "etc") {
85
+            return ["&#8943;", true];
86
+        } else if (ENT_SYM.includes(ident)) {
87
+            return [ucFromNcr(ident), true];
88
+        } else {
89
+            return [ident, false];
90
+        }
91
+    },
92
+    // should this node get parenthesised for multiplication and such?
93
+    shouldParenMul() {
94
+        if ([1, 2, 4, 6].includes(this.ts)) {
95
+            return false;
96
+        } else if (this.ts == 3) {
97
+            return this.m > 10 || [0, 1, 6, 9, 10].includes(this.m);
98
+        } else if (this.ts == 5) {
99
+            return this.l > 10 || [0, 1, 6, 9].includes(this.l);
100
+        } else if ((this.ts == 7 || this.ts == 8) && this.l.ts == 1) {
101
+            return ["all", "any", "if", "map", "pm", "sum"].includes(this.l.v);
102
+        } else {
103
+            return true;
104
+        }
105
+    },
106
+    // should this node get parenthesised as the base of exponentiation and such?
107
+    shouldParenExp() {
108
+        return (this.ts == 3 && this.l != 5) || this.shouldParenMul();
109
+    },
110
+    // fancy HTML version of the AST
111
+    toHTML() {
112
+        const lp = `<span class="big">(</span>`;
113
+        const rp = `<span class="big">)</span>`;
114
+        switch (this.ts) {
115
+            case 1:
116
+                // underscores indicate subscripts
117
+                let sbu = this.v.replace(/'/g, ucFromNcr("prime")).
118
+                    split("_").map(s => RTensorRender.unicodify(s)).
119
+                    map(([s, b]) => {
120
+                        const s2 = new String(s);
121
+                        s2.greek = b;
122
+                        return s2;
123
+                    });
124
+                const cgify = s => s.greek ? `<span class="greek">${s}</span>` : s;
125
+                const it = sbu.length > 1 ?
126
+                    `${cgify(sbu[0])}<sub>${sbu.slice(1).map(cgify).join("_")}</sub>`:
127
+                    cgify(sbu[0]);
128
+                const spacify = sbu[0].replace(new RegExp(ucFromNcr("prime"), "g"), "").length >= 2 ? ` class="spaced"` : ``;
129
+                return (BUILTIN_FUNCS[this.v] != null || EXTRA_BUILTINS.has(this.v)) ?
130
+                    `<var class="builtin">${it}</var>` :
131
+                    `<var${spacify}>${it}</var>`;
132
+                break;
133
+            case 2:
134
+                return `${this.v}`;
135
+            case 3:
136
+                const l = this.l.toHTML();
137
+                const r = this.r.toHTML();
138
+                const pl = this.l.shouldParenMul() ? `${lp}${l}${rp}` : l;
139
+                const pr = this.r.shouldParenMul() ? `${lp}${r}${rp}` : r;
140
+                switch (this.m) {
141
+                    case 0:
142
+                        if ((this.r.ts == 7 || this.r.ts == 8) && this.r.l.v == "pm") {
143
+                            return `${l} ${r}`;
144
+                        }
145
+                        return `${l} + ${r}`;
146
+                    case 1:
147
+                        return `${l} ${ucFromNcr("minus")} ${pr}`;
148
+                    case 2:
149
+                        const dot = this.l.ts == 2 && this.r.ts == 2;
150
+                        return `${pl}${dot ? ` ${ucFromNcr("sdot")} ` : ""}${pr}`;
151
+                    case 3:
152
+                        return `<table class="frac">
153
+                        <tr class="numer"><td>${l}</td></tr>
154
+                        <tr class="denom"><td>${r}</td></tr>
155
+                        </table>`;
156
+                    case 4:
157
+                        const spe = this.l.shouldParenExp();
158
+                        return `${spe ? lp : ""}${l}${spe ? rp : ""}<sup>${r}</sup>`;
159
+                    case 5:
160
+                        return `log<sub>${l}</sub>${lp}${r}${rp}`;
161
+                    case 6:
162
+                    case 11:
163
+                        return `${l} = ${r}`;
164
+                    case 7:
165
+                        return `<table class="rboth"><tr>
166
+                        <td><sup><sup><sup class="rord">${l}</sup></sup></sup><span class="rcal">${ucFromNcr("radic")}</span></td>
167
+                        <td><span class="rcand">${r}</span></td>
168
+                        </tr></table>`;
169
+                    // TODO: case 8 not handled, because comma operator is confusing
170
+                    case 8:
171
+                        return `${l}, ${r}`;
172
+                    case 9:
173
+                        return `${l} &lt; ${r}`;
174
+                    case 10:
175
+                        return `${l} &#8614; ${r}`;
176
+                    default:
177
+                        return `${this.l.toHTML()} ? ${this.r.toHTML()}`;
178
+                }
179
+                break;
180
+            case 4:
181
+                return this.l ? `${this.m.toHTML()} = ${this.r.toHTML()}` : this.m.toHTML();
182
+            case 5:
183
+                const m = this.m.toHTML();
184
+                const pm = this.m.shouldParenMul() ? `${lp}${m}${rp}` : m;
185
+                switch (this.l) {
186
+                    case 0:
187
+                        return `+${pm}`;
188
+                    case 1:
189
+                        return ucFromNcr("minus") + pm;
190
+                    // case 2 not handled, because unary asterisk is not defined
191
+                    case 3:
192
+                        return `<table class="frac">
193
+                        <tr class="numer"><td>1</td></tr>
194
+                        <tr class="denom"><td>${m}</td></tr>
195
+                        </table>`;
196
+                    case 4:
197
+                        return `<var>e</var><sup>${m}</sup>`;
198
+                    case 5:
199
+                        return `ln(${m})`;
200
+                    case 6:
201
+                        return `${m} = 0`;
202
+                    case 7:
203
+                        return `<table class="rboth"><tr>
204
+                        <td><span class="rcal">${ucFromNcr("radic")}</span></td>
205
+                        <td><span class="rcand">${m}</span></td>
206
+                        </tr></table>`;
207
+                    // likewise, no unary comma
208
+                    case 9:
209
+                        return `${m} &lt; 0`;
210
+                    // likewise, no unary maplet
211
+                    // likewise, no unary assignment
212
+                    default:
213
+                        return `? ${m}`;
214
+                }
215
+                break;
216
+            case 6:
217
+                return `${this.l.toHTML()}[${this.m.toHTML()}]`;
218
+            case 7:
219
+            case 8:
220
+                const fa = this.m[0] ? this.m[0].toHTML() : "?";
221
+                const sa = this.m[1] ? this.m[1].toHTML() : "?";
222
+                const ta = this.m[2] ? this.m[2].toHTML() : "?";
223
+                const al = this.m.map(x => x.toHTML());
224
+                // special functions
225
+                if (this.l.ts == 1) {
226
+                    switch (this.l.v) {
227
+                        case "abs":
228
+                            return `|${fa}|`;
229
+                        case "all":
230
+                            return `${al.map(x => "(" + x + ")").join(` ${ucFromNcr("and")} `)}`;
231
+                        case "any":
232
+                            return `${al.map(x => "(" + x + ")").join(` ${ucFromNcr("or")} `)}`;
233
+                        case "approx":
234
+                            return `${fa} ${ucFromNcr("approx")} ${sa}`;
235
+                        case "at":
236
+                            return `${lp}${fa}${rp}<span class="ebar">|</span><table class="ebounds">
237
+                            <tr><td>${this.m[2] ? ta : ""}</td></tr><tr><td>${sa}</td></tr>
238
+                            </table>`;
239
+                        case "brace":
240
+                            return `<span class="big">{</span>${fa}<span class="big">}</span>`;
241
+                        case "bracket":
242
+                            return `<span class="big">[</span>${fa}<span class="big">]</span>`;
243
+                        case "ceil":
244
+                            return ucFromNcr("lceil") + fa + ucFromNcr("rceil");
245
+                        case "cross":
246
+                            return `${fa} ${ucFromNcr("Cross")} ${sa}`;
247
+                        case "dot":
248
+                            return `${fa} ${ucFromNcr("sdot")} ${sa}`;
249
+                        case "fact":
250
+                            const clp = this.m[0].shouldParenMul() ? lp : "";
251
+                            const crp = this.m[0].shouldParenMul() ? rp : "";
252
+                            return `${clp}${fa}${crp}!`;
253
+                        case "filter":
254
+                            return `{<var>x</var> ${ucFromNcr("in")} ${fa} | ${sa}(<var>x</var>)}`;
255
+                        case "floor":
256
+                            return ucFromNcr("lfloor") + fa + ucFromNcr("rfloor");
257
+                        case "hat":
258
+                            return `${fa}&#770;`;
259
+                        case "if":
260
+                            return `<span class="big">{</span><table class="pwf"><tr>
261
+                            <td>${sa}</td>
262
+                            <td class="cond">if ${fa}</td>
263
+                            </tr><tr>
264
+                            <td>${ta}</td>
265
+                            <td class="cond">otherwise</td>
266
+                            </tr></table>`;
267
+                        case "in":
268
+                            return `${fa} <span class="greek">${ucFromNcr("in")}</span> ${sa}`;
269
+                        case "int":
270
+                            return this.m[1] ?
271
+                                `<span class="intsym">${ucFromNcr("int")}</span> <table class="intbs">
272
+                                <tr class="intb"><td class="ub">${sa}</td></tr>
273
+                                <tr class="intb"><td>${fa}</td></tr>
274
+                                </table> ${ta}` :
275
+                                `<span class="intsym">${ucFromNcr("int")}</span> ${fa}`;
276
+                        case "intersection":
277
+                            return `${fa} <span class="greek">${ucFromNcr("Intersection")}</span> ${sa}`;
278
+                        case "lim":
279
+                            return `<table class="limls">
280
+                            <tr><td>${sa}</td></tr>
281
+                            <tr><td>${true ? `<span class="big">${ucFromNcr("Lambda")}</span>` : "lim"}</td></tr>
282
+                            <tr><td>${fa}</td></tr>
283
+                            </table> ${ta}`;
284
+                        case "map":
285
+                            return `${fa} &#120499; ${sa}`;
286
+                        case "matrix":
287
+                            const rc = parseInt(this.m[0]?.v || "2") || 2;
288
+                            const cc = parseInt(this.m[1]?.v || "2") || 2;
289
+                            return `<table class="mat">
290
+                                <tr><td>${(new Array(rc)).fill(0).map((_x, i) =>
291
+                                this.m.slice(2 + i * cc, 2 + (i + 1) * cc).map(el => el.toHTML()).join("</td><td>")).
292
+                                join("</td></tr>\n<tr><td>")}</td></tr>
293
+                                </table>`;
294
+                        case "paren":
295
+                            return `${lp}${fa}${rp}`;
296
+                        case "pm":
297
+                            return `${ucFromNcr("plusmn")} ${this.m[0].shouldParenMul() ? lp + fa + rp : fa}`;
298
+                        case "product":
299
+                            return `<table class="sum">
300
+                            <tr><td>${sa}</td></tr>
301
+                            <tr><td><span class="big">${ucFromNcr("Product")}</span></td></tr>
302
+                            <tr><td>${fa}</td></tr>
303
+                            </table> ${ta}`;
304
+                        case "sum":
305
+                            return `<table class="sum">
306
+                            <tr><td>${sa}</td></tr>
307
+                            <tr><td><span class="big">${ucFromNcr("sum")}</span></td></tr>
308
+                            <tr><td>${fa}</td></tr>
309
+                            </table> ${ta}`;
310
+                        case "union":
311
+                            return `${fa} <span class="greek">${ucFromNcr("Union")}</span> ${sa}`;
312
+                        case "vec":
313
+                            // TODO: there's a better way (include notation in variable names)
314
+                            const m = fa.match(/(.*)(\<sub\>.*\<\/sub\>)?(.*?)/);
315
+                            //alert(`${m[1]} and ${m[2]} and ${m[3]}`);
316
+                            return `${m[1]}&#8407;${m[2] || ""}${m[3] || ""}`;
317
+                    }
318
+                }
319
+                // anything else
320
+                return `${this.l.toHTML()}(${this.m.map(x => x.toHTML()).join(", ")})`;
321
+            default:
322
+                return "?";
323
+        }
324
+    },
325
+};
326
+
327
+AST.prototype.shouldParenMul = RTensorRender.shouldParenMul;
328
+AST.prototype.shouldParenExp = RTensorRender.shouldParenExp;
329
+AST.prototype.toHTML = RTensorRender.toHTML;
... ...
@@ -0,0 +1,38 @@
1
+"use strict";
2
+
3
+class BasicNumber {
4
+    constructor() {}
5
+
6
+    sub(z) {
7
+        return this.add(z.neg());
8
+    }
9
+
10
+    div(z) {
11
+        return this.mul(z.recip());
12
+    }
13
+
14
+    pow(z) {
15
+        return this.ln().mul(z).exp();
16
+    }
17
+
18
+    log(z) {
19
+        return this.ln().div(z.ln());
20
+    }
21
+
22
+    isNeg() {
23
+        return this.lt(this.constructor.zero());
24
+    }
25
+
26
+    rangeTo(b) {
27
+        const al = Math.ceil(b.sub(this).mag() + 1);
28
+        const ud = b.sub(this).dir();
29
+        return (new Array(al)).fill(0).map((_z, i) => this.add(ud.mul(this.constructor.ify(i))));
30
+    }
31
+
32
+    dir() {
33
+        return this.mag().eq(0) ? this.constructor.one() : this.div(this.constructor.ify(this.mag()));
34
+    }
35
+}
36
+
37
+// because the prototype fields are nonenumerable argh
38
+BasicNumber.keys = ["sub", "div", "pow", "log", "isNeg", "rangeTo", "dir"];
... ...
@@ -0,0 +1,39 @@
1
+"use strict";
2
+
3
+BasicNumber.keys.forEach(k => (BigInt.prototype[k] = BasicNumber.prototype[k]));
4
+
5
+BigInt.ify = function(x) {
6
+    if (typeof x == "object" && x.constructor == Complex) {
7
+        return x.im ? null : BigInt(Math.floor(x.re));
8
+    } else if (typeof x == "number") {
9
+        return BigInt(x);
10
+    } else {
11
+        return x;
12
+    }
13
+};
14
+
15
+BigInt.prototype.invalid = () => false;
16
+
17
+BigInt.zero = () => 0n;
18
+
19
+BigInt.one = () => 1n;
20
+
21
+BigInt.prototype.add = function(n) { return this + n; };
22
+
23
+BigInt.prototype.neg = function() { return -this; };
24
+
25
+BigInt.prototype.mul = function(n) { return this * n; };
26
+
27
+BigInt.prototype.div = function(n) { return this / n; };
28
+
29
+BigInt.prototype.exp = function() { return 2n ** this; };
30
+
31
+BigInt.prototype.pow = function(n) { return this ** n; };
32
+
33
+BigInt.prototype.eq = function(n) { return this == n; };
34
+
35
+BigInt.prototype.lt = function(n) { return this < n; };
36
+
37
+BigInt.prototype.mag = function() { return Math.abs((new Number(this)).valueOf()); };
38
+
39
+BigInt.prototype.floor = function() { return this; };
... ...
@@ -0,0 +1,130 @@
1
+"use strict";
2
+
3
+class Complex extends BasicNumber {
4
+    constructor(x, y) {
5
+        super();
6
+        this.re = x;
7
+        this.im = y || 0;
8
+    }
9
+
10
+    toString() {
11
+        if (this.im == 0) {
12
+            return this.re.toString();
13
+        } else {
14
+            return (this.re == 0 ? "" : this.re.toString() + (this.im > 0 ? " + " : " - ")) + Math.abs(this.im).toString() + "*i";
15
+        }
16
+    }
17
+
18
+    static ify(x) {
19
+        if (typeof x == "object" && x.constructor == Complex) {
20
+            return x;
21
+        } else if (typeof x == "number") {
22
+            return new Complex(x, 0);
23
+        } else {
24
+            return x;
25
+        }
26
+    }
27
+
28
+    invalid() {
29
+        return isNaN(this.re) || isNaN(this.im);
30
+    }
31
+
32
+    static zero() {
33
+        return new Complex(0, 0);
34
+    }
35
+
36
+    static one() {
37
+        return new Complex(1, 0);
38
+    }
39
+
40
+    add(z) {
41
+        return new Complex(this.re + z.re, this.im + z.im);
42
+    }
43
+
44
+    neg() {
45
+        return new Complex(-this.re, -this.im);
46
+    }
47
+
48
+    mul(z) {
49
+        return new Complex(this.re * z.re - this.im * z.im, this.re * z.im + this.im * z.re);
50
+    }
51
+
52
+    recip() {
53
+        const norm2 = this.re ** 2 + this.im ** 2;
54
+        return new Complex(this.re / norm2, -this.im / norm2);
55
+    }
56
+
57
+    exp() {
58
+        const sc = Math.exp(this.re);
59
+        return new Complex(sc * Math.cos(this.im), sc * Math.sin(this.im));
60
+    }
61
+
62
+    ln() {
63
+        return new Complex(Math.log(Math.sqrt(this.re ** 2 + this.im ** 2)), Math.atan2(this.im, this.re));
64
+    }
65
+
66
+    pow(z) {
67
+        // exact repeated-multiplication exponentiation
68
+        if (z.im == 0 && Math.floor(z.re) == z.re) {
69
+            let ret = Complex.one();
70
+            const sign = Math.sign(z.re);
71
+            let sse = Math.abs(z.re);
72
+            let spt = this;
73
+            // logarithmic-time exponent algorithm
74
+            while (sse > 0) {
75
+                if (sse % 2 == 1) {
76
+                    ret = ret.mul(spt);
77
+                }
78
+                spt = spt.mul(spt);
79
+                sse >>= 1;
80
+            }
81
+            return (sign < 0) ? ret.recip() : ret;
82
+        // arbitrary powers
83
+        } else {
84
+            return this.ln().mul(z).exp();
85
+        }
86
+    }
87
+
88
+    eq(z) {
89
+        return this.re == z.re && this.im == z.im;
90
+    }
91
+
92
+    sqrt() {
93
+        return this.pow(new Complex(0.5, 0));
94
+    }
95
+
96
+    sin() {
97
+        const iz = new Complex(-this.im, this.re);
98
+        return (iz.exp().sub(iz.neg().exp())).div(new Complex(0, 2));
99
+    }
100
+
101
+    cos() {
102
+        const iz = new Complex(-this.im, this.re);
103
+        return (iz.exp().add(iz.neg().exp())).div(new Complex(2, 0));
104
+    }
105
+
106
+    asin() {
107
+        const iz = new Complex(-this.im, this.re);
108
+        const root = Complex.one().sub(this.mul(this)).sqrt();
109
+        const lv = (iz.add(root)).ln();
110
+        return new Complex(lv.im, -lv.re);
111
+    }
112
+
113
+    acos() {
114
+        return (new Complex(Math.PI / 2, 0)).sub(this.asin());
115
+    }
116
+
117
+    atan(x) {
118
+        // TODO: handle two-argument
119
+        const iz = new Complex(-this.im, this.re);
120
+        return (Complex.one().sub(iz).ln().sub(Complex.one().add(iz).ln())).div(new Complex(0, -2));
121
+    }
122
+
123
+    isNeg() {
124
+        return this.re < 0;
125
+    }
126
+
127
+    mag() {
128
+        return Math.sqrt(this.re ** 2 + this.im ** 2);
129
+    }
130
+}
... ...
@@ -0,0 +1,100 @@
1
+"use strict";
2
+
3
+// check if vectors (Arrays of numbers) a and b are completely equal
4
+const vecEq = (a, b) => (a?.eq && b?.eq) ?
5
+    (a.eq(b)) :
6
+    // divide-by-1 to avoid undefined == undefined giving true
7
+    ((a.length / 1 == b.length / 1) &&
8
+        a.map((x, i) => (x == b[i])).reduce((p, q) => p && q, true));
9
+
10
+// a collection of case -> expr mappings
11
+// each case is a list of zero or more identifiers or constant values
12
+// (in the form of ASTs)
13
+// each expr is an expression (wow); more precisely, a function of the variables
14
+class MathsFunc extends BasicNumber {
15
+    // cems is an Array of Array-pairs of Array of AST and functions
16
+    // de is an evaluation input in accordance with AST.evaluate
17
+    constructor(cems, de) {
18
+        super();
19
+        this.cases = [];
20
+        for (let i = 0; i < cems.length; i++) {
21
+            this.addCase(cems[i], de);
22
+        }
23
+    }
24
+    
25
+    // cem is an Array-pair of Array of AST and function
26
+    // de is an evaluation input in accordance with AST.evaluate
27
+    addCase(cem, de) {
28
+        const nc = [];
29
+        // for each argument ...
30
+        for (let i = 0; i < cem[0].length; i++) {
31
+            // if the argument is a simple identifier ...
32
+            if (cem[0][i].ts == 1) {
33
+                // it's an input variable
34
+                nc.push(cem[0][i].v || i.toString());
35
+            // else ...
36
+            } else {
37
+                // evaluate the expression to get the constant
38
+                const eres = cem[0][i].evaluate(de);
39
+                if (eres == null) {
40
+                    // ah hecc
41
+                } else {
42
+                    nc.push(eres);
43
+                }
44
+            }
45
+        }
46
+        this.cases.unshift([nc, cem[1]]);
47
+    }
48
+    
49
+    // inps is an Array of numbers or Arrays (vectors)
50
+    // de is an evaluation input in accordance with AST.evaluate
51
+    apply(self, [inps, de]) {
52
+        let rv = null;
53
+        for (let i = 0; i < self.cases.length; i++) {
54
+            const cc = self.cases[i];
55
+            if (cc[0].length == inps.length) {
56
+                let isMatch = true;
57
+                for (let j = 0; j < cc[0].length; j++) {
58
+                    if (!((typeof cc[0][j]) == "string" || vecEq(cc[0][j], inps[j]))) {
59
+                        isMatch = false;
60
+                        break;
61
+                    }
62
+                }
63
+                if (isMatch) {
64
+                    const pinps = Object.fromEntries(inps.map(
65
+                        (x, j) => [cc[0][j], x]).filter(
66
+                        x => (typeof x[0]) == "string"));
67
+                    rv = cc[1].apply(cc[1], [pinps, de]);
68
+                    return rv;
69
+                }
70
+            }
71
+        }
72
+        throw `no suitable argument pattern to match ${inps.map(fts).join(", ")}`;
73
+        return null;
74
+    }
75
+    
76
+    toString() {
77
+        return this.cases.map(
78
+            x => "(" + x.toString() + ")").reduce(
79
+            (a, b) => a + ", " + b);
80
+    }
81
+}
82
+
83
+["neg", "recip", "exp", "ln", "sqrt", "sin", "cos", "asin", "acos", "mag", "floor"].forEach(k => MathsFunc.prototype[k] = function() {
84
+    const il = this.cases[0][0].length;
85
+    const f = this;
86
+    return new MathsFunc([[(new Array(il)).fill(0).map(_x => IDENT_AST), function(args, de) {
87
+        const a = f.apply(f, [Object.entries(args).map(([k, v]) => v), de]);
88
+        return vecOp(a, 0, (x, _y) => NumericType.ify(x)[k]());
89
+    }]], {});
90
+});
91
+
92
+["add", "mul", "pow"].forEach(k => MathsFunc.prototype[k] = function(g) {
93
+    const il = this.cases[0][0].length;
94
+    const f = this;
95
+    return new MathsFunc([[(new Array(il)).fill(0).map(_x => IDENT_AST), function(args, de) {
96
+        const a = f.apply(f, [Object.entries(args).map(([k, v]) => v), de]);
97
+        const b = g.apply ? g.apply(g, [Object.entries(args).map(([k, v]) => v), de]) : g;
98
+        return vecOp(a, b, (x, y) => NumericType.ify(x)[k](NumericType.ify(y)));
99
+    }]], {});
100
+});
... ...
@@ -0,0 +1,55 @@
1
+"use strict";
2
+
3
+BasicNumber.keys.forEach(k => (Number.prototype[k] = BasicNumber.prototype[k]));
4
+
5
+Number.ify = function(x) {
6
+    if (typeof x == "object" && x.constructor == Complex) {
7
+        return x.im ? null : new Number(x.re);
8
+    } else if (typeof x == "boolean") {
9
+        return 1*x;
10
+    } else {
11
+        return x;
12
+    }
13
+};
14
+
15
+Number.prototype.invalid = function() { return isNaN(this); };
16
+
17
+Number.zero = () => 0;
18
+
19
+Number.one = () => 1;
20
+
21
+Number.prototype.add = function(x) { return this + x; };
22
+
23
+Number.prototype.neg = function() { return -this; };
24
+
25
+Number.prototype.mul = function(x) { return this * x; };
26
+
27
+Number.prototype.recip = function() { return 1 / this; };
28
+
29
+Number.prototype.div = function(x) { return this / x; };
30
+
31
+Number.prototype.exp = function() { return Math.exp(this); };
32
+
33
+Number.prototype.ln = function() { return Math.log(this); };
34
+
35
+Number.prototype.pow = function(x) { return this ** x; };
36
+
37
+Number.prototype.sqrt = function() { return Math.sqrt(this); };
38
+
39
+Number.prototype.sin = function() { return Math.sin(this); };
40
+
41
+Number.prototype.cos = function() { return Math.cos(this); };
42
+
43
+Number.prototype.asin = function() { return Math.asin(this); };
44
+
45
+Number.prototype.acos = function() { return Math.acos(this); };
46
+
47
+Number.prototype.atan = function(x) { if (x) { return Math.atan2(x, this); } else { return Math.atan(this); } };
48
+
49
+Number.prototype.eq = function(x) { return this == x; };
50
+
51
+Number.prototype.lt = function(x) { return this < x; };
52
+
53
+Number.prototype.mag = function() { return Math.abs(this); };
54
+
55
+Number.prototype.floor = function() { return Math.floor(this); };
0 56