Skip to content

Commit 87381c0

Browse files
committed
optimized bracket cleanups
1 parent 8b25ec9 commit 87381c0

6 files changed

Lines changed: 83 additions & 52 deletions

File tree

pom.xml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<modelVersion>4.0.0</modelVersion>
44
<groupId>com.github.gbenroscience</groupId>
55
<artifactId>parser-ng</artifactId>
6-
<version>1.0.4</version>
6+
<version>1.0.5</version>
77
<packaging>jar</packaging>
88
<!--
99
I started this project 2009 and have been upgrading it since then.
@@ -12,8 +12,10 @@
1212
-->
1313

1414
<name>ParserNG</name>
15-
<description>Rich and Performant, Cross Platform Java Library(100% Java)...Version 1.0.3 retains high frequency execution and multi-mode/multi-functional nature
16-
And it adds a function, the rotor function, `rot` which allows users to rotates point, lines, planesand cuved surfaces, curves and functions in 3D space. Now you can know what a curve will look like when it is rotated about a certain point in a given direction.
15+
<description>Rich and Performant, Cross Platform Java Library(100% Java).No native dependencies.
16+
ParserNG is the height of interpreted Math parsing in Java. It is the fastest of all interpreted Java math parsers.
17+
Version 1.0.5 retains high frequency execution and multi-mode/multi-functional nature
18+
Fixes bugs in the rot function and optimizes some inner loops during syntax and semantic analysis for speedier parsing
1719
</description>
1820
<url>https://github.com/gbenroscience/ParserNG</url>
1921

src/main/java/com/github/gbenroscience/parser/BracketCleaner.java

Lines changed: 31 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.github.gbenroscience.util.VariableManager;
2121
import static com.github.gbenroscience.parser.Operator.*;
2222
import static com.github.gbenroscience.parser.Variable.*;
23+
import com.github.gbenroscience.util.FunctionManager;
2324
import java.util.*;
2425

2526
/**
@@ -42,22 +43,22 @@
4243
*/
4344
public class BracketCleaner {
4445

46+
4547
// Fast lookup for known functions (covers Method.isMethodName)
4648
private static final List<String> FUNCTIONS = Arrays.asList(Method.getAllFunctions());
4749

4850
/**
49-
* Removes excess brackets **in-place** and returns the same list for convenience.
50-
* The input list is modified directly.
51+
* Removes excess brackets **in-place**.
52+
* The list you pass in is modified directly — exactly like your original method.
5153
*/
52-
public static List<String> removeExcessBrackets(List<String> scanner) {
54+
public static void removeExcessBrackets(List<String> scanner) {
5355
if (scanner == null || scanner.isEmpty()) {
54-
return scanner;
56+
return;
5557
}
5658

5759
boolean changed;
5860
do {
5961
changed = false;
60-
// Build pairMap ONCE per full pass (efficient)
6162
int[] pairMap = buildPairMap(scanner);
6263

6364
for (int i = 0; i < scanner.size(); i++) {
@@ -68,74 +69,62 @@ public static List<String> removeExcessBrackets(List<String> scanner) {
6869
int open = i;
6970
int close = pairMap[i];
7071

71-
// 1. Redundant nested brackets: ((x)) → (x) (safe even after @, sin, etc.)
72+
// 1. Redundant nested brackets ((...)) → (...) (always safe)
7273
if (open + 1 < scanner.size() && pairMap[open + 1] == close - 1) {
73-
scanner.remove(close); // outer )
74-
scanner.remove(open); // outer (
74+
scanner.remove(close);
75+
scanner.remove(open);
7576
changed = true;
76-
break; // restart with fresh pairMap
77+
break;
7778
}
7879

79-
// 2. Single token inside: (x) → x
80-
// FULL original safety logic restored (this fixes @(x,y) and all your other cases)
80+
// 2. Single-token brackets (x) → x with ALL your original safety checks
8181
if (close == open + 2) {
82-
boolean mustKeepBrackets = false;
83-
82+
boolean mustKeep = false;
8483
if (open > 0) {
8584
String prev = scanner.get(open - 1);
8685
if (isVariableString(prev) ||
87-
isAtOperator(prev) || // ← handles @ operator
88-
FUNCTIONS.contains(prev) || // covers Method.isMethodName
86+
isAtOperator(prev) ||
87+
FUNCTIONS.contains(prev) || // covers Method.isMethodName
8988
isUnaryPreOperator(prev)) {
90-
mustKeepBrackets = true;
89+
mustKeep = true;
9190
}
9291
}
93-
// if open == 0 → safe to remove (original behaviour)
94-
95-
if (!mustKeepBrackets) {
92+
if (!mustKeep) {
9693
scanner.remove(close);
9794
scanner.remove(open);
9895
changed = true;
9996
break;
10097
}
10198
}
10299

103-
// 3. Empty brackets: () → remove only if not a function / @ / etc.
100+
// 3. Empty brackets () → remove only when safe
104101
if (close == open + 1) {
105-
boolean mustKeepBrackets = false;
106-
102+
boolean mustKeep = false;
107103
if (open > 0) {
108104
String prev = scanner.get(open - 1);
109105
if (isVariableString(prev) ||
110106
isAtOperator(prev) ||
111107
FUNCTIONS.contains(prev) ||
112108
isUnaryPreOperator(prev)) {
113-
mustKeepBrackets = true;
109+
mustKeep = true;
114110
}
115111
}
116-
117-
if (!mustKeepBrackets) {
112+
if (!mustKeep) {
118113
scanner.remove(close);
119114
scanner.remove(open);
120115
changed = true;
121116
break;
122117
}
123118
}
124119
}
125-
} while (changed); // Repeat until no more changes (handles deep nesting)
126-
127-
return scanner;
120+
} while (changed); // handles deep nesting like (((x)))
128121
}
129122

130-
/**
131-
* Builds a map of matching bracket indices.
132-
*/
133123
private static int[] buildPairMap(List<String> tokens) {
134124
int n = tokens.size();
135125
int[] pairMap = new int[n];
136126
Arrays.fill(pairMap, -1);
137127
Deque<Integer> stack = new ArrayDeque<>();
138-
139128
for (int i = 0; i < n; i++) {
140129
String t = tokens.get(i);
141130
if ("(".equals(t)) {
@@ -148,17 +137,19 @@ private static int[] buildPairMap(List<String> tokens) {
148137
}
149138
return pairMap;
150139
}
151-
152-
153-
/**
154-
* Test method – run this to verify @(x,y) and all your cases.
155-
*/
140+
156141
public static void main(String[] args) {
142+
143+
MathExpression mexie = new MathExpression("v=@(x,y)sin(3*log((((4*x-3*y),3)))-4);v(3,2)");
144+
157145
// Example with @ operator
158-
List<String> scanner = new MathExpression("v=@(x,y)sin(3*log((((x-3*y),3)))-4)")
159-
.getScanner();
146+
List<String> scanner = mexie.getScanner();
160147

148+
System.out.println("FUNCTIONS: " + FunctionManager.FUNCTIONS);
161149
System.out.println("Raw scanner: " + scanner);
162-
System.out.println("Cleaned scanner:" + removeExcessBrackets(scanner));
150+
removeExcessBrackets(scanner);
151+
System.out.println("Cleaned scanner:" + scanner );
152+
System.out.println("Cleaned scanner:" + scanner );
153+
System.out.println("eval:" + mexie.solve() );
163154
}
164155
}

src/main/java/com/github/gbenroscience/parser/Function.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,6 @@ public void setMathExpression(MathExpression mathExpression) {
583583
MathExpression oldMe = this.mathExpression;
584584
try {
585585
this.mathExpression = mathExpression;
586-
System.out.println("expr="+mathExpression.getExpression());
587586
this.turboExpr = TurboEvaluatorFactory.getCompiler(mathExpression, false).compile();
588587
this.type = TYPE.ALGEBRAIC_EXPRESSION;
589588
} catch (Throwable ex) {

src/main/java/com/github/gbenroscience/parser/MathExpression.java

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1227,6 +1227,32 @@ protected List<String> solve(List<String> list) {
12271227
return Arrays.asList(String.valueOf(GG.evaluate(list)));
12281228
}
12291229

1230+
1231+
// Automatically strips unbalanced grouping parentheses from captured arguments
1232+
private static String cleanArgument(String arg) {
1233+
if (arg == null) return "";
1234+
arg = arg.trim();
1235+
int open = 0, close = 0;
1236+
1237+
for (int i = 0; i < arg.length(); i++) {
1238+
if (arg.charAt(i) == '(') open++;
1239+
else if (arg.charAt(i) == ')') close++;
1240+
}
1241+
1242+
// Strip leading redundant '('
1243+
while (open > close && arg.startsWith("(")) {
1244+
arg = arg.substring(1).trim();
1245+
open--;
1246+
}
1247+
1248+
// Strip trailing redundant ')'
1249+
while (close > open && arg.endsWith(")")) {
1250+
arg = arg.substring(0, arg.length() - 1).trim();
1251+
close--;
1252+
}
1253+
1254+
return arg;
1255+
}
12301256
private Token translate(String s, String next) {
12311257
if (s == null || s.isEmpty()) {
12321258
return null;
@@ -1338,7 +1364,7 @@ class FuncArgTracker {
13381364
continue;
13391365
}
13401366

1341-
// ==========================================
1367+
// ==========================================
13421368
// PHASE A: PASSIVE STRING TRACKING
13431369
// ==========================================
13441370
boolean isFuncParen = false;
@@ -1356,20 +1382,23 @@ class FuncArgTracker {
13561382
for (int i = 0; i < trackers.size(); i++) {
13571383
FuncArgTracker tracker = trackers.get(i);
13581384

1359-
// A comma or right paren ONLY belongs to this function if it's at the function's base depth
1360-
boolean isOwningComma = (t.kind == Token.COMMA && currentParenDepth == tracker.depthLevel);
1385+
// FIX: A comma belongs to this function if it is the innermost open function
1386+
// This allows the comma to penetrate redundant grouping parentheses!
1387+
boolean isOwningComma = (t.kind == Token.COMMA && i == trackers.size() - 1);
1388+
1389+
// The function still closes exactly when its base parenthesis depth resolves
13611390
boolean isOwningRParen = (t.kind == Token.RPAREN && currentParenDepth == tracker.depthLevel);
13621391

13631392
// Prevent the function from capturing its own opening parenthesis
13641393
boolean isStartingParen = (t.kind == Token.LPAREN && isFuncParen && i == trackers.size() - 1);
13651394

13661395
if (isOwningComma) {
1367-
// Lock in the finished argument and clear the builder for the next one
1368-
tracker.args.add(tracker.currentArg.toString().trim());
1396+
// Lock in the finished argument and auto-balance any stripped parens
1397+
tracker.args.add(cleanArgument(tracker.currentArg.toString()));
13691398
tracker.currentArg.setLength(0);
13701399
} else if (isOwningRParen) {
13711400
// Function is closing. Lock in the final argument.
1372-
String lastArg = tracker.currentArg.toString().trim();
1401+
String lastArg = cleanArgument(tracker.currentArg.toString());
13731402
if (!lastArg.isEmpty() || !tracker.args.isEmpty()) {
13741403
if (!lastArg.isEmpty()) {
13751404
tracker.args.add(lastArg);
@@ -1378,7 +1407,7 @@ class FuncArgTracker {
13781407

13791408
// Wire up the perfectly parsed arguments directly to the Token
13801409
tracker.funcToken.rawArgs = tracker.args.toArray(new String[0]);
1381-
tracker.funcToken.arity = tracker.args.size(); // Perfect arity counting (handles 0-arg funcs seamlessly)
1410+
tracker.funcToken.arity = tracker.args.size(); // Perfect arity counting
13821411

13831412
} else if (!isStartingParen) {
13841413
// It's a regular token inside an argument, just append it!

src/main/java/com/github/gbenroscience/parser/MathScanner.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1383,7 +1383,17 @@ private static void replaceWithFunctionName(List<String> scanner, int start, int
13831383
* @param scanner The list of scanned tokens.
13841384
*
13851385
*/
1386+
1387+
13861388
public static void removeExcessBrackets(List<String> scanner) {
1389+
BracketCleaner.removeExcessBrackets(scanner);
1390+
}
1391+
/**
1392+
* Old version of {@linkplain MathScanner#removeExcessBrackets(java.util.List) }
1393+
* @deprecated
1394+
* @param scanner
1395+
*/
1396+
public static void removeExcessBrackets1(List<String> scanner) {
13871397

13881398
for (int i = 0; i < scanner.size(); i++) {
13891399

@@ -1434,7 +1444,6 @@ public static void removeExcessBrackets(List<String> scanner) {
14341444

14351445
}
14361446
}
1437-
14381447
}
14391448

14401449
/**

src/main/java/com/github/gbenroscience/util/Utils.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ public static void logInfo(String message) {
139139
Log.i("Kalculitzer", message);
140140
}
141141
}
142+
142143

143144
public static boolean isPerfectSquare(int num) {
144145
if (num < 0) {

0 commit comments

Comments
 (0)