Delphi & Free Pascal · Windows & Linux · MIT

Type a formula.
Get a number.

An expression parser that reads what you write and answers in the type you asked for. No grammar to learn, no visitor to implement, and for a single answer nothing to create or free.

program Hero;

{$APPTYPE CONSOLE}

uses
  CalcUtils;

begin
  Writeln(AsInteger('2 + 2'));
end.

That is the whole program: it prints 4. The matrix compiles and runs it, so this listing cannot drift from what works.

Sin (6 * T) 361 points, joined by chords

163callable functions, from sin to weeksbetween
4build targets, one source
3 000fuzzed formulas · zero disagreement
108×the interpreter, inside a script loop

Three layers, and you can take any of them.

each builds on the one below


LAYER I MathParser

Ask for the type you want.

One call · no configuration

Parser.AsInteger('2 ** 10')1024
Parser.AsDouble('pi / 6')0.5235988
Parser.AsBoolean('3 > 2')True
Parser.AsExtended('sqrt(2)')1.4142136
Parser.AsString('2 + 2')'4'
Parser.AsDateTime('encodedate(2026, 7, 24)')2026-07-24
P := TMathParser.Create(nil);
try
  Write(Answer(P):0:2, ' ');
finally
  P.Free;
end;
P := TJitParser.Create(nil);
try
  Writeln(Answer(P):0:2);
finally
  P.Free;
end;

Excerpt from samples/docs/swap.dpr: both halves print the same 5.00, and the matrix runs the file to prove it.

One word, and the hot path becomes machine code. Everything else - the formulas, the variables, the calls - stays exactly as it was. Whatever the compiler declines, it hands back to the interpreter without saying a word, so the answer is never fast but wrong. How it works.

Priorities are data try

Operators here are registered functions whose precedence is a value, not grammar. Flip one and the same characters parse into a different tree: raise * above / and 12 / 3 * 2 turns from 8 into 2 - the bracketed line is the parser's own decompiler reporting the tree it actually built. Coverage is the second knob: how far a raised or lowered priority reaches. Comparison ships as lower + total, which is why 1 + 2 = 3 compares the sum and answers -1, the parser's true. Switch = to local and it binds neighbours only: the same line now evaluates as 1 + (2 = 3), and the value flips to 1. The engine is the real parser, compiled to WebAssembly. Plus and minus have no knobs at all - they are how a script joins its items, not functions. Reload the page to reset.


LAYER II CrossGraph

Give it a range, and you have a picture.

Every point on this page came out of the parser - one formula, sampled across its parameter. Turning those numbers into a line is a loop you already know how to write; a ready-made component for VCL and LCL is a separate package. What matters here is that the values are right: poles left open, undefined stretches skipped, and the captions are the formulas exactly as they were parsed.

The component adds what a plot actually needs: curves sampled across threads, adaptive density, intersections, and extrema found rather than guessed, polar and cartesian on the same canvas.

Exp (Sin T) - 2 * Cos (4 * T) + Sin ((2 * T - Pi) / 24) ** 5Temple Fay, 1989 · 24π
Cos (31 * T / 30)k = 31/30 · interference
Cos (Pi * X) + Cos (3 * Pi * X) / 2 + Cos (9 * Pi * X) / 4 + Cos (27 * Pi * X) / 8continuous, nowhere smooth
Sin (12 * T)n = 12, d = 163
Exp (0.15 * T)spira mirabilis
Sin (X) * Sin (16 * X)beats · sin x · sin 16x
Abs (X - Floor (X) - 0.5) + Abs (2 * X - Floor (2 * X) - 0.5) / 2 + Abs (4 * X - Floor (4 * X) - 0.5) / 4 + Abs (8 * X - Floor (8 * X) - 0.5) / 8 + Abs (16 * X - Floor (16 * X) - 0.5) / 16 self-similar · built from floor

LAYER III GraphBuilder

And here it is, doing the job.

A panel inside Notepad++: type a formula, press build, read the answer off the canvas. Nothing above is hidden from you - the plugin is the component, and the component is the parser. It is built twice, by Delphi and by Lazarus/FPC, from one set of sources and behind one interface; the binary on the release page is the FPC one, so a free toolchain is enough to reproduce it.

It also runs right here in your browser - the same panel on the same engine, compiled to WebAssembly. What the demo computes is what the plugin computes; only the x86-64 accelerator stays native.

