polish-engine
v2.0.1
Published
A typed expression engine using postfix notation, normalized results, complex numbers, and calculation steps.
Downloads
225
Maintainers
Readme
Polish Engine
Polish Engine is a small TypeScript library that parses infix mathematical expressions, converts them to postfix notation (RPN), and evaluates them. It supports variables, normalized real and complex results, and optional calculation steps suitable for educational tools, calculators, and math-oriented interfaces.
It is an expression evaluator, not a computer algebra system: it evaluates numeric expressions but does not perform symbolic simplification, differentiation, integration, or equation solving.
Installation
npm install polish-enginePolish Engine is published as an ES module and includes TypeScript declarations.
Quick start
import { polishEngine } from "polish-engine";
const engine = new polishEngine();
const output = engine.evaluate("2 + 3 * 4", { steps: false });
console.log(output.display); // "14"Evaluation modes
evaluate(expression, options) uses normalized output and includes calculation steps by default.
| Options | Return value |
| --- | --- |
| omitted, or { steps: true, normalize: true } | { result: NormalizedValue, steps: NormalizedStep[] } |
| { steps: false } | NormalizedValue |
| { normalize: false } | { result: RawValue, steps: Step[] } |
| { steps: false, normalize: false } | RawValue |
Basic evaluation
engine.evaluate("(2 + 3) * 4", { steps: false });
// { type: "real", value: 20, display: "20" }Variables
Pass variables inside the options object:
engine.evaluate("2x + 1", {
variables: { x: 3 },
steps: false
});
// { type: "real", value: 7, display: "7" }The v1-compatible variables object is also supported. It uses the v2 defaults, including steps and normalization:
engine.evaluate("2x + 1", { x: 3 });
// { result: { type: "real", value: 7, display: "7" }, steps: [...] }Calculation steps
With the default options, the result includes the operations performed by the RPN evaluator:
const output = engine.evaluate("2 + 3");
console.log(output.result.display); // "5"
console.log(output.steps[0]);
// {
// type: "Operator",
// name: "+",
// operands: [...],
// result: { type: "real", value: 5, display: "5" },
// stackBefore: [...],
// stackAfter: [...]
// }Each step records the operation or function name, consumed operands, result, and stack state before and after the operation.
Normalized results
Normalized values use a discriminated union:
type NormalizedValue =
| { type: "real"; value: number; display: string }
| { type: "complex"; re: number; im: number; display: string };Use display as the recommended presentation value for user interfaces. Use the numeric fields when the consumer needs to perform additional calculations.
Complex results
Operations supported by complex.js, such as the square root of a negative number, can return a normalized complex value:
engine.evaluate("sqrt(-4)", { steps: false });
// { type: "complex", re: 0, im: 2, display: "2i" }Most trigonometric and logarithmic functions currently evaluate their argument in the real domain. Complex support is therefore operation-dependent rather than a promise that every function accepts complex inputs.
Raw results
Disable normalization to receive numbers or complex.js instances directly:
import Complex from "complex.js";
const value = engine.evaluate("sqrt(-4)", {
steps: false,
normalize: false
});
if (value instanceof Complex) {
console.log(value.re, value.im); // 0 2
}With steps enabled and normalization disabled, values inside the calculation steps are raw as well.
API reference
polishEngine
The primary consumer API.
const engine = new polishEngine();
engine.evaluate(expression, options?);EvaluateOptions
interface EvaluateOptions {
variables?: Record<string, number>;
steps?: boolean; // default: true
normalize?: boolean; // default: true
}The generated declarations provide overloads for the four steps/normalize return modes. When either flag is a non-literal boolean, TypeScript exposes the complete EvaluateOutput union.
Exported result types
The package exports RawValue, NormalizedValue, Step, NormalizedStep, RawEvaluation, NormalizedEvaluation, EvaluationWithSteps, and EvaluateOutput.
Lower-level exports such as Tokenizer, parser, evaluator, PreprocessModule, ResultNormalizer, and EvaluateOptionsResolver are retained for v2 compatibility. New consumers should prefer the polishEngine facade unless they specifically need an individual pipeline stage.
Supported capabilities
- Infix-to-postfix conversion with operator precedence and right-associative exponentiation.
- Arithmetic operators, exponentiation, factorial, and supported comparisons.
- Parentheses and comma-separated supported functions.
- Named numeric variables and the constants
eandπ. - Implicit multiplication in supported forms such as
2xand(2 + 3)4. - Common functions implemented by the current evaluator, including trigonometric, hyperbolic, logarithmic, root, absolute-value, modulo, percentage, and degree conversions.
- Normalized real/complex presentation and optional RPN stack traces.
Error behavior
evaluate() returns a value on success and throws an Error on invalid input. It does not encode failures as result objects.
Existing public failures include:
Invalid parenthesesorUnbalanced parenthesesfor malformed grouping.Undefined variable: <name>when an expression references a missing variable.Error: insufficient operandswhen an operator cannot consume enough values.Missing argument for <function>for incomplete function calls.Unknown function: <name>orUnknown operator: <operator>when unsupported tokens reach the evaluator.Error: malformed expressionwhen evaluation does not finish with exactly one stack value.- Domain-specific errors such as attempting factorial or comparison with unsupported complex operands.
Messages are useful for diagnostics but are not exported as a formal error-code taxonomy. Consumers should catch Error and avoid depending on exact message text as a stable machine-readable contract.
try {
engine.evaluate("x + 1", { steps: false });
} catch (error) {
if (error instanceof Error) {
console.error(error.message);
}
}Architecture
Tokenizer
↓
Parser (infix → postfix/RPN)
↓
Evaluator
↓
Result normalization
↓
Optional calculation stepsThe facade preprocesses the expression, tokenizes it, converts it to postfix order, evaluates the RPN stack, and finally applies the selected raw/normalized and steps/no-steps output mode. Each stage remains independently testable.
Known limits
- Polish Engine is not a symbolic CAS.
- Variables accept real JavaScript numbers.
- Complex-number behavior varies by operation; most trigonometric and logarithmic functions use real components.
- The parser accepts the library's documented expression grammar, not arbitrary JavaScript syntax.
- Angle units are not globally configurable.
- Error messages are descriptive strings rather than stable error codes.
Development
npm ci
npm run typecheck
npm run build
npm test
npm pack --dry-runThe repository uses npm as its only package manager. CI runs the same validation before changes are merged, and npm publication is triggered only by a deliberate v* Git tag.
Version and releases
Current repository version: 2.0.1 (prepared, not yet published). Current npm release: 2.0.0.
Version 2 introduced option-driven steps and normalization while retaining the legacy variables input. See CHANGELOG.md. Git tags and npm versions are intended to match; release preparation does not publish automatically from ordinary pushes to main.
