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 — 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 — 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) — 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 — 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` — which starts as `x = init` — 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 — the HTML-targeting maths typesetter — 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 — based upon JavaScript — 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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><style>text { font: italic bold 40px "Garamond", serif; }</style><text fill="none" stroke="white" stroke-width="16px" y="40" x="8" textLength="56px">f(x)</text><text y="40" x="8" textLength="56px">f(x)</text></svg>' />
|
|
| 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 < b</td><td>comparison</td></tr> |
|
| 73 |
+ <tr class="bad"><td>a < x < 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>< 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 => x^2</td><td>anonymous function (saved to variable here)</td></tr> |
|
| 100 |
+ <tr><td class="excode">f = x => y => x + y; f(3)(5)</td><td>two-argument function by currying</td></tr> |
|
| 101 |
+ <tr><td class="excode">f(x) = y => x + y; f(3)(5)</td><td>mixed currying syntax</td></tr> |
|
| 102 |
+ <tr><td class="excode">f = x, y => x + y; f(3, 5)</td><td>two-argument function</td></tr> |
|
| 103 |
+ <tr><td class="excode">f(x) = (a => 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 < x, x < 1)), ^/-(1 - x^2), 0)</td> |
|
| 128 |
+ <td>the Ψ 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 => (n => (if(n==1,(_=>1),f(f)))(n-1) * n))((f => (n => (if(n==1,(_=>1),f(f)))(n-1) * n))))(6)</td> |
|
| 140 |
+ <td>factorial without named functions</td> |
|
| 141 |
+ </tr> |
|
| 142 |
+ <tr><td>filter(v, (x => 1 - <x))</td><td>remove negative items from vector</td></tr> |
|
| 143 |
+ <tr><td>reduce(v, (a, b => 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) < 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) < n / g, (g = if(<(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 => div(n, k))))<br /> |
|
| 162 |
+ last(v) = v(len(v))<br /> |
|
| 163 |
+ fnp(n) = for[(v = zero(0)) + (p = 2), len(v) < 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 => 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) < 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 => x < v[1]))) .. (.. v[1]) .. qs(filter(tail(v), (x => 1 - (x < v[1]))))<br /> |
|
| 187 |
+ qs(zero(0)) = zero(0)<br /> |
|
| 188 |
+ tv = map(zero(30), (_x => 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) < 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 => (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 π ÷ 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 |
+ "α": { "codepoints":[945], "characters":"α" },
|
|
| 17 |
+ "β":{"codepoints":[946],"characters":"β"},
|
|
| 18 |
+ "γ":{"codepoints":[947],"characters":"γ"},
|
|
| 19 |
+ "δ":{"codepoints":[948],"characters":"δ"},
|
|
| 20 |
+ "ε":{"codepoints":[949],"characters":"ε"},
|
|
| 21 |
+ "ζ":{"codepoints":[950],"characters":"ζ"},
|
|
| 22 |
+ "η":{"codepoints":[951],"characters":"η"},
|
|
| 23 |
+ "θ":{"codepoints":[952],"characters":"θ"},
|
|
| 24 |
+ "λ":{"codepoints":[955],"characters":"λ"},
|
|
| 25 |
+ "μ":{"codepoints":[956],"characters":"μ"},
|
|
| 26 |
+ "ν":{"codepoints":[957],"characters":"ν"},
|
|
| 27 |
+ "ξ":{"codepoints":[958],"characters":"ξ"},
|
|
| 28 |
+ "π":{"codepoints":[960],"characters":"π"},
|
|
| 29 |
+ "ρ":{"codepoints":[961],"characters":"ρ"},
|
|
| 30 |
+ "σ":{"codepoints":[963],"characters":"σ"},
|
|
| 31 |
+ "τ":{"codepoints":[964],"characters":"τ"},
|
|
| 32 |
+ "φ":{"codepoints":[966],"characters":"φ"},
|
|
| 33 |
+ "ψ":{"codepoints":[968],"characters":"ψ"},
|
|
| 34 |
+ "ω":{"codepoints":[969],"characters":"ω"},
|
|
| 35 |
+ "Γ":{"codepoints":[915],"characters":"Γ"},
|
|
| 36 |
+ "Δ":{"codepoints":[916],"characters":"Δ"},
|
|
| 37 |
+ "Θ":{"codepoints":[920],"characters":"Θ"},
|
|
| 38 |
+ "Λ":{"codepoints":[923],"characters":"Λ"},
|
|
| 39 |
+ "Π":{"codepoints":[928],"characters":"Π"},
|
|
| 40 |
+ "Σ":{"codepoints":[931],"characters":"Σ"},
|
|
| 41 |
+ "Φ":{"codepoints":[934],"characters":"Φ"},
|
|
| 42 |
+ "′": { "codepoints": [8242], "characters": "\u2032" },
|
|
| 43 |
+ "Ψ":{"codepoints":[936],"characters":"Ψ"},
|
|
| 44 |
+ "Ω":{"codepoints":[937],"characters":"Ω"},
|
|
| 45 |
+ "∇": { "codepoints": [8711], "characters": "\u2207" },
|
|
| 46 |
+ "∂": { "codepoints": [8706], "characters": "\u2202" },
|
|
| 47 |
+ "ℂ": { "codepoints": [8450], "characters": "\u2102" },
|
|
| 48 |
+ "ℕ": { "codepoints": [8469], "characters": "\u2115" },
|
|
| 49 |
+ "ℚ": { "codepoints": [8474], "characters": "\u211A" },
|
|
| 50 |
+ "ℝ": { "codepoints": [8477], "characters": "\u211D" },
|
|
| 51 |
+ "ℤ": { "codepoints": [8484], "characters": "\u2124" },
|
|
| 52 |
+ // above: "Greek", below: operations |
|
| 53 |
+ "∧": { "codepoints": [8743], "characters": "\u2227" },
|
|
| 54 |
+ "≈": { "codepoints": [8776], "characters": "\u2248" },
|
|
| 55 |
+ "⨯": { "codepoints": [10799], "characters": "\u2A2F" },
|
|
| 56 |
+ "∈": { "codepoints": [8712], "characters": "\u2208" },
|
|
| 57 |
+ "∞": { "codepoints": [8734], "characters": "\u221E" },
|
|
| 58 |
+ "∫": { "codepoints": [8747], "characters": "\u222B" },
|
|
| 59 |
+ "⋂": { "codepoints": [8898], "characters": "\u22C2" },
|
|
| 60 |
+ "⌈": { "codepoints": [8968], "characters": "\u2308" },
|
|
| 61 |
+ "⌊": { "codepoints": [8970], "characters": "\u230A" },
|
|
| 62 |
+ "−": { "codepoints": [8722], "characters": "\u2212" },
|
|
| 63 |
+ "∨": { "codepoints": [8744], "characters": "\u2228" },
|
|
| 64 |
+ "±": { "codepoints": [177], "characters": "\u00B1" },
|
|
| 65 |
+ "∏": { "codepoints": [8719], "characters": "\u220F" },
|
|
| 66 |
+ "√": { "codepoints": [8730], "characters": "\u221A" },
|
|
| 67 |
+ "⌉": { "codepoints": [8969], "characters": "\u2309" },
|
|
| 68 |
+ "⌋": { "codepoints": [8971], "characters": "\u230B" },
|
|
| 69 |
+ "→": { "codepoints": [8594], "characters": "\u2192" },
|
|
| 70 |
+ "⋅": { "codepoints": [8901], "characters": "\u22C5" },
|
|
| 71 |
+ "∑": { "codepoints": [8721], "characters": "\u2211" },
|
|
| 72 |
+ "⋃": { "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 ["⋯", 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} < ${r}`;
|
|
| 174 |
+ case 10: |
|
| 175 |
+ return `${l} ↦ ${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} < 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}̂`;
|
|
| 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} 𝚳 ${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]}⃗${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 |