Live Open the panel The engine loads into your browser and computes as you type. Nothing is sent anywhere.
Formulasas many as you like, each toggled and coloured on its own
Coordinatescartesian and polar, switched without retyping
Findsintersections between curves, minima and maxima, values under the cursor
Reportroots, breaks, monotone stretches, area and mean - computed, not guessed
Bookmarksten slots for the whole state, kept between sessions
Themesfollows the editor, light and dark

Three ways in

Quick start

Y := AsDouble('sin(1) + sqrt(2)');
Writeln(Y:0:4);
P := TMathParser.Create(nil);
try
  P.AddVariable('x', X);
  for I := 0 to 100 do
  begin
    X := I / 10;
    Writeln(X:6:2, P.AsDouble('x*x - 2*x + 1'):12:4);
  end;
finally
  P.Free;
end;
Jit := TJitParser.Create(nil);
try
  Jit.AddVariable('x', X);
  Writeln(Jit.AsDouble('x*2 + 1'):0:4);
finally
  Jit.Free;
end;

Releases

newest first

v1.1.0 10 August 2026

Thirty-two bits, a package that asks for nothing, and a defect the accelerator hid

Added

  • Thirty-two bits, on both compilers. Windows i386 joins win64 and linux64, and the whole battery runs there: sixteen test programs on Free Pascal 3.2.2, the documentation samples, the packages themselves. It is a separate installation of the compiler rather than a switch, because Free Pascal will not target i386 from a host whose Extended is a Double - and on Win64 it is.
  • The Lazarus packages no longer ask for the LCL. They are built with NOFORMS and NOGRAPHICS, so a console program links against them with nothing else in its uses clause - no Interfaces, no widgetset. Delphi is untouched by this: it builds from the sources, where neither define is set. Two features step aside for it, and the README says which and how to get them back.
  • Project files for the eight documentation samples. Open any of them in Lazarus and build - that is the whole recipe. They were written earlier but never reached a release: the slicer did not carry the extension, so the files existed and travelled nowhere.

Fixed

  • The accelerator got the order wrong when a function call stood on the right of a division. Reading the right operand consumed the rest of the term instead of one step, so 6 / cos(x) / 16 was evaluated as 6 / (cos(x) / 16), and x / sqr(y) * y as x / (sqr(y) * y). Only a call was affected - with a variable or a constant there the fold stayed left to right, which is why it hid. On x86-64 the emitter takes such formulas and the emitter is right, so the wrong answers surfaced only where there is no emitter. Checked by comparing three thousand random formulas against the interpreter on every target: zero disagreements.
  • The differential check that should have caught it was comparing nothing. It skipped every formula the accelerator declined, and off x86-64 that is every formula there is - the check reported three thousand compared and zero disagreements while comparing none of them. It now reads the level off the interpreter counter, and the floor it guards is a real number again.
  • The value record was a different size on 32 bits: twenty bytes instead of twenty-four, with the payload four bytes in rather than eight. The compiled script format carries that record verbatim, so a script built on 64 bits would not have loaded on 32. The directive that was supposed to keep the layout identical sets a limit on alignment, not a size, and on i386 nothing in the record asked for eight. It is now padded out by hand, and the layout is the same everywhere.
  • The package used to hand a unit of its own to anyone who installed it, under a name the system already uses. On Windows Messages comes from the runtime, and ours stood in front of it; the LCL does not survive that, and a project that used both stopped with a message naming a unit it could see. The stand-in is now added only where the system has no such unit at all.
  • Installing the accelerator package rebuilt forty-one units of the parser into a second directory of its own. The two sets of compiled units then disagreed, and a sample that used both stopped on a unit that was lying right there. The accelerator now uses what the parser package built, as it was always meant to: six units instead of forty-seven.
v1.0.8 7 August 2026

Three ways into an evaluation, and the mask now covers all of them

Fixed

  • Compiled code ran without the parser's exception mask. Compile a script with CompileScript and run the compiled object - which is what the accelerator documentation recommends for evaluating from several threads - and division by zero raised where the library promises infinity. Two of the three ways into an evaluation had been covered in 1.0.7; this was the third, and the one the documentation points at
  • A parser evaluated from inside another parser ignored its own ExceptionMask. The test for "is this the outermost evaluation" asked whether any frame existed in the thread rather than whether the mask differed from its own, so a nested parser silently inherited the mask of whoever called it
  • Arming a loop guard inside another one switched off the outer cancellation. A nested ArmLoopGuard without a flag of its own wrote nil over the flag that was there, so an owner asking the work to stop went unheard for the whole of the inner run. Budgets may be replaced by an inner run; cancellation may not
  • ExceptionMask was documented as yours to narrow and declared protected, which put it out of reach of the code that was supposed to narrow it. It is public now
  • Looking a name up in the parser tables converted the string to lower case twice per lookup, once for the hash and once for the comparison. Same string, same result, two trips to the memory manager. Deriv went from fourteen allocations per call to eleven and from 4.05 to 3.40 microseconds
v1.0.7 7 August 2026

Three pieces of thread state that belonged to somebody else

Added

  • ArmLoopGuard and DisarmLoopGuard arm the loop guard in a pair, and disarming puts back whatever was there before. The guard lives in thread variables, but a run does not: a budget that ran out is recorded as a negative number and used to outlive the run that spent it. Whatever came next in that thread inherited the refusal - a different parser, a later button press, code that never armed a guard at all - and was stopped on an honest ten turn loop. Arming nests too, so a formula that calls Parse may set a budget of its own

Fixed

  • The floating point exception mask was installed by the parser constructor, so it belonged to the thread that happened to create the object. Evaluate on a shared parser from a worker thread - the arrangement the plotting component uses - and division by zero raised EZeroDivide instead of answering infinity. In the other direction, a living parser held the mask for the whole program, and neighbouring code in the same thread quietly stopped getting its own exceptions. An evaluation now installs the mask and hands the caller's back, and the accelerator does the same around machine code. If you share one parser between threads, or if your program narrows the mask for its own arithmetic, this is the release that makes the documented behaviour true for you
  • The lock around Deriv and Parse was one lock for every parser in the process. Four threads with four unrelated parsers queued up behind each other on any formula containing a derivative. It is now a lock per parser, and Parse holds it only while it compiles: running the compiled script under the lock meant holding it across arbitrary user code, which is how deadlocks are made
  • The plotting engine has never built on Free Pascal 3.2.2 and cannot: a geometry dependency sorts points with an anonymous comparer, and function references arrived in 3.3.1. The parser next door does build with 3.2.2. Both facts are now stated - in the crossgraph README, and by the Linux build script, which says so instead of stopping with a syntax error in the middle of a file you did not write
v1.0.6 7 August 2026

A hidden button is hidden for real

Fixed

  • The two buttons that send the report into the editor still showed up in the live demo, where there is no editor to send anything to. 1.0.5 taught the page to ask the host first, and the host answers correctly - but the buttons were being hidden with the hidden attribute alone, and that attribute is only a display:none from the browser stylesheet. The panel sets display:grid on its buttons, which wins. Measured on the published demo: hidden was true and the button was still thirty pixels wide
  • And underneath that, a second one it had been hiding. The panel asks the host whether there is an editor, and the Lazarus host answered in the same reply it uses to hand back the previous session - so whenever the panel opened with work in it, which is nearly always, the answer never arrived at all. It was invisible while the buttons were showing anyway. The greeting is now its own message and goes out first
v1.0.5 7 August 2026

The library builds with Free Pascal 3.2.2 again, and the Linux matrix says so

Added

  • Free Pascal 3.2.2 builds the library again. Function references arrived in 3.3.1, so on 3.2.2 the iterator callbacks are method pointers instead - you pass a method where you would otherwise pass an anonymous function, and nothing else changes
  • Nine functions 3.2.2 lacks in its Math unit - ArcCot, ArcCotH, ArcCsc, ArcCscH, ArcSec, ArcSecH, CotH, CscH, SecH - travel with the library, taken verbatim from the 3.3.1 runtime so the values agree to the last bit
  • MathFamilyTest guards that whole family by contract rather than by a table of numbers: a reciprocal multiplied by its base is one, an inverse returns the argument of the direct function, and ArcCotan answers in the branch it promises
  • The README says what the matrices run, including which two units ask for the LCL and how to switch them off

Fixed

  • Two accelerator tests and the thread-safety sample died on Linux before reaching their first line: on Unix the thread driver has to be the FIRST unit, and Classes standing ahead of it was enough to break that
  • A test compared a bound against Double(High(NativeInt)), which reinterprets the bits rather than converting the value - 0x7FFFFFFFFFFFFFFF read as a number is NaN. The comparison was silently against garbage wherever the compiler took the cast literally
  • The accelerator now says why it declined machine code even when the interpreter picked the work up, so the contract about wide Extended can be checked at all
  • The Linux test script looks for the widgetset folder instead of naming one, so a Lazarus built with gtk2 no longer reports a missing Interfaces unit
v1.0.4 8 August 2026

The plugin reads the formula under the mouse, and sends the report back into the editor

Added

  • The build that ships - the Lazarus one - picks the formula up from the editor: point at a line and the curve appears, select an expression and the selection wins over the line. Only the Delphi build did that before, and the Delphi build is not what ships
  • The report travels back the other way: one button opens it in a new tab, another drops it at the caret
  • It leaves as Markdown with the curve embedded as SVG - text, so it survives in a text editor, and still a drawn curve wherever Markdown is rendered
  • The panel keeps one slot for whatever the editor offers, so pointing around a file does not fill the list with formulas nobody asked for
  • The line under the cursor is compiled by a parser the plugin keeps for that alone, in the thread the editor calls from: compiling on the parser that is drawing the graph is outside the documented thread-safe subset

Fixed

  • The README said to select an expression and press Alt+G, which was never how it worked: the formula is taken from under the mouse pointer, and Alt+G only opens the panel
v1.0.3 7 August 2026

An Exit reaches the evaluation it belongs to, and the thread-safety contract stops overpromising

Added

  • A routing test suite for Exit: recursion, a chain through one foreign parser, a chain through two, an Exit owned by the parser in the middle, the legacy constructor inside and outside an evaluation
  • A README section that states the thread-safety contract in full, including the one rule the previous text left out: every simultaneously active evaluation needs script storage of its own
  • The plugin carries version information and unpacks the way Plugins Admin expects, so it can be listed in the Notepad++ plugin catalogue

Fixed

  • A parser standing between an Exit and the evaluation it belongs to swallowed it: with A calling B and B calling back into A, the Exit raised in A ended up as the result of B, and A quietly finished a different sum. The exception now carries its owner, and only the evaluation it names may take it
  • Looking for the enclosing evaluation moved off the path of an ordinary formula: it is asked only when an exception actually appears
  • The package descriptions said "Copyright Yuriy Pisarev" where the repository is MIT, and carried a version unrelated to the product
v1.0.2 6 August 2026

One parser, many threads: Exit belongs to its own evaluation

Added

  • A thread-safety test that pins down who owns an Exit: parallel roots, recursion, two parsers in one thread, a notification that starts its own evaluation
  • The loop guards are documented: a README section with a compiled-and-run example, and the exact scope - guards belong to the thread and are set at the root of an evaluation

Fixed

  • Exit inside a formula answered to the thread scheduler: the nesting depth lived in a field shared by every thread using the parser, so a parallel Exit escaped as an exception and a lost update could leave Exit broken until another race repaired it - the depth now lives in a frame on the stack of the call
  • Exit inside brackets now ends the whole evaluation in both evaluation modes: 99 + (Exit(42)) is 42 everywhere, where the evaluate-up-front mode used to answer 141
  • The plugin archive is reproducible: repacking the same content gives the same checksum
v1.0.1 5 August 2026

Interruptible loops and per-system formula sheets

Added

  • A loop guard: a break flag and a turn budget, both off by default - a formula that never ends becomes a formula error, not a frozen tab or a killed worker thread
  • Unwind descriptions for generated x86-64 code on Windows: an exception thrown through it reaches the handler instead of taking the process down

Fixed

  • A deadlock on the first parse from a worker thread: the smart cache sent a synchronous window message across threads
  • Each coordinate system keeps a formula sheet of its own, the way the classic window always did, and a deliberately emptied sheet survives a reload
  • The intersection finder merged genuinely distinct neighbouring crossings as duplicates and silenced a fast curve as an indistinguishable stretch
  • Bulk evaluation fills the answers or says it did not; a formula the accelerator declines falls back to the ordinary parser
  • The plugin archive shrank from 9.6 MB to 1.4 MB: debug information no longer ships inside the library
v1.0.0 4 August 2026

First public release

Added

  • MathParser: parser, flat bytecode, interpreter, shape cache, 163 callable functions
  • The accelerator: x86-64 machine code with an automatic fall back to the interpreter
  • CrossGraph: a plotting engine and a visual component for Delphi and Lazarus
  • A plugin for Notepad++, built with Lazarus and Free Pascal - ready to download
  • A live demo that runs the real engine in the browser, compiled to WebAssembly
  • Reference pages for syntax, the accelerator and the limitations
  • Build matrix across Delphi win32 and win64, FPC on Windows and Linux