From a1ff52da63392de26d98b835bd212792d9dff51d Mon Sep 17 00:00:00 2001 From: FourteenBrush Date: Thu, 20 Jun 2024 17:25:11 +0200 Subject: [PATCH] Add ExecutionEnv::remove support --- .../symbol/ExecutionEnv.java | 9 +++ .../symbol/SymbolLookup.java | 64 ++++++++++++++++++- .../ExecutionEnvTest.java | 37 ++++++++++- 3 files changed, 107 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/me/fourteendoggo/mathexpressionparser/symbol/ExecutionEnv.java b/core/src/main/java/me/fourteendoggo/mathexpressionparser/symbol/ExecutionEnv.java index 1dc1b85..13a8376 100644 --- a/core/src/main/java/me/fourteendoggo/mathexpressionparser/symbol/ExecutionEnv.java +++ b/core/src/main/java/me/fourteendoggo/mathexpressionparser/symbol/ExecutionEnv.java @@ -166,6 +166,15 @@ public Symbol insertSymbolIfAbsent(Symbol symbol) { return symbolLookup.insertIfAbsent(symbol); } + /** + * Removes a {@link Symbol} from this environment. + * @param name the name, not validated. + * @return the removed symbol, or null. + */ + public Symbol removeSymbol(String name) { + return symbolLookup.remove(name); + } + /** * Looks up a symbol based on an input * diff --git a/core/src/main/java/me/fourteendoggo/mathexpressionparser/symbol/SymbolLookup.java b/core/src/main/java/me/fourteendoggo/mathexpressionparser/symbol/SymbolLookup.java index 16c3e1c..435d8bc 100644 --- a/core/src/main/java/me/fourteendoggo/mathexpressionparser/symbol/SymbolLookup.java +++ b/core/src/main/java/me/fourteendoggo/mathexpressionparser/symbol/SymbolLookup.java @@ -4,9 +4,10 @@ import me.fourteendoggo.mathexpressionparser.utils.Utility; import me.fourteendoggo.mathexpressionparser.exceptions.SyntaxException; import org.jetbrains.annotations.Debug; +import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.VisibleForTesting; -import java.util.Arrays; +import java.util.*; /** * An efficient lookup tree for {@link Symbol}s. @@ -97,6 +98,44 @@ private Node putVal(Symbol symbol, boolean expectUnoccupied) { return node.insertValueChild(lastChar, symbol, expectUnoccupied); } + public Symbol remove(String name) { + // root followed by children of different levels + List traversed = new ArrayList<>(); + traversed.add(root); + + Node last = root; + Node prevLast = root; + + for (int i = 0; i < name.length(); i++) { + Node node = last.getChildSafe(name.charAt(i)); + if (node == null) return null; + + traversed.add(node); + prevLast = last; + last = node; + } + + if (!(last instanceof ValueHoldingNode valueHoldingNode)) return null; + + if (last.hasChildren()) { + // unfortunately we don't have pointers, otherwise this would look like + // *last = new Node() + prevLast.demoteChild(name.charAt(name.length() - 1)); + } else { + while (true) { + Node child = traversed.remove(traversed.size() - 1); + Node parent = traversed.remove(traversed.size() - 1); + + int childIdx = indexLookup[child.getCharacter()]; + parent.children[childIdx] = null; + + if (parent.hasChildren()) break; + } + } + + return valueHoldingNode.symbol; + } + /** * Looks up a {@link Symbol} in the given char buffer, starting at the given position. * @@ -190,7 +229,28 @@ private Node insertValueChild(char value, Symbol symbol, boolean expectUnoccupie return oldValue; } - private boolean hasChildren() { + private void demoteChild(char value) { + int idx = indexOrThrow(value); + if (!(children[idx] instanceof ValueHoldingNode node)) return; + + // NOTE: assumes caller verified hasChildren() + children[idx] = new Node(value, node.children); + } + + @Nullable + private Node getChildSafe(char value) { + int idx = getIndexSafe(value); + return idx != -1 ? children[idx] : null; + } + + private int getIndexSafe(char value) { + if (value > MAX_RANGE_CHAR) return -1; + int idx = indexLookup[value]; + if (idx == INVALID_IDX) return -1; + return idx; + } + + boolean hasChildren() { return data >> HAS_CHILDREN_SHIFT == HAS_CHILDREN; } diff --git a/core/src/test/java/me/fourteendoggo/mathexpressionparser/ExecutionEnvTest.java b/core/src/test/java/me/fourteendoggo/mathexpressionparser/ExecutionEnvTest.java index 6a87e88..a3c9f1a 100644 --- a/core/src/test/java/me/fourteendoggo/mathexpressionparser/ExecutionEnvTest.java +++ b/core/src/test/java/me/fourteendoggo/mathexpressionparser/ExecutionEnvTest.java @@ -24,7 +24,6 @@ void setUp() { env = ExecutionEnv.empty(); } - // TODO: revision static Stream provideEnvironments() { return Stream.of( ExecutionEnv.empty(), @@ -215,4 +214,40 @@ void insertIfAbsentOnPresentFunction_toDoubleFunction_differentParamCount(Execut assertThat(ExpressionParser.parse("a()", env)).isEqualTo(1); } + + // TODO: some more tests on removing + + @Test + void testRemovingOnEmptyEnv() { + assertThat(env.removeSymbol("test")).isNull(); + assertThat(env.removeSymbol("")).isNull(); + assertThat(env.removeSymbol("_")).isNull(); + } + + @ParameterizedTest + @MethodSource("provideEnvironments") + void testRemovingIllegalNames(ExecutionEnv env) { + String[] idents = {"#", "'", "é", " ", " "}; + + for (String ident : idents) { + assertThatCode(() -> { + Symbol sym = env.removeSymbol(ident); + assertThat(sym).isNull(); + }).doesNotThrowAnyException(); + } + } + + @Test + void testRemovingBuiltinFunctions() { + ExecutionEnv env = ExecutionEnv.defaulted(); + String[] idents = {"sin", "cos", "max", "abs", "signum", "now", "bool", "and"}; + + for (String ident : idents) { + assertThat(env.removeSymbol(ident)).isNotNull().matches(sym -> sym.getName().equals(ident)); + assertThatThrownBy(() -> env.lookupSymbol(ident.toCharArray(), 0)) + .isInstanceOf(SymbolNotFoundException.class); + + assertThat(env.insertFunctionIfAbsent(ident, () -> 2)).isNull(); + } + } }