Skip to content

feat: math functions and function names with digits (3.7.13) - #699

Merged
ddehilster merged 4 commits into
masterfrom
feat/nlpxx-math-functions
Jul 26, 2026
Merged

ddehilster merged 4 commits into
masterfrom
feat/nlpxx-math-functions

Conversation

@ddehilster

@ddehilster ddehilster commented Jul 26, 2026 •

Copy link
Copy Markdown
Member

Two changes shipping in one build as 3.7.13.

Fixes #698


1. Function names containing digits or underscores (#698)

A user function whose name contained a digit (Round2, Clamp01, Fixed2) or an underscore (Round_2) never compiled. The rule-file tokenizer built _LIT from a single _xALPHA token, so Round2 reached the function grammar as _LIT("Round") + _NUM(2) and never matched _FNCALLLIST <- _LIT \( in gram2.nlp. The diagnostic was [Warning: Missing ';'] + [Syntax error.] on a function header that is itself valid — which sends you looking at the previous statement instead of at the name.

Takes the fix the issue prefers — accept the digits — rather than just reporting a better error, since digits have always been legal in L() variable names.

bigtok.nlp gloms adjacent alpha/numeric/underscore tokens into one _LIT, but only when immediately followed by an unescaped ( — a function definition header or a call. That gate is what keeps the change contained: @RULES literals must write \( and key=(val) pairs put = before the paren, so element literals (covid19 stays two elements) and pair values tokenize exactly as before. The second element is [plus], so single-token names never reach the rule and the keyword rules (if/while/return/L/N/X/G/P/s) still win.

postrfa.cpp gives rfaname() a two-arg form that gathers covered text across a range of collects; the one-arg form is untouched. The name still starts at an _xALPHA, so identifiers Ifunc::genDecls emits into generated C++ stay legal.

Verified: all forms from the issue table now run (Round2, R2ound, x2Round, Round_2, Clamp01, Round_Two). parse-en-us final.tree is byte-identical to the CI baseline, and an A/B run of all 15 tutorial/nlpfix/corporate analyzers against the unpatched grammar shows zero drift.

Adds .github/workflows/tests/fnname-digits plus a step in the Windows and Linux builds next to the existing #669 regression. The step asserts out.txt rather than the exit code — the engine exits 0 even when the analyzer fails to build. Confirmed the step fails on the unpatched grammar.


2. NLP++ math functions

NLP++ had only logten, abs, mod, factorial and randomint. This adds the rounding family and the remaining core C/C++ math functions.

Function Returns Notes
floor(num) NUM round down, toward −∞
ceiling(num) NUM round up, toward +∞
round(num) NUM nearest whole number, halfway away from zero
truncate(num) NUM discard the fraction, toward zero
sqrt(num) FLOAT errors on a negative argument
pow(base,exp) FLOAT errors when the result is NaN or out of float range
log(num) FLOAT natural (base e) log, argument must be positive

The four rounding functions take a float and return a whole number, which is what callers actually want. floor and truncate deliberately differ on negatives.

Registered in the parallel funcs.h / func_defs.h tables (verified in sync — 340 entries each), declared in fn.h, dispatched and implemented in fn.cpp.

Compiled analyzers: codegen in iaction.cpp emits Arun::<fnname> for every builtin, so each function also gets Arun overloads in fnrun.cpp and declarations in Arun.h — including the four RFASem/float combinations for the two-argument pow. The global C functions are called with the :: qualifier so the new Arun members don't recurse into themselves.

Help: seven new pages under visualtext/Help/helps modeled on logten.htm, TOC entries in Help.hhc, and Table_of_Math_Functions.htm rebuilt. That table previously listed logten alone, so abs, factorial, mod and randomint are now documented there too.

Testing — built clean and verified against a live analyzer:

floor(3.7)     = 3     floor(-3.7)     = -4
ceiling(3.2)   = 4     ceiling(-3.7)   = -3
round(3.5)     = 4     round(-3.5)     = -4
round(3.4)     = 3
truncate(3.7)  = 3     truncate(-3.7)  = -3
sqrt(16)       = 4     sqrt(2)         = 1.41421
pow(2,10)      = 1024  pow(9,0.5)      = 3
log(2.718282)  = 1     logten(1000)    = 3

sqrt of a negative and log(0) are rejected rather than propagating NaN.


Version

NLP_ENGINE_VERSION in nlp/main.cpp bumped to 3.7.13 — that #define is what --version prints, since the generated Version() is commented out at the callsite. Confirmed nlp --version reports 3.7.13.

Notes for review

  • Negative literals as arguments (e.g. floor(-3.2)) fail to parse. Pre-existing, not introduced here — abs(-5), mod(-7,3) and logten(-1.0) fail identically. Passing a negative variable works. Worth noting the existing abs help page documents abs(-7), which does not actually run.
  • abs, mod and randomint have no Arun:: overloads, so they are broken in compiled analyzers today. Not fixed here to keep scope contained — happy to follow up.
  • min/max deliberately omitted. NLP++ distinguishes NUM from FLOAT and these are the only functions in the set with a genuinely ambiguous return type (a float-based min(2,3) could print 2.000000). Worth deciding explicitly rather than picking silently.

Companion PRs: VisualText/vscode-nlp#1112 (editor support), VisualText/visualtext-files#197 (help docs)

🤖 Generated with Claude Code

NLP++ had only logten, abs, mod, factorial and randomint for math. Add the
rounding family and the remaining core C/C++ math functions:

  floor(num)      NUM     round down, toward -infinity
  ceiling(num)    NUM     round up, toward +infinity
  round(num)      NUM     nearest whole number, halfway away from zero
  truncate(num)   NUM     discard the fraction, toward zero
  sqrt(num)       FLOAT   square root, errors on a negative argument
  pow(base,exp)   FLOAT   errors when the result is NaN or out of float range
  log(num)        FLOAT   natural (base e) log, argument must be positive

The four rounding functions take a float and return a whole number, which is
what callers actually want; floor and truncate deliberately differ on
negatives (floor(-3.7) = -4, truncate(-3.7) = -3).

Registered in the parallel funcs.h / func_defs.h tables (kept in sync, 340
entries each), declared in fn.h, dispatched and implemented in fn.cpp.

Compiled analyzers need matching runtime entry points: codegen in
iaction.cpp emits Arun::<fnname> for every builtin, so each function also
gets Arun overloads in fnrun.cpp and declarations in Arun.h, including the
four RFASem/float combinations for the two-argument pow. The global C
functions are called with the :: qualifier so the new Arun members do not
recurse into themselves.

Help: seven new pages under visualtext/Help/helps modeled on logten.htm,
TOC entries in Help.hhc, and Table_of_Math_Functions.htm rebuilt. That table
previously listed logten alone, so abs, factorial, mod and randomint are now
documented there as well.

Verified against a live analyzer: all values correct, including the
floor/truncate split on negatives and round(-3.5) = -4. Note that negative
literals as arguments (e.g. floor(-3.2)) fail to parse, but that is a
pre-existing NLP++ limitation -- abs(-5), mod(-7,3) and logten(-1.0) fail
the same way; passing a negative variable works.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The math-function commit named 3.7.13 in its subject but left the #define at
3.7.12, so nlp --version would have under-reported the build. Note that this
#define is what --version actually prints; the generated Version() from
version.cpp is commented out at the callsite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ddehilster
ddehilster force-pushed the feat/nlpxx-math-functions branch from 46db686 to da93da0 Compare July 26, 2026 13:02
A user function whose name contained a digit (Round2, Clamp01, Fixed2) or an
underscore (Round_2) never compiled. The rule-file tokenizer built _LIT from a
single _xALPHA token, so "Round2" arrived at the function grammar as
_LIT("Round") + _NUM(2) and never matched "_FNCALLLIST <- _LIT \(" in
gram2.nlp. The diagnostic was "[Warning: Missing ';']" + "[Syntax error.]" on a
function header that is itself valid, which sends you looking at the previous
statement instead of at the name.

Take the fix the issue prefers -- accept the digits -- rather than reporting a
better error, since digits have always been legal in L() variable names.

bigtok.nlp: glom adjacent alpha/numeric/underscore tokens into one _LIT, but
only when they are immediately followed by an UNESCAPED "(", ie a function
definition header or a call. That gate is what keeps the change contained:
@rules literals must write "\(" and key=(val) pairs put "=" before the paren,
so element literals ("covid19" stays two elements) and pair values tokenize
exactly as before. The second element is [plus], so single-token names never
reach the rule and the keyword rules (if/while/return/L/N/X/G/P/s) still win.

postrfa.cpp: rfaname() gains a two-arg form that gathers the covered text
across a range of collects; the one-arg form is untouched. The name still
starts at an _xALPHA, so the identifiers Ifunc::genDecls emits into generated
C++ stay legal.

Verified: all forms from the issue table now run (Round2, R2ound, x2Round,
Round_2, Clamp01, plus Round_Two). parse-en-us final.tree is byte-identical to
the CI baseline, and an A/B run of all 15 tutorial/nlpfix/corporate analyzers
against the unpatched grammar shows zero drift.

Adds .github/workflows/tests/fnname-digits plus a step in the Windows and Linux
builds next to the existing #669 regression. The step asserts out.txt rather
than the exit code -- the engine exits 0 even when the analyzer fails to build.
Confirmed the step fails on the unpatched grammar.

No version bump: this rides along with 3.7.13 so both changes ship in one build.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ddehilster ddehilster changed the title feat: add floor/ceiling/round and math functions to NLP++ (3.7.13) feat: math functions for NLP++, and digits/underscores in function names (3.7.13) Jul 26, 2026
@ddehilster ddehilster changed the title feat: math functions for NLP++, and digits/underscores in function names (3.7.13) feat: math functions and function names with digits (3.7.13) Jul 26, 2026
The Linux jobs went red on the new step even though the analyzer built fine
("[Loaded analyzer: 0.005928 sec]", no errors). Windows always leaves a
logs/make_ana.log behind, but Linux only writes one when the build had
something to report, so an unconditional `cat` hit "No such file or directory"
and `set -e` killed the step.

Print the log only when it exists, and add an explicit "no out.txt -- the
analyzer did not build" guard so a genuine build failure still fails loudly
instead of tripping over a missing file. Verified both ways locally: green with
the fix (with and without a logs dir), and red against the pre-fix grammar.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ddehilster
ddehilster merged commit 2b95962 into master Jul 26, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Function names containing a digit (Round2, Clamp01) fail to compile with a misleading "Missing ';'" error

1 participant