Contributors · Internals

Parser

The notation front end has a clean three-stage shape: tokenize the source, apply the Shunting Yard algorithm to produce reverse Polish notation, then evaluate that RPN into a parser entity.

The parser pipelineParser.parse() runs the full path
flowchart LR A["source string"] --> B["normalize bracketless functions"] B --> C["tokenize"] C --> D["nested token scopes"] D --> E["toRPN"] E --> F["RPN scope"] F --> G["parseRPN"] G --> H["operator routing"] G --> I["function dispatch"] H --> J{"ParserEntity"} I --> J

A concrete example

The parser tests use x+3*y as a simple RPN example. Multiplication has higher precedence, so the infix source becomes x 3 y * + before evaluation.

1 · sourcex + 3*y

Human-facing notation.

2 · RPNx 3 y * +

Operator precedence is now encoded by order rather than parentheses.

3 · resultExpression

The RPN stack is evaluated by parser operations and function dispatch.

Stage 1: tokenization

tokenize() recognizes numbers, variables, operators, functions, brackets, collections, and other parser syntax. Bracketed regions become nested scopes, so later stages can work with structure instead of repeatedly rescanning raw characters.

Whitespace is syntax-sensitive only where the parser says it is. Newlines, tabs, and returns are part of the parser's whitespace handling; scripting statements still need their supported separators when multiple statements share one input.

Stage 2: Shunting Yard and RPN

toRPN() uses operator precedence and associativity to reorder a token scope. Parentheses and nested scopes no longer need to drive precedence once the sequence has been converted.

Parser.toRPN(Parser.tokenize('x+3*y'))
// x 3 y * +

This exact transformation is protected by the tokenizer/parser specs.

Stage 3: evaluate the RPN stack

parseRPN() consumes the RPN representation and routes operations to the core arithmetic handlers or registered functions. The stack can produce scalar expressions as well as equations and structured parser values.

Two routes out of the RPN evaluatorOperators and named functions meet again as parser entities
flowchart TD A["RPN item"] --> B{"kind"} B -->|operator| C["route operator"] C --> D["add / subtract / multiply / divide / power / compare / ..."] B -->|function| E["mathFunctionRegistry"] E --> F["callFunction"] D --> G["ParserEntity"] F --> G

Function dispatch is data-driven

src/core/dispatch.ts maps parser-visible names to implementations and metadata. That metadata lets the parser know argument counts, whether equations are accepted in particular positions, whether structured values distribute element-wise, whether argument evaluation is deferred, and whether the entry belongs to ordinary notation or Nerdamer Scripting.

ExampleArgumentsElement-wiseDeferred argsUse
factor1yesnoNerdamer Notation
diff1–3yesnoNerdamer Notation
matrix1+nonoNerdamer Notation
if2–3noyesNerdamer Scripting

Why deferred arguments exist

Some scripting constructs must decide whether or when to evaluate their arguments. An if cannot eagerly evaluate both branches, and loops need to retain executable parser work rather than only final values. The dispatch metadata marks those constructs so the parser can preserve the argument RPN for later evaluation.

Parser state is shared more widely than one object

Parser.create() returns another parser object with its own known-value table, but it is not a fully isolated parser environment. Settings, constants, operators, precision, and the imaginary-unit symbol are shared at package level. That distinction matters when debugging tests that mutate parser configuration.

Useful places to look

Parser.ts

Tokenization, RPN conversion, parsing, values, operators, and parser configuration.

open source

operations/

Core parser arithmetic and comparison routes such as addition, multiplication, division, and powers.

open source

dispatch.ts

The registry connecting parser names to the actual algebra, calculus, math, solver, and scripting implementations.

open source