Skip to content

Commit

Permalink
Add ExecutionEnv::remove support
Browse files Browse the repository at this point in the history
  • Loading branch information
FourteenBrush committed Jun 20, 2024
1 parent b83a83d commit a1ff52d
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<Node> 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.
*
Expand Down Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ void setUp() {
env = ExecutionEnv.empty();
}

// TODO: revision
static Stream<Arguments> provideEnvironments() {
return Stream.of(
ExecutionEnv.empty(),
Expand Down Expand Up @@ -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();
}
}
}

0 comments on commit a1ff52d

Please sign in to comment.