From 90d1cc6c9f47f1b6f74fb57e07865795a46c23b8 Mon Sep 17 00:00:00 2001 From: bculkin2442 Date: Fri, 8 Apr 2016 13:29:48 -0400 Subject: Change to data interfaces, as well as prepare to rewrite parser --- .../java/bjc/dicelang/DiceExpressionParser.java | 12 +- .../src/main/java/bjc/dicelang/PolyhedralDice.java | 63 ++++ .../bjc/dicelang/ast/DiceASTDefinedChecker.java | 61 ---- .../java/bjc/dicelang/ast/DiceASTExpression.java | 307 ------------------ .../java/bjc/dicelang/ast/DiceASTFlattener.java | 145 --------- .../main/java/bjc/dicelang/ast/DiceASTInliner.java | 136 -------- .../main/java/bjc/dicelang/ast/DiceASTParser.java | 144 --------- .../bjc/dicelang/ast/DiceASTReferenceChecker.java | 60 ---- .../java/bjc/dicelang/ast/IOperatorCollapser.java | 18 -- .../bjc/dicelang/ast/nodes/LiteralDiceNode.java | 13 +- .../bjc/dicelang/ast/nodes/OperatorDiceNode.java | 8 +- .../ast/optimization/DiceASTOptimizer.java | 59 ++-- .../dicelang/old/ast/DiceASTDefinedChecker.java | 61 ++++ .../bjc/dicelang/old/ast/DiceASTExpression.java | 344 +++++++++++++++++++++ .../bjc/dicelang/old/ast/DiceASTFlattener.java | 145 +++++++++ .../java/bjc/dicelang/old/ast/DiceASTInliner.java | 138 +++++++++ .../java/bjc/dicelang/old/ast/DiceASTParser.java | 146 +++++++++ .../dicelang/old/ast/DiceASTReferenceChecker.java | 60 ++++ .../bjc/dicelang/old/ast/IOperatorCollapser.java | 18 ++ 19 files changed, 1025 insertions(+), 913 deletions(-) delete mode 100644 dice-lang/src/main/java/bjc/dicelang/ast/DiceASTDefinedChecker.java delete mode 100644 dice-lang/src/main/java/bjc/dicelang/ast/DiceASTExpression.java delete mode 100644 dice-lang/src/main/java/bjc/dicelang/ast/DiceASTFlattener.java delete mode 100644 dice-lang/src/main/java/bjc/dicelang/ast/DiceASTInliner.java delete mode 100644 dice-lang/src/main/java/bjc/dicelang/ast/DiceASTParser.java delete mode 100644 dice-lang/src/main/java/bjc/dicelang/ast/DiceASTReferenceChecker.java delete mode 100644 dice-lang/src/main/java/bjc/dicelang/ast/IOperatorCollapser.java create mode 100644 dice-lang/src/main/java/bjc/dicelang/old/ast/DiceASTDefinedChecker.java create mode 100644 dice-lang/src/main/java/bjc/dicelang/old/ast/DiceASTExpression.java create mode 100644 dice-lang/src/main/java/bjc/dicelang/old/ast/DiceASTFlattener.java create mode 100644 dice-lang/src/main/java/bjc/dicelang/old/ast/DiceASTInliner.java create mode 100644 dice-lang/src/main/java/bjc/dicelang/old/ast/DiceASTParser.java create mode 100644 dice-lang/src/main/java/bjc/dicelang/old/ast/DiceASTReferenceChecker.java create mode 100644 dice-lang/src/main/java/bjc/dicelang/old/ast/IOperatorCollapser.java (limited to 'dice-lang/src/main/java') diff --git a/dice-lang/src/main/java/bjc/dicelang/DiceExpressionParser.java b/dice-lang/src/main/java/bjc/dicelang/DiceExpressionParser.java index 05e8942..8a8ded3 100644 --- a/dice-lang/src/main/java/bjc/dicelang/DiceExpressionParser.java +++ b/dice-lang/src/main/java/bjc/dicelang/DiceExpressionParser.java @@ -5,8 +5,8 @@ import java.util.Stack; import org.apache.commons.lang3.StringUtils; -import bjc.utils.funcdata.FunctionalList; import bjc.utils.funcdata.FunctionalStringTokenizer; +import bjc.utils.funcdata.IFunctionalList; import bjc.utils.parserutils.ShuntingYard; /** @@ -25,7 +25,7 @@ public class DiceExpressionParser { * The enviroment to use when parsing expressions * @return The parsed dice expression */ - public IDiceExpression parse(String exp, + public static IDiceExpression parse(String exp, Map env) { /* * Create a tokenizer over the strings @@ -50,8 +50,8 @@ public class DiceExpressionParser { /* * Shunt the expression to postfix form */ - FunctionalList ls = - yard.postfix(fst.toList(s -> s), s -> s); + IFunctionalList ls = yard.postfix(fst.toList(s -> s), + s -> s); /* * Create a stack for building an expression from parts @@ -82,8 +82,8 @@ public class DiceExpressionParser { * Handle scalar numbers */ dexps.push(new ScalarDie(Integer.parseInt(tok))); - } catch (NumberFormatException nfex) { - + } catch (@SuppressWarnings("unused") NumberFormatException nfex) { + // We don't care about details, just that it failed if (dexps.size() >= 2) { /* * Apply an operation to two dice diff --git a/dice-lang/src/main/java/bjc/dicelang/PolyhedralDice.java b/dice-lang/src/main/java/bjc/dicelang/PolyhedralDice.java index e0bef64..9f609b4 100644 --- a/dice-lang/src/main/java/bjc/dicelang/PolyhedralDice.java +++ b/dice-lang/src/main/java/bjc/dicelang/PolyhedralDice.java @@ -18,6 +18,15 @@ public class PolyhedralDice { return new ComplexDice(nDice, 10); } + /** + * Produce a single d10 + * + * @return A single d10 + */ + public static IDiceExpression d10() { + return d10(1); + } + /** * Produce the specified number of 100-sided dice * @@ -29,6 +38,15 @@ public class PolyhedralDice { return new ComplexDice(nDice, 100); } + /** + * Produce a single d100 + * + * @return A single d100 + */ + public static IDiceExpression d100() { + return d100(1); + } + /** * Produce the specified number of 12-sided dice * @@ -40,6 +58,15 @@ public class PolyhedralDice { return new ComplexDice(nDice, 12); } + /** + * Produce a single d12 + * + * @return A single d12 + */ + public static IDiceExpression d12() { + return d12(1); + } + /** * Produce the specified number of 20-sided dice * @@ -51,6 +78,15 @@ public class PolyhedralDice { return new ComplexDice(nDice, 20); } + /** + * Produce a single d20 + * + * @return A single d20 + */ + public static IDiceExpression d20() { + return d20(1); + } + /** * Produce the specified number of 4-sided dice * @@ -62,6 +98,15 @@ public class PolyhedralDice { return new ComplexDice(nDice, 4); } + /** + * Produce a single d4 + * + * @return A single d4 + */ + public static IDiceExpression d4() { + return d4(1); + } + /** * Produce the specified number of 6-sided dice * @@ -73,6 +118,15 @@ public class PolyhedralDice { return new ComplexDice(nDice, 6); } + /** + * Produce a single d6 + * + * @return A single d6 + */ + public static IDiceExpression d6() { + return d6(1); + } + /** * Produce the specified number of 8-sided dice * @@ -83,4 +137,13 @@ public class PolyhedralDice { public static IDiceExpression d8(int nDice) { return new ComplexDice(nDice, 8); } + + /** + * Produce a single d8 + * + * @return A single d8 + */ + public static IDiceExpression d8() { + return d8(1); + } } diff --git a/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTDefinedChecker.java b/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTDefinedChecker.java deleted file mode 100644 index c26098c..0000000 --- a/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTDefinedChecker.java +++ /dev/null @@ -1,61 +0,0 @@ -package bjc.dicelang.ast; - -import java.util.Map; -import java.util.function.Consumer; - -import bjc.dicelang.ast.nodes.DiceASTType; -import bjc.dicelang.ast.nodes.IDiceASTNode; -import bjc.dicelang.ast.nodes.VariableDiceNode; -import bjc.utils.data.IHolder; - -/** - * Check if the specified node references a particular variable - * - * @author ben - * - */ -public final class DiceASTDefinedChecker - implements Consumer { - /** - * This is true if the specified node references the set variable - */ - private IHolder referencesVariable; - - private Map enviroment; - - /** - * Create a new reference checker - * - * @param referencesVar - * The holder of whether the variable is referenced or not - * @param env - * The enviroment to check undefinedness against - */ - public DiceASTDefinedChecker(IHolder referencesVar, - Map env) { - this.referencesVariable = referencesVar; - this.enviroment = env; - } - - @Override - public void accept(IDiceASTNode astNode) { - referencesVariable.transform((bool) -> checkUndefined(astNode)); - } - - /** - * Check if a given AST node references an undefined variable - * - * @param astNode - * The node to check - * @return Whether or not the node directly the variable - */ - private boolean checkUndefined(IDiceASTNode astNode) { - if (astNode.getType() == DiceASTType.VARIABLE) { - VariableDiceNode node = (VariableDiceNode) astNode; - - return !enviroment.containsKey(node.getVariable()); - } else { - return false; - } - } -} \ No newline at end of file diff --git a/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTExpression.java b/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTExpression.java deleted file mode 100644 index 3552926..0000000 --- a/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTExpression.java +++ /dev/null @@ -1,307 +0,0 @@ -package bjc.dicelang.ast; - -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; - -import bjc.dicelang.ComplexDice; -import bjc.dicelang.IDiceExpression; -import bjc.dicelang.ast.nodes.DiceASTType; -import bjc.dicelang.ast.nodes.IDiceASTNode; -import bjc.dicelang.ast.nodes.LiteralDiceNode; -import bjc.dicelang.ast.nodes.OperatorDiceNode; -import bjc.dicelang.ast.nodes.VariableDiceNode; -import bjc.utils.data.GenHolder; -import bjc.utils.data.Pair; -import bjc.utils.funcdata.bst.ITreePart.TreeLinearizationMethod; -import bjc.utils.parserutils.AST; - -/** - * An implementation of {@link IDiceExpression} backed by an AST of - * {@link IDiceASTNode}s - * - * @author ben - * - */ -public class DiceASTExpression implements IDiceExpression { - - private static final class VariableRetriever - implements Function { - @Override - public String apply(IDiceASTNode node) { - if (node.getType() != DiceASTType.VARIABLE) { - throw new UnsupportedOperationException( - "Attempted to assign to something that isn't a variable." - + " This isn't supported yet. The problem node is " - + node); - } - - return ((VariableDiceNode) node).getVariable(); - } - } - - /** - * Build the map of operations to use when collapsing the AST - * - * @param enviroment - * The enviroment to evaluate bindings and such against - * @return The operations to use when collapsing the AST - */ - private static Map buildOperations( - Map enviroment) { - Map operatorCollapsers = new HashMap<>(); - - operatorCollapsers.put(OperatorDiceNode.ADD, - (leftNode, rightNode) -> { - return leftNode.merge((leftValue, leftAST) -> { - return rightNode.merge((rightValue, rightAST) -> { - return new Pair<>(leftValue + rightValue, - new AST<>(OperatorDiceNode.ADD, - leftAST, rightAST)); - }); - }); - - }); - operatorCollapsers.put(OperatorDiceNode.SUBTRACT, - (leftNode, rightNode) -> { - return leftNode.merge((leftValue, leftAST) -> { - return rightNode.merge((rightValue, rightAST) -> { - return new Pair<>(leftValue - rightValue, - new AST<>(OperatorDiceNode.SUBTRACT, - leftAST, rightAST)); - }); - }); - }); - - operatorCollapsers.put(OperatorDiceNode.MULTIPLY, - (leftNode, rightNode) -> { - return leftNode.merge((leftValue, leftAST) -> { - return rightNode.merge((rightValue, rightAST) -> { - return new Pair<>(leftValue * rightValue, - new AST<>(OperatorDiceNode.MULTIPLY, - leftAST, rightAST)); - }); - }); - - }); - operatorCollapsers.put(OperatorDiceNode.DIVIDE, - (leftNode, rightNode) -> { - return leftNode.merge((leftValue, leftAST) -> { - return rightNode.merge((rightValue, rightAST) -> { - if (rightValue == 0) { - throw new ArithmeticException( - "Attempted to divide by zero. The AST of the problem expression is " - + rightAST); - } - - return new Pair<>(leftValue / rightValue, - new AST<>(OperatorDiceNode.DIVIDE, - leftAST, rightAST)); - }); - }); - }); - - operatorCollapsers.put(OperatorDiceNode.ASSIGN, (left, right) -> { - return parseBinding(enviroment, left, right); - }); - - operatorCollapsers.put(OperatorDiceNode.COMPOUND, - DiceASTExpression::parseCompound); - - operatorCollapsers.put(OperatorDiceNode.GROUP, - DiceASTExpression::parseGroup); - - return operatorCollapsers; - } - - private static Pair> parseBinding( - Map enviroment, - Pair> left, - Pair> right) { - return left.merge((leftValue, leftAST) -> { - return right.merge((rightValue, rightAST) -> { - String variableName = leftAST - .collapse(new VariableRetriever(), (operator) -> { - throw new UnsupportedOperationException( - "Can only assign to plain variable names. The problem operator is " - + operator); - }, (returnedAST) -> returnedAST); - - GenHolder selfReference = new GenHolder<>(false); - - DiceASTReferenceChecker refChecker = new DiceASTReferenceChecker( - selfReference, variableName); - - rightAST.traverse(TreeLinearizationMethod.PREORDER, - refChecker); - - // Ignore meta-variable that'll be auto-frozen to restore - // definition sanity - if (selfReference.unwrap((bool) -> bool) - && !variableName.equals("last")) { - throw new UnsupportedOperationException( - "Variable '" + variableName - + "' references itself. Problematic definition: \n\t" - + rightAST); - } - - if (!variableName.equals("last")) { - enviroment.put(variableName, - new DiceASTExpression(rightAST, enviroment)); - } else { - // Do nothing, last is a auto-handled meta-variable - } - - return new Pair<>(rightValue, new AST<>( - OperatorDiceNode.ASSIGN, leftAST, rightAST)); - }); - }); - } - - private static Pair> parseCompound( - Pair> leftNode, - Pair> rightNode) { - return leftNode.merge((leftValue, leftAST) -> { - return rightNode.merge((rightValue, rightAST) -> { - int compoundValue = Integer - .parseInt(Integer.toString(leftValue) - + Integer.toString(rightValue)); - - return new Pair<>(compoundValue, new AST<>( - OperatorDiceNode.COMPOUND, leftAST, rightAST)); - }); - }); - } - - private static Pair> parseGroup( - Pair> leftNode, - Pair> rightNode) { - return leftNode.merge((leftValue, leftAST) -> { - return rightNode.merge((rightValue, rightAST) -> { - if (leftValue < 0) { - throw new UnsupportedOperationException( - "Can't attempt to roll a negative number of dice." - + " The problematic AST is " - + leftAST); - } else if (rightValue < 1) { - throw new UnsupportedOperationException( - "Can't roll dice with less than one side." - + " The problematic AST is " - + rightAST); - } - - int rolledValue = new ComplexDice(leftValue, rightValue) - .roll(); - - return new Pair<>(rolledValue, new AST<>( - OperatorDiceNode.GROUP, leftAST, rightAST)); - }); - }); - } - - /** - * The AST this expression will evaluate - */ - private AST ast; - - /** - * The enviroment to evaluate bindings and such against - */ - private Map env; - - /** - * Create a new dice expression backed by an AST - * - * @param ast - * The AST backing this expression - * @param env - * The enviroment to evaluate bindings against - */ - public DiceASTExpression(AST ast, - Map env) { - this.ast = ast; - this.env = env; - } - - /** - * Expand a leaf AST token into a pair for evaluation - * - * @param leafNode - * The token to evaluate - * @return A pair consisting of the token's value and the AST it - * represents - */ - private Pair> evaluateLeaf( - IDiceASTNode leafNode) { - if (leafNode.getType() == DiceASTType.VARIABLE) { - VariableDiceNode node = (VariableDiceNode) leafNode; - - return parseVariable(node); - } else if (leafNode.getType() == DiceASTType.LITERAL) { - LiteralDiceNode node = (LiteralDiceNode) leafNode; - - return node.toParseValue(); - } else { - throw new UnsupportedOperationException("Found leaf operator " - + leafNode + ". These aren't supported."); - } - } - - private Pair> parseVariable( - VariableDiceNode leafNode) { - String varName = leafNode.getVariable(); - - if (env.containsKey(varName)) { - return new Pair<>(env.get(varName).roll(), - new AST<>(leafNode)); - } else { - // Handle special case for defining variables - return new Pair<>(0, new AST<>(leafNode)); - } - } - - /** - * Get the AST bound to this expression - * - * @return the ast - */ - public AST getAst() { - return ast; - } - - /* - * (non-Javadoc) - * - * @see bjc.utils.dice.IDiceExpression#roll() - */ - @Override - public int roll() { - Map operations = buildOperations( - env); - - return ast.collapse(this::evaluateLeaf, operations::get, - (returnedValue) -> returnedValue - .merge((left, right) -> left)); - } - - /* - * (non-Javadoc) - * - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return ast.toString(); - } - - @Override - public int optimize() { - throw new UnsupportedOperationException( - "Use DiceASTOptimizer for optimizing these"); - } - - @Override - public boolean canOptimize() { - return false; - } -} \ No newline at end of file diff --git a/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTFlattener.java b/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTFlattener.java deleted file mode 100644 index a54a088..0000000 --- a/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTFlattener.java +++ /dev/null @@ -1,145 +0,0 @@ -package bjc.dicelang.ast; - -import java.util.HashMap; -import java.util.Map; -import java.util.function.BinaryOperator; -import java.util.function.Function; - -import org.apache.commons.lang3.StringUtils; - -import bjc.dicelang.BindingDiceExpression; -import bjc.dicelang.ComplexDice; -import bjc.dicelang.CompoundDice; -import bjc.dicelang.OperatorDiceExpression; -import bjc.dicelang.DiceExpressionType; -import bjc.dicelang.IDiceExpression; -import bjc.dicelang.ReferenceDiceExpression; -import bjc.dicelang.ScalarDie; -import bjc.dicelang.ast.nodes.DiceASTType; -import bjc.dicelang.ast.nodes.IDiceASTNode; -import bjc.dicelang.ast.nodes.LiteralDiceNode; -import bjc.dicelang.ast.nodes.OperatorDiceNode; -import bjc.dicelang.ast.nodes.VariableDiceNode; -import bjc.utils.parserutils.AST; - -/** - * Flatten an {@link AST} of {@link IDiceASTNode} into a - * {@link IDiceExpression} - * - * @author ben - * - */ -public class DiceASTFlattener { - private static final class NodeCollapser - implements Function { - private Map enviroment; - - public NodeCollapser(Map env) { - this.enviroment = env; - } - - @Override - public IDiceExpression apply(IDiceASTNode nod) { - if (nod.getType() == DiceASTType.LITERAL) { - return expFromLiteral((LiteralDiceNode) nod); - } else if (nod.getType() == DiceASTType.VARIABLE) { - String varName = ((VariableDiceNode) nod).getVariable(); - - return new ReferenceDiceExpression(varName, enviroment); - } else { - throw new UnsupportedOperationException( - "Attempted to flatten something that can't be" - + " flattened. The culprit is " + nod); - } - } - - /** - * Create a dice expression from a literal token - * - * @param tok - * The token to convert to an expression - * @return The dice expression represented by the token - */ - private static IDiceExpression - expFromLiteral(LiteralDiceNode tok) { - String data = tok.getData(); - - if (data.equals("")) { - throw new UnsupportedOperationException( - "Can't convert a blank token into a literal"); - } - - if (StringUtils.countMatches(data, 'c') == 1 - && !data.equalsIgnoreCase("c")) { - String[] strangs = data.split("c"); - - return new CompoundDice(ComplexDice.fromString(strangs[0]), - ComplexDice.fromString(strangs[1])); - } else if (StringUtils.countMatches(data, 'd') == 1 - && !data.equalsIgnoreCase("d")) { - return ComplexDice.fromString(data); - } else { - return new ScalarDie(Integer.parseInt(data)); - } - } - } - - /** - * Build the operations to use for tree flattening - * - * @param env - * The enviroment the tree will be flattened against - * @return The operations needed for tree flattening - */ - private static Map> - buildOperations(Map env) { - Map> opCollapsers = - new HashMap<>(); - - opCollapsers.put(OperatorDiceNode.ADD, (left, right) -> { - return new OperatorDiceExpression(right, left, - DiceExpressionType.ADD); - }); - opCollapsers.put(OperatorDiceNode.SUBTRACT, (left, right) -> { - return new OperatorDiceExpression(right, left, - DiceExpressionType.SUBTRACT); - }); - opCollapsers.put(OperatorDiceNode.MULTIPLY, (left, right) -> { - return new OperatorDiceExpression(right, left, - DiceExpressionType.MULTIPLY); - }); - opCollapsers.put(OperatorDiceNode.DIVIDE, (left, right) -> { - return new OperatorDiceExpression(right, left, - DiceExpressionType.DIVIDE); - }); - opCollapsers.put(OperatorDiceNode.ASSIGN, (left, right) -> { - return new BindingDiceExpression(left, right, env); - }); - opCollapsers.put(OperatorDiceNode.COMPOUND, (left, right) -> { - return new CompoundDice(left, right); - }); - opCollapsers.put(OperatorDiceNode.GROUP, (left, right) -> { - return new ComplexDice(left, right); - }); - - return opCollapsers; - } - - /** - * Flatten a AST into a dice expression - * - * @param ast - * The AST to flatten - * @param env - * The enviroment to flatten against - * @return The AST, flattened into a dice expression - */ - public static IDiceExpression flatten(AST ast, - Map env) { - Map> opCollapsers = - buildOperations(env); - - return ast.collapse(new NodeCollapser(env), opCollapsers::get, - (r) -> r); - } -} diff --git a/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTInliner.java b/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTInliner.java deleted file mode 100644 index ef4a904..0000000 --- a/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTInliner.java +++ /dev/null @@ -1,136 +0,0 @@ -package bjc.dicelang.ast; - -import java.util.function.Function; - -import bjc.dicelang.ast.nodes.DiceASTType; -import bjc.dicelang.ast.nodes.IDiceASTNode; -import bjc.dicelang.ast.nodes.VariableDiceNode; -import bjc.utils.funcdata.FunctionalList; -import bjc.utils.funcdata.FunctionalMap; -import bjc.utils.parserutils.AST; - -/** - * Inline references in a dice AST, replacing variable references with what - * the variables refer to - * - * @author ben - * - */ -public class DiceASTInliner { - private static class NodeInliner - implements Function> { - private FunctionalMap> enviroment; - - public NodeInliner(FunctionalMap> env) { - enviroment = env; - } - - @Override - public AST apply(IDiceASTNode nod) { - if (nod.getType() == DiceASTType.VARIABLE) { - return expandNode((VariableDiceNode) nod); - } else { - return new AST<>(nod); - } - } - - protected AST - expandNode(VariableDiceNode variableNode) { - String varName = variableNode.getVariable(); - - if (!enviroment.containsKey(varName)) { - throw new IllegalArgumentException( - "Attempted to freeze reference" - + " to an undefined variable " + varName); - } - - return enviroment.get(varName); - } - } - - private static final class SelectiveInliner extends NodeInliner { - - private FunctionalList variableNames; - - public SelectiveInliner( - FunctionalMap> env, - FunctionalList varNames) { - super(env); - - variableNames = varNames; - } - - @Override - protected AST - expandNode(VariableDiceNode variableNode) { - if (variableNames.contains(variableNode.getVariable())) { - return super.expandNode(variableNode); - } else { - return new AST<>(variableNode); - } - } - } - - /** - * Inline the references in an AST - * - * @param tree - * The tree to inline references in - * @param env - * The enviroment to get reference values from - * @return The tree with references inlined - */ - public static AST inlineAST(AST tree, - FunctionalMap> env) { - return selectiveInline(tree, env); - } - - /** - * Inline the references in an expression backed by an AST - * - * @param tree - * The tree-backed expression to inline references in - * @param env - * The enviroment to get reference values from - * @return The tree with references inlined - */ - public static AST inlineAST(DiceASTExpression tree, - FunctionalMap env) { - return inlineAST(tree.getAst(), - env.mapValues(expression -> expression.getAst())); - } - - /** - * Inline references to specified variables - * - * @param tree - * The tree-backed expression to inline references in - * @param env - * The enviroment to resolve variables against - * @param varNames - * The names of the variables to inline - * @return An AST with the specified variables inlined - */ - public static AST selectiveInline(AST tree, - FunctionalMap> env, - String... varNames) { - return selectiveInline(tree, env, new FunctionalList<>(varNames)); - } - - /** - * Inline references to specified variables - * - * @param tree - * The tree-backed expression to inline references in - * @param env - * The enviroment to resolve variables against - * @param varNames - * The names of the variables to inline - * @return An AST with the specified variables inline - */ - public static AST selectiveInline(AST tree, - FunctionalMap> env, - FunctionalList varNames) { - return tree.expand(new SelectiveInliner(env, varNames)); - } -} \ No newline at end of file diff --git a/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTParser.java b/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTParser.java deleted file mode 100644 index fb77ad5..0000000 --- a/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTParser.java +++ /dev/null @@ -1,144 +0,0 @@ -package bjc.dicelang.ast; - -import java.util.Deque; -import java.util.LinkedList; -import java.util.function.Function; - -import org.apache.commons.lang3.StringUtils; - -import bjc.dicelang.ast.nodes.IDiceASTNode; -import bjc.dicelang.ast.nodes.LiteralDiceNode; -import bjc.dicelang.ast.nodes.OperatorDiceNode; -import bjc.dicelang.ast.nodes.VariableDiceNode; -import bjc.utils.data.Pair; -import bjc.utils.funcdata.FunctionalList; -import bjc.utils.funcdata.FunctionalStringTokenizer; -import bjc.utils.funcutils.ListUtils; -import bjc.utils.parserutils.AST; -import bjc.utils.parserutils.ShuntingYard; -import bjc.utils.parserutils.TreeConstructor; - -/** - * Create an AST from a string expression - * - * @author ben - * - */ -public class DiceASTParser { - private static final class NodeBaker - implements Function { - @Override - public IDiceASTNode apply(String tok) { - if (isOperator(tok)) { - return OperatorDiceNode.fromString(tok); - } else if (NodeBaker.isLiteral(tok)) { - return new LiteralDiceNode(tok); - } else { - return new VariableDiceNode(tok); - } - } - - /** - * Check if a token represents a literal - * - * @param tok - * The token to check - * @return Whether or not the token represents a literal - */ - private static boolean isLiteral(String tok) { - if (StringUtils.countMatches(tok, 'c') == 1 - && !tok.equalsIgnoreCase("c")) { - return true; - } else if (StringUtils.countMatches(tok, 'd') == 1 - && !tok.equalsIgnoreCase("d")) { - return true; - } else { - try { - Integer.parseInt(tok); - return true; - } catch (NumberFormatException nfx) { - return false; - } - } - } - } - - /** - * The yard to use for shunting expressions - */ - private static ShuntingYard yard; - - static { - yard = new ShuntingYard<>(); - - yard.addOp("d", 5); // dice operator: use for creating variable - // size dice groups - yard.addOp("c", 6); // compound operator: use for creating compound - // dice from expressions - yard.addOp(":=", 0); // binding operator: Bind a name to a variable - // expression - } - - /** - * Build an AST from a string expression - * - * @param exp - * The string to build from - * @return An AST built from the passed in string - */ - public AST buildAST(String exp) { - FunctionalList tokens = - FunctionalStringTokenizer.fromString(exp).toList(); - - Deque> ops = new LinkedList<>(); - - ops.add(new Pair<>("+", "\\+")); - ops.add(new Pair<>("-", "-")); - ops.add(new Pair<>("*", "\\*")); - ops.add(new Pair<>("/", "/")); - ops.add(new Pair<>(":=", ":=")); - - FunctionalList semiExpandedTokens = - ListUtils.splitTokens(tokens, ops); - - ops = new LinkedList<>(); - - ops.add(new Pair<>("(", "\\(")); - ops.add(new Pair<>(")", "\\)")); - - FunctionalList fullyExpandedTokens = - ListUtils.deAffixTokens(semiExpandedTokens, ops); - - FunctionalList shunted = - yard.postfix(fullyExpandedTokens, (s) -> s); - - AST rawAST = TreeConstructor.constructTree(shunted, - DiceASTParser::isOperator); - - AST bakedAST = rawAST.transmuteAST(new NodeBaker()); - - return bakedAST; - } - - /** - * Check if a token represents an operator - * - * @param tok - * The token to check if it represents an operator - * @return Whether or not the token represents an operator - */ - private static boolean isOperator(String tok) { - switch (tok) { - case ":=": - case "+": - case "-": - case "*": - case "/": - case "c": - case "d": - return true; - default: - return false; - } - } -} diff --git a/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTReferenceChecker.java b/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTReferenceChecker.java deleted file mode 100644 index 8fa4d55..0000000 --- a/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTReferenceChecker.java +++ /dev/null @@ -1,60 +0,0 @@ -package bjc.dicelang.ast; - -import java.util.function.Consumer; - -import bjc.dicelang.ast.nodes.DiceASTType; -import bjc.dicelang.ast.nodes.IDiceASTNode; -import bjc.dicelang.ast.nodes.VariableDiceNode; -import bjc.utils.data.IHolder; - -/** - * Check if the specified node references a particular variable - * - * @author ben - * - */ -public final class DiceASTReferenceChecker - implements Consumer { - /** - * This is true if the specified node references the set variable - */ - private IHolder referencesVariable; - - private String varName; - - /** - * Create a new reference checker - * - * @param referencesVar - * The holder of whether the variable is referenced or not - * @param varName - * The variable to check for references in - */ - public DiceASTReferenceChecker(IHolder referencesVar, - String varName) { - this.referencesVariable = referencesVar; - this.varName = varName; - } - - @Override - public void accept(IDiceASTNode astNode) { - referencesVariable.transform((bool) -> isDirectReference(astNode)); - } - - /** - * Check if a given AST node directly references the specified variable - * - * @param astNode - * The node to check - * @return Whether or not the node directly the variable - */ - private boolean isDirectReference(IDiceASTNode astNode) { - if (astNode.getType() == DiceASTType.VARIABLE) { - VariableDiceNode node = (VariableDiceNode) astNode; - - return node.getVariable().equals(varName); - } else { - return false; - } - } -} \ No newline at end of file diff --git a/dice-lang/src/main/java/bjc/dicelang/ast/IOperatorCollapser.java b/dice-lang/src/main/java/bjc/dicelang/ast/IOperatorCollapser.java deleted file mode 100644 index c872583..0000000 --- a/dice-lang/src/main/java/bjc/dicelang/ast/IOperatorCollapser.java +++ /dev/null @@ -1,18 +0,0 @@ -package bjc.dicelang.ast; - -import java.util.function.BinaryOperator; - -import bjc.dicelang.ast.nodes.IDiceASTNode; -import bjc.utils.data.Pair; -import bjc.utils.parserutils.AST; - -/** - * Alias for operator collapsers. Because 68-char types are too long - * - * @author ben - * - */ -public interface IOperatorCollapser - extends BinaryOperator>> { - -} diff --git a/dice-lang/src/main/java/bjc/dicelang/ast/nodes/LiteralDiceNode.java b/dice-lang/src/main/java/bjc/dicelang/ast/nodes/LiteralDiceNode.java index e689c7f..1a6d2bf 100644 --- a/dice-lang/src/main/java/bjc/dicelang/ast/nodes/LiteralDiceNode.java +++ b/dice-lang/src/main/java/bjc/dicelang/ast/nodes/LiteralDiceNode.java @@ -38,7 +38,9 @@ public class LiteralDiceNode implements IDiceASTNode { /** * Create a new node with the given value - * @param val The value for this node + * + * @param val + * The value for this node */ public LiteralDiceNode(int val) { this(Integer.toString(val)); @@ -126,8 +128,12 @@ public class LiteralDiceNode implements IDiceASTNode { try { return new ScalarDie(Integer.parseInt(literalData)); } catch (NumberFormatException nfex) { - throw new UnsupportedOperationException( + UnsupportedOperationException usex = new UnsupportedOperationException( "Found malformed leaf token " + this); + + usex.initCause(nfex); + + throw usex; } } } @@ -165,7 +171,8 @@ public class LiteralDiceNode implements IDiceASTNode { try { Integer.parseInt(value); return true; - } catch (NumberFormatException nfex) { + } catch (@SuppressWarnings("unused") NumberFormatException nfex) { + // We don't care about details return false; } } diff --git a/dice-lang/src/main/java/bjc/dicelang/ast/nodes/OperatorDiceNode.java b/dice-lang/src/main/java/bjc/dicelang/ast/nodes/OperatorDiceNode.java index e1eb316..2820cfb 100644 --- a/dice-lang/src/main/java/bjc/dicelang/ast/nodes/OperatorDiceNode.java +++ b/dice-lang/src/main/java/bjc/dicelang/ast/nodes/OperatorDiceNode.java @@ -38,7 +38,11 @@ public enum OperatorDiceNode implements IDiceASTNode { /** * Represents subtracting two nodes */ - SUBTRACT; + SUBTRACT, + /** + * Represents executing one statement in the context of the other + */ + LET; /** * Create a operator node from a string @@ -63,6 +67,8 @@ public enum OperatorDiceNode implements IDiceASTNode { return GROUP; case "c": return COMPOUND; + case "->": + return LET; default: throw new IllegalArgumentException( s + " is not a valid operator node"); diff --git a/dice-lang/src/main/java/bjc/dicelang/ast/optimization/DiceASTOptimizer.java b/dice-lang/src/main/java/bjc/dicelang/ast/optimization/DiceASTOptimizer.java index e6c62ee..f6be7ab 100644 --- a/dice-lang/src/main/java/bjc/dicelang/ast/optimization/DiceASTOptimizer.java +++ b/dice-lang/src/main/java/bjc/dicelang/ast/optimization/DiceASTOptimizer.java @@ -36,26 +36,26 @@ public class DiceASTOptimizer { @Override public AST apply(AST leftAST, AST rightAST) { - AST rightBranchOfLeftAST = - leftAST.applyToRight((rightSideAST) -> rightSideAST); - AST leftBranchOfLeftAST = - leftAST.applyToRight((rightSideAST) -> rightSideAST); + AST rightBranchOfLeftAST = leftAST + .applyToRight((rightSideAST) -> rightSideAST); + AST leftBranchOfLeftAST = leftAST + .applyToRight((rightSideAST) -> rightSideAST); boolean leftContainsNestedConstant = DiceASTOptimizer .checkNodeType(rightBranchOfLeftAST, LITERAL) && DiceASTOptimizer.isNodeConstant(leftAST); - boolean isRightConstant = - DiceASTOptimizer.checkNodeType(rightAST, LITERAL) - && DiceASTOptimizer.isNodeConstant(leftAST); + boolean isRightConstant = DiceASTOptimizer + .checkNodeType(rightAST, LITERAL) + && DiceASTOptimizer.isNodeConstant(leftAST); if (leftContainsNestedConstant && isRightConstant) { int combinedValue = valueCollapser.apply( getNodeValue(rightBranchOfLeftAST), getNodeValue(rightAST)); - AST newRightBranch = - new AST<>(new LiteralDiceNode(combinedValue)); + AST newRightBranch = new AST<>( + new LiteralDiceNode(combinedValue)); return new AST<>(type, leftBranchOfLeftAST, newRightBranch); @@ -82,13 +82,13 @@ public class DiceASTOptimizer { @Override public AST apply(AST leftAST, AST rightAST) { - boolean isLeftConstant = - DiceASTOptimizer.checkNodeType(leftAST, LITERAL) - && DiceASTOptimizer.isNodeConstant(leftAST); + boolean isLeftConstant = DiceASTOptimizer + .checkNodeType(leftAST, LITERAL) + && DiceASTOptimizer.isNodeConstant(leftAST); - boolean isRightConstant = - DiceASTOptimizer.checkNodeType(rightAST, LITERAL) - && DiceASTOptimizer.isNodeConstant(leftAST); + boolean isRightConstant = DiceASTOptimizer + .checkNodeType(rightAST, LITERAL) + && DiceASTOptimizer.isNodeConstant(leftAST); if (isLeftConstant) { if (isRightConstant) { @@ -107,10 +107,8 @@ public class DiceASTOptimizer { } } - private static Map>> - buildConstantCollapsers() { - Map>> operatorCollapsers = - new HashMap<>(); + private static Map>> buildConstantCollapsers() { + Map>> operatorCollapsers = new HashMap<>(); operatorCollapsers.put(OperatorDiceNode.ADD, new ArithmeticOperationCollapser(OperatorDiceNode.ADD, @@ -131,10 +129,8 @@ public class DiceASTOptimizer { return operatorCollapsers; } - private static Map>> - buildNestedConstantCollapsers() { - Map>> operatorCollapsers = - new HashMap<>(); + private static Map>> buildNestedConstantCollapsers() { + Map>> operatorCollapsers = new HashMap<>(); operatorCollapsers.put(OperatorDiceNode.ADD, new NestedArithmeticOperationCollapser( @@ -167,9 +163,9 @@ public class DiceASTOptimizer { IDiceExpression leaf) { if (leaf.canOptimize()) { return new LiteralDiceNode(Integer.toString(leaf.optimize())); - } else { - return node; } + + return node; } private static AST finishTree(AST tree) { @@ -184,14 +180,13 @@ public class DiceASTOptimizer { * @return The optimized tree */ public static AST optimizeTree(AST tree) { - AST astWithConstantsFolded = - tree.collapse(DiceASTOptimizer::collapseLeaf, - buildConstantCollapsers()::get, - DiceASTOptimizer::finishTree); + AST astWithConstantsFolded = tree.collapse( + DiceASTOptimizer::collapseLeaf, + buildConstantCollapsers()::get, + DiceASTOptimizer::finishTree); - AST astWithNestedConstantsFolded = - astWithConstantsFolded.collapse( - DiceASTOptimizer::collapseLeaf, + AST astWithNestedConstantsFolded = astWithConstantsFolded + .collapse(DiceASTOptimizer::collapseLeaf, buildNestedConstantCollapsers()::get, DiceASTOptimizer::finishTree); diff --git a/dice-lang/src/main/java/bjc/dicelang/old/ast/DiceASTDefinedChecker.java b/dice-lang/src/main/java/bjc/dicelang/old/ast/DiceASTDefinedChecker.java new file mode 100644 index 0000000..4a20a82 --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/old/ast/DiceASTDefinedChecker.java @@ -0,0 +1,61 @@ +package bjc.dicelang.old.ast; + +import java.util.Map; +import java.util.function.Consumer; + +import bjc.dicelang.ast.nodes.DiceASTType; +import bjc.dicelang.ast.nodes.IDiceASTNode; +import bjc.dicelang.ast.nodes.VariableDiceNode; +import bjc.utils.data.IHolder; + +/** + * Check if the specified node references a particular variable + * + * @author ben + * + */ +public final class DiceASTDefinedChecker + implements Consumer { + /** + * This is true if the specified node references the set variable + */ + private IHolder referencesVariable; + + private Map enviroment; + + /** + * Create a new reference checker + * + * @param referencesVar + * The holder of whether the variable is referenced or not + * @param env + * The enviroment to check undefinedness against + */ + public DiceASTDefinedChecker(IHolder referencesVar, + Map env) { + this.referencesVariable = referencesVar; + this.enviroment = env; + } + + @Override + public void accept(IDiceASTNode astNode) { + referencesVariable.transform((bool) -> checkUndefined(astNode)); + } + + /** + * Check if a given AST node references an undefined variable + * + * @param astNode + * The node to check + * @return Whether or not the node directly the variable + */ + private boolean checkUndefined(IDiceASTNode astNode) { + if (astNode.getType() == DiceASTType.VARIABLE) { + VariableDiceNode node = (VariableDiceNode) astNode; + + return !enviroment.containsKey(node.getVariable()); + } else { + return false; + } + } +} \ No newline at end of file diff --git a/dice-lang/src/main/java/bjc/dicelang/old/ast/DiceASTExpression.java b/dice-lang/src/main/java/bjc/dicelang/old/ast/DiceASTExpression.java new file mode 100644 index 0000000..7651093 --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/old/ast/DiceASTExpression.java @@ -0,0 +1,344 @@ +package bjc.dicelang.old.ast; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BinaryOperator; +import java.util.function.Function; + +import bjc.dicelang.ComplexDice; +import bjc.dicelang.IDiceExpression; +import bjc.dicelang.ast.nodes.DiceASTType; +import bjc.dicelang.ast.nodes.IDiceASTNode; +import bjc.dicelang.ast.nodes.LiteralDiceNode; +import bjc.dicelang.ast.nodes.OperatorDiceNode; +import bjc.dicelang.ast.nodes.VariableDiceNode; +import bjc.utils.data.GenHolder; +import bjc.utils.data.Pair; +import bjc.utils.funcdata.bst.ITreePart.TreeLinearizationMethod; +import bjc.utils.parserutils.AST; + +/** + * An implementation of {@link IDiceExpression} backed by an AST of + * {@link IDiceASTNode}s + * + * @author ben + * + */ +public class DiceASTExpression implements IDiceExpression { + private static final class ArithmeticCollapser + implements IOperatorCollapser { + private OperatorDiceNode type; + + private BinaryOperator valueOp; + + public ArithmeticCollapser(OperatorDiceNode type, + BinaryOperator valueOp) { + this.type = type; + this.valueOp = valueOp; + } + + @Override + public Pair> apply( + Pair> leftNode, + Pair> rightNode) { + return leftNode.merge((leftValue, leftAST) -> { + return rightNode.merge((rightValue, rightAST) -> { + if (type == OperatorDiceNode.DIVIDE + && rightValue == 0) { + throw new ArithmeticException( + "Attempted to divide by zero. The AST of the problem expression is " + + rightAST); + } + + return new Pair<>(valueOp.apply(leftValue, rightValue), + new AST<>(type, leftAST, rightAST)); + }); + }); + } + } + + private static final class VariableRetriever + implements Function { + @Override + public String apply(IDiceASTNode node) { + if (node.getType() != DiceASTType.VARIABLE) { + throw new UnsupportedOperationException( + "Attempted to assign to something that isn't a variable." + + " This isn't supported yet. The problem node is " + + node); + } + + return ((VariableDiceNode) node).getVariable(); + } + } + + /** + * Build the map of operations to use when collapsing the AST + * + * @param enviroment + * The enviroment to evaluate bindings and such against + * @return The operations to use when collapsing the AST + */ + private static Map + buildOperations(Map enviroment) { + Map operatorCollapsers = + new HashMap<>(); + + operatorCollapsers.put(OperatorDiceNode.ADD, + new ArithmeticCollapser(OperatorDiceNode.ADD, + (left, right) -> left + right)); + + operatorCollapsers.put(OperatorDiceNode.SUBTRACT, + new ArithmeticCollapser(OperatorDiceNode.SUBTRACT, + (left, right) -> left - right)); + + operatorCollapsers.put(OperatorDiceNode.MULTIPLY, + new ArithmeticCollapser(OperatorDiceNode.MULTIPLY, + (left, right) -> left * right)); + + operatorCollapsers.put(OperatorDiceNode.DIVIDE, + new ArithmeticCollapser(OperatorDiceNode.DIVIDE, + (left, right) -> left / right)); + + operatorCollapsers.put(OperatorDiceNode.ASSIGN, (left, right) -> { + return parseBinding(enviroment, left, right); + }); + + operatorCollapsers.put(OperatorDiceNode.COMPOUND, + DiceASTExpression::parseCompound); + + operatorCollapsers.put(OperatorDiceNode.GROUP, + DiceASTExpression::parseGroup); + + operatorCollapsers.put(OperatorDiceNode.LET, (left, right) -> { + return doLet(enviroment, left, right); + }); + + return operatorCollapsers; + } + + private static Pair> doLet( + Map enviroment, + Pair> left, + Pair> right) { + return left.merge((leftValue, leftAST) -> { + return right.merge((rightValue, rightAST) -> { + if (!leftAST + .applyToHead(DiceASTExpression::isAssignNode)) { + // Just ignore the left block then + return new Pair<>(rightValue, rightAST); + } else { + // Left block has an assignment to handle + String varName = leftAST.applyToLeft((leftBranch) -> { + return getAssignmentVar(leftBranch); + }); + + return null; + } + }); + }); + } + + private static String getAssignmentVar(AST leftBranch) { + return leftBranch.applyToHead((node) -> { + return ((VariableDiceNode) node).getVariable(); + }); + } + + private static Boolean isAssignNode(IDiceASTNode node) { + return node.getType() == DiceASTType.OPERATOR + && node == OperatorDiceNode.ASSIGN; + } + + private static Pair> parseBinding( + Map enviroment, + Pair> left, + Pair> right) { + return left.merge((leftValue, leftAST) -> { + return right.merge((rightValue, rightAST) -> { + String variableName = leftAST + .collapse(new VariableRetriever(), (operator) -> { + throw new UnsupportedOperationException( + "Can only assign to plain variable names. The problem operator is " + + operator); + }, (returnedAST) -> returnedAST); + + GenHolder selfReference = new GenHolder<>(false); + + DiceASTReferenceChecker refChecker = + new DiceASTReferenceChecker(selfReference, + variableName); + + rightAST.traverse(TreeLinearizationMethod.PREORDER, + refChecker); + + // Ignore meta-variable that'll be auto-frozen to restore + // definition sanity + if (selfReference.unwrap((bool) -> bool) + && !variableName.equals("last")) { + throw new UnsupportedOperationException( + "Variable '" + variableName + + "' references itself. Problematic definition: \n\t" + + rightAST); + } + + if (!variableName.equals("last")) { + enviroment.put(variableName, + new DiceASTExpression(rightAST, enviroment)); + } else { + // Do nothing, last is a auto-handled meta-variable + } + + return new Pair<>(rightValue, new AST<>( + OperatorDiceNode.ASSIGN, leftAST, rightAST)); + }); + }); + } + + private static Pair> parseCompound( + Pair> leftNode, + Pair> rightNode) { + return leftNode.merge((leftValue, leftAST) -> { + return rightNode.merge((rightValue, rightAST) -> { + int compoundValue = + Integer.parseInt(Integer.toString(leftValue) + + Integer.toString(rightValue)); + + return new Pair<>(compoundValue, new AST<>( + OperatorDiceNode.COMPOUND, leftAST, rightAST)); + }); + }); + } + + private static Pair> parseGroup( + Pair> leftNode, + Pair> rightNode) { + return leftNode.merge((leftValue, leftAST) -> { + return rightNode.merge((rightValue, rightAST) -> { + if (leftValue < 0) { + throw new UnsupportedOperationException( + "Can't attempt to roll a negative number of dice." + + " The problematic AST is " + + leftAST); + } else if (rightValue < 1) { + throw new UnsupportedOperationException( + "Can't roll dice with less than one side." + + " The problematic AST is " + + rightAST); + } + + int rolledValue = + new ComplexDice(leftValue, rightValue).roll(); + + return new Pair<>(rolledValue, new AST<>( + OperatorDiceNode.GROUP, leftAST, rightAST)); + }); + }); + } + + /** + * The AST this expression will evaluate + */ + private AST ast; + + /** + * The enviroment to evaluate bindings and such against + */ + private Map env; + + /** + * Create a new dice expression backed by an AST + * + * @param ast + * The AST backing this expression + * @param env + * The enviroment to evaluate bindings against + */ + public DiceASTExpression(AST ast, + Map env) { + this.ast = ast; + this.env = env; + } + + /** + * Expand a leaf AST token into a pair for evaluation + * + * @param leafNode + * The token to evaluate + * @return A pair consisting of the token's value and the AST it + * represents + */ + private Pair> + evaluateLeaf(IDiceASTNode leafNode) { + if (leafNode.getType() == DiceASTType.VARIABLE) { + VariableDiceNode node = (VariableDiceNode) leafNode; + + return parseVariable(node); + } else if (leafNode.getType() == DiceASTType.LITERAL) { + LiteralDiceNode node = (LiteralDiceNode) leafNode; + + return node.toParseValue(); + } else { + throw new UnsupportedOperationException("Found leaf operator " + + leafNode + ". These aren't supported."); + } + } + + private Pair> + parseVariable(VariableDiceNode leafNode) { + String varName = leafNode.getVariable(); + + if (env.containsKey(varName)) { + return new Pair<>(env.get(varName).roll(), + new AST<>(leafNode)); + } else { + // Handle special case for defining variables + return new Pair<>(0, new AST<>(leafNode)); + } + } + + /** + * Get the AST bound to this expression + * + * @return the ast + */ + public AST getAst() { + return ast; + } + + /* + * (non-Javadoc) + * + * @see bjc.utils.dice.IDiceExpression#roll() + */ + @Override + public int roll() { + Map operations = + buildOperations(env); + + return ast.collapse(this::evaluateLeaf, operations::get, + (returnedValue) -> returnedValue + .merge((left, right) -> left)); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return ast.toString(); + } + + @Override + public int optimize() { + throw new UnsupportedOperationException( + "Use DiceASTOptimizer for optimizing these"); + } + + @Override + public boolean canOptimize() { + return false; + } +} \ No newline at end of file diff --git a/dice-lang/src/main/java/bjc/dicelang/old/ast/DiceASTFlattener.java b/dice-lang/src/main/java/bjc/dicelang/old/ast/DiceASTFlattener.java new file mode 100644 index 0000000..a7500a8 --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/old/ast/DiceASTFlattener.java @@ -0,0 +1,145 @@ +package bjc.dicelang.old.ast; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BinaryOperator; +import java.util.function.Function; + +import org.apache.commons.lang3.StringUtils; + +import bjc.dicelang.BindingDiceExpression; +import bjc.dicelang.ComplexDice; +import bjc.dicelang.CompoundDice; +import bjc.dicelang.OperatorDiceExpression; +import bjc.dicelang.DiceExpressionType; +import bjc.dicelang.IDiceExpression; +import bjc.dicelang.ReferenceDiceExpression; +import bjc.dicelang.ScalarDie; +import bjc.dicelang.ast.nodes.DiceASTType; +import bjc.dicelang.ast.nodes.IDiceASTNode; +import bjc.dicelang.ast.nodes.LiteralDiceNode; +import bjc.dicelang.ast.nodes.OperatorDiceNode; +import bjc.dicelang.ast.nodes.VariableDiceNode; +import bjc.utils.parserutils.AST; + +/** + * Flatten an {@link AST} of {@link IDiceASTNode} into a + * {@link IDiceExpression} + * + * @author ben + * + */ +public class DiceASTFlattener { + private static final class NodeCollapser + implements Function { + private Map enviroment; + + public NodeCollapser(Map env) { + this.enviroment = env; + } + + @Override + public IDiceExpression apply(IDiceASTNode nod) { + if (nod.getType() == DiceASTType.LITERAL) { + return expFromLiteral((LiteralDiceNode) nod); + } else if (nod.getType() == DiceASTType.VARIABLE) { + String varName = ((VariableDiceNode) nod).getVariable(); + + return new ReferenceDiceExpression(varName, enviroment); + } else { + throw new UnsupportedOperationException( + "Attempted to flatten something that can't be" + + " flattened. The culprit is " + nod); + } + } + + /** + * Create a dice expression from a literal token + * + * @param tok + * The token to convert to an expression + * @return The dice expression represented by the token + */ + private static IDiceExpression + expFromLiteral(LiteralDiceNode tok) { + String data = tok.getData(); + + if (data.equals("")) { + throw new UnsupportedOperationException( + "Can't convert a blank token into a literal"); + } + + if (StringUtils.countMatches(data, 'c') == 1 + && !data.equalsIgnoreCase("c")) { + String[] strangs = data.split("c"); + + return new CompoundDice(ComplexDice.fromString(strangs[0]), + ComplexDice.fromString(strangs[1])); + } else if (StringUtils.countMatches(data, 'd') == 1 + && !data.equalsIgnoreCase("d")) { + return ComplexDice.fromString(data); + } else { + return new ScalarDie(Integer.parseInt(data)); + } + } + } + + /** + * Build the operations to use for tree flattening + * + * @param env + * The enviroment the tree will be flattened against + * @return The operations needed for tree flattening + */ + private static Map> + buildOperations(Map env) { + Map> opCollapsers = + new HashMap<>(); + + opCollapsers.put(OperatorDiceNode.ADD, (left, right) -> { + return new OperatorDiceExpression(right, left, + DiceExpressionType.ADD); + }); + opCollapsers.put(OperatorDiceNode.SUBTRACT, (left, right) -> { + return new OperatorDiceExpression(right, left, + DiceExpressionType.SUBTRACT); + }); + opCollapsers.put(OperatorDiceNode.MULTIPLY, (left, right) -> { + return new OperatorDiceExpression(right, left, + DiceExpressionType.MULTIPLY); + }); + opCollapsers.put(OperatorDiceNode.DIVIDE, (left, right) -> { + return new OperatorDiceExpression(right, left, + DiceExpressionType.DIVIDE); + }); + opCollapsers.put(OperatorDiceNode.ASSIGN, (left, right) -> { + return new BindingDiceExpression(left, right, env); + }); + opCollapsers.put(OperatorDiceNode.COMPOUND, (left, right) -> { + return new CompoundDice(left, right); + }); + opCollapsers.put(OperatorDiceNode.GROUP, (left, right) -> { + return new ComplexDice(left, right); + }); + + return opCollapsers; + } + + /** + * Flatten a AST into a dice expression + * + * @param ast + * The AST to flatten + * @param env + * The enviroment to flatten against + * @return The AST, flattened into a dice expression + */ + public static IDiceExpression flatten(AST ast, + Map env) { + Map> opCollapsers = + buildOperations(env); + + return ast.collapse(new NodeCollapser(env), opCollapsers::get, + (r) -> r); + } +} diff --git a/dice-lang/src/main/java/bjc/dicelang/old/ast/DiceASTInliner.java b/dice-lang/src/main/java/bjc/dicelang/old/ast/DiceASTInliner.java new file mode 100644 index 0000000..3a8e796 --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/old/ast/DiceASTInliner.java @@ -0,0 +1,138 @@ +package bjc.dicelang.old.ast; + +import java.util.function.Function; + +import bjc.dicelang.ast.nodes.DiceASTType; +import bjc.dicelang.ast.nodes.IDiceASTNode; +import bjc.dicelang.ast.nodes.VariableDiceNode; +import bjc.utils.funcdata.FunctionalList; +import bjc.utils.funcdata.FunctionalMap; +import bjc.utils.funcdata.IFunctionalList; +import bjc.utils.funcdata.IFunctionalMap; +import bjc.utils.parserutils.AST; + +/** + * Inline references in a dice AST, replacing variable references with what + * the variables refer to + * + * @author ben + * + */ +public class DiceASTInliner { + private static class NodeInliner + implements Function> { + private IFunctionalMap> enviroment; + + public NodeInliner(IFunctionalMap> env) { + enviroment = env; + } + + @Override + public AST apply(IDiceASTNode nod) { + if (nod.getType() == DiceASTType.VARIABLE) { + return expandNode((VariableDiceNode) nod); + } + + return new AST<>(nod); + } + + protected AST expandNode( + VariableDiceNode variableNode) { + String varName = variableNode.getVariable(); + + if (!enviroment.containsKey(varName)) { + throw new IllegalArgumentException( + "Attempted to freeze reference" + + " to an undefined variable " + varName); + } + + return enviroment.get(varName); + } + } + + private static final class SelectiveInliner extends NodeInliner { + + private IFunctionalList variableNames; + + public SelectiveInliner( + IFunctionalMap> env, + IFunctionalList varNames) { + super(env); + + variableNames = varNames; + } + + @Override + protected AST expandNode( + VariableDiceNode variableNode) { + if (variableNames.contains(variableNode.getVariable())) { + return super.expandNode(variableNode); + } + + return new AST<>(variableNode); + } + } + + /** + * Inline the references in an AST + * + * @param tree + * The tree to inline references in + * @param env + * The enviroment to get reference values from + * @return The tree with references inlined + */ + public static AST inlineAST(AST tree, + IFunctionalMap> env) { + return selectiveInline(tree, env); + } + + /** + * Inline the references in an expression backed by an AST + * + * @param tree + * The tree-backed expression to inline references in + * @param env + * The enviroment to get reference values from + * @return The tree with references inlined + */ + public static AST inlineAST(DiceASTExpression tree, + FunctionalMap env) { + return inlineAST(tree.getAst(), + env.mapValues(expression -> expression.getAst())); + } + + /** + * Inline references to specified variables + * + * @param tree + * The tree-backed expression to inline references in + * @param env + * The enviroment to resolve variables against + * @param varNames + * The names of the variables to inline + * @return An AST with the specified variables inlined + */ + public static AST selectiveInline(AST tree, + IFunctionalMap> env, + String... varNames) { + return selectiveInline(tree, env, new FunctionalList<>(varNames)); + } + + /** + * Inline references to specified variables + * + * @param tree + * The tree-backed expression to inline references in + * @param env + * The enviroment to resolve variables against + * @param varNames + * The names of the variables to inline + * @return An AST with the specified variables inline + */ + public static AST selectiveInline(AST tree, + IFunctionalMap> env, + IFunctionalList varNames) { + return tree.flatMapTree(new SelectiveInliner(env, varNames)); + } +} \ No newline at end of file diff --git a/dice-lang/src/main/java/bjc/dicelang/old/ast/DiceASTParser.java b/dice-lang/src/main/java/bjc/dicelang/old/ast/DiceASTParser.java new file mode 100644 index 0000000..d8e13f7 --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/old/ast/DiceASTParser.java @@ -0,0 +1,146 @@ +package bjc.dicelang.old.ast; + +import java.util.Deque; +import java.util.LinkedList; +import java.util.function.Function; + +import org.apache.commons.lang3.StringUtils; + +import bjc.dicelang.ast.nodes.IDiceASTNode; +import bjc.dicelang.ast.nodes.LiteralDiceNode; +import bjc.dicelang.ast.nodes.OperatorDiceNode; +import bjc.dicelang.ast.nodes.VariableDiceNode; +import bjc.utils.data.IPair; +import bjc.utils.data.Pair; +import bjc.utils.funcdata.FunctionalStringTokenizer; +import bjc.utils.funcdata.IFunctionalList; +import bjc.utils.funcutils.ListUtils; +import bjc.utils.parserutils.AST; +import bjc.utils.parserutils.ShuntingYard; +import bjc.utils.parserutils.TreeConstructor; + +/** + * Create an AST from a string expression + * + * @author ben + * + */ +public class DiceASTParser { + private static final class NodeBaker + implements Function { + @Override + public IDiceASTNode apply(String tok) { + if (isOperator(tok)) { + return OperatorDiceNode.fromString(tok); + } else if (NodeBaker.isLiteral(tok)) { + return new LiteralDiceNode(tok); + } else { + return new VariableDiceNode(tok); + } + } + + /** + * Check if a token represents a literal + * + * @param tok + * The token to check + * @return Whether or not the token represents a literal + */ + private static boolean isLiteral(String tok) { + if (StringUtils.countMatches(tok, 'c') == 1 + && !tok.equalsIgnoreCase("c")) { + return true; + } else if (StringUtils.countMatches(tok, 'd') == 1 + && !tok.equalsIgnoreCase("d")) { + return true; + } else { + try { + Integer.parseInt(tok); + return true; + } catch (@SuppressWarnings("unused") NumberFormatException nfex) { + // We don't care about details + return false; + } + } + } + } + + /** + * The yard to use for shunting expressions + */ + private static ShuntingYard yard; + + static { + yard = new ShuntingYard<>(); + + yard.addOp("d", 5); // dice operator: use for creating variable + // size dice groups + yard.addOp("c", 6); // compound operator: use for creating compound + // dice from expressions + yard.addOp(":=", 0); // binding operator: Bind a name to a variable + // expression + } + + /** + * Build an AST from a string expression + * + * @param exp + * The string to build from + * @return An AST built from the passed in string + */ + public static AST buildAST(String exp) { + IFunctionalList tokens = FunctionalStringTokenizer + .fromString(exp).toList(); + + Deque> ops = new LinkedList<>(); + + ops.add(new Pair<>("+", "\\+")); + ops.add(new Pair<>("-", "-")); + ops.add(new Pair<>("*", "\\*")); + ops.add(new Pair<>("/", "/")); + ops.add(new Pair<>(":=", ":=")); + + IFunctionalList semiExpandedTokens = ListUtils + .splitTokens(tokens, ops); + + ops = new LinkedList<>(); + + ops.add(new Pair<>("(", "\\(")); + ops.add(new Pair<>(")", "\\)")); + + IFunctionalList fullyExpandedTokens = ListUtils + .deAffixTokens(semiExpandedTokens, ops); + + IFunctionalList shunted = yard.postfix(fullyExpandedTokens, + (s) -> s); + + AST rawAST = TreeConstructor.constructTree(shunted, + DiceASTParser::isOperator); + + AST bakedAST = rawAST.transmuteAST(new NodeBaker()); + + return bakedAST; + } + + /** + * Check if a token represents an operator + * + * @param tok + * The token to check if it represents an operator + * @return Whether or not the token represents an operator + */ + private static boolean isOperator(String tok) { + switch (tok) { + case ":=": + case "+": + case "-": + case "*": + case "/": + case "c": + case "d": + return true; + default: + return false; + } + } +} diff --git a/dice-lang/src/main/java/bjc/dicelang/old/ast/DiceASTReferenceChecker.java b/dice-lang/src/main/java/bjc/dicelang/old/ast/DiceASTReferenceChecker.java new file mode 100644 index 0000000..e526ee9 --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/old/ast/DiceASTReferenceChecker.java @@ -0,0 +1,60 @@ +package bjc.dicelang.old.ast; + +import java.util.function.Consumer; + +import bjc.dicelang.ast.nodes.DiceASTType; +import bjc.dicelang.ast.nodes.IDiceASTNode; +import bjc.dicelang.ast.nodes.VariableDiceNode; +import bjc.utils.data.IHolder; + +/** + * Check if the specified node references a particular variable + * + * @author ben + * + */ +public final class DiceASTReferenceChecker + implements Consumer { + /** + * This is true if the specified node references the set variable + */ + private IHolder referencesVariable; + + private String varName; + + /** + * Create a new reference checker + * + * @param referencesVar + * The holder of whether the variable is referenced or not + * @param varName + * The variable to check for references in + */ + public DiceASTReferenceChecker(IHolder referencesVar, + String varName) { + this.referencesVariable = referencesVar; + this.varName = varName; + } + + @Override + public void accept(IDiceASTNode astNode) { + referencesVariable.transform((bool) -> isDirectReference(astNode)); + } + + /** + * Check if a given AST node directly references the specified variable + * + * @param astNode + * The node to check + * @return Whether or not the node directly the variable + */ + private boolean isDirectReference(IDiceASTNode astNode) { + if (astNode.getType() == DiceASTType.VARIABLE) { + VariableDiceNode node = (VariableDiceNode) astNode; + + return node.getVariable().equals(varName); + } else { + return false; + } + } +} \ No newline at end of file diff --git a/dice-lang/src/main/java/bjc/dicelang/old/ast/IOperatorCollapser.java b/dice-lang/src/main/java/bjc/dicelang/old/ast/IOperatorCollapser.java new file mode 100644 index 0000000..65cb264 --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/old/ast/IOperatorCollapser.java @@ -0,0 +1,18 @@ +package bjc.dicelang.old.ast; + +import java.util.function.BinaryOperator; + +import bjc.dicelang.ast.nodes.IDiceASTNode; +import bjc.utils.data.Pair; +import bjc.utils.parserutils.AST; + +/** + * Alias for operator collapsers. Because 68-char types are too long + * + * @author ben + * + */ +public interface IOperatorCollapser + extends BinaryOperator>> { + // Just an alias +} -- cgit v1.2.3