nerdamer 1.1.13 → 2.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +184 -0
- package/README.md +293 -312
- package/dist/bundle.js +2 -0
- package/dist/bundle.js.LICENSE.txt +7 -0
- package/dist/parser.js +2 -0
- package/dist/parser.js.LICENSE.txt +7 -0
- package/docs-data/parser-functions.json +1960 -0
- package/index.d.ts +10 -426
- package/output/algebra/adapters.d.ts +33 -0
- package/output/algebra/adapters.js +109 -0
- package/output/algebra/algorithms/arith.d.ts +84 -0
- package/output/algebra/algorithms/arith.js +323 -0
- package/output/algebra/algorithms/factor.d.ts +3 -0
- package/output/algebra/algorithms/factor.js +5 -0
- package/output/algebra/algorithms/factorMultivariate.d.ts +23 -0
- package/output/algebra/algorithms/factorMultivariate.js +2393 -0
- package/output/algebra/algorithms/factorUnivariate.d.ts +72 -0
- package/output/algebra/algorithms/factorUnivariate.js +1072 -0
- package/output/algebra/algorithms/gcd.d.ts +22 -0
- package/output/algebra/algorithms/gcd.js +690 -0
- package/output/algebra/algorithms/groebnerBase.d.ts +157 -0
- package/output/algebra/algorithms/groebnerBase.js +1166 -0
- package/output/algebra/algorithms/multiPoly/MultiPoly.d.ts +137 -0
- package/output/algebra/algorithms/multiPoly/MultiPoly.js +346 -0
- package/output/algebra/algorithms/poly.d.ts +228 -0
- package/output/algebra/algorithms/poly.js +1299 -0
- package/output/algebra/algorithms/rational.d.ts +17 -0
- package/output/algebra/algorithms/rational.js +115 -0
- package/output/algebra/factor/factor.d.ts +62 -0
- package/output/algebra/factor/factor.js +158 -0
- package/output/algebra/gcd/gcd.d.ts +29 -0
- package/output/algebra/gcd/gcd.js +141 -0
- package/output/algebra/groebner.d.ts +21 -0
- package/output/algebra/groebner.js +42 -0
- package/output/algebra/partfrac.d.ts +19 -0
- package/output/algebra/partfrac.js +398 -0
- package/output/algebra/polynomialize.d.ts +20 -0
- package/output/algebra/polynomialize.js +24 -0
- package/output/algebra/simplify/complexsimp.d.ts +7 -0
- package/output/algebra/simplify/complexsimp.js +24 -0
- package/output/algebra/simplify/factorCommon.d.ts +13 -0
- package/output/algebra/simplify/factorCommon.js +136 -0
- package/output/algebra/simplify/funcsimp.d.ts +26 -0
- package/output/algebra/simplify/funcsimp.js +633 -0
- package/output/algebra/simplify/invtrigrewrite.d.ts +10 -0
- package/output/algebra/simplify/invtrigrewrite.js +96 -0
- package/output/algebra/simplify/ratsimp.d.ts +21 -0
- package/output/algebra/simplify/ratsimp.js +140 -0
- package/output/algebra/simplify/simplify.d.ts +25 -0
- package/output/algebra/simplify/simplify.js +228 -0
- package/output/algebra/simplify/trigreduce.d.ts +38 -0
- package/output/algebra/simplify/trigreduce.js +424 -0
- package/output/algebra/simplify/trigrewrite.d.ts +17 -0
- package/output/algebra/simplify/trigrewrite.js +156 -0
- package/output/algebra/simplify/trigsimp.d.ts +11 -0
- package/output/algebra/simplify/trigsimp.js +369 -0
- package/output/algebra/simplify/utils.d.ts +32 -0
- package/output/algebra/simplify/utils.js +56 -0
- package/output/algebra/utils.d.ts +32 -0
- package/output/algebra/utils.js +48 -0
- package/output/api/advanced.d.ts +7 -0
- package/output/api/advanced.js +18 -0
- package/output/api/algebra.d.ts +17 -0
- package/output/api/algebra.js +32 -0
- package/output/api/assumptions.d.ts +4 -0
- package/output/api/assumptions.js +11 -0
- package/output/api/calculus.d.ts +9 -0
- package/output/api/calculus.js +21 -0
- package/output/api/core.d.ts +17 -0
- package/output/api/core.js +41 -0
- package/output/api/debug.d.ts +16 -0
- package/output/api/debug.js +23 -0
- package/output/api/parser.d.ts +85 -0
- package/output/api/parser.js +16 -0
- package/output/api/solve.d.ts +10 -0
- package/output/api/solve.js +16 -0
- package/output/api/structures.d.ts +10 -0
- package/output/api/structures.js +22 -0
- package/output/calculus/derivative/diff.d.ts +30 -0
- package/output/calculus/derivative/diff.js +238 -0
- package/output/calculus/fresnel.d.ts +16 -0
- package/output/calculus/fresnel.js +39 -0
- package/output/calculus/integrate/byParts.d.ts +18 -0
- package/output/calculus/integrate/byParts.js +339 -0
- package/output/calculus/integrate/bySubstitution.d.ts +135 -0
- package/output/calculus/integrate/bySubstitution.js +411 -0
- package/output/calculus/integrate/integrate.d.ts +38 -0
- package/output/calculus/integrate/integrate.js +652 -0
- package/output/calculus/integrate/integrationTable.d.ts +2 -0
- package/output/calculus/integrate/integrationTable.js +914 -0
- package/output/calculus/integrate/utils.d.ts +28 -0
- package/output/calculus/integrate/utils.js +96 -0
- package/output/calculus/laplace/ilaplace.d.ts +31 -0
- package/output/calculus/laplace/ilaplace.js +192 -0
- package/output/calculus/laplace/ilaplaceTable.d.ts +2 -0
- package/output/calculus/laplace/ilaplaceTable.js +200 -0
- package/output/calculus/laplace/laplace.d.ts +22 -0
- package/output/calculus/laplace/laplace.js +109 -0
- package/output/calculus/laplace/laplaceTable.d.ts +2 -0
- package/output/calculus/laplace/laplaceTable.js +187 -0
- package/output/calculus/limit/limit.d.ts +35 -0
- package/output/calculus/limit/limit.js +1183 -0
- package/output/calculus/limit/limitsTable.d.ts +2 -0
- package/output/calculus/limit/limitsTable.js +7 -0
- package/output/core/Settings.d.ts +32 -0
- package/output/core/Settings.js +68 -0
- package/output/core/adapters.d.ts +18 -0
- package/output/core/adapters.js +40 -0
- package/output/core/classes/assumption/Assumption.d.ts +218 -0
- package/output/core/classes/assumption/Assumption.js +605 -0
- package/output/core/classes/assumption/assertiveFunctions.d.ts +6 -0
- package/output/core/classes/assumption/assertiveFunctions.js +64 -0
- package/output/core/classes/assumption/assume.d.ts +90 -0
- package/output/core/classes/assumption/assume.js +145 -0
- package/output/core/classes/collection/Collection.d.ts +107 -0
- package/output/core/classes/collection/Collection.js +231 -0
- package/output/core/classes/complex/Complex.d.ts +99 -0
- package/output/core/classes/complex/Complex.js +135 -0
- package/output/core/classes/decimalSet/DecimalSet.d.ts +226 -0
- package/output/core/classes/decimalSet/DecimalSet.js +453 -0
- package/output/core/classes/dictionary/Dictionary.d.ts +102 -0
- package/output/core/classes/dictionary/Dictionary.js +213 -0
- package/output/core/classes/equation/Equation.d.ts +256 -0
- package/output/core/classes/equation/Equation.js +350 -0
- package/output/core/classes/expression/CoeffObject.d.ts +100 -0
- package/output/core/classes/expression/CoeffObject.js +198 -0
- package/output/core/classes/expression/Expression.d.ts +1674 -0
- package/output/core/classes/expression/Expression.js +2332 -0
- package/output/core/classes/expression/analysis.d.ts +25 -0
- package/output/core/classes/expression/analysis.js +218 -0
- package/output/core/classes/expression/collect.d.ts +9 -0
- package/output/core/classes/expression/collect.js +49 -0
- package/output/core/classes/expression/format.d.ts +45 -0
- package/output/core/classes/expression/format.js +217 -0
- package/output/core/classes/expression/products.d.ts +23 -0
- package/output/core/classes/expression/products.js +115 -0
- package/output/core/classes/expression/shortcuts.d.ts +35 -0
- package/output/core/classes/expression/shortcuts.js +129 -0
- package/output/core/classes/expression/traversal.d.ts +31 -0
- package/output/core/classes/expression/traversal.js +216 -0
- package/output/core/classes/expression/trig.d.ts +25 -0
- package/output/core/classes/expression/trig.js +40 -0
- package/output/core/classes/expression/utils.d.ts +52 -0
- package/output/core/classes/expression/utils.js +189 -0
- package/output/core/classes/lookupTable/LookupTable.d.ts +12 -0
- package/output/core/classes/lookupTable/LookupTable.js +55 -0
- package/output/core/classes/matrix/Matrix.d.ts +227 -0
- package/output/core/classes/matrix/Matrix.js +889 -0
- package/output/core/classes/matrix/Sylvester.d.ts +11 -0
- package/output/core/classes/matrix/Sylvester.js +54 -0
- package/output/core/classes/matrix/functions.d.ts +26 -0
- package/output/core/classes/matrix/functions.js +52 -0
- package/output/core/classes/matrix/utils.d.ts +9 -0
- package/output/core/classes/matrix/utils.js +22 -0
- package/output/core/classes/parser/Parser.d.ts +480 -0
- package/output/core/classes/parser/Parser.js +1812 -0
- package/output/core/classes/parser/Token.d.ts +58 -0
- package/output/core/classes/parser/Token.js +137 -0
- package/output/core/classes/parser/constants.d.ts +160 -0
- package/output/core/classes/parser/constants.js +227 -0
- package/output/core/classes/parser/helpers.d.ts +21 -0
- package/output/core/classes/parser/helpers.js +42 -0
- package/output/core/classes/parser/operations/add.d.ts +15 -0
- package/output/core/classes/parser/operations/add.js +238 -0
- package/output/core/classes/parser/operations/comma.d.ts +7 -0
- package/output/core/classes/parser/operations/comma.js +11 -0
- package/output/core/classes/parser/operations/compare.d.ts +21 -0
- package/output/core/classes/parser/operations/compare.js +461 -0
- package/output/core/classes/parser/operations/divide.d.ts +2 -0
- package/output/core/classes/parser/operations/divide.js +72 -0
- package/output/core/classes/parser/operations/functions.d.ts +46 -0
- package/output/core/classes/parser/operations/functions.js +430 -0
- package/output/core/classes/parser/operations/multiply.d.ts +11 -0
- package/output/core/classes/parser/operations/multiply.js +321 -0
- package/output/core/classes/parser/operations/power.d.ts +48 -0
- package/output/core/classes/parser/operations/power.js +673 -0
- package/output/core/classes/parser/operations/subtract.d.ts +3 -0
- package/output/core/classes/parser/operations/subtract.js +35 -0
- package/output/core/classes/parser/preprocess.d.ts +14 -0
- package/output/core/classes/parser/preprocess.js +259 -0
- package/output/core/classes/parser/scripting/controlFlow.d.ts +121 -0
- package/output/core/classes/parser/scripting/controlFlow.js +355 -0
- package/output/core/classes/parser/scripting/evaluate.d.ts +10 -0
- package/output/core/classes/parser/scripting/evaluate.js +112 -0
- package/output/core/classes/parser/scripting/functions.d.ts +14 -0
- package/output/core/classes/parser/scripting/functions.js +59 -0
- package/output/core/classes/parser/scripting/scope.d.ts +35 -0
- package/output/core/classes/parser/scripting/scope.js +155 -0
- package/output/core/classes/parser/types.d.ts +125 -0
- package/output/core/classes/parser/types.js +2 -0
- package/output/core/classes/parser/wrappers/IndexedReference.d.ts +31 -0
- package/output/core/classes/parser/wrappers/IndexedReference.js +68 -0
- package/output/core/classes/parser/wrappers/KeyValuePair.d.ts +24 -0
- package/output/core/classes/parser/wrappers/KeyValuePair.js +51 -0
- package/output/core/classes/polynomial/Polynomial.d.ts +500 -0
- package/output/core/classes/polynomial/Polynomial.js +1149 -0
- package/output/core/classes/polynomial/Term.d.ts +222 -0
- package/output/core/classes/polynomial/Term.js +430 -0
- package/output/core/classes/polynomial/functions.d.ts +69 -0
- package/output/core/classes/polynomial/functions.js +126 -0
- package/output/core/classes/polynomial/utils.d.ts +62 -0
- package/output/core/classes/polynomial/utils.js +206 -0
- package/output/core/classes/rational/Rational.d.ts +504 -0
- package/output/core/classes/rational/Rational.js +779 -0
- package/output/core/classes/seq/SEQ.d.ts +13 -0
- package/output/core/classes/seq/SEQ.js +137 -0
- package/output/core/classes/valuesSet/ValuesSet.d.ts +179 -0
- package/output/core/classes/valuesSet/ValuesSet.js +381 -0
- package/output/core/classes/vector/Vector.d.ts +222 -0
- package/output/core/classes/vector/Vector.js +497 -0
- package/output/core/classes/vector/functions.d.ts +28 -0
- package/output/core/classes/vector/functions.js +47 -0
- package/output/core/common/classes/MathematicalAggregate.d.ts +53 -0
- package/output/core/common/classes/MathematicalAggregate.js +140 -0
- package/output/core/common/classes/Scope.d.ts +62 -0
- package/output/core/common/classes/Scope.js +122 -0
- package/output/core/common/classes/StructuredEntity.d.ts +55 -0
- package/output/core/common/classes/StructuredEntity.js +151 -0
- package/output/core/common/common.d.ts +63 -0
- package/output/core/common/common.js +279 -0
- package/output/core/common/functions/functions.d.ts +14 -0
- package/output/core/common/functions/functions.js +33 -0
- package/output/core/common/functions/structuredEntityUtils.d.ts +27 -0
- package/output/core/common/functions/structuredEntityUtils.js +40 -0
- package/output/core/converters/BaseConverter.d.ts +97 -0
- package/output/core/converters/BaseConverter.js +405 -0
- package/output/core/converters/Converter.d.ts +111 -0
- package/output/core/converters/Converter.js +802 -0
- package/output/core/converters/Pattern.d.ts +71 -0
- package/output/core/converters/Pattern.js +302 -0
- package/output/core/dispatch.d.ts +34 -0
- package/output/core/dispatch.js +304 -0
- package/output/core/errors.d.ts +531 -0
- package/output/core/errors.js +621 -0
- package/output/core/functions/bigint/bigint.d.ts +121 -0
- package/output/core/functions/bigint/bigint.js +392 -0
- package/output/core/functions/bigint/primeFactor.d.ts +49 -0
- package/output/core/functions/bigint/primeFactor.js +267 -0
- package/output/core/functions/bigint/primes.d.ts +1 -0
- package/output/core/functions/bigint/primes.js +11 -0
- package/output/core/functions/build/definitions.d.ts +12 -0
- package/output/core/functions/build/definitions.js +133 -0
- package/output/core/functions/build/index.d.ts +32 -0
- package/output/core/functions/build/index.js +136 -0
- package/output/core/functions/complex.d.ts +90 -0
- package/output/core/functions/complex.js +482 -0
- package/output/core/functions/decimal.d.ts +24 -0
- package/output/core/functions/decimal.js +664 -0
- package/output/core/functions/expand/expand.d.ts +42 -0
- package/output/core/functions/expand/expand.js +376 -0
- package/output/core/functions/fresnelNumeric.d.ts +12 -0
- package/output/core/functions/fresnelNumeric.js +123 -0
- package/output/core/functions/numeric.d.ts +140 -0
- package/output/core/functions/numeric.js +790 -0
- package/output/core/functions/rationalNormalization.d.ts +11 -0
- package/output/core/functions/rationalNormalization.js +117 -0
- package/output/core/functions/setFunction.d.ts +19 -0
- package/output/core/functions/setFunction.js +75 -0
- package/output/core/functions/string.d.ts +26 -0
- package/output/core/functions/string.js +101 -0
- package/output/core/functions/subst.d.ts +90 -0
- package/output/core/functions/subst.js +498 -0
- package/output/core/functions/utils.d.ts +24 -0
- package/output/core/functions/utils.js +51 -0
- package/output/core/types.d.ts +48 -0
- package/output/core/types.js +2 -0
- package/output/index.d.ts +339 -0
- package/output/index.js +715 -0
- package/output/math/defint/defintDecimal.d.ts +37 -0
- package/output/math/defint/defintDecimal.js +320 -0
- package/output/math/defint/defintNative.d.ts +57 -0
- package/output/math/defint/defintNative.js +292 -0
- package/output/math/geometry.d.ts +8 -0
- package/output/math/geometry.js +32 -0
- package/output/math/math.d.ts +655 -0
- package/output/math/math.js +1811 -0
- package/output/math/trig.d.ts +518 -0
- package/output/math/trig.js +1444 -0
- package/output/math/trunc.d.ts +19 -0
- package/output/math/trunc.js +36 -0
- package/output/math/utils.d.ts +60 -0
- package/output/math/utils.js +180 -0
- package/output/solve/classes/DecimalMatrix.d.ts +17 -0
- package/output/solve/classes/DecimalMatrix.js +86 -0
- package/output/solve/classes/FunctionSolver.d.ts +135 -0
- package/output/solve/classes/FunctionSolver.js +433 -0
- package/output/solve/classes/MultivariateSolver.d.ts +84 -0
- package/output/solve/classes/MultivariateSolver.js +240 -0
- package/output/solve/classes/PolynomialSolver.d.ts +85 -0
- package/output/solve/classes/PolynomialSolver.js +292 -0
- package/output/solve/classes/SolutionSet.d.ts +153 -0
- package/output/solve/classes/SolutionSet.js +357 -0
- package/output/solve/classes/Solver.d.ts +17 -0
- package/output/solve/classes/Solver.js +132 -0
- package/output/solve/classes/SymbolicSolver.d.ts +74 -0
- package/output/solve/classes/SymbolicSolver.js +601 -0
- package/output/solve/linsolve.d.ts +51 -0
- package/output/solve/linsolve.js +225 -0
- package/output/solve/solve.d.ts +34 -0
- package/output/solve/solve.js +260 -0
- package/output/solve/solveSystem.d.ts +31 -0
- package/output/solve/solveSystem.js +290 -0
- package/output/solve/utils.d.ts +9 -0
- package/output/solve/utils.js +62 -0
- package/output/utils/array.d.ts +42 -0
- package/output/utils/array.js +98 -0
- package/output/utils/debug.d.ts +131 -0
- package/output/utils/debug.js +295 -0
- package/output/utils/decimal.d.ts +3 -0
- package/output/utils/decimal.js +16 -0
- package/output/utils/numeric.d.ts +14 -0
- package/output/utils/numeric.js +51 -0
- package/output/utils/object.d.ts +26 -0
- package/output/utils/object.js +56 -0
- package/package.json +213 -57
- package/.travis.yml +0 -3
- package/Algebra.js +0 -4568
- package/BREAKING_CHANGES.md +0 -5
- package/CODE_OF_CONDUCT.md +0 -46
- package/CONTRIBUTING.md +0 -14
- package/Calculus.js +0 -2675
- package/Extra.js +0 -622
- package/Solve.js +0 -1783
- package/all.js +0 -16
- package/all.min.js +0 -1
- package/gulpfile.js +0 -16
- package/index.html +0 -158
- package/license.txt +0 -19
- package/nerdamer.core.js +0 -12510
- package/spec/LaTeX.spec.js +0 -302
- package/spec/TeXConvert.spec.js +0 -8
- package/spec/algebra.spec.js +0 -287
- package/spec/basic_parser.spec.js +0 -134
- package/spec/build.spec.js +0 -131
- package/spec/calculus.spec.js +0 -183
- package/spec/core.spec.js +0 -2970
- package/spec/extra.spec.js +0 -54
- package/spec/solve.spec.js +0 -126
- package/spec/support/jasmine.json +0 -11
- package/spec/support/utils.js +0 -42
- package/spec/text.spec.js +0 -132
|
@@ -0,0 +1,1812 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Parser = void 0;
|
|
4
|
+
const array_1 = require("../../../utils/array");
|
|
5
|
+
const Scope_1 = require("../../common/classes/Scope");
|
|
6
|
+
const common_1 = require("../../common/common");
|
|
7
|
+
const functions_1 = require("../../common/functions/functions");
|
|
8
|
+
const structuredEntityUtils_1 = require("../../common/functions/structuredEntityUtils");
|
|
9
|
+
const dispatch_1 = require("../../dispatch");
|
|
10
|
+
const errors_1 = require("../../errors");
|
|
11
|
+
const string_1 = require("../../functions/string");
|
|
12
|
+
const Settings_1 = require("../../Settings");
|
|
13
|
+
const Collection_1 = require("../collection/Collection");
|
|
14
|
+
const Dictionary_1 = require("../dictionary/Dictionary");
|
|
15
|
+
const Equation_1 = require("../equation/Equation");
|
|
16
|
+
const Expression_1 = require("../expression/Expression");
|
|
17
|
+
const Matrix_1 = require("../matrix/Matrix");
|
|
18
|
+
const Rational_1 = require("../rational/Rational");
|
|
19
|
+
const ValuesSet_1 = require("../valuesSet/ValuesSet");
|
|
20
|
+
const Vector_1 = require("../vector/Vector");
|
|
21
|
+
const constants_1 = require("./constants");
|
|
22
|
+
const functions_2 = require("./operations/functions");
|
|
23
|
+
const preprocess_1 = require("./preprocess");
|
|
24
|
+
const Token_1 = require("./Token");
|
|
25
|
+
const IndexedReference_1 = require("./wrappers/IndexedReference");
|
|
26
|
+
const KeyValuePair_1 = require("./wrappers/KeyValuePair");
|
|
27
|
+
function shouldPopOperator(stackOperator, incomingOperator) {
|
|
28
|
+
let retval = stackOperator.precedence > incomingOperator.precedence;
|
|
29
|
+
if (!retval && stackOperator.precedence === incomingOperator.precedence) {
|
|
30
|
+
retval = incomingOperator.leftAssoc;
|
|
31
|
+
}
|
|
32
|
+
return retval;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Parses Nerdamer notation into symbolic and structured parser entities.
|
|
36
|
+
*
|
|
37
|
+
* @remarks
|
|
38
|
+
* The parser has three main stages: {@link tokenize} builds a nested token tree,
|
|
39
|
+
* {@link toRPN} applies the Shunting Yard algorithm within those scopes, and
|
|
40
|
+
* {@link parseRPN} evaluates the resulting stack representation. {@link parse} is
|
|
41
|
+
* the ordinary front end that runs that pipeline for a caller.
|
|
42
|
+
*
|
|
43
|
+
* Parsing does not always produce an {@link Expression}. Depending on the syntax
|
|
44
|
+
* and functions involved, the result can also be an equation, vector, matrix,
|
|
45
|
+
* collection, finite value set, or dictionary. Callers that require a specific
|
|
46
|
+
* parser entity should narrow the returned type rather than assuming an expression.
|
|
47
|
+
*
|
|
48
|
+
* The exported {@link Parser} singleton is the normal parser instance. Several
|
|
49
|
+
* parts of parser behavior are process-wide rather than instance-local, including
|
|
50
|
+
* parser settings, the constant and operator registries, Rational/Decimal precision,
|
|
51
|
+
* and the symbol reserved for the imaginary unit. {@link create} therefore creates
|
|
52
|
+
* another parser object, but it does not create an isolated parser context.
|
|
53
|
+
*
|
|
54
|
+
* The parser is a convenient notation front end, not the only supported way to use
|
|
55
|
+
* Nerdamer. Algorithms that already have {@link Expression} or other Nerdamer
|
|
56
|
+
* objects can operate on those objects directly without converting through text.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* Parser.parse('2*x + 1').text(); // "1+2*x"
|
|
61
|
+
* Parser.parse('x^2', { x: 3 }).text(); // "9"
|
|
62
|
+
* Parser.parse('[x, 2]').text(); // "[x, 2]"
|
|
63
|
+
* Parser.parse('{a => x, b => 2}').text(); // "{a => x, b => 2}"
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
class ExpressionParser {
|
|
67
|
+
/**
|
|
68
|
+
* Registry of named constants recognized by this parser.
|
|
69
|
+
*
|
|
70
|
+
* @remarks
|
|
71
|
+
* The registry object is shared by parser instances. Use {@link setConstants}
|
|
72
|
+
* instead of replacing or mutating this object directly so constant registration
|
|
73
|
+
* follows the parser's normal behavior.
|
|
74
|
+
*/
|
|
75
|
+
CONSTANTS = constants_1.PARSER_CONSTANTS;
|
|
76
|
+
/**
|
|
77
|
+
* The maximum number of operators a compound operator can consist of
|
|
78
|
+
*/
|
|
79
|
+
MAX_COMPOUND_OPERATOR_LENGTH = 2;
|
|
80
|
+
// Operators supported by the parser
|
|
81
|
+
operators = (0, functions_1.getOperators)();
|
|
82
|
+
VALUE_SETS = [constants_1.VECTOR, constants_1.MATRIX];
|
|
83
|
+
// Define whitespace to be ignored.
|
|
84
|
+
WHITE_SPACE = [constants_1.NEWLINE, constants_1.TAB, constants_1.RETURN];
|
|
85
|
+
/**
|
|
86
|
+
* Persistent known-value substitutions consulted during parsing.
|
|
87
|
+
*
|
|
88
|
+
* @remarks
|
|
89
|
+
* Known values are substituted only while the global `SUBSTITUTE` setting is
|
|
90
|
+
* enabled. A stored parser entity is copied before it is inserted into a parsed
|
|
91
|
+
* result, so ordinary substitution does not hand out the stored object itself.
|
|
92
|
+
*/
|
|
93
|
+
KNOWN_VALUES = {};
|
|
94
|
+
/**
|
|
95
|
+
* Converts an index scope to the zero-based indices used internally.
|
|
96
|
+
*
|
|
97
|
+
* Literal numeric indices are converted directly without parsing or evaluation.
|
|
98
|
+
* Symbolic or computed indices are returned as Expressions when they cannot yet
|
|
99
|
+
* be resolved. Resolved numeric values subtract `INDEX_BASE`; symbolic values retain
|
|
100
|
+
* the public index expressions so they can be evaluated later.
|
|
101
|
+
*/
|
|
102
|
+
extractIndices(scope, values, assertive) {
|
|
103
|
+
const indices = [];
|
|
104
|
+
let allNumeric = true;
|
|
105
|
+
for (let k = 0; k < scope.length; k++) {
|
|
106
|
+
const item = scope[k];
|
|
107
|
+
if (Scope_1.Scope.isScope(item)) {
|
|
108
|
+
allNumeric = false;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
if (item.type === Token_1.Token.OPERATOR) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (item.type === Token_1.Token.NUMBER) {
|
|
115
|
+
indices.push(Number(item.value) - Settings_1.Settings.INDEX_BASE);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
allNumeric = false;
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
let retval;
|
|
123
|
+
if (allNumeric && indices.length > 0) {
|
|
124
|
+
retval = indices;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
// Retain the parsed public expressions if evaluation is not concrete yet.
|
|
128
|
+
const indexExpr = this.parseRPN(scope, values, assertive);
|
|
129
|
+
const symbolicIndices = [];
|
|
130
|
+
if (Vector_1.Vector.isVector(indexExpr)) {
|
|
131
|
+
for (let j = 0; j < indexExpr.elements.length; j++) {
|
|
132
|
+
symbolicIndices.push(Expression_1.Expression.create(indexExpr.elements[j]));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else if (Collection_1.Collection.isCollection(indexExpr)) {
|
|
136
|
+
for (const el of indexExpr.getElements()) {
|
|
137
|
+
symbolicIndices.push(Expression_1.Expression.create(el));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
symbolicIndices.push(Expression_1.Expression.create(indexExpr));
|
|
142
|
+
}
|
|
143
|
+
const resolvedIndices = [];
|
|
144
|
+
let allResolved = true;
|
|
145
|
+
for (let j = 0; j < symbolicIndices.length; j++) {
|
|
146
|
+
const numericIndex = Number(symbolicIndices[j].evaluate().text());
|
|
147
|
+
if (!Number.isFinite(numericIndex)) {
|
|
148
|
+
allResolved = false;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
resolvedIndices.push(numericIndex - Settings_1.Settings.INDEX_BASE);
|
|
152
|
+
}
|
|
153
|
+
retval = allResolved ? resolvedIndices : symbolicIndices;
|
|
154
|
+
}
|
|
155
|
+
return retval;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Extracts string keys from an index scope for Dictionary access.
|
|
159
|
+
* For d[x], the scope contains a single variable token "x" → returns ["x"].
|
|
160
|
+
* For d[myKey], returns ["myKey"].
|
|
161
|
+
*/
|
|
162
|
+
extractStringKeys(scope) {
|
|
163
|
+
const keys = [];
|
|
164
|
+
for (let k = 0; k < scope.length; k++) {
|
|
165
|
+
const item = scope[k];
|
|
166
|
+
if (Scope_1.Scope.isScope(item)) {
|
|
167
|
+
// Nested scope — not expected for dictionary keys
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (item.type === Token_1.Token.OPERATOR) {
|
|
171
|
+
// Skip comma separators
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
// Use the token value as the string key
|
|
175
|
+
keys.push(item.value);
|
|
176
|
+
}
|
|
177
|
+
return keys;
|
|
178
|
+
}
|
|
179
|
+
/** Creates another spelling for an existing operator without changing its action. */
|
|
180
|
+
aliasOperator(symbol, alias) {
|
|
181
|
+
const source = this.operators[symbol];
|
|
182
|
+
if (!source) {
|
|
183
|
+
throw new errors_1.ParserError(`Unknown operator ${symbol}`);
|
|
184
|
+
}
|
|
185
|
+
this.operators[alias] = {
|
|
186
|
+
...source,
|
|
187
|
+
operator: alias,
|
|
188
|
+
postfixVariant: source.postfixVariant
|
|
189
|
+
? { ...source.postfixVariant, operator: alias }
|
|
190
|
+
: undefined,
|
|
191
|
+
};
|
|
192
|
+
(0, Token_1.refreshOperatorSymbols)();
|
|
193
|
+
return this;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Creates another parser instance.
|
|
197
|
+
*
|
|
198
|
+
* @remarks
|
|
199
|
+
* The returned parser object is distinct and starts with its own `KNOWN_VALUES`
|
|
200
|
+
* table, but it is not an isolated parser environment. Parser settings, constants,
|
|
201
|
+
* operators, Rational/Decimal precision, and the imaginary-unit symbol are shared
|
|
202
|
+
* process-wide. Changes made through methods such as {@link set},
|
|
203
|
+
* {@link setConstants}, {@link setPrecision}, or {@link setI} therefore affect
|
|
204
|
+
* parsing outside the newly created instance as well.
|
|
205
|
+
*
|
|
206
|
+
* @returns A distinct parser instance that shares Nerdamer's global parser state.
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* ```ts
|
|
210
|
+
* const parser = Parser.create();
|
|
211
|
+
*
|
|
212
|
+
* parser === Parser; // false
|
|
213
|
+
* parser.parse('x + 1').text(); // "1+x"
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
create() {
|
|
217
|
+
return new ExpressionParser();
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Parses an expression with evaluation mode temporarily enabled.
|
|
221
|
+
*
|
|
222
|
+
* @remarks
|
|
223
|
+
* Supported numeric constants and functions are evaluated eagerly at the current
|
|
224
|
+
* configured precision, but this method does not guarantee a purely numeric result;
|
|
225
|
+
* symbolic terms that cannot be evaluated remain symbolic. The previous global
|
|
226
|
+
* `EVALUATE` setting is restored after parsing, including when parsing throws.
|
|
227
|
+
*
|
|
228
|
+
* @param str - Expression text to parse while evaluation mode is enabled.
|
|
229
|
+
* @param values - Optional call-scoped substitutions applied as in {@link parse}.
|
|
230
|
+
* @returns The parser entity produced by the evaluated parse.
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* ```ts
|
|
234
|
+
* Parser.evaluate('2^10').text(); // "1024"
|
|
235
|
+
* Parser.evaluate('x+y', { x: 1, y: 2 }).text(); // "3"
|
|
236
|
+
* Parser.evaluate('x+1').text(); // "1+x"
|
|
237
|
+
* ```
|
|
238
|
+
*/
|
|
239
|
+
evaluate(str, values) {
|
|
240
|
+
return (0, Settings_1.scopedBlock)('EVALUATE', true, () => {
|
|
241
|
+
return exports.Parser.parse(str, values);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Reads the current value of a process-wide parser setting.
|
|
246
|
+
*
|
|
247
|
+
* @param setting - The parser setting to read.
|
|
248
|
+
* @returns The setting's current global value.
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* ```ts
|
|
252
|
+
* const evaluate = Parser.get('EVALUATE');
|
|
253
|
+
* const indexBase = Parser.get('INDEX_BASE');
|
|
254
|
+
* ```
|
|
255
|
+
*/
|
|
256
|
+
get(setting) {
|
|
257
|
+
return Settings_1.Settings[setting];
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Returns the symbol currently reserved for the imaginary unit.
|
|
261
|
+
*
|
|
262
|
+
* @remarks
|
|
263
|
+
* The symbol is process-wide and can be changed through {@link setI}. Changing it
|
|
264
|
+
* affects subsequent parser behavior and complex-number classification; it does
|
|
265
|
+
* not rewrite expressions that have already been created.
|
|
266
|
+
*
|
|
267
|
+
* @returns The current imaginary-unit variable name.
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* ```ts
|
|
271
|
+
* Parser.getI(); // "i" with the default configuration
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
274
|
+
getI() {
|
|
275
|
+
return Expression_1.Expression.imaginary;
|
|
276
|
+
}
|
|
277
|
+
/** Returns a detached snapshot of registered operator metadata. */
|
|
278
|
+
getOperator(symbol) {
|
|
279
|
+
const operator = this.operators[symbol];
|
|
280
|
+
let retval;
|
|
281
|
+
if (operator) {
|
|
282
|
+
retval = {
|
|
283
|
+
...operator,
|
|
284
|
+
postfixVariant: operator.postfixVariant
|
|
285
|
+
? { ...operator.postfixVariant }
|
|
286
|
+
: undefined,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
return retval;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Returns the current shared precision used by Decimal-backed numeric operations.
|
|
293
|
+
*
|
|
294
|
+
* @remarks
|
|
295
|
+
* This is the same precision reported by {@link Rational.getPrecision}. Exact
|
|
296
|
+
* rational arithmetic remains exact; the setting matters when Nerdamer crosses
|
|
297
|
+
* into Decimal-backed numerical work, generates numerical constants, or formats
|
|
298
|
+
* precision-sensitive decimal results.
|
|
299
|
+
*
|
|
300
|
+
* @returns The configured number of significant digits.
|
|
301
|
+
*/
|
|
302
|
+
getPrecision() {
|
|
303
|
+
return Rational_1.Rational.getPrecision();
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Parses supported expression input into a Nerdamer parser entity.
|
|
307
|
+
*
|
|
308
|
+
* @remarks
|
|
309
|
+
* The input is converted to text, tokenized with {@link tokenize}, transformed
|
|
310
|
+
* with {@link toRPN}, and evaluated with {@link parseRPN}. Passing an existing
|
|
311
|
+
* {@link Expression} therefore reparses its textual representation rather than
|
|
312
|
+
* preserving that object's identity.
|
|
313
|
+
*
|
|
314
|
+
* The result is determined by the parsed syntax. Ordinary scalar input produces
|
|
315
|
+
* an {@link Expression}, while equations and structured syntax can produce other
|
|
316
|
+
* parser entities such as vectors, matrices, collections, finite value sets, or
|
|
317
|
+
* dictionaries.
|
|
318
|
+
*
|
|
319
|
+
* The `values` object provides substitutions for this parse call. The caller's
|
|
320
|
+
* object is not mutated; scalar values are normalized to expressions before use,
|
|
321
|
+
* while existing parser entities are preserved structurally. Each inserted parser
|
|
322
|
+
* entity is copied. Registered string constants are resolved before call-scoped
|
|
323
|
+
* values. Call-scoped values in turn take precedence over persistent known values
|
|
324
|
+
* and over function-backed constants such as `pi` and `e` when those constants are
|
|
325
|
+
* being numerically evaluated.
|
|
326
|
+
*
|
|
327
|
+
* Parser and operator errors are propagated when the input is malformed or an
|
|
328
|
+
* operation is not valid for the parsed operands.
|
|
329
|
+
*
|
|
330
|
+
* @param str - Expression-compatible input whose textual form should be parsed.
|
|
331
|
+
* @param values - Optional call-scoped substitutions. Scalar values are normalized
|
|
332
|
+
* to {@link Expression} objects; existing parser entities are preserved structurally
|
|
333
|
+
* and copied when inserted.
|
|
334
|
+
* @returns The expression or structured parser entity represented by the input.
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* ```ts
|
|
338
|
+
* Parser.parse('x^2 + 2*x + 1').text(); // "1+2*x+x^2"
|
|
339
|
+
* Parser.parse('a + b', { a: 3, b: 4 }).text(); // "7"
|
|
340
|
+
* Parser.parse('x = 2').text(); // "x=2"
|
|
341
|
+
* Parser.parse('[x, 2, 3]').text(); // "[x, 2, 3]"
|
|
342
|
+
* ```
|
|
343
|
+
*/
|
|
344
|
+
parse(str, values) {
|
|
345
|
+
// Normalize scalar values once while preserving existing parser entities.
|
|
346
|
+
if (values) {
|
|
347
|
+
// We don't want to modify the object.
|
|
348
|
+
const valuesObj = {};
|
|
349
|
+
for (const value in values) {
|
|
350
|
+
// Ignore values such as e or pi
|
|
351
|
+
// if (SPECIAL.includes(value)) {
|
|
352
|
+
// continue;
|
|
353
|
+
// }
|
|
354
|
+
const input = values[value];
|
|
355
|
+
valuesObj[value] =
|
|
356
|
+
Expression_1.Expression.isExpression(input) || Equation_1.Equation.isEquation(input) || (0, structuredEntityUtils_1.isEnumerable)(input)
|
|
357
|
+
? input
|
|
358
|
+
: Expression_1.Expression.create(input);
|
|
359
|
+
}
|
|
360
|
+
values = valuesObj;
|
|
361
|
+
}
|
|
362
|
+
// Normalize accepted parser shorthand before the ordinary tokenizer resolves
|
|
363
|
+
// adjacency and implicit multiplication.
|
|
364
|
+
const source = (0, preprocess_1.preprocess)(String(str), dispatch_1.mathFunctionRegistry);
|
|
365
|
+
const rpn = this.toRPN(this.tokenize(source));
|
|
366
|
+
// Read it into an expression.
|
|
367
|
+
const retval = this.parseRPN(rpn, values);
|
|
368
|
+
return retval;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Evaluates a token scope that has been converted to Reverse Polish Notation.
|
|
372
|
+
*
|
|
373
|
+
* @remarks
|
|
374
|
+
* This is the parser's core stack evaluator. It applies prefix, postfix, and
|
|
375
|
+
* binary operators; dispatches function calls; resolves substitutions and known
|
|
376
|
+
* values; performs indexing and indexed assignment; and routes operations involving
|
|
377
|
+
* equations or enumerable structures to the appropriate aggregate implementation.
|
|
378
|
+
*
|
|
379
|
+
* Square scopes become vectors. Curly scopes become dictionaries when they contain
|
|
380
|
+
* `=>` key/value markers and otherwise become finite value sets. Multiple unresolved
|
|
381
|
+
* top-level values become a collection. Internal stack markers used for indexing and
|
|
382
|
+
* dictionary construction are resolved or consumed before a successful return.
|
|
383
|
+
*
|
|
384
|
+
* When `assertive` is `true`, operators use their assertive actions where defined.
|
|
385
|
+
* This is used by assumption-related functions so comparisons can assert conditions
|
|
386
|
+
* instead of performing ordinary comparison evaluation.
|
|
387
|
+
*
|
|
388
|
+
* @param rpn - RPN scope, normally produced by {@link toRPN}.
|
|
389
|
+
* @param values - Optional call-scoped substitutions already prepared for parser use.
|
|
390
|
+
* @param assertive - Use assertion-oriented operator actions where the operator defines one.
|
|
391
|
+
* @returns The parser entity produced by evaluating the scope.
|
|
392
|
+
* @throws {@link ParserError} When the stack contains a malformed or unresolved parser construct.
|
|
393
|
+
*/
|
|
394
|
+
parseRPN(rpn, values, assertive) {
|
|
395
|
+
// The output stack
|
|
396
|
+
const output = [];
|
|
397
|
+
const operators = this.operators;
|
|
398
|
+
// TODO: this needs to be applied at the operation level. It's somewhat clunky to apply this here.
|
|
399
|
+
// const precision = this.getPrecision();
|
|
400
|
+
function addToOutput(expression) {
|
|
401
|
+
if (expression === undefined) {
|
|
402
|
+
throw new errors_1.ParserError((0, errors_1.message)('malformedExpression'));
|
|
403
|
+
}
|
|
404
|
+
// Keep track of the precision used to make this calculation
|
|
405
|
+
// TODO: this needs to be applied at the operation level. It's somewhat clunky to apply this here.
|
|
406
|
+
// expression.precision = expression.precision || precision;
|
|
407
|
+
output.push(expression);
|
|
408
|
+
}
|
|
409
|
+
function resolveStackValue(value) {
|
|
410
|
+
if (IndexedReference_1.IndexedReference.isIndexedReference(value)) {
|
|
411
|
+
return value.resolve();
|
|
412
|
+
}
|
|
413
|
+
if (KeyValuePair_1.KeyValuePair.isKeyValuePair(value) || typeof value === 'function') {
|
|
414
|
+
throw new errors_1.ParserError((0, errors_1.message)('malformedExpression'));
|
|
415
|
+
}
|
|
416
|
+
return value;
|
|
417
|
+
}
|
|
418
|
+
function resolveStackValues(values) {
|
|
419
|
+
const resolved = [];
|
|
420
|
+
for (const value of values) {
|
|
421
|
+
resolved.push(resolveStackValue(value));
|
|
422
|
+
}
|
|
423
|
+
return resolved;
|
|
424
|
+
}
|
|
425
|
+
const createDeferredArgument = (getArgumentRPN, inheritedValues, functionAssertive) => {
|
|
426
|
+
let argumentRPN;
|
|
427
|
+
return (options) => {
|
|
428
|
+
const rpn = argumentRPN ?? getArgumentRPN();
|
|
429
|
+
argumentRPN = rpn;
|
|
430
|
+
let argumentValues = options?.inheritValues === false ? undefined : inheritedValues;
|
|
431
|
+
if (argumentValues && options?.excludeValues?.length) {
|
|
432
|
+
argumentValues = { ...argumentValues };
|
|
433
|
+
for (const name of options.excludeValues) {
|
|
434
|
+
delete argumentValues[name];
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
let retval;
|
|
438
|
+
if (typeof options?.substitute === 'boolean') {
|
|
439
|
+
retval = (0, Settings_1.scopedBlock)('SUBSTITUTE', options.substitute, () => {
|
|
440
|
+
return this.parseRPN(rpn, argumentValues, functionAssertive);
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
retval = this.parseRPN(rpn, argumentValues, functionAssertive);
|
|
445
|
+
}
|
|
446
|
+
return retval;
|
|
447
|
+
};
|
|
448
|
+
};
|
|
449
|
+
// Begin parsing of tokens
|
|
450
|
+
for (let i = 0; i < rpn.length; i++) {
|
|
451
|
+
// Grab the token or Scope
|
|
452
|
+
const token = rpn[i];
|
|
453
|
+
if (Scope_1.Scope.isScope(token)) {
|
|
454
|
+
const lastOutput = output[output.length - 1];
|
|
455
|
+
if (token.deferRHSResolution) {
|
|
456
|
+
addToOutput(createDeferredArgument(() => token, values, assertive));
|
|
457
|
+
}
|
|
458
|
+
else if (token.deferLHSResolution) {
|
|
459
|
+
// Left-hand targets are parsed without substitution so assignment can update a
|
|
460
|
+
// variable that already has a known value.
|
|
461
|
+
const parsed = (0, Settings_1.scopedBlock)('SUBSTITUTE', false, () => {
|
|
462
|
+
return this.parseRPN(token, undefined, assertive);
|
|
463
|
+
});
|
|
464
|
+
addToOutput(parsed);
|
|
465
|
+
}
|
|
466
|
+
// A square scope is a candidate for indexing or implicit multiplication ONLY
|
|
467
|
+
// when the tokenizer flagged it with implicitMultiply (meaning '[' directly
|
|
468
|
+
// followed a non-operator/non-function token). Without that flag, the scope
|
|
469
|
+
// is a standalone vector literal (e.g. in matrix([1,0],[2,3]) where commas
|
|
470
|
+
// separate the square scopes).
|
|
471
|
+
else if (token.type === 'square' && token.implicitMultiply && lastOutput) {
|
|
472
|
+
if ((0, structuredEntityUtils_1.isEnumerable)(lastOutput) ||
|
|
473
|
+
IndexedReference_1.IndexedReference.isIndexedReference(lastOutput)) {
|
|
474
|
+
// Indexing: target[indices]
|
|
475
|
+
const resolvedTarget = IndexedReference_1.IndexedReference.isIndexedReference(lastOutput)
|
|
476
|
+
? lastOutput.resolve()
|
|
477
|
+
: lastOutput;
|
|
478
|
+
const previousRPN = rpn[i - 1];
|
|
479
|
+
let targetName;
|
|
480
|
+
if (Token_1.Token.isToken(previousRPN) &&
|
|
481
|
+
previousRPN.type === Token_1.Token.VARIABLE &&
|
|
482
|
+
!(values && previousRPN.value in values) &&
|
|
483
|
+
previousRPN.value in this.KNOWN_VALUES) {
|
|
484
|
+
targetName = previousRPN.value;
|
|
485
|
+
}
|
|
486
|
+
let indices;
|
|
487
|
+
if (Dictionary_1.Dictionary.isDictionary(resolvedTarget)) {
|
|
488
|
+
// Dictionary indexing: extract string keys from the index scope.
|
|
489
|
+
// d[x] → key "x"
|
|
490
|
+
const keys = this.extractStringKeys(token);
|
|
491
|
+
indices = keys[0];
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
// Numeric indexing for Vector, Matrix, etc.
|
|
495
|
+
indices = this.extractIndices(token, values, assertive);
|
|
496
|
+
}
|
|
497
|
+
output.pop();
|
|
498
|
+
if (Array.isArray(indices) &&
|
|
499
|
+
indices.length > 0 &&
|
|
500
|
+
Expression_1.Expression.isExpression(indices[0])) {
|
|
501
|
+
const symbolicIndices = indices;
|
|
502
|
+
let symbolicTarget;
|
|
503
|
+
if ((Vector_1.Vector.isVector(resolvedTarget) || Matrix_1.Matrix.isMatrix(resolvedTarget)) &&
|
|
504
|
+
resolvedTarget.symbolicTarget) {
|
|
505
|
+
symbolicTarget = resolvedTarget.symbolicTarget;
|
|
506
|
+
}
|
|
507
|
+
else if (Token_1.Token.isToken(previousRPN) &&
|
|
508
|
+
previousRPN.type === Token_1.Token.VARIABLE) {
|
|
509
|
+
symbolicTarget = Expression_1.Expression.Variable(previousRPN.value);
|
|
510
|
+
}
|
|
511
|
+
if (!symbolicTarget ||
|
|
512
|
+
!(Vector_1.Vector.isVector(resolvedTarget) || Matrix_1.Matrix.isMatrix(resolvedTarget))) {
|
|
513
|
+
throw new errors_1.ParserError('Symbolic indexed access requires a named Vector or Matrix target.');
|
|
514
|
+
}
|
|
515
|
+
if (Vector_1.Vector.isVector(resolvedTarget) && symbolicIndices.length !== 1) {
|
|
516
|
+
throw new errors_1.ParserError('Symbolic Vector access requires exactly one index.');
|
|
517
|
+
}
|
|
518
|
+
if (Matrix_1.Matrix.isMatrix(resolvedTarget) && symbolicIndices.length !== 2) {
|
|
519
|
+
if (symbolicIndices.length === 1) {
|
|
520
|
+
throw new errors_1.ParserError('Symbolic Matrix row access is not scalar and cannot be deferred as an Expression.');
|
|
521
|
+
}
|
|
522
|
+
throw new errors_1.ParserError('Symbolic Matrix cell access requires exactly two indices.');
|
|
523
|
+
}
|
|
524
|
+
addToOutput(resolvedTarget.withSymbolicAccessor(symbolicTarget, symbolicIndices));
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
addToOutput(new IndexedReference_1.IndexedReference(resolvedTarget, indices, targetName));
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
else if (Expression_1.Expression.isExpression(lastOutput) && lastOutput.isPlainVariable()) {
|
|
531
|
+
// An unresolved variable followed by symbolic indices cannot yet be
|
|
532
|
+
// classified from runtime type information. Preserve the access in a
|
|
533
|
+
// structured carrier; concrete bracket adjacency keeps its legacy
|
|
534
|
+
// implicit-multiplication behavior.
|
|
535
|
+
const indices = this.extractIndices(token, values, assertive);
|
|
536
|
+
if (indices.length > 0 &&
|
|
537
|
+
Expression_1.Expression.isExpression(indices[0])) {
|
|
538
|
+
const symbolicIndices = indices;
|
|
539
|
+
if (symbolicIndices.length > 2) {
|
|
540
|
+
throw new errors_1.ParserError('Symbolic indexed access supports at most two unresolved indices.');
|
|
541
|
+
}
|
|
542
|
+
const carrier = symbolicIndices.length === 2 ? new Matrix_1.Matrix([]) : new Vector_1.Vector();
|
|
543
|
+
output.pop();
|
|
544
|
+
addToOutput(carrier.withSymbolicAccessor(lastOutput, symbolicIndices));
|
|
545
|
+
}
|
|
546
|
+
else if (Settings_1.Settings.ALLOW_IMPLICIT_MULTIPLICATION) {
|
|
547
|
+
const indexExpr = this.parseRPN(token, values, assertive);
|
|
548
|
+
const mulFn = functions_2._['multiply'];
|
|
549
|
+
const a = resolveStackValue(output.pop());
|
|
550
|
+
addToOutput(mulFn(a, indexExpr));
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
const parsed = this.parseRPN(token, values, assertive);
|
|
554
|
+
addToOutput(parsed);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
else if (token.implicitMultiply && Settings_1.Settings.ALLOW_IMPLICIT_MULTIPLICATION) {
|
|
558
|
+
// Implicit multiplication: x[1,2] where x is not a structured entity.
|
|
559
|
+
const indexExpr = this.parseRPN(token, values, assertive);
|
|
560
|
+
const mulFn = functions_2._['multiply'];
|
|
561
|
+
const a = resolveStackValue(output.pop());
|
|
562
|
+
addToOutput(mulFn(a, indexExpr));
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
const parsed = this.parseRPN(token, values, assertive);
|
|
566
|
+
addToOutput(parsed);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
else {
|
|
570
|
+
// Non-square scope or inside parentheses or nothing on output — parse normally
|
|
571
|
+
const parsed = this.parseRPN(token, values, assertive);
|
|
572
|
+
addToOutput(parsed);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
if (token.type === Token_1.Token.OPERATOR) {
|
|
577
|
+
// Grab the operator. The action will be defined by the operation property
|
|
578
|
+
const operator = token.resolvedOperator ?? this.operators[token.value];
|
|
579
|
+
// Get the last element on output
|
|
580
|
+
let b = output.pop();
|
|
581
|
+
let result;
|
|
582
|
+
// Comma is the only operation that passes parser stack markers through unchanged.
|
|
583
|
+
// This allows KeyValuePair markers to survive until the enclosing curly scope is
|
|
584
|
+
// converted to a Dictionary.
|
|
585
|
+
if (operator.action === 'comma') {
|
|
586
|
+
const a = output.pop();
|
|
587
|
+
const fn = functions_2._[operator.action];
|
|
588
|
+
result = fn(a, b);
|
|
589
|
+
}
|
|
590
|
+
// If it's a postfix operator then the operation occurs only on the previous element
|
|
591
|
+
else if (operator.isPostfix) {
|
|
592
|
+
b = resolveStackValue(b);
|
|
593
|
+
b = Expression_1.Expression.fromSymbolicAccess(b) ?? b;
|
|
594
|
+
const fn = functions_2._[operator.action];
|
|
595
|
+
result = fn(b);
|
|
596
|
+
}
|
|
597
|
+
else {
|
|
598
|
+
let a = output.pop();
|
|
599
|
+
const previousRPN = rpn[i - 1];
|
|
600
|
+
const possibleFunction = rpn[i - 2];
|
|
601
|
+
if (token.position === -1 &&
|
|
602
|
+
operator.action === 'times' &&
|
|
603
|
+
Scope_1.Scope.isScope(previousRPN) &&
|
|
604
|
+
Collection_1.Collection.isCollection(b) &&
|
|
605
|
+
Token_1.Token.isToken(possibleFunction) &&
|
|
606
|
+
possibleFunction.type === Token_1.Token.VARIABLE &&
|
|
607
|
+
Expression_1.Expression.isExpression(a) &&
|
|
608
|
+
a.isPlainVariable()) {
|
|
609
|
+
throw new errors_1.ParserError(`Unsupported function ${possibleFunction.value}`);
|
|
610
|
+
}
|
|
611
|
+
// Handle indexed assignment before ordinary left-hand resolution so the
|
|
612
|
+
// reference itself remains available to the setter.
|
|
613
|
+
if (operator.action === 'assign' &&
|
|
614
|
+
IndexedReference_1.IndexedReference.isIndexedReference(a)) {
|
|
615
|
+
b = resolveStackValue(b);
|
|
616
|
+
if (typeof a.indices === 'string') {
|
|
617
|
+
a.target.__set__(a.indices, b);
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
a.target.__set__(a.indices, b);
|
|
621
|
+
}
|
|
622
|
+
if (a.targetName) {
|
|
623
|
+
this.KNOWN_VALUES[a.targetName] = a.target;
|
|
624
|
+
}
|
|
625
|
+
result = a.target;
|
|
626
|
+
}
|
|
627
|
+
else if (operator.deferRHSResolution) {
|
|
628
|
+
a = resolveStackValue(a);
|
|
629
|
+
if (typeof b !== 'function') {
|
|
630
|
+
throw new errors_1.ParserError((0, errors_1.message)('malformedExpression'));
|
|
631
|
+
}
|
|
632
|
+
const fn = functions_2._[operator.action];
|
|
633
|
+
result = fn(a, b);
|
|
634
|
+
}
|
|
635
|
+
else if (operator.deferLHSResolution) {
|
|
636
|
+
a = resolveStackValue(a);
|
|
637
|
+
b = resolveStackValue(b);
|
|
638
|
+
const fn = functions_2._[operator.action];
|
|
639
|
+
result = fn(a, b);
|
|
640
|
+
}
|
|
641
|
+
else if (operator.action === 'mapTo') {
|
|
642
|
+
// '=>' operator: create a key-value pair for Dictionary construction
|
|
643
|
+
// 'a' is the key (should be a variable name), 'b' is the value
|
|
644
|
+
a = resolveStackValue(a);
|
|
645
|
+
b = resolveStackValue(b);
|
|
646
|
+
const key = Expression_1.Expression.isExpression(a) ? a.text() : String(a.text());
|
|
647
|
+
result = new KeyValuePair_1.KeyValuePair(key, b);
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
// Resolve concrete indexed references and normalize symbolic structured
|
|
651
|
+
// access before ordinary scalar/aggregate operator routing.
|
|
652
|
+
a = resolveStackValue(a);
|
|
653
|
+
b = resolveStackValue(b);
|
|
654
|
+
a = Expression_1.Expression.fromSymbolicAccess(a) ?? a;
|
|
655
|
+
b = Expression_1.Expression.fromSymbolicAccess(b) ?? b;
|
|
656
|
+
const action = assertive && operator.assertiveAction
|
|
657
|
+
? operator.assertiveAction
|
|
658
|
+
: operator.action;
|
|
659
|
+
const fn = functions_2._[action];
|
|
660
|
+
if (a.isEnumerable ||
|
|
661
|
+
b.isEnumerable ||
|
|
662
|
+
Equation_1.Equation.isEquation(a) ||
|
|
663
|
+
Equation_1.Equation.isEquation(b)) {
|
|
664
|
+
// Delegate this to the router who can handle more
|
|
665
|
+
result = (0, functions_2.route)(a, b, operator.action);
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
result = fn(a, b);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
if (Array.isArray(result)) {
|
|
673
|
+
addToOutput(result[0]);
|
|
674
|
+
addToOutput(result[1]);
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
addToOutput(result);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
else if (token.type === Token_1.Token.FUNCTION) {
|
|
681
|
+
// Get the next token and move along
|
|
682
|
+
const argsScope = rpn[++i];
|
|
683
|
+
if (token.value === constants_1.SYMBOLIC_ACCESSOR) {
|
|
684
|
+
// Preserve the original target and index expressions while resolving the
|
|
685
|
+
// same RPN scope under the active values. This avoids serializing either
|
|
686
|
+
// side back through tokenize/toRPN just to apply substitutions.
|
|
687
|
+
const preserved = (0, Settings_1.scopedBlock)('SUBSTITUTE', false, () => {
|
|
688
|
+
return this.parseRPN(argsScope, undefined, assertive);
|
|
689
|
+
});
|
|
690
|
+
const preservedArgs = Collection_1.Collection.isCollection(preserved)
|
|
691
|
+
? preserved.getElements()
|
|
692
|
+
: [preserved];
|
|
693
|
+
const resolved = this.parseRPN(argsScope, values, assertive);
|
|
694
|
+
const resolvedArgs = Collection_1.Collection.isCollection(resolved)
|
|
695
|
+
? resolved.getElements()
|
|
696
|
+
: [resolved];
|
|
697
|
+
if (preservedArgs.length < 2 ||
|
|
698
|
+
preservedArgs.length !== resolvedArgs.length ||
|
|
699
|
+
!Expression_1.Expression.isExpression(preservedArgs[0]) ||
|
|
700
|
+
!preservedArgs[0].isPlainVariable()) {
|
|
701
|
+
throw new errors_1.ParserError('Malformed internal symbolic accessor.');
|
|
702
|
+
}
|
|
703
|
+
const symbolicTarget = preservedArgs[0];
|
|
704
|
+
const resolvedTarget = resolvedArgs[0];
|
|
705
|
+
const symbolicIndices = [];
|
|
706
|
+
const numericIndices = [];
|
|
707
|
+
let allResolved = true;
|
|
708
|
+
for (let j = 1; j < preservedArgs.length; j++) {
|
|
709
|
+
const preservedIndex = preservedArgs[j];
|
|
710
|
+
const resolvedIndex = resolvedArgs[j];
|
|
711
|
+
if (!Expression_1.Expression.isExpression(preservedIndex)) {
|
|
712
|
+
throw new errors_1.ParserError('Malformed internal symbolic accessor index.');
|
|
713
|
+
}
|
|
714
|
+
if (!Expression_1.Expression.isExpression(resolvedIndex)) {
|
|
715
|
+
throw new errors_1.ParserError('Symbolic accessor indices must evaluate to scalar expressions.');
|
|
716
|
+
}
|
|
717
|
+
symbolicIndices.push(resolvedIndex);
|
|
718
|
+
const numericIndex = Number(resolvedIndex.text());
|
|
719
|
+
if (Number.isFinite(numericIndex)) {
|
|
720
|
+
numericIndices.push(numericIndex - Settings_1.Settings.INDEX_BASE);
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
allResolved = false;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
if (symbolicIndices.length > 2) {
|
|
727
|
+
throw new errors_1.ParserError('Symbolic indexed access supports at most two unresolved indices.');
|
|
728
|
+
}
|
|
729
|
+
if (Vector_1.Vector.isVector(resolvedTarget) && symbolicIndices.length !== 1) {
|
|
730
|
+
throw new errors_1.ParserError('Symbolic Vector access requires exactly one index.');
|
|
731
|
+
}
|
|
732
|
+
if (Matrix_1.Matrix.isMatrix(resolvedTarget) && symbolicIndices.length !== 2) {
|
|
733
|
+
if (symbolicIndices.length === 1) {
|
|
734
|
+
throw new errors_1.ParserError('Symbolic Matrix row access is not scalar and cannot be deferred as an Expression.');
|
|
735
|
+
}
|
|
736
|
+
throw new errors_1.ParserError('Symbolic Matrix cell access requires exactly two indices.');
|
|
737
|
+
}
|
|
738
|
+
let result;
|
|
739
|
+
if ((Vector_1.Vector.isVector(resolvedTarget) || Matrix_1.Matrix.isMatrix(resolvedTarget)) && allResolved) {
|
|
740
|
+
result = resolvedTarget.__get__(numericIndices);
|
|
741
|
+
}
|
|
742
|
+
else {
|
|
743
|
+
let carrier;
|
|
744
|
+
if (Vector_1.Vector.isVector(resolvedTarget) || Matrix_1.Matrix.isMatrix(resolvedTarget)) {
|
|
745
|
+
carrier = resolvedTarget;
|
|
746
|
+
}
|
|
747
|
+
else if (Expression_1.Expression.isExpression(resolvedTarget) &&
|
|
748
|
+
resolvedTarget.isPlainVariable()) {
|
|
749
|
+
carrier = symbolicIndices.length === 2 ? new Matrix_1.Matrix([]) : new Vector_1.Vector();
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
throw new errors_1.ParserError('Symbolic indexed access target must resolve to a Vector or Matrix.');
|
|
753
|
+
}
|
|
754
|
+
result = carrier.withSymbolicAccessor(symbolicTarget, symbolicIndices);
|
|
755
|
+
}
|
|
756
|
+
addToOutput(result);
|
|
757
|
+
}
|
|
758
|
+
else if (dispatch_1.mathFunctionRegistry[token.value]?.deferArguments) {
|
|
759
|
+
// Deferred functions receive callable arguments and decide when, whether, and how
|
|
760
|
+
// often each argument is evaluated.
|
|
761
|
+
const argumentScopes = [];
|
|
762
|
+
if (argsScope.length > 0) {
|
|
763
|
+
let argumentScope = new Scope_1.Scope(argsScope.type, argsScope.column);
|
|
764
|
+
for (const argumentToken of argsScope) {
|
|
765
|
+
if (Token_1.Token.isToken(argumentToken) &&
|
|
766
|
+
argumentToken.type === Token_1.Token.OPERATOR &&
|
|
767
|
+
argumentToken.value === constants_1.COMMA) {
|
|
768
|
+
argumentScopes.push(argumentScope);
|
|
769
|
+
argumentScope = new Scope_1.Scope(argsScope.type, argsScope.column);
|
|
770
|
+
}
|
|
771
|
+
else {
|
|
772
|
+
argumentScope.push(argumentToken);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
argumentScopes.push(argumentScope);
|
|
776
|
+
}
|
|
777
|
+
const normalizeArguments = dispatch_1.mathFunctionRegistry[token.value].normalizeDeferredArguments;
|
|
778
|
+
const normalizedScopes = normalizeArguments
|
|
779
|
+
? normalizeArguments(argumentScopes)
|
|
780
|
+
: argumentScopes;
|
|
781
|
+
const functionAssertive = common_1.ASSERTIVE_FUNCTIONS.includes(token.value);
|
|
782
|
+
const args = normalizedScopes.map(argumentScope => {
|
|
783
|
+
return createDeferredArgument(() => this.toRPN(argumentScope), values, functionAssertive);
|
|
784
|
+
});
|
|
785
|
+
const result = (0, functions_2.callFunction)(token.value, args);
|
|
786
|
+
addToOutput(result);
|
|
787
|
+
}
|
|
788
|
+
else if (argsScope.deferLHSResolution) {
|
|
789
|
+
let args = [];
|
|
790
|
+
if (argsScope.length > 0) {
|
|
791
|
+
const parsed = (0, Settings_1.scopedBlock)('SUBSTITUTE', false, () => {
|
|
792
|
+
return this.parseRPN(argsScope, undefined, assertive);
|
|
793
|
+
});
|
|
794
|
+
args = Collection_1.Collection.isCollection(parsed) ? [...parsed.getElements()] : [parsed];
|
|
795
|
+
}
|
|
796
|
+
// Preserve the scoped values followed by their left-hand target. The operator
|
|
797
|
+
// action is responsible for interpreting that structure.
|
|
798
|
+
args.push(Expression_1.Expression.Variable(token.value));
|
|
799
|
+
addToOutput(new Collection_1.Collection(args));
|
|
800
|
+
}
|
|
801
|
+
else {
|
|
802
|
+
// Parse it as an argument. If the value is of the token is an assertive function then parseRPN needs to
|
|
803
|
+
// know so it can modify it's behavior accordingly. This would be in the case of functions like assume where
|
|
804
|
+
// the operators assert a value rather than compare it.
|
|
805
|
+
const parsed = this.parseRPN(argsScope, values, common_1.ASSERTIVE_FUNCTIONS.includes(token.value));
|
|
806
|
+
const args = Collection_1.Collection.isCollection(parsed) ? parsed.getElements() : [parsed];
|
|
807
|
+
const result = (0, functions_2.callFunction)(token.value, args);
|
|
808
|
+
addToOutput(result);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
else if (token.type === Token_1.Token.PREFIX) {
|
|
812
|
+
const resolved = resolveStackValue(output.pop());
|
|
813
|
+
const e = Expression_1.Expression.fromSymbolicAccess(resolved) ?? resolved;
|
|
814
|
+
const prefixAction = `${operators[token.value].action}Prefix`;
|
|
815
|
+
const fn = functions_2._[prefixAction];
|
|
816
|
+
// TODO: Rethink. What happens when a prefix is applied to a vector
|
|
817
|
+
addToOutput(fn(e));
|
|
818
|
+
}
|
|
819
|
+
else {
|
|
820
|
+
let expression;
|
|
821
|
+
const constant = this.CONSTANTS[token.value];
|
|
822
|
+
// User-defined constants are stored as strings and substitute during ordinary parsing.
|
|
823
|
+
// They take precedence over scoped values, matching the public setConstant method.
|
|
824
|
+
if (typeof constant === 'string') {
|
|
825
|
+
expression = this.parse(constant);
|
|
826
|
+
}
|
|
827
|
+
// Substitute values from the values object. A number is not considered a proper LH value.
|
|
828
|
+
else if (values && token.value in values && !token.is(Token_1.Token.NUMBER)) {
|
|
829
|
+
// expression = this.parse(String(values[token.value]), values);
|
|
830
|
+
const scopedValue = values[token.value];
|
|
831
|
+
if (Expression_1.Expression.isExpression(scopedValue) ||
|
|
832
|
+
Equation_1.Equation.isEquation(scopedValue) ||
|
|
833
|
+
(0, structuredEntityUtils_1.isEnumerable)(scopedValue)) {
|
|
834
|
+
expression = scopedValue.copy();
|
|
835
|
+
}
|
|
836
|
+
else {
|
|
837
|
+
expression = Expression_1.Expression.create(scopedValue, undefined, true);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
else if (token.value in this.KNOWN_VALUES && Settings_1.Settings.SUBSTITUTE) {
|
|
841
|
+
expression = this.KNOWN_VALUES[token.value].copy();
|
|
842
|
+
}
|
|
843
|
+
// Built-in constants are factories so they evaluate at the active precision.
|
|
844
|
+
else if (Settings_1.Settings.EVALUATE && typeof constant === 'function') {
|
|
845
|
+
expression = this.parse(constant(), values);
|
|
846
|
+
}
|
|
847
|
+
else {
|
|
848
|
+
// At this point the expression type is variable or a number
|
|
849
|
+
switch (token.type) {
|
|
850
|
+
case Token_1.Token.FUNCTION:
|
|
851
|
+
expression = Expression_1.Expression.Function(token.value);
|
|
852
|
+
break;
|
|
853
|
+
case Token_1.Token.VARIABLE:
|
|
854
|
+
if (constants_1.INFINITY.includes(token.value)) {
|
|
855
|
+
expression = Expression_1.Expression.Inf();
|
|
856
|
+
}
|
|
857
|
+
else {
|
|
858
|
+
expression = Expression_1.Expression.Variable(token.value);
|
|
859
|
+
}
|
|
860
|
+
break;
|
|
861
|
+
default:
|
|
862
|
+
expression = Expression_1.Expression.Number(token.value);
|
|
863
|
+
break;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
// Add it to output
|
|
867
|
+
addToOutput(expression);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
let retval;
|
|
872
|
+
// Since as function args can have multiple arguments in the output
|
|
873
|
+
// the entire array gets returned. We do the same with
|
|
874
|
+
if (rpn.type === 'square') {
|
|
875
|
+
retval = new Vector_1.Vector(resolveStackValues(output));
|
|
876
|
+
}
|
|
877
|
+
else if (rpn.type === 'curly') {
|
|
878
|
+
// If the output contains KeyValuePairs (from '=>' operators), build a Dictionary.
|
|
879
|
+
// Otherwise build a ValuesSet as before.
|
|
880
|
+
if (output.length > 0 && KeyValuePair_1.KeyValuePair.isKeyValuePair(output[0])) {
|
|
881
|
+
const dict = new Dictionary_1.Dictionary();
|
|
882
|
+
for (const item of output) {
|
|
883
|
+
if (KeyValuePair_1.KeyValuePair.isKeyValuePair(item)) {
|
|
884
|
+
dict.set(item.key, item.value);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
retval = dict;
|
|
888
|
+
}
|
|
889
|
+
else {
|
|
890
|
+
retval = new ValuesSet_1.ValuesSet(resolveStackValues(output));
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
else if (output.length > 1) {
|
|
894
|
+
retval = new Collection_1.Collection(resolveStackValues(output));
|
|
895
|
+
}
|
|
896
|
+
else {
|
|
897
|
+
retval = resolveStackValue(output[0]);
|
|
898
|
+
}
|
|
899
|
+
return retval;
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Converts a preprocessed TeX token scope to Nerdamer's ordinary parser syntax.
|
|
903
|
+
*
|
|
904
|
+
* @remarks
|
|
905
|
+
* This is a low-level conversion helper used by the TeX converter, not a complete
|
|
906
|
+
* LaTeX parser. The input is the nested scope produced after TeX-specific cleanup
|
|
907
|
+
* and tokenization; despite this method's historical name, callers should not assume
|
|
908
|
+
* that the scope has passed through {@link toRPN}.
|
|
909
|
+
*
|
|
910
|
+
* The converter recognizes the subset of commands handled in this routine, including
|
|
911
|
+
* fractions, integrals, sums, products, limits, square roots, infinity, real/imaginary
|
|
912
|
+
* part notation, and common trigonometric functions. Some command branches consume or
|
|
913
|
+
* modify child scopes while assembling the output, so callers should treat the supplied
|
|
914
|
+
* token tree as conversion input rather than as an immutable value to reuse afterward.
|
|
915
|
+
*
|
|
916
|
+
* Prefer the public TeX conversion API for ordinary LaTeX input.
|
|
917
|
+
*
|
|
918
|
+
* @param rpn - Preprocessed TeX token scope to convert.
|
|
919
|
+
* @returns Equivalent Nerdamer parser text for the supported TeX subset.
|
|
920
|
+
*
|
|
921
|
+
* @example
|
|
922
|
+
* ```ts
|
|
923
|
+
* // Internal conversion examples:
|
|
924
|
+
* // \frac{x}{2} -> "(x)/(2)"
|
|
925
|
+
* // \int x^2 dx -> "int(x^2,x)"
|
|
926
|
+
* ```
|
|
927
|
+
*/
|
|
928
|
+
parseTeXRPN(rpn) {
|
|
929
|
+
const commands = ['int', 'int_', 'frac', 'sum_', 'prod_', 'lim_', 'infty', 'mathrm'];
|
|
930
|
+
const functionMap = {
|
|
931
|
+
Re: 'realpart',
|
|
932
|
+
Im: 'imagpart',
|
|
933
|
+
arccos: 'acos',
|
|
934
|
+
arcsin: 'asin',
|
|
935
|
+
arctan: 'atan',
|
|
936
|
+
};
|
|
937
|
+
const output = [];
|
|
938
|
+
const { FUNCTION, VARIABLE } = Token_1.Token;
|
|
939
|
+
// Declare globally since it get increment in multiple loops
|
|
940
|
+
let i;
|
|
941
|
+
const find = (condition) => {
|
|
942
|
+
const retval = new Scope_1.Scope('', -1);
|
|
943
|
+
for (; i < rpn.length; i++) {
|
|
944
|
+
const token = rpn[i];
|
|
945
|
+
retval.push(token);
|
|
946
|
+
if (condition(token)) {
|
|
947
|
+
break;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return retval;
|
|
951
|
+
};
|
|
952
|
+
const integral = () => {
|
|
953
|
+
// Get the tokens up until the first token that starts with d
|
|
954
|
+
const tokens = find(token => {
|
|
955
|
+
return /^d/.test(token.value);
|
|
956
|
+
});
|
|
957
|
+
// The last items returned should be dx so we can remove that get the variable of integration but the remove the d
|
|
958
|
+
const voi = Token_1.Token.V(tokens.pop().text().substring(1));
|
|
959
|
+
// Process the remainder
|
|
960
|
+
if (tokens.at(-1)?.text() === constants_1.COMMA) {
|
|
961
|
+
tokens.pop();
|
|
962
|
+
}
|
|
963
|
+
return [tokens, voi];
|
|
964
|
+
};
|
|
965
|
+
const getLimits = (limits) => {
|
|
966
|
+
const start = new Scope_1.Scope('', -1);
|
|
967
|
+
const end = new Scope_1.Scope('', -1);
|
|
968
|
+
let target = start;
|
|
969
|
+
for (let j = 0; j < limits.length; j++) {
|
|
970
|
+
const token = limits[j];
|
|
971
|
+
if (token.text() === 'to') {
|
|
972
|
+
target = end;
|
|
973
|
+
continue;
|
|
974
|
+
}
|
|
975
|
+
target.push(token);
|
|
976
|
+
}
|
|
977
|
+
return [start, end];
|
|
978
|
+
};
|
|
979
|
+
// Begin parsing of tokens
|
|
980
|
+
for (i = 0; i < rpn.length; i++) {
|
|
981
|
+
// Grab the token or Scope
|
|
982
|
+
const token = rpn[i];
|
|
983
|
+
if (Scope_1.Scope.isScope(token)) {
|
|
984
|
+
// Read it to an expression and put it to output
|
|
985
|
+
const parsed = this.parseTeXRPN(token);
|
|
986
|
+
output.push(`(${parsed})`);
|
|
987
|
+
}
|
|
988
|
+
else if (token.is(FUNCTION) ||
|
|
989
|
+
(token.is(VARIABLE) && commands.includes(token.value))) {
|
|
990
|
+
// Move forward to skip over the command
|
|
991
|
+
i++;
|
|
992
|
+
// Provide a "cleaned" optional function name
|
|
993
|
+
const f = token.text().split('_')[0];
|
|
994
|
+
const command = token.text();
|
|
995
|
+
switch (token.value) {
|
|
996
|
+
case 'cos':
|
|
997
|
+
case 'sin':
|
|
998
|
+
case 'tan':
|
|
999
|
+
case 'sec':
|
|
1000
|
+
case 'csc':
|
|
1001
|
+
case 'cot':
|
|
1002
|
+
case 'acos':
|
|
1003
|
+
case 'arccos':
|
|
1004
|
+
case 'arcsin':
|
|
1005
|
+
case 'arctan':
|
|
1006
|
+
case 'asec':
|
|
1007
|
+
case 'acsc':
|
|
1008
|
+
case 'acot':
|
|
1009
|
+
case 'sqrt':
|
|
1010
|
+
case 'gcd':
|
|
1011
|
+
case 'Re':
|
|
1012
|
+
case 'Im': {
|
|
1013
|
+
output.push(`${functionMap[command] || command}(${this.parseTeXRPN(rpn[i])})`);
|
|
1014
|
+
break;
|
|
1015
|
+
}
|
|
1016
|
+
case 'sum_':
|
|
1017
|
+
case 'prod_': {
|
|
1018
|
+
const lower = rpn[i];
|
|
1019
|
+
const ios = lower.shift().text();
|
|
1020
|
+
// Remove the equal sign
|
|
1021
|
+
lower.shift();
|
|
1022
|
+
const start = this.parseTeXRPN(lower);
|
|
1023
|
+
// Just forward two spots
|
|
1024
|
+
i += 2;
|
|
1025
|
+
const end = this.parseTeXRPN(rpn[i]);
|
|
1026
|
+
output.push(`${f}(${this.parseTeXRPN(rpn[++i])},${ios},${start},${end})`);
|
|
1027
|
+
break;
|
|
1028
|
+
}
|
|
1029
|
+
case 'lim_': {
|
|
1030
|
+
const [start, end] = getLimits(rpn[i]);
|
|
1031
|
+
output.push(`${f}(${this.parseTeXRPN(rpn[++i])},${start},${end})`);
|
|
1032
|
+
break;
|
|
1033
|
+
}
|
|
1034
|
+
case 'int_': {
|
|
1035
|
+
// Get the tokens up until the first token that starts with d
|
|
1036
|
+
const [tokens, voi] = integral();
|
|
1037
|
+
const a = this.parseTeXRPN(tokens.shift());
|
|
1038
|
+
// discard the caret
|
|
1039
|
+
tokens.shift();
|
|
1040
|
+
const b = this.parseTeXRPN(tokens.shift());
|
|
1041
|
+
// return the output
|
|
1042
|
+
output.push(`defint(${this.parseTeXRPN(tokens)},${a},${b},${voi})`);
|
|
1043
|
+
break;
|
|
1044
|
+
}
|
|
1045
|
+
case 'int': {
|
|
1046
|
+
// Get the tokens up until the first token that starts with d
|
|
1047
|
+
const [tokens, voi] = integral();
|
|
1048
|
+
// return the output
|
|
1049
|
+
output.push(`${f}(${this.parseTeXRPN(tokens)},${voi})`);
|
|
1050
|
+
break;
|
|
1051
|
+
}
|
|
1052
|
+
case 'frac': {
|
|
1053
|
+
const a = this.parseTeXRPN(rpn[i]);
|
|
1054
|
+
const b = this.parseTeXRPN(rpn[++i]);
|
|
1055
|
+
output.push(`(${a})/(${b})`);
|
|
1056
|
+
// Get the next two
|
|
1057
|
+
break;
|
|
1058
|
+
}
|
|
1059
|
+
case 'infty': {
|
|
1060
|
+
output.push(constants_1.INFINITY[0]);
|
|
1061
|
+
break;
|
|
1062
|
+
}
|
|
1063
|
+
case 'mathrm': {
|
|
1064
|
+
output.push(`${this.parseTeXRPN(rpn[i])}(${this.parseTeXRPN(rpn[++i])})`);
|
|
1065
|
+
break;
|
|
1066
|
+
}
|
|
1067
|
+
default: {
|
|
1068
|
+
output.push(`${command}(${this.parseTeXRPN(rpn[i])})`);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
else {
|
|
1073
|
+
// White space is only used when looking ahead and can be discarded at this point
|
|
1074
|
+
if (!/\s+/.test(token.value)) {
|
|
1075
|
+
output.push(token.value);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
return output.join('');
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* Runs a callback while temporarily overriding one global parser setting.
|
|
1083
|
+
*
|
|
1084
|
+
* @remarks
|
|
1085
|
+
* The previous value is restored in a `finally` block, so it is restored even if
|
|
1086
|
+
* the callback throws. The override is process-wide for the duration of the
|
|
1087
|
+
* synchronous callback; it is not an isolated setting attached to this parser
|
|
1088
|
+
* instance. The `setting` parameter remains a string for compatibility, so callers
|
|
1089
|
+
* should supply a valid parser setting name.
|
|
1090
|
+
*
|
|
1091
|
+
* @param setting - Parser setting to override for the callback.
|
|
1092
|
+
* @param value - Temporary boolean value for that setting.
|
|
1093
|
+
* @param callback - Synchronous parser operation to run under the temporary setting.
|
|
1094
|
+
* @returns The parser entity returned by `callback`.
|
|
1095
|
+
*
|
|
1096
|
+
* @example
|
|
1097
|
+
* ```ts
|
|
1098
|
+
* const symbolicPi = Parser.scopedBlock('EVALUATE', false, () => {
|
|
1099
|
+
* return Parser.parse('pi');
|
|
1100
|
+
* });
|
|
1101
|
+
*
|
|
1102
|
+
* symbolicPi.text(); // "pi"
|
|
1103
|
+
* ```
|
|
1104
|
+
*/
|
|
1105
|
+
scopedBlock(setting, value, callback) {
|
|
1106
|
+
return (0, Settings_1.scopedBlock)(setting, value, callback);
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Changes one or more process-wide parser settings.
|
|
1110
|
+
*
|
|
1111
|
+
* @remarks
|
|
1112
|
+
* Settings persist until changed again and are shared by the exported parser,
|
|
1113
|
+
* parsers returned by {@link create}, and higher-level Nerdamer entry points.
|
|
1114
|
+
* Use {@link scopedBlock} when a setting should apply only for one synchronous
|
|
1115
|
+
* operation.
|
|
1116
|
+
*
|
|
1117
|
+
* This method accepts string keys for compatibility and does not validate arbitrary
|
|
1118
|
+
* setting names at runtime. Callers should use the settings defined by Nerdamer
|
|
1119
|
+
* rather than adding ad hoc properties to the settings object.
|
|
1120
|
+
*
|
|
1121
|
+
* @param setting - Setting name, or an object containing settings to update.
|
|
1122
|
+
* @param value - New value when `setting` is supplied as a single name.
|
|
1123
|
+
* @returns This parser instance for chaining.
|
|
1124
|
+
*
|
|
1125
|
+
* @example
|
|
1126
|
+
* ```ts
|
|
1127
|
+
* Parser.set('EVALUATE', false);
|
|
1128
|
+
* Parser.set({ EVALUATE: false, ALLOW_IMPLICIT_MULTIPLICATION: true });
|
|
1129
|
+
* ```
|
|
1130
|
+
*/
|
|
1131
|
+
set(setting, value) {
|
|
1132
|
+
if (typeof setting === 'object') {
|
|
1133
|
+
for (const x in setting) {
|
|
1134
|
+
Settings_1.Settings[x] = setting[x];
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
else {
|
|
1138
|
+
Settings_1.Settings[setting] = value;
|
|
1139
|
+
}
|
|
1140
|
+
return this;
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Registers, updates, or removes named parser constants.
|
|
1144
|
+
*
|
|
1145
|
+
* @remarks
|
|
1146
|
+
* The constant registry is shared by parser instances, so changes affect subsequent
|
|
1147
|
+
* parsing throughout the process. String-valued constants are reparsed and substituted
|
|
1148
|
+
* whenever their name is encountered, even when evaluation mode is disabled. They also
|
|
1149
|
+
* take precedence over a same-named value supplied in a parse call.
|
|
1150
|
+
*
|
|
1151
|
+
* Function-valued constants are deferred factories. They are invoked when evaluation
|
|
1152
|
+
* mode is enabled; otherwise the constant name remains symbolic. Nerdamer's built-in
|
|
1153
|
+
* `pi` and `e` factories use this mechanism to generate values at the active precision.
|
|
1154
|
+
*
|
|
1155
|
+
* Assign an empty string to an existing constant to remove it. Changing the registry
|
|
1156
|
+
* does not rewrite expressions that have already been parsed.
|
|
1157
|
+
*
|
|
1158
|
+
* @param constants - Constant names mapped to parser text or to factories that return
|
|
1159
|
+
* parser text.
|
|
1160
|
+
*
|
|
1161
|
+
* @example
|
|
1162
|
+
* ```ts
|
|
1163
|
+
* Parser.setConstants({ G: '6.674e-11' });
|
|
1164
|
+
* Parser.parse('G').text(); // "0.00000000006674"
|
|
1165
|
+
*
|
|
1166
|
+
* Parser.setConstants({ G: '' });
|
|
1167
|
+
* Parser.parse('G').text(); // "G"
|
|
1168
|
+
* ```
|
|
1169
|
+
*/
|
|
1170
|
+
setConstants(constants) {
|
|
1171
|
+
for (const name in constants) {
|
|
1172
|
+
const value = constants[name];
|
|
1173
|
+
if (typeof value === 'string' && value === '' && name in this.CONSTANTS) {
|
|
1174
|
+
delete this.CONSTANTS[name];
|
|
1175
|
+
}
|
|
1176
|
+
else {
|
|
1177
|
+
this.CONSTANTS[name] = value;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Changes the symbol Nerdamer treats as the imaginary unit.
|
|
1183
|
+
*
|
|
1184
|
+
* @remarks
|
|
1185
|
+
* This is a process-wide change. The previous imaginary-unit name is removed from
|
|
1186
|
+
* Nerdamer's restricted-variable list, the new name is reserved, and
|
|
1187
|
+
* {@link Expression.imaginary} is updated. Existing expression trees are not renamed
|
|
1188
|
+
* or reparsed, so changing this setting affects subsequent interpretation rather than
|
|
1189
|
+
* rewriting values that already exist.
|
|
1190
|
+
*
|
|
1191
|
+
* @param variable - Parser variable name to reserve for the imaginary unit.
|
|
1192
|
+
* @returns This parser instance for chaining.
|
|
1193
|
+
*
|
|
1194
|
+
* @example
|
|
1195
|
+
* ```ts
|
|
1196
|
+
* const previous = Parser.getI();
|
|
1197
|
+
* Parser.setI('j');
|
|
1198
|
+
* Parser.parse('3+2*j').text(); // "3+2*j"
|
|
1199
|
+
* Parser.setI(previous);
|
|
1200
|
+
* ```
|
|
1201
|
+
*/
|
|
1202
|
+
setI(variable) {
|
|
1203
|
+
const i = Expression_1.Expression.imaginary;
|
|
1204
|
+
// Remove it from the restricted list
|
|
1205
|
+
(0, array_1.remove)(Settings_1.RESTRICTED, i);
|
|
1206
|
+
// Point to the new variable
|
|
1207
|
+
Expression_1.Expression.imaginary = variable;
|
|
1208
|
+
// Add the new variable to the restricted list
|
|
1209
|
+
Settings_1.RESTRICTED.push(variable);
|
|
1210
|
+
return this;
|
|
1211
|
+
}
|
|
1212
|
+
/**
|
|
1213
|
+
* Alias for {@link setI}.
|
|
1214
|
+
*
|
|
1215
|
+
* @param variable - Parser variable name to reserve for the imaginary unit.
|
|
1216
|
+
* @returns This parser instance for chaining.
|
|
1217
|
+
*/
|
|
1218
|
+
setImaginary(variable) {
|
|
1219
|
+
return this.setI(variable);
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Registers or updates an operator in the shared parser registry. Legacy `prefix`
|
|
1223
|
+
* and `postfix` fields are accepted alongside the current metadata names.
|
|
1224
|
+
*/
|
|
1225
|
+
setOperator(definition, action) {
|
|
1226
|
+
const { prefix, postfix, ...metadata } = definition;
|
|
1227
|
+
const operator = {
|
|
1228
|
+
...metadata,
|
|
1229
|
+
isPostfix: metadata.isPostfix ?? postfix ?? false,
|
|
1230
|
+
isPrefix: metadata.isPrefix ?? prefix ?? false,
|
|
1231
|
+
iterates: metadata.iterates ?? true,
|
|
1232
|
+
};
|
|
1233
|
+
if (action) {
|
|
1234
|
+
functions_2._[operator.action] = action;
|
|
1235
|
+
}
|
|
1236
|
+
else if (!functions_2._[operator.action]) {
|
|
1237
|
+
throw new errors_1.ParserError(`Unknown operator action ${operator.action}`);
|
|
1238
|
+
}
|
|
1239
|
+
this.operators[operator.operator] = operator;
|
|
1240
|
+
(0, Token_1.refreshOperatorSymbols)();
|
|
1241
|
+
return this;
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Sets the shared precision used by Decimal-backed numerical calculations.
|
|
1245
|
+
*
|
|
1246
|
+
* @remarks
|
|
1247
|
+
* This delegates to {@link Rational.set}. It updates the global `decimal.js`
|
|
1248
|
+
* precision and recomputes Nerdamer's finite-precision `pi` and `e` rational
|
|
1249
|
+
* constants. Exact numerator/denominator arithmetic remains exact and does not
|
|
1250
|
+
* become approximate merely because this setting changes.
|
|
1251
|
+
*
|
|
1252
|
+
* The setting is process-wide and affects parsers returned by {@link create} as
|
|
1253
|
+
* well as the exported {@link Parser} singleton.
|
|
1254
|
+
*
|
|
1255
|
+
* @param precision - Number of significant digits for Decimal-backed work.
|
|
1256
|
+
*
|
|
1257
|
+
* @example
|
|
1258
|
+
* ```ts
|
|
1259
|
+
* const previous = Parser.getPrecision();
|
|
1260
|
+
* Parser.setPrecision(50);
|
|
1261
|
+
* Parser.getPrecision(); // 50
|
|
1262
|
+
* Parser.setPrecision(previous);
|
|
1263
|
+
* ```
|
|
1264
|
+
*/
|
|
1265
|
+
setPrecision(precision) {
|
|
1266
|
+
Rational_1.Rational.set({ precision: precision });
|
|
1267
|
+
}
|
|
1268
|
+
/**
|
|
1269
|
+
* Tokenizes parser text into a nested scope tree.
|
|
1270
|
+
*
|
|
1271
|
+
* @remarks
|
|
1272
|
+
* The tokenizer recognizes numbers, variables, functions, keyword and symbolic
|
|
1273
|
+
* operators, and round/square/curly bracket scopes. Scientific notation is normalized
|
|
1274
|
+
* to decimal token text and configured aliases such as `π` and `∞` are normalized to
|
|
1275
|
+
* their parser names.
|
|
1276
|
+
*
|
|
1277
|
+
* When implicit multiplication is enabled, the normal mode inserts synthetic `*`
|
|
1278
|
+
* tokens where adjacency implies multiplication. Square-bracket adjacency is deferred:
|
|
1279
|
+
* the child scope is marked so {@link parseRPN} can decide at evaluation time whether
|
|
1280
|
+
* the construct is indexing or multiplication after the left operand's type is known.
|
|
1281
|
+
* Unknown variable-parenthesis adjacency keeps its synthetic multiplication token but
|
|
1282
|
+
* is also marked so {@link toRPN} can reinterpret it as a function call if the name is
|
|
1283
|
+
* registered before that scope is converted. `options.pure` suppresses inserted
|
|
1284
|
+
* multiplication tokens and is used by conversion code that needs the original token
|
|
1285
|
+
* relationships. `options.keepWhiteSpace` retains whitespace tokens instead of
|
|
1286
|
+
* discarding them.
|
|
1287
|
+
*
|
|
1288
|
+
* This is a low-level parser-pipeline API. Ordinary callers should normally use
|
|
1289
|
+
* {@link parse} instead of depending on token representation details.
|
|
1290
|
+
*
|
|
1291
|
+
* @param inputStr - Parser text to tokenize.
|
|
1292
|
+
* @param options - Tokenization options. Set `pure` to suppress synthetic
|
|
1293
|
+
* implicit-multiplication tokens, or `keepWhiteSpace` to retain whitespace tokens.
|
|
1294
|
+
* @returns The root `Scope` containing the nested token tree.
|
|
1295
|
+
* @throws {@link UnexpectedTokenError} When brackets are mismatched or token adjacency is invalid.
|
|
1296
|
+
*
|
|
1297
|
+
* @example
|
|
1298
|
+
* ```ts
|
|
1299
|
+
* const tokens = Parser.tokenize('2*x + 1');
|
|
1300
|
+
* tokens.text(); // "2 * x + 1"
|
|
1301
|
+
* ```
|
|
1302
|
+
*/
|
|
1303
|
+
tokenize(inputStr, options) {
|
|
1304
|
+
// options = Object.assign({ pure: false, keepWhiteSpace: false }, options);
|
|
1305
|
+
options = { ...{ pure: false, keepWhiteSpace: false }, ...options };
|
|
1306
|
+
// A key element to be aware of is that a terminating character is appended to the string.
|
|
1307
|
+
// This avoids an extra step since the last item is ignored. Brackets are wrapped in a Scope object
|
|
1308
|
+
// which preserves the type for later parsing.
|
|
1309
|
+
const terminator = '\u0000';
|
|
1310
|
+
// Input must be a string and append a terminator. This avoids having to iterate over undefined will be ignored at the end.
|
|
1311
|
+
let str = String(inputStr) + terminator;
|
|
1312
|
+
// Remove whitespace if allows
|
|
1313
|
+
if (!options.keepWhiteSpace) {
|
|
1314
|
+
str = str.replace(/\s+/g, ' ').trim();
|
|
1315
|
+
}
|
|
1316
|
+
// The list of operators supported at the time of tokenization
|
|
1317
|
+
const operators = Object.keys(this.operators).sort();
|
|
1318
|
+
const deferredLHSOperators = operators
|
|
1319
|
+
.filter(operator => this.operators[operator].deferLHSResolution)
|
|
1320
|
+
.sort((a, b) => b.length - a.length);
|
|
1321
|
+
// The column position that the token was found
|
|
1322
|
+
let col = 0;
|
|
1323
|
+
// The tokens container. A Scope object is used to denote tokens between brackets. This greatly simplifies
|
|
1324
|
+
// parsing in the future. This allows the tracking of the bracket type and enables parsing to be limited to one scope.
|
|
1325
|
+
const tokens = new Scope_1.Scope('top', 0);
|
|
1326
|
+
// The scope being appended to
|
|
1327
|
+
let scope = tokens;
|
|
1328
|
+
// The object to hold portions of the string between tokens. We create an object so we can pass it around by reference.
|
|
1329
|
+
const tokenBuffer = {
|
|
1330
|
+
tokenType: Token_1.Token.UNDEFINED,
|
|
1331
|
+
chars: [],
|
|
1332
|
+
inputString: str,
|
|
1333
|
+
last: function () {
|
|
1334
|
+
return this.chars[this.chars.length - 1];
|
|
1335
|
+
},
|
|
1336
|
+
next: function (at) {
|
|
1337
|
+
return this.inputString.charAt(at + 1);
|
|
1338
|
+
},
|
|
1339
|
+
is(tokenType) {
|
|
1340
|
+
return this.tokenType === tokenType;
|
|
1341
|
+
},
|
|
1342
|
+
prev: function (at) {
|
|
1343
|
+
return this.inputString.charAt(at - 1);
|
|
1344
|
+
},
|
|
1345
|
+
scientificNumber: false,
|
|
1346
|
+
};
|
|
1347
|
+
// Traverse the string and look at each character
|
|
1348
|
+
for (; col < str.length; col++) {
|
|
1349
|
+
const ch = str.charAt(col);
|
|
1350
|
+
// Get the character type for comparison. Search for changes in the character type to determine
|
|
1351
|
+
// the end of a token.
|
|
1352
|
+
const charType = Token_1.Token.getCharType(ch, col, tokenBuffer);
|
|
1353
|
+
// Brackets point to a new scope. Once one is encountered, update the target and point to the new scope
|
|
1354
|
+
// Subsequent tokens are now pushed to that scope
|
|
1355
|
+
if (tokenBuffer.is(Token_1.Token.BRACKET)) {
|
|
1356
|
+
// Get the bracket.
|
|
1357
|
+
const bracketCharacter = tokenBuffer.last();
|
|
1358
|
+
if (bracketCharacter === undefined) {
|
|
1359
|
+
throw new errors_1.ParserError('Bracket token is missing its character.');
|
|
1360
|
+
}
|
|
1361
|
+
const bracket = common_1.brackets[bracketCharacter];
|
|
1362
|
+
if (bracket.isOpen) {
|
|
1363
|
+
// Check to see if there's a variable on the token stack, if it's a variable, and if it's in functions
|
|
1364
|
+
const lastToken = scope[scope.length - 1];
|
|
1365
|
+
if (lastToken &&
|
|
1366
|
+
lastToken.type === Token_1.Token.VARIABLE &&
|
|
1367
|
+
(lastToken.value in dispatch_1.mathFunctionRegistry || lastToken.value === constants_1.SYMBOLIC_ACCESSOR)) {
|
|
1368
|
+
// Mark it as a function
|
|
1369
|
+
lastToken.type = Token_1.Token.FUNCTION;
|
|
1370
|
+
}
|
|
1371
|
+
// Implicit multiplication
|
|
1372
|
+
// Track whether to mark the new scope for deferred implicit multiplication
|
|
1373
|
+
let deferImplicitMultiply = false;
|
|
1374
|
+
if (lastToken &&
|
|
1375
|
+
!(lastToken.type === Token_1.Token.FUNCTION || lastToken.type === Token_1.Token.OPERATOR)) {
|
|
1376
|
+
if (bracket.type === 'square') {
|
|
1377
|
+
// Don't insert implicit multiplication before a square bracket.
|
|
1378
|
+
// Mark it so parseRPN can decide whether it's indexing or multiplication
|
|
1379
|
+
// once resolved types are known.
|
|
1380
|
+
deferImplicitMultiply = true;
|
|
1381
|
+
}
|
|
1382
|
+
else if (Settings_1.Settings.ALLOW_IMPLICIT_MULTIPLICATION) {
|
|
1383
|
+
if (bracket.type === 'parenthesis' && lastToken.type === Token_1.Token.VARIABLE) {
|
|
1384
|
+
// Preserve the multiplication fallback and its precedence, but remember that
|
|
1385
|
+
// this name may become a registered function before deferred evaluation.
|
|
1386
|
+
deferImplicitMultiply = true;
|
|
1387
|
+
}
|
|
1388
|
+
// Push a multiplication token with position -1 to indicate that it was added
|
|
1389
|
+
if (!options.pure) {
|
|
1390
|
+
scope.push(new Token_1.Token('*', Token_1.Token.OPERATOR, -1));
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
else {
|
|
1394
|
+
throw new errors_1.UnexpectedTokenError(`Expected operator or function name but "${lastToken.value}" found!`);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
// Go down in scope to the new bracket
|
|
1398
|
+
scope = scope.addScope(bracket.type, col);
|
|
1399
|
+
if (deferImplicitMultiply) {
|
|
1400
|
+
scope.implicitMultiply = true;
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
else {
|
|
1404
|
+
// Ensure that the brackets match
|
|
1405
|
+
const bracketsMatch = scope.type === bracket.type;
|
|
1406
|
+
if (bracketsMatch && bracket.type === 'parenthesis' && scope.parent) {
|
|
1407
|
+
const remainingInput = str.slice(col).trimStart();
|
|
1408
|
+
const deferredOperator = deferredLHSOperators.find(operator => {
|
|
1409
|
+
return remainingInput.startsWith(operator);
|
|
1410
|
+
});
|
|
1411
|
+
if (deferredOperator) {
|
|
1412
|
+
const parentScope = scope.parent;
|
|
1413
|
+
let lhsIndex = parentScope.length - 2;
|
|
1414
|
+
let lhsToken = parentScope[lhsIndex];
|
|
1415
|
+
// Unknown function notation normally inserts an implicit multiplication
|
|
1416
|
+
// before the parenthesis. The following operator has asked us not to
|
|
1417
|
+
// resolve that ambiguity yet, so remove only that synthetic operator.
|
|
1418
|
+
if (Token_1.Token.isToken(lhsToken) &&
|
|
1419
|
+
lhsToken.type === Token_1.Token.OPERATOR &&
|
|
1420
|
+
lhsToken.value === '*' &&
|
|
1421
|
+
lhsToken.position === -1) {
|
|
1422
|
+
parentScope.splice(lhsIndex, 1);
|
|
1423
|
+
lhsToken = parentScope[--lhsIndex];
|
|
1424
|
+
}
|
|
1425
|
+
if (Token_1.Token.isToken(lhsToken) &&
|
|
1426
|
+
(lhsToken.type === Token_1.Token.VARIABLE || lhsToken.type === Token_1.Token.FUNCTION)) {
|
|
1427
|
+
lhsToken.type = Token_1.Token.FUNCTION;
|
|
1428
|
+
scope.deferLHSResolution = true;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
// We're done so we can go up in scope to the parent scope
|
|
1433
|
+
scope = scope.upperScope();
|
|
1434
|
+
// If there's not scope or the types don't match then we have a mismatched bracket
|
|
1435
|
+
if (scope === undefined || !bracketsMatch) {
|
|
1436
|
+
throw new errors_1.UnexpectedTokenError(`Missing opening bracket for "${tokenBuffer.last()}":${col}`);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
// Discard the bracket since it's no longer needed
|
|
1440
|
+
tokenBuffer.chars.pop();
|
|
1441
|
+
}
|
|
1442
|
+
// Handle single variables and brackets since we don't have compound brackets nor will we support them.
|
|
1443
|
+
// If the type has changed but doesn't equal UNDEFINED, then finalize it
|
|
1444
|
+
if (tokenBuffer.tokenType !== Token_1.Token.UNDEFINED && tokenBuffer.tokenType !== charType) {
|
|
1445
|
+
// Collapse the tokenBuffer
|
|
1446
|
+
let tokenStr = tokenBuffer.chars.join(constants_1.BLANK);
|
|
1447
|
+
// If there's an alias then use that instead
|
|
1448
|
+
const alias = constants_1.ALIASES[tokenStr];
|
|
1449
|
+
if (alias) {
|
|
1450
|
+
tokenStr = alias;
|
|
1451
|
+
}
|
|
1452
|
+
// Mark the beginning of the token
|
|
1453
|
+
const SOT = col - tokenBuffer.chars.length;
|
|
1454
|
+
// In order to support keyword operators, we can check here
|
|
1455
|
+
if (operators.includes(tokenStr) && tokenBuffer.tokenType !== Token_1.Token.PREFIX) {
|
|
1456
|
+
tokenBuffer.tokenType = Token_1.Token.OPERATOR;
|
|
1457
|
+
}
|
|
1458
|
+
// type prefix operators and compound operators
|
|
1459
|
+
else if (tokenBuffer.is(Token_1.Token.OPERATOR) && tokenBuffer.chars.length > 1) {
|
|
1460
|
+
let compoundOperatorStr = tokenBuffer.chars[0];
|
|
1461
|
+
// Get the largest possible operator chunks
|
|
1462
|
+
for (let i = 1; i < tokenBuffer.chars.length + 1; i++) {
|
|
1463
|
+
// Check if the current combination is an operator
|
|
1464
|
+
const tempStr = compoundOperatorStr + tokenBuffer.chars[i];
|
|
1465
|
+
// If it is then just make that the chunk
|
|
1466
|
+
if (tempStr in this.operators) {
|
|
1467
|
+
compoundOperatorStr = tempStr;
|
|
1468
|
+
}
|
|
1469
|
+
else {
|
|
1470
|
+
// Place it on the tokens stack
|
|
1471
|
+
scope.push(new Token_1.Token(compoundOperatorStr, Token_1.Token.OPERATOR, SOT + i - compoundOperatorStr.length));
|
|
1472
|
+
// Reset the compound operator and move forward one position
|
|
1473
|
+
compoundOperatorStr = tokenBuffer.chars[i];
|
|
1474
|
+
// Clear the token string
|
|
1475
|
+
tokenStr = constants_1.BLANK;
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
// Reset the buffer
|
|
1479
|
+
tokenBuffer.chars = [];
|
|
1480
|
+
tokenBuffer.scientificNumber = false;
|
|
1481
|
+
}
|
|
1482
|
+
// Ignore blanks and spaces
|
|
1483
|
+
// Don't add the token if it's been cleared
|
|
1484
|
+
const isWhiteSpace = tokenStr === constants_1.SPACE;
|
|
1485
|
+
if (isWhiteSpace && options.keepWhiteSpace) {
|
|
1486
|
+
scope.push(new Token_1.Token(tokenStr, Token_1.Token.SPACE, SOT));
|
|
1487
|
+
}
|
|
1488
|
+
else if (!(tokenStr === constants_1.BLANK || isWhiteSpace)) {
|
|
1489
|
+
if (tokenBuffer.scientificNumber) {
|
|
1490
|
+
tokenStr = (0, string_1.scientificToDecimal)(tokenStr);
|
|
1491
|
+
tokenBuffer.scientificNumber = false;
|
|
1492
|
+
}
|
|
1493
|
+
const lastToken = scope[scope.length - 1];
|
|
1494
|
+
const lastOperator = Token_1.Token.isToken(lastToken) && lastToken.type === Token_1.Token.OPERATOR
|
|
1495
|
+
? this.operators[lastToken.value]
|
|
1496
|
+
: undefined;
|
|
1497
|
+
// Implicit multiplication
|
|
1498
|
+
if (lastToken &&
|
|
1499
|
+
((tokenBuffer.is(Token_1.Token.VARIABLE) &&
|
|
1500
|
+
!(lastToken.type === Token_1.Token.OPERATOR || lastToken.type === Token_1.Token.PREFIX)) ||
|
|
1501
|
+
((tokenBuffer.is(Token_1.Token.VARIABLE) || tokenBuffer.is(Token_1.Token.NUMBER)) &&
|
|
1502
|
+
lastOperator?.isPostfix))) {
|
|
1503
|
+
if (Settings_1.Settings.ALLOW_IMPLICIT_MULTIPLICATION) {
|
|
1504
|
+
if (!options.pure) {
|
|
1505
|
+
// Push a multiplication token with position -1 to indicate that it was added
|
|
1506
|
+
scope.push(new Token_1.Token('*', Token_1.Token.OPERATOR, -1));
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
else {
|
|
1510
|
+
throw new errors_1.UnexpectedTokenError(`Expected operator or function name but "${lastToken.value}" found!`);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
// Allows for each single letter in a variable to be treated like an individual variable
|
|
1514
|
+
if (Settings_1.Settings.USE_SINGLE_LETTER_VARIABLES && !(tokenStr in dispatch_1.mathFunctionRegistry)) {
|
|
1515
|
+
// Insert a new multiplication token between them and add them to the scope
|
|
1516
|
+
scope.push(...tokenStr
|
|
1517
|
+
.split('')
|
|
1518
|
+
.map(x => {
|
|
1519
|
+
return new Token_1.Token(x, tokenBuffer.tokenType, -1);
|
|
1520
|
+
// Add the multiplication operator between them
|
|
1521
|
+
})
|
|
1522
|
+
.flatMap(x => [new Token_1.Token('*', Token_1.Token.OPERATOR, -1), x])
|
|
1523
|
+
.slice(1));
|
|
1524
|
+
}
|
|
1525
|
+
else {
|
|
1526
|
+
// We can collapse the buffer
|
|
1527
|
+
scope.push(new Token_1.Token(tokenStr, tokenBuffer.tokenType, SOT));
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
// Clear the buffer and place last character on stack
|
|
1531
|
+
tokenBuffer.chars = [ch];
|
|
1532
|
+
}
|
|
1533
|
+
else {
|
|
1534
|
+
tokenBuffer.chars.push(ch);
|
|
1535
|
+
}
|
|
1536
|
+
tokenBuffer.tokenType = charType;
|
|
1537
|
+
}
|
|
1538
|
+
// If the last item in the tokens array is a scope and it has a parent then it's missing a closing bracket
|
|
1539
|
+
const lastItem = tokens[tokens.length - 1];
|
|
1540
|
+
if (Scope_1.Scope.isScope(lastItem) && lastItem.isOpen) {
|
|
1541
|
+
throw new errors_1.UnexpectedTokenError(`Missing closing bracket for "${str.charAt(lastItem.column - 1)}":${lastItem.column}`);
|
|
1542
|
+
}
|
|
1543
|
+
return tokens;
|
|
1544
|
+
}
|
|
1545
|
+
/**
|
|
1546
|
+
* Converts a tokenized scope to Nerdamer's nested Reverse Polish Notation form.
|
|
1547
|
+
*
|
|
1548
|
+
* @remarks
|
|
1549
|
+
* The conversion uses the Shunting Yard algorithm to apply precedence,
|
|
1550
|
+
* associativity, prefix/postfix rules, and bracket scoping. Nested scopes remain
|
|
1551
|
+
* nested; each child scope is recursively converted rather than flattening the
|
|
1552
|
+
* entire expression into one token array. Deferred adjacency markers are retained
|
|
1553
|
+
* on child scopes so square-bracket indexing can be decided by {@link parseRPN} and
|
|
1554
|
+
* newly registered function names can be recognized before parenthesis adjacency is
|
|
1555
|
+
* committed to implicit multiplication.
|
|
1556
|
+
*
|
|
1557
|
+
* The returned `Scope` containers are new, but token objects are reused. Prefix
|
|
1558
|
+
* detection can retag those shared `Token` objects while converting the input, so
|
|
1559
|
+
* callers should not treat the original token tree as an immutable snapshot after
|
|
1560
|
+
* this method has run.
|
|
1561
|
+
*
|
|
1562
|
+
* @param scope - Tokenized scope, normally produced by {@link tokenize}.
|
|
1563
|
+
* @returns A new scope hierarchy containing the tokens in evaluation order.
|
|
1564
|
+
* @throws {@link ParserError} When an invalid operator sequence requires a prefix operator.
|
|
1565
|
+
*
|
|
1566
|
+
* @example
|
|
1567
|
+
* ```ts
|
|
1568
|
+
* const tokens = Parser.tokenize('2 + 3 * x');
|
|
1569
|
+
* const rpn = Parser.toRPN(tokens);
|
|
1570
|
+
*
|
|
1571
|
+
* rpn.text(); // "2 3 x * +"
|
|
1572
|
+
* ```
|
|
1573
|
+
*/
|
|
1574
|
+
toRPN(scope) {
|
|
1575
|
+
// The output to be returned
|
|
1576
|
+
const output = new Scope_1.Scope(scope.type, scope.depth);
|
|
1577
|
+
// The operator stack
|
|
1578
|
+
const stack = [];
|
|
1579
|
+
/**
|
|
1580
|
+
* Return the last bracket on the stack. Must be guarded against an empty stack.
|
|
1581
|
+
* @returns Bracket
|
|
1582
|
+
*/
|
|
1583
|
+
const peek = () => {
|
|
1584
|
+
const lastToken = stack[stack.length - 1];
|
|
1585
|
+
return lastToken;
|
|
1586
|
+
};
|
|
1587
|
+
const peekOperator = () => {
|
|
1588
|
+
const operatorToken = peek();
|
|
1589
|
+
if (operatorToken) {
|
|
1590
|
+
return operatorToken.resolvedOperator ?? this.operators[operatorToken.value];
|
|
1591
|
+
}
|
|
1592
|
+
};
|
|
1593
|
+
// Begin Shunting Yard
|
|
1594
|
+
for (let i = 0; i < scope.length; i++) {
|
|
1595
|
+
// Read the token
|
|
1596
|
+
let token = scope[i];
|
|
1597
|
+
if (Scope_1.Scope.isScope(token)) {
|
|
1598
|
+
const previousToken = output[output.length - 1];
|
|
1599
|
+
const preserveArguments = Token_1.Token.isToken(previousToken) &&
|
|
1600
|
+
previousToken.type === Token_1.Token.FUNCTION &&
|
|
1601
|
+
dispatch_1.mathFunctionRegistry[previousToken.value]?.deferArguments;
|
|
1602
|
+
if (preserveArguments) {
|
|
1603
|
+
// Keep deferred function arguments tokenized so the registered function can
|
|
1604
|
+
// control when and how often they are evaluated.
|
|
1605
|
+
output.push(token);
|
|
1606
|
+
}
|
|
1607
|
+
else {
|
|
1608
|
+
// Send the contents of the scope to be put into RPN
|
|
1609
|
+
const rpnScope = this.toRPN(token);
|
|
1610
|
+
// Preserve parser decisions made while tokenizing the original scope.
|
|
1611
|
+
rpnScope.implicitMultiply = token.implicitMultiply;
|
|
1612
|
+
rpnScope.deferLHSResolution = token.deferLHSResolution;
|
|
1613
|
+
rpnScope.deferRHSResolution = token.deferRHSResolution;
|
|
1614
|
+
output.push(rpnScope);
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
else {
|
|
1618
|
+
const implicitOperator = scope[i + 1];
|
|
1619
|
+
const implicitScope = scope[i + 2];
|
|
1620
|
+
if (token.type === Token_1.Token.VARIABLE &&
|
|
1621
|
+
Token_1.Token.isToken(implicitOperator) &&
|
|
1622
|
+
implicitOperator.type === Token_1.Token.OPERATOR &&
|
|
1623
|
+
implicitOperator.value === '*' &&
|
|
1624
|
+
implicitOperator.position === -1 &&
|
|
1625
|
+
Scope_1.Scope.isScope(implicitScope) &&
|
|
1626
|
+
implicitScope.type === 'parenthesis' &&
|
|
1627
|
+
implicitScope.implicitMultiply &&
|
|
1628
|
+
token.value in dispatch_1.mathFunctionRegistry) {
|
|
1629
|
+
// The source was tokenized before this name was registered. Reclassify only
|
|
1630
|
+
// for this conversion so reusable deferred token scopes remain unchanged.
|
|
1631
|
+
token = new Token_1.Token(token.value, Token_1.Token.FUNCTION, token.position);
|
|
1632
|
+
i++;
|
|
1633
|
+
}
|
|
1634
|
+
// If it's a number then there's nothing else to evaluate so it can go straight to output
|
|
1635
|
+
switch (token.type) {
|
|
1636
|
+
case Token_1.Token.OPERATOR: {
|
|
1637
|
+
// Get the operator
|
|
1638
|
+
let operator = token.resolvedOperator ?? this.operators[token.value];
|
|
1639
|
+
// Some operators can be either infix or postfix. Resolve that role from
|
|
1640
|
+
// syntax before prefix detection so the rest of Shunting Yard only sees
|
|
1641
|
+
// one unambiguous operator definition.
|
|
1642
|
+
if (operator.postfixVariant) {
|
|
1643
|
+
let nextIndex = i + 1;
|
|
1644
|
+
let nextToken = scope[nextIndex];
|
|
1645
|
+
while (Token_1.Token.isToken(nextToken) &&
|
|
1646
|
+
(nextToken.type === Token_1.Token.SPACE ||
|
|
1647
|
+
nextToken.type === Token_1.Token.UNDEFINED)) {
|
|
1648
|
+
nextToken = scope[++nextIndex];
|
|
1649
|
+
}
|
|
1650
|
+
if (nextToken === undefined ||
|
|
1651
|
+
(Token_1.Token.isToken(nextToken) &&
|
|
1652
|
+
(nextToken.type === Token_1.Token.OPERATOR ||
|
|
1653
|
+
nextToken.type === Token_1.Token.PREFIX))) {
|
|
1654
|
+
operator = operator.postfixVariant;
|
|
1655
|
+
token.resolvedOperator = operator;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
// Get the last operator on the stack
|
|
1659
|
+
let lastOperator = peekOperator();
|
|
1660
|
+
/******************** PREFIX OPERATORS ********************/
|
|
1661
|
+
// We can now traverse the tokens since we've encountered an operator. Any remaining operators should be prefix operators
|
|
1662
|
+
// If not then complain.
|
|
1663
|
+
for (let j = i + 1; j < scope.length; j++) {
|
|
1664
|
+
const prefixOperator = scope[j];
|
|
1665
|
+
// If a non-operator token is encountered, then we're done. Or if we're at a postfix operator then the next operator may be a valid one and this assumption no longer holds true
|
|
1666
|
+
if (!prefixOperator ||
|
|
1667
|
+
prefixOperator.type !== Token_1.Token.OPERATOR ||
|
|
1668
|
+
operator.isPostfix) {
|
|
1669
|
+
break;
|
|
1670
|
+
}
|
|
1671
|
+
// If the encountered operator is not a prefix then complain
|
|
1672
|
+
if (!this.operators[prefixOperator.value].isPrefix) {
|
|
1673
|
+
throw new errors_1.ParserError(`Prefix operator expected but ${prefixOperator.value} encountered.`);
|
|
1674
|
+
}
|
|
1675
|
+
// We mark it so the next time it's encountered, it's placed to output immediately
|
|
1676
|
+
prefixOperator.type = Token_1.Token.PREFIX;
|
|
1677
|
+
}
|
|
1678
|
+
// If we're at the beginning then it's a prefix operator so put it to output and break
|
|
1679
|
+
// If the last item on output is a prefix then this has to be a prefix
|
|
1680
|
+
const last = output[output.length - 1];
|
|
1681
|
+
if (operator.isPrefix &&
|
|
1682
|
+
(output.length === 0 ||
|
|
1683
|
+
(output.length > 0 && last && last.type === Token_1.Token.PREFIX))) {
|
|
1684
|
+
token.type = Token_1.Token.PREFIX;
|
|
1685
|
+
stack.push(token);
|
|
1686
|
+
break;
|
|
1687
|
+
}
|
|
1688
|
+
const greaterPrecedence = () => {
|
|
1689
|
+
let retval = false;
|
|
1690
|
+
if (lastOperator) {
|
|
1691
|
+
retval = shouldPopOperator(lastOperator, operator);
|
|
1692
|
+
// Prefixes have to happen first. For instance x^-1. The minus comes first but not for -x!.
|
|
1693
|
+
const lastOnStack = stack[stack.length - 1];
|
|
1694
|
+
if (!retval &&
|
|
1695
|
+
lastOnStack &&
|
|
1696
|
+
lastOnStack.type === Token_1.Token.PREFIX &&
|
|
1697
|
+
operator.leftAssoc) {
|
|
1698
|
+
retval = true;
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
return retval;
|
|
1702
|
+
};
|
|
1703
|
+
while (greaterPrecedence()) {
|
|
1704
|
+
// Move it to the output
|
|
1705
|
+
output.push(stack.pop());
|
|
1706
|
+
// Get the next operator on the stack
|
|
1707
|
+
lastOperator = peekOperator();
|
|
1708
|
+
}
|
|
1709
|
+
if (operator.deferLHSResolution) {
|
|
1710
|
+
const lhs = output[output.length - 1];
|
|
1711
|
+
if (Token_1.Token.isToken(lhs) && lhs.type === Token_1.Token.VARIABLE) {
|
|
1712
|
+
// Preserve a plain assignment target as its own scope so it can bypass
|
|
1713
|
+
// known-value substitution without changing evaluation of the right side.
|
|
1714
|
+
const deferredLHS = new Scope_1.Scope('', lhs.position);
|
|
1715
|
+
deferredLHS.deferLHSResolution = true;
|
|
1716
|
+
deferredLHS.push(output.pop());
|
|
1717
|
+
output.push(deferredLHS);
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
if (operator.deferRHSResolution) {
|
|
1721
|
+
const deferredRHS = new Scope_1.Scope('', token.position);
|
|
1722
|
+
let rhsEnd = i + 1;
|
|
1723
|
+
for (; rhsEnd < scope.length; rhsEnd++) {
|
|
1724
|
+
const rhsToken = scope[rhsEnd];
|
|
1725
|
+
if (Token_1.Token.isToken(rhsToken) && rhsToken.type === Token_1.Token.OPERATOR) {
|
|
1726
|
+
const rhsOperator = rhsToken.resolvedOperator ?? this.operators[rhsToken.value];
|
|
1727
|
+
if (rhsOperator &&
|
|
1728
|
+
!shouldPopOperator(rhsOperator, operator)) {
|
|
1729
|
+
break;
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
deferredRHS.push(rhsToken);
|
|
1733
|
+
}
|
|
1734
|
+
if (deferredRHS.length === 0) {
|
|
1735
|
+
throw new errors_1.ParserError((0, errors_1.message)('malformedExpression'));
|
|
1736
|
+
}
|
|
1737
|
+
const deferredRPN = this.toRPN(deferredRHS);
|
|
1738
|
+
deferredRPN.deferRHSResolution = true;
|
|
1739
|
+
output.push(deferredRPN);
|
|
1740
|
+
i = rhsEnd - 1;
|
|
1741
|
+
}
|
|
1742
|
+
// Put the last operator on the stack
|
|
1743
|
+
stack.push(token);
|
|
1744
|
+
break;
|
|
1745
|
+
}
|
|
1746
|
+
case Token_1.Token.BRACKET: {
|
|
1747
|
+
// Add brackets to output right away since they have the highest precedence
|
|
1748
|
+
const bracket = common_1.brackets[String(token.value)];
|
|
1749
|
+
if (bracket.isOpen) {
|
|
1750
|
+
stack.push(token);
|
|
1751
|
+
}
|
|
1752
|
+
else {
|
|
1753
|
+
output.push(token);
|
|
1754
|
+
const lastToken = peek();
|
|
1755
|
+
while (lastToken) {
|
|
1756
|
+
const popped = stack.pop();
|
|
1757
|
+
output.push(popped);
|
|
1758
|
+
// Exit if it's a matching bracket
|
|
1759
|
+
// The resulting RPN will be a reverse bracket. Nonetheless, it's a good way to know that every operation is
|
|
1760
|
+
// within a new scope.
|
|
1761
|
+
if (popped.type === Token_1.Token.BRACKET) {
|
|
1762
|
+
if (common_1.brackets[token.value].matches === popped.value) {
|
|
1763
|
+
// Done
|
|
1764
|
+
break;
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
break;
|
|
1770
|
+
}
|
|
1771
|
+
case Token_1.Token.PREFIX: {
|
|
1772
|
+
// The prefix belongs to the next token so it goes to output right away
|
|
1773
|
+
stack.push(token);
|
|
1774
|
+
break;
|
|
1775
|
+
}
|
|
1776
|
+
case Token_1.Token.UNDEFINED:
|
|
1777
|
+
case Token_1.Token.SPACE: {
|
|
1778
|
+
// Do nothing and discard
|
|
1779
|
+
break;
|
|
1780
|
+
}
|
|
1781
|
+
default: {
|
|
1782
|
+
output.push(token);
|
|
1783
|
+
break;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
// Clear the stack
|
|
1789
|
+
while (stack.length) {
|
|
1790
|
+
const token = stack.pop();
|
|
1791
|
+
output.push(token);
|
|
1792
|
+
}
|
|
1793
|
+
return output;
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
/**
|
|
1797
|
+
* The shared parser instance used by Nerdamer's ordinary parsing APIs.
|
|
1798
|
+
*
|
|
1799
|
+
* @remarks
|
|
1800
|
+
* {@link Parser.parse} is the primary notation-to-object entry point. State-changing
|
|
1801
|
+
* methods on this object can affect the rest of Nerdamer because settings, constants,
|
|
1802
|
+
* numeric precision, operator metadata, and the imaginary-unit symbol are process-wide.
|
|
1803
|
+
* Use scoped setting changes where possible, and do not assume that {@link Parser.create}
|
|
1804
|
+
* provides an isolated parser environment.
|
|
1805
|
+
*
|
|
1806
|
+
* @example
|
|
1807
|
+
* ```ts
|
|
1808
|
+
* Parser.parse('x^2 + 1').text(); // "1+x^2"
|
|
1809
|
+
* Parser.evaluate('2^10').text(); // "1024"
|
|
1810
|
+
* ```
|
|
1811
|
+
*/
|
|
1812
|
+
exports.Parser = new ExpressionParser();
|