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.

Numbers work as well as formulas, and this part is worth spelling out because nothing on screen announces it. Paste a table into the panel - two columns, any of the usual separators - and the pairs become points on the canvas. A row appears for them beside the formulas, and in that row you pick a fit: a straight line, a polynomial of the second to the fifth degree, exponential, power or logarithmic. The fitted formula is laid over the points with its R^2 and the caveats that belong beside it. It is an ordinary formula from then on, so everything else works on it - tracing, extrema, intersections, the report.

Live Open the panel The engine loads into your browser and computes as you type. Paste a table and it fits a formula to your points. Nothing is sent anywhere.
Formulasas many as you like, each toggled and coloured on its own
Datapaste a table or a csv - the two columns become points on the canvas
Regressionfit a formula to those points: line, polynomial, exponential, power, log
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
Six planets as points and the fitted power law over them
Six rows of a table, and out comes a law of nature. Orbital radius against period for the planets from Mercury to Saturn - the numbers an astronomy textbook gives. A power fit answers 1.00029 * Exp(1.499715 * Ln(X)): an exponent of 1.4997 and a factor of 1.0003. That exponent is Kepler's third law, which took him years of observation. Nothing was told to the panel except the six pairs of numbers. Copy the table, paste it into the panel, pick power in the row that appears:
radius;period
0.387;0.241
0.723;0.615
1.000;1.000
1.524;1.881
5.203;11.862
9.537;29.457
Eleven measured points and the parabola fitted through them
Eleven measurements of a throw, and the parabola behind them. A polynomial of the second degree answers -0.495688 * X * X + 4.972331 * X + 0.014685 with R^2 = 0.9997. The caveat sits beside the answer rather than in the documentation: R^2 grows with the number of coefficients by itself, so a higher degree would fit better and mean less. Same table, same two steps - pick polynomial 2 this time:
x;height
0;0.0
1;4.5
2;8.1
3;10.4
4;11.9
5;12.4
6;12.0
7;10.6
8;8.2
9;4.6
10;0.1

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.3.0 17 August 2026

A table becomes points, and the points become a formula

Added

  • A selection in the editor is plotted while you are making it. Select a pair of columns in a csv and the points appear on the canvas at once; the next selection replaces them, so nothing piles up. Alt+Shift+G keeps what is shown for good, as an ordinary row of the list.
  • Text with numbers becomes a series of points. The separator is worked out from the data - a semicolon, a tab, a comma, a space - and a decimal comma is not mistaken for one. A header row names the series. Points are drawn as points, not joined by a line: a line between measurements would draw values where nothing was measured.
  • A formula is fitted to those points: a straight line, a polynomial of the second to the fifth degree, exponential, power or logarithmic. The result is an ordinary formula string, so everything already there works on it - tracing, extrema, intersections, the report, bookmarks, the share link.
  • The caveats stand beside the answer rather than in the documentation. R^2 grows with the number of coefficients by itself; the exponential, power and logarithmic fits are computed through logarithms, so it is the deviation of the logarithms that is least; least squares is sensitive to outliers.
  • Undo. Ctrl+Z, or the button next to the bin, takes back the last destructive action - clearing the panel, removing a curve or a series, a fit. Twenty steps, and the tooltip names what will be undone.
  • The panel says why it is empty. Without the WebView2 runtime it used to stay blank with no explanation; now it names the reason and what to do about it.

Fixed

  • The colour you picked is the colour the curve is drawn in. While "a colour of its own for every formula" was set, the chosen colour was not used at all, and the control looked as though it worked. Picking a colour now turns that mark off, and the switch for it stands in plain sight.
  • A bookmark slot stays occupied after you restore from it, and you can return to the same bookmark as many times as you like. Restoring used to free the slot, so click after click went remember - restore - remember. Ctrl+click writes over a slot, Shift+click clears it, and both gestures are written in the tooltip.
  • Printing puts the whole report on paper, not the part that happened to be visible. The report lived in a scrolling window and was clipped along with the scrollbar; the graph is printed too, on a page of its own.
  • Clear stands at the end of the button row, behind a separator, with undo before it. It used to sit between Print and Report - three neighbours, two of which show something and the third wiped the list without a question. It now clears the whole panel, series included, and the caption says so.
  • A formula whose values run off to infinity no longer brings the browser demo down. X // Sin (X) beside other curves made the build fail outright: near the zeros of the sine the chord between neighbouring nodes came out infinite, and the search for touches let it through - both of its gates compare on "greater", and infinity on the right lets everything past. The plugin was never affected: there the intersections are computed by the engine, and in the browser they are written again in JavaScript.
v1.2.1 16 August 2026

An extremum is a property of the function, not of the sampling

Fixed

  • The extremum marked on a curve was the nearest computed point rather than the extremum itself. Three things followed from that, and a reader of the plugin noticed all three: for y = x*x the answer was not zero and did not agree with the value the report printed; the mark moved when the window or the quality changed, because the grid of computed points moved with them; and raising the accuracy brought the answer closer without ever reaching it. The vertex of the drawn polyline now only opens the search - the extremum itself is found on the function, by ternary search inside the bracket the vertex gives. Measured on a shifted parabola, a shifted sine and a cusp: the answer lands within about a hundred-millionth, and it is the same answer at two different zooms.
  • A parabola tested at a symmetric window passed while all of this was broken, because zero fell exactly on a grid point and the right answer came out by alignment rather than by calculation. The checks that guard this now put the extremum deliberately off the grid, and ask for the same answer at two zooms rather than one.
  • The build button carried a word next to its icon. The word is gone; the button keeps its name for the tooltip and for a screen reader. In a docked panel this also takes pressure off the width of the row, and a row that wraps is what used to make the panel rebuild itself.
v1.2.0 15 August 2026

The libraries build on older Delphi

Added

  • The parser and the plotting engine build on Delphi 10.2 Tokyo through 13; the plugin builds on 11 Alexandria through 13. Every version in that range is a real installation rather than an expectation: before a release each one compiles the units one at a time, and the range printed in the README moves only when that run agrees. The plugin stops at 11 for a reason that is not in our code - it reaches WebView2 through a unit Embarcadero began shipping in the RTL with that version, and on 10.2 through 10.4 the unit is simply absent.

Fixed

  • A version test stopped the build on every Delphi up to and including 11. One unit chose between two shapes of an iterator callback by testing a Free Pascal version constant, and the test sat inside a branch Delphi never takes. That is not as safe as it looks: Delphi evaluates the expression regardless of the branch, meets a constant it has never heard of, and stops. The choice is made by generation symbols now, which either exist or do not and need nothing evaluated to find out.
  • An array literal bound to the wrong type on Delphi before 12. Until that version the dynamic string array and the generic array of string are two distinct types rather than one, so a literal passed to a parameter taken by reference bound to the type the call did not want. The two calls in the parser and one declaration in the plugin build their array in a variable now.
  • The token handed back by GdiplusStartup changed type in the RTL between 10.3 and 10.4, from a plain unsigned long to a pointer-sized one. It is taken by reference, so the mismatch was an error rather than a warning, and the plotting engine would not build on the two older versions. The variable is declared by a numeric test on the compiler version now.
  • A form knows its own pixels per inch, but that property is protected until Delphi 11, and the dark-theme code read it directly. An accessor class opens it, and the one line now works on every version without a version test of any kind.
  • A conditional symbol with a dot in its name does not ask what it appears to ask. Delphi cuts the name at the dot, so the symbols naming the point releases of the 10 line all resolve to the same thing and answer yes as far back as 10 Seattle. Measured on a 10.2 installation, all three of them answer yes. Tests that have to tell those versions apart use the numeric compiler version instead.
v1.1.2 15 August 2026

The component survives being dropped on a form

Fixed

  • Dropped on a form under Free Pascal, the graph component could take the application down with it. The component drives itself from a precise timer, and the two compilers give that timer different lives: under Delphi it is a window timer whose handler runs in the main thread, under Free Pascal the same class runs a thread of its own and calls the handler there. So the handler painted from a worker thread while the main thread painted the same control, and the pen cache of the LCL does not survive that. Measured before the fix: six crashes in ten runs; after it: none in a hundred and fifteen. The handlers no longer do the work - each posts a message to its own window and the work happens in the main thread. The contract of the timer is now written down in the header of its unit, so the next reader does not have to discover it from a crash.
  • The component came up black on a form under Lazarus. Under the LCL the default colour of a control is not a colour at all but an instruction to ask the environment for one, and an off-screen bitmap has no environment to ask: the request resolved to black, while the same line under Delphi produced the ordinary window colour. The buffer is filled with a resolved colour now, and the two fonts the component owns are given explicit defaults instead of inheriting the same uncertainty.
  • The packages compiled and then did nothing: the components never reached the palette. Three things were missing at once - none of the packages declared itself usable at design time, eight units registered components without saying so in the package description, and the parser package declared no dependencies at all, so the IDE would not rebuild without the unit that provides the registration interface. Any one of them alone leaves the same symptom, so all three are fixed together. Checked the only way that means anything: by installing the packages into an IDE and placing a component on a form with the mouse.
  • Pasting into the formula list did nothing in the Delphi build of the plugin. The panel is a page inside the plugin, and text pasted from the clipboard has to travel from the host to that page; the host recognised only its own saved state and dropped everything else on the floor. Both builds now decide the same way - the clipboard either holds a saved state, which is decoded and checked before it is trusted, or it holds plain text, which is handed to the page as text. The two builds are fed the same nine cases and required to answer identically, in either direction.
  • A step of the mouse wheel could be lost. The flag saying which way to zoom was written by the main thread and read - and cleared - by the timer thread, so a click that landed between the reading and the clearing vanished without a trace. Both ends are in the main thread now.
  • A unit the plugin needs was missing from the published composition, so the Delphi build could not be built from a fresh clone at all. The same class of omission had happened once before with another unit, and the warning about it was written down in the very file where it happened again. It is now caught by a check rather than by a reader.
v1.1.1 14 August 2026

The docked panel stops redrawing itself

Fixed

  • Docked in a side panel, the plugin redrew its picture about twice a second while nobody touched it. The status line at the bottom has little room in a narrow panel, so its text wrapped: "ready" took one line, the report about the points took two. That changed the height of the bottom bar, the canvas lost and regained the same 64 pixels, the size observer read it as a real resize and asked for a rebuild, and the rebuild changed the text back. Measured on a docked panel: 36 rebuilds in 20 seconds of an untouched window, 437 ms of computation each. Now none, while a real resize still rebuilds. The status line keeps to one line and holds a fixed share of the row, so nothing it displays can move the layout. A floating window is wide enough that the wrap never happens, which is why the report named the docked panel and not the floating one.
  • The component that does the computing kept a window on screen underneath the page and painted nothing into it. Every time the container resized the panel, that window was told to repaint and left standing whatever had been there. Measured: seven such repaints for six resizes of a docked panel, not one of them drawing a pixel. It is off the screen now while the page draws; the computation is untouched, and results still arrive in that window from the worker threads.
  • The graph component painted outside the normal repaint cycle. An invalidation called the painting routine directly, which left the window still invalid, so the system sent a real repaint afterwards and the picture was drawn twice with the background erased in between; results from the worker threads went to the screen the same way, out of turn; and the tracing line was drawn over the buffer rather than into it, so it vanished at the next repaint. Now an invalidation marks the window and the system repaints once, the tracing line lives in the buffer, and the control declares itself opaque - it covers every pixel it owns, so erasing the background under it was only a flash.
  • A docking panel whose window was recreated stayed registered with Notepad++ under the old handle, and the editor had nothing live to put back into the dock. Registration now happens on every window creation, as the modeless registration already did. Three docking notifications that were declared and never handled - dropped after dragging, switched in, switched off - are handled as well.
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