@@ -595,6 +595,14 @@ public void setVariableManager(VariableManager variableManager) {
595595 this .variableManager = variableManager ;
596596 }
597597
598+ public VariableRegistry getRegistry () {
599+ return registry ;
600+ }
601+
602+ public boolean hasVariable (String var ) {
603+ return registry .hasVariable (var );
604+ }
605+
598606 /**
599607 * Retrieves a Variable handle from the expression's registry. This handle
600608 * is "Pre-Bound" to the correct slot in the execution frame.
@@ -620,8 +628,7 @@ public Variable getVariable(String name) {
620628
621629 return v ;
622630 }
623-
624- throw new NoSuchElementException ("Variable '" + name + "' was not found in the compiled expression." );
631+ return null ;
625632 }
626633
627634 public static final class Slot {
@@ -1049,13 +1056,19 @@ public void updateArgs(double... values) {
10491056 */
10501057 public void updateArgs (int [] slots , double ... values ) {
10511058 for (int i = 0 ; i < slots .length ; i ++) {
1052- this .executionFrame [slots [i ]] = values [i ];
1059+ int slot = slots [i ];
1060+ double value = values [i ];
1061+ if (slot >= 0 && slot < this .executionFrame .length ) {
1062+ this .executionFrame [slot ] = value ;
1063+ }
10531064 }
10541065 }
10551066
10561067 // Or, for a single variable update (the most common benchmark case):
10571068 public void updateSlot (int slot , double value ) {
1058- this .executionFrame [slot ] = value ;
1069+ if (slot >= 0 && slot < this .executionFrame .length ) {
1070+ this .executionFrame [slot ] = value ;
1071+ }
10591072 }
10601073
10611074 public void setReturnType (TYPE returnType ) {
@@ -1275,7 +1288,7 @@ private void compileToPostfix() {
12751288 Token callable = opStack .pop ();
12761289
12771290 int actualArgCount = argCounts .pop ();
1278- lastWasComma .pop ();
1291+ lastWasComma .pop ();
12791292 callable .arity = Math .max (1 , actualArgCount );
12801293 postfix [p ++] = callable ;
12811294
@@ -1351,17 +1364,24 @@ private final class ExpressionSolver {
13511364 private static final int ABSOLUTE_MAX_ARITY = 100_000 ;
13521365
13531366 private EvalResult [][] argCache = new EvalResult [MAX_ARITY + 1 ][];
1367+ private EvalResult [] stack ;
13541368
13551369 public ExpressionSolver () {
13561370 // Pre-allocate ONE array per arity that we reuse
13571371 for (int i = 0 ; i <= MAX_ARITY ; i ++) {
13581372 argCache [i ] = new EvalResult [i ];
13591373 }
1360-
13611374 }
13621375
13631376 public EvalResult evaluate () {
1364- final EvalResult [] stack = new EvalResult [Math .max (cachedPostfix .length * 2 , 64 )];
1377+ if (stack == null ){
1378+ stack = new EvalResult [Math .max (cachedPostfix .length * 2 , 64 )];
1379+ }else {
1380+ if (cachedPostfix .length > stack .length ){
1381+ stack = new EvalResult [Math .max (cachedPostfix .length * 2 , 64 )];
1382+ }
1383+ }
1384+
13651385 int ptr = -1 ;
13661386
13671387 for (int i = 0 ; i < cachedPostfix .length ; i ++) {
@@ -1432,7 +1452,7 @@ public EvalResult evaluate() {
14321452 int valuesOnStack = ptr + 1 ;
14331453
14341454 if (arity == 0 ) {
1435- EvalResult result = t .action .calc (getNextResult (), arity );
1455+ EvalResult result = t .action .calc (getNextResult (), arity , argCache [ arity ] );
14361456 stack [++ptr ] = result ;
14371457 break ;
14381458 }
@@ -1607,11 +1627,10 @@ private void foldConstantsWithSafetyGuards() {
16071627 if ((t .kind == Token .FUNCTION || t .kind == Token .METHOD ) && writePtr >= t .arity ) {
16081628
16091629 // CRITICAL: Never fold trigonometric functions!
1610- /* if (isTrigonometricFunction(t)) {
1630+ /* if (isTrigonometricFunction(t)) {
16111631 folded[writePtr++] = t; // Keep the function token as-is
16121632 continue;
16131633 }*/
1614-
16151634 if (!isConstantFoldableFunction (t )) {
16161635 folded [writePtr ++] = t ;
16171636 continue ;
@@ -1654,37 +1673,6 @@ private void foldConstantsWithSafetyGuards() {
16541673 }
16551674 }
16561675
1657- /**
1658- * CRITICAL: Detect trigonometric functions that must NOT be folded because
1659- * setDRG() needs to be able to modify them after compilation.
1660- */
1661- private boolean isTrigonometricFunction (Token t ) {
1662- if (t .name == null ) {
1663- return false ;
1664- }
1665-
1666- String name = t .name .toLowerCase ();
1667-
1668- // All trig functions (in any DRG variant) must NOT be folded
1669- java .util .Set <String > trigFunctions = new java .util .HashSet <>(
1670- Arrays .asList (
1671- // Basic trig
1672- "sin_deg" , "sin_rad" , "sin_grad" , "sin" ,
1673- "cos_deg" , "cos_rad" , "cos_grad" , "cos" ,
1674- "tan_deg" , "tan_rad" , "tan_grad" , "tan" ,
1675- // Inverse trig
1676- "asin" , "acos" , "atan" , "atan2" ,
1677- // Hyperbolic (also angle-dependent in some contexts)
1678- "sinh" , "cosh" , "tanh" , "asinh" , "acosh" , "atanh" ,
1679- // Cotangent, Secant, Cosecant (if you support them)
1680- "cot" , "sec" , "csc" ,
1681- // DRG-aware variants
1682- "arcsin" , "arccos" , "arctan" )
1683- );
1684-
1685- return trigFunctions .stream ().anyMatch (name ::equalsIgnoreCase );
1686- }
1687-
16881676 /**
16891677 * Whitelist of functions that ARE safe to constant-fold. Note: Trig
16901678 * functions are handled separately (never folded).
@@ -1696,36 +1684,79 @@ private boolean isConstantFoldableFunction(Token t) {
16961684
16971685 String name = t .name .toLowerCase ();
16981686
1699- // ===== BLACKLIST: NON-FOLDABLE =====
1687+ // ===== BLACKLIST: NON-FOLDABLE (Side effects, randomness, I/O, user-defined) =====
17001688 java .util .Set <String > nonFoldable = new java .util .HashSet <>(
17011689 Arrays .asList (
1702- // Stochastic
1703- "rand" , "random" , "randbetween" , "randi" , "randint" , "randnormal" ,"rnd" ,
1690+ // Stochastic - different result each time
1691+ "rand" , "random" , "randbetween" , "randi" , "randint" , "randnormal" , "rnd" ,
17041692 // Time-dependent
17051693 "now" , "time" , "today" , "clock" , "timestamp" , "millis" ,
1706- // Side effects
1707- "print" , "println" , "debug" , "log" ,
1694+ // Side effects (I/O, output)
1695+ "print" , "println" , "debug" , "log" , "plot" ,
17081696 // System-dependent
17091697 "random_seed" , "env" , "getenv" ,
1710- // I/O
1711- "read" , "readline" , "input" , "scan" )
1698+ // I/O operations
1699+ "read" , "readline" , "input" , "scan" ,
1700+ // User-defined or dynamic
1701+ "diff" , "intg" , "differentiation" , "integration" ,
1702+ // Matrix operations (complex state-dependent results)
1703+ "linear_sys" , "det" , "determinant" , "invert" , "inverse_matrix" ,
1704+ "tri_mat" , "triangular_matrix" , "echelon" , "echelon_matrix" ,
1705+ "matrix_mul" , "matrix_multiply" , "matrix_div" , "matrix_divide" ,
1706+ "matrix_add" , "matrix_subtract" , "matrix_sub" ,
1707+ "matrix_pow" , "matrix_power" , "transpose" , "matrix_edit" ,
1708+ "matrix_cofactors" , "cofactor" , "matrix_adjoint" , "adjoint" ,
1709+ "matrix_eigenvec" , "eigvec" , "matrix_eigenvalues" , "eigvalues" ,
1710+ "matrix_eigenpoly" , "eigpoly" ,
1711+ // List sorting (order-dependent, may depend on implementation)
1712+ "sort" ,
1713+ // Help
1714+ "help" ,
1715+ // Quadratic/root solving (may have multiple solutions or complex numbers)
1716+ "quadratic" , "tartaglia_roots" , "t_root" , "general_root" , "root" )
17121717 );
17131718
17141719 if (nonFoldable .contains (name )) {
17151720 return false ;
17161721 }
17171722
1718- // ===== WHITELIST: SAFE TO FOLD =====
1719- java .util .Set <String > definitelyFoldable = new java .util .HashSet <>(
1720- Arrays .asList ()
1723+ // ===== SPECIAL HANDLING: Trigonometric functions =====
1724+ // These ARE foldable, but ONLY if NOT being used with setDRG() changes
1725+ // For safety, we fold them ONLY when the expression contains constants
1726+ // Since we're in compile-time, we CAN fold them with current DRG mode
1727+ java .util .Set <String > trigonometric = new java .util .HashSet <>(
1728+ Arrays .asList (MethodRegistry .expandedTrigAndHypMethodNames )
17211729 );
17221730
1723- if (definitelyFoldable .contains (name )) {
1724- return true ;
1725- }
1726-
1727- // Conservative: don't fold unknown functions
1728- return false ;
1731+ // ===== WHITELIST: DEFINITELY FOLDABLE =====
1732+ java .util .Set <String > definitelyFoldable = new java .util .HashSet <>(
1733+ Arrays .asList (
1734+ // ===== EXPONENTIAL & LOGARITHMIC =====
1735+ "exp" , "ln" , "lg" , "log" , "log10" , "log2" ,
1736+ "ln-¹" , "lg-¹" , "log-¹" , "aln" , "alg" , "alog" ,
1737+ // ===== POWER & ROOT =====
1738+ "sqrt" , "cbrt" , "pow" , "hypot" ,
1739+ "inverse" , "square" , "cube" ,
1740+ "fact" ,
1741+ // ===== COMBINATORICS =====
1742+ "comb" , "perm" ,
1743+ // ===== ROUNDING & ABSOLUTE =====
1744+ "abs" , "floor" , "ceil" , "round" , "trunc" , "sign" ,
1745+ // ===== LIST STATISTICS (pure functions - no state) =====
1746+ "listsum" , "sum" , "prod" , "product" ,
1747+ "mean" , "listavg" , "avg" , "average" ,
1748+ "median" , "med" ,
1749+ "min" , "max" ,
1750+ "mode" , "sort" ,
1751+ "range" , "rng" , "mid_range" , "mrng" ,
1752+ "rms" ,
1753+ "s_d" , "std_dev" ,
1754+ "variance" , "st_err" ,
1755+ "cov"
1756+ ));
1757+ definitelyFoldable .addAll (trigonometric );
1758+
1759+ return definitelyFoldable .contains (name );
17291760 }
17301761
17311762 /**
@@ -2241,13 +2272,12 @@ private static void testConstantFolding() {
22412272 "2*sinh(5)"
22422273 };
22432274
2244- double [] expected = {8.0 , 9.0 , 16.0 , 2.0 , 2.0 , 17.0 , 0.01 , 0.42478219352309348656738397432167 ,5 * 148.40642115557751795401894399213 };
2275+ double [] expected = {8.0 , 9.0 , 16.0 , 2.0 , 2.0 , 17.0 , 0.01 , 0.42478219352309348656738397432167 , 5 * 148.40642115557751795401894399213 };
22452276
22462277 for (int i = 0 ; i < exprs .length ; i ++) {
22472278 try {
22482279 MathExpression me = new MathExpression (exprs [i ]);
22492280 double result = Double .parseDouble (me .solve ());
2250-
22512281
22522282 String status = Math .abs (result - expected [i ]) < 1e-9 ? "✓" : "✗" ;
22532283 System .out .printf ("%s %s = %.6f (expected: %.6f)%n" ,
@@ -2449,16 +2479,15 @@ public static void main(String... args) {
24492479 System .out .println ("VARIABLES--1 = " + VariableManager .VARIABLES );
24502480 Function f = FunctionManager .add ("f(x,y) = x - x/y" );
24512481 System .out .println ("VARIABLES--2 = " + VariableManager .VARIABLES );
2452-
2453-
2454- f .updateArgs (2 ,3 );
2482+
2483+ f .updateArgs (2 , 3 );
24552484 double r = f .calc ();
24562485 System .out .println ("VARIABLES--3 = " + VariableManager .VARIABLES );
24572486 System .out .println ("r = " + r );
24582487 int iterations = 1 ;
24592488 double vvv [] = new double [1 ];
24602489 long start = System .nanoTime ();
2461- f .updateArgs (2 ,3 );
2490+ f .updateArgs (2 , 3 );
24622491 for (int i = 1 ; i <= iterations ; i ++) {
24632492 vvv [0 ] = f .calc ();
24642493 }
@@ -2533,9 +2562,9 @@ public static void main(String... args) {
25332562 System .out .println (quadRoots1 .solve ());
25342563 MathExpression tartRoots = new MathExpression ("t_root(@(x)5*x^3-12*x+120)" );
25352564 System .out .println (tartRoots .solve ());
2536-
2565+
25372566 MathExpression printer = new MathExpression ("print(anon22,C)" );
2538- System .out .println (printer .solve ());
2567+ System .out .println (printer .solve ());
25392568 System .out .println (new MathExpression ("M=@(x)7*x^2;M(2)" ).solve ());
25402569
25412570 // double N = 100;
0 commit comments