diff options
| author | bculkin2442 <bjculkin@mix.wvu.edu> | 2016-10-27 21:56:18 -0400 |
|---|---|---|
| committer | bculkin2442 <bjculkin@mix.wvu.edu> | 2016-10-27 22:12:47 -0400 |
| commit | e7413128ff4e376997de6e94e4bea5eca14811ef (patch) | |
| tree | 0749e270fdb754d04dc223abd95d47436508047f /dice-lang/src/bjc/dicelang/ast | |
| parent | e13a6981bd278c2cfc3b5ecb2517367b117f7a52 (diff) | |
Moved examples
Diffstat (limited to 'dice-lang/src/bjc/dicelang/ast')
30 files changed, 2165 insertions, 0 deletions
diff --git a/dice-lang/src/bjc/dicelang/ast/ArithmeticCollapser.java b/dice-lang/src/bjc/dicelang/ast/ArithmeticCollapser.java new file mode 100644 index 0000000..2b9eaa4 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/ArithmeticCollapser.java @@ -0,0 +1,192 @@ +package bjc.dicelang.ast; + +import java.util.function.BinaryOperator; + +import bjc.utils.data.IPair; +import bjc.utils.data.Pair; +import bjc.utils.funcdata.IList; +import bjc.utils.funcdata.ITree; +import bjc.utils.funcdata.Tree; + +import bjc.dicelang.ast.nodes.IDiceASTNode; +import bjc.dicelang.ast.nodes.OperatorDiceNode; + +/** + * Responsible for collapsing arithmetic operators + * + * @author ben + * + */ +final class ArithmeticCollapser implements IOperatorCollapser { + // The type of operator we're collapsing + private OperatorDiceNode type; + + // The operator to use to collapse operators + private BinaryOperator<Integer> valueOp; + + private int initialValue; + + public ArithmeticCollapser(OperatorDiceNode type, + BinaryOperator<Integer> valueOp, int initVal) { + this.type = type; + this.valueOp = valueOp; + this.initialValue = initVal; + } + + @Override + public IPair<IResult, ITree<IDiceASTNode>> apply( + IList<IPair<IResult, ITree<IDiceASTNode>>> nodes) { + IPair<IResult, ITree<IDiceASTNode>> initialState = new Pair<>( + new IntegerResult(initialValue), new Tree<>(type)); + + BinaryOperator<IPair<IResult, ITree<IDiceASTNode>>> reducer = ( + currentState, accumulatedState) -> { + // Force evaluation of accumulated state to prevent + // certain bugs from occuring + // accumulatedState.merge((l, r) -> null); + + return reduceStates(accumulatedState, currentState); + }; + + IPair<IResult, ITree<IDiceASTNode>> reducedState = nodes + .reduceAux(initialState, reducer, (state) -> state); + + return reducedState; + } + + private IList<IResult> combineArrayResults(IResult accumulatedValue, + IResult currentValue) { + IList<IResult> currentList = ((ArrayResult) currentValue) + .getValue(); + IList<IResult> accumulatedList = ((ArrayResult) accumulatedValue) + .getValue(); + + if (currentList.getSize() != accumulatedList.getSize()) { + throw new UnsupportedOperationException( + "Can only apply operations to equal-length arrays"); + } + + IList<IResult> resultList = currentList.combineWith( + accumulatedList, (currentNode, accumulatedNode) -> { + boolean currentNotInt = currentNode + .getType() != ResultType.INTEGER; + boolean accumulatedNotInt = accumulatedNode + .getType() != ResultType.INTEGER; + + if (currentNotInt || accumulatedNotInt) { + throw new UnsupportedOperationException( + "Nesting of array operations isn't allowed"); + } + + int accumulatedInt = ((IntegerResult) accumulatedNode) + .getValue(); + int currentInt = ((IntegerResult) currentNode) + .getValue(); + + IResult combinedValue = new IntegerResult( + valueOp.apply(accumulatedInt, currentInt)); + return combinedValue; + }); + return resultList; + } + + private IPair<IResult, ITree<IDiceASTNode>> doArithmeticCollapse( + IResult accumulatedValue, ITree<IDiceASTNode> accumulatedTree, + IResult currentValue) { + if (accumulatedValue.getType() == ResultType.DUMMY + || currentValue.getType() == ResultType.DUMMY) { + DummyResult result = new DummyResult( + "Found dummy result with either accumulated dummy (" + + ((DummyResult) accumulatedValue).getData() + + ") or current dummy (" + + ((DummyResult) currentValue).getData() + + ")."); + + return new Pair<>(result, accumulatedTree); + } + + boolean currentIsInt = currentValue + .getType() == ResultType.INTEGER; + boolean accumulatedIsInt = accumulatedValue + .getType() == ResultType.INTEGER; + + if (!currentIsInt) { + if (!accumulatedIsInt) { + IList<IResult> resultList = combineArrayResults( + accumulatedValue, currentValue); + + return new Pair<>(new ArrayResult(resultList), + accumulatedTree); + } + + IList<IResult> resultList = halfCombineLists( + ((ArrayResult) currentValue).getValue(), + accumulatedValue, true); + + return new Pair<>(new ArrayResult(resultList), + accumulatedTree); + } else if (!accumulatedIsInt) { + IList<IResult> resultList = halfCombineLists( + ((ArrayResult) accumulatedValue).getValue(), + currentValue, false); + + return new Pair<>(new ArrayResult(resultList), + accumulatedTree); + } + + int accumulatedInt = ((IntegerResult) accumulatedValue).getValue(); + int currentInt = ((IntegerResult) currentValue).getValue(); + + int combinedValue = valueOp.apply(accumulatedInt, currentInt); + + return new Pair<>(new IntegerResult(combinedValue), + accumulatedTree); + } + + private IList<IResult> halfCombineLists(IList<IResult> list, + IResult scalar, boolean scalarLeft) { + if (scalar.getType() != ResultType.INTEGER) { + throw new UnsupportedOperationException( + "Nested array operations not supported"); + } + + int scalarInt = ((IntegerResult) scalar).getValue(); + + return list.map((element) -> { + if (element.getType() != ResultType.INTEGER) { + throw new UnsupportedOperationException( + "Nested array operations not supported"); + } + + int elementInt = ((IntegerResult) element).getValue(); + + IResult combinedValue; + + if (scalarLeft) { + combinedValue = new IntegerResult( + valueOp.apply(scalarInt, elementInt)); + } else { + combinedValue = new IntegerResult( + valueOp.apply(elementInt, scalarInt)); + } + + return combinedValue; + }); + } + + private IPair<IResult, ITree<IDiceASTNode>> reduceStates( + IPair<IResult, ITree<IDiceASTNode>> accumulatedState, + IPair<IResult, ITree<IDiceASTNode>> currentState) { + return accumulatedState + .bind((accumulatedValue, accumulatedTree) -> { + return currentState + .bind((currentValue, currentTree) -> { + accumulatedTree.addChild(currentTree); + + return doArithmeticCollapse( + accumulatedValue, accumulatedTree, + currentValue); + }); + }); + } +}
\ No newline at end of file diff --git a/dice-lang/src/bjc/dicelang/ast/ArrayResult.java b/dice-lang/src/bjc/dicelang/ast/ArrayResult.java new file mode 100644 index 0000000..ac78287 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/ArrayResult.java @@ -0,0 +1,43 @@ +package bjc.dicelang.ast; + +import bjc.utils.funcdata.IList; + +/** + * Represents a result that is an array of other results + * + * @author ben + * + * TODO finish implementing me + */ +public class ArrayResult implements IResult { + private IList<IResult> arrayContents; + + /** + * Create a new array-valued result + * + * @param results + * The results in the array + */ + public ArrayResult(IList<IResult> results) { + this.arrayContents = results; + } + + @Override + public ResultType getType() { + return ResultType.ARRAY; + } + + /** + * Get the value of this result + * + * @return The value of this result + */ + public IList<IResult> getValue() { + return arrayContents; + } + + @Override + public String toString() { + return arrayContents.toString(); + } +} diff --git a/dice-lang/src/bjc/dicelang/ast/DiceASTEvaluator.java b/dice-lang/src/bjc/dicelang/ast/DiceASTEvaluator.java new file mode 100644 index 0000000..cef2e19 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/DiceASTEvaluator.java @@ -0,0 +1,321 @@ +package bjc.dicelang.ast; + +import java.util.function.Supplier; + +import bjc.utils.data.IHolder; +import bjc.utils.data.IPair; +import bjc.utils.data.Identity; +import bjc.utils.data.LazyPair; +import bjc.utils.data.Pair; +import bjc.utils.funcdata.FunctionalList; +import bjc.utils.funcdata.FunctionalMap; +import bjc.utils.funcdata.IList; +import bjc.utils.funcdata.IMap; +import bjc.utils.funcdata.ITree; +import bjc.utils.funcdata.Tree; + +import bjc.dicelang.ComplexDice; +import bjc.dicelang.ast.nodes.DiceASTType; +import bjc.dicelang.ast.nodes.DiceLiteralNode; +import bjc.dicelang.ast.nodes.DiceLiteralType; +import bjc.dicelang.ast.nodes.IDiceASTNode; +import bjc.dicelang.ast.nodes.ILiteralDiceNode; +import bjc.dicelang.ast.nodes.IntegerLiteralNode; +import bjc.dicelang.ast.nodes.OperatorDiceNode; +import bjc.dicelang.ast.nodes.VariableDiceNode; + +/** + * Evaluate a dice AST to an integer value + * + * @author ben + * + */ +public class DiceASTEvaluator { + private static IResult bindLiteralValue(IDiceASTNode leafNode, + IMap<String, ITree<IDiceASTNode>> enviroment) { + String variableName = ((VariableDiceNode) leafNode).getVariable(); + + if (enviroment.containsKey(variableName)) { + IResult result = evaluateAST(enviroment.get(variableName), + enviroment); + + return result; + } + + // Return a DummyResult to handle lets properly + return new DummyResult( + "Attempted to deref unbound variable " + variableName); + } + + /** + * 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 IMap<IDiceASTNode, IOperatorCollapser> buildOperations( + IMap<String, ITree<IDiceASTNode>> enviroment) { + IMap<IDiceASTNode, IOperatorCollapser> operatorCollapsers = new FunctionalMap<>(); + + operatorCollapsers.put(OperatorDiceNode.ADD, + new ArithmeticCollapser(OperatorDiceNode.ADD, + (left, right) -> left + right, 0)); + + operatorCollapsers.put(OperatorDiceNode.SUBTRACT, + new ArithmeticCollapser(OperatorDiceNode.SUBTRACT, + (left, right) -> left - right, 0)); + + operatorCollapsers.put(OperatorDiceNode.MULTIPLY, + new ArithmeticCollapser(OperatorDiceNode.MULTIPLY, + (left, right) -> left * right, 1)); + + operatorCollapsers.put(OperatorDiceNode.DIVIDE, + new ArithmeticCollapser(OperatorDiceNode.DIVIDE, + (left, right) -> left / right, 1)); + + operatorCollapsers.put(OperatorDiceNode.ASSIGN, (nodes) -> { + return parseBinding(enviroment, nodes); + }); + + operatorCollapsers.put(OperatorDiceNode.COMPOUND, + new ArithmeticCollapser(OperatorDiceNode.COMPOUND, + (left, right) -> { + return Integer.parseInt(Integer.toString(left) + + Integer.toString(right)); + }, 0)); + + operatorCollapsers.put(OperatorDiceNode.GROUP, + DiceASTEvaluator::parseGroup); + + operatorCollapsers.put(OperatorDiceNode.LET, (nodes) -> { + // @TODO Fix lets prematurely evaluating things + return parseLet(enviroment, nodes); + }); + + operatorCollapsers.put(OperatorDiceNode.ARRAY, (nodes) -> { + + // This is so that arrays respect lazy results properly + Supplier<IResult> resultSupplier = () -> { + IList<IResult> resultList = new FunctionalList<>(); + + nodes.forEach((node) -> { + resultList.add(node.getLeft()); + }); + + return new ArrayResult(resultList); + }; + + Supplier<ITree<IDiceASTNode>> treeSupplier = () -> { + ITree<IDiceASTNode> returnedTree = new Tree<>( + OperatorDiceNode.ARRAY); + + nodes.forEach((element) -> { + returnedTree.addChild(element.getRight()); + }); + + return returnedTree; + }; + + return new LazyPair<>(resultSupplier, treeSupplier); + }); + + return operatorCollapsers; + } + + private static void doArrayAssign( + IMap<String, ITree<IDiceASTNode>> enviroment, + IPair<IResult, ITree<IDiceASTNode>> nameNode, + ITree<IDiceASTNode> nameTree, ITree<IDiceASTNode> valueTree, + IHolder<Integer> childCount, ITree<IDiceASTNode> child) { + if (nameTree.getHead().getType() != DiceASTType.VARIABLE) { + throw new UnsupportedOperationException( + "Assigning to complex variables isn't supported. Problem node is " + + nameNode.getRight()); + } + + String varName = child.transformHead((nameNod) -> { + return ((VariableDiceNode) nameNod).getVariable(); + }); + + enviroment.put(varName, valueTree.getChild(childCount.getValue())); + + childCount.transform(val -> val + 1); + } + + /** + * Evaluate the provided AST to a numeric value + * + * @param expression + * The expression to evaluate + * @param enviroment + * The enviroment to look up variables in + * @return The integer value of the expression + */ + public static IResult evaluateAST(ITree<IDiceASTNode> expression, + IMap<String, ITree<IDiceASTNode>> enviroment) { + IMap<IDiceASTNode, IOperatorCollapser> collapsers = buildOperations( + enviroment); + + return expression.collapse( + (node) -> evaluateLeaf(node, enviroment), collapsers::get, + (pair) -> pair.getLeft()); + } + + private static IPair<IResult, ITree<IDiceASTNode>> evaluateLeaf( + IDiceASTNode leafNode, + IMap<String, ITree<IDiceASTNode>> enviroment) { + ITree<IDiceASTNode> returnedAST = new Tree<>(leafNode); + + switch (leafNode.getType()) { + case LITERAL: + return new Pair<>(evaluateLiteral(leafNode), returnedAST); + + case VARIABLE: + return new LazyPair<>(() -> { + return bindLiteralValue(leafNode, enviroment); + }, () -> returnedAST); + + case OPERATOR: + default: + throw new UnsupportedOperationException( + "Node '" + leafNode + "' cannot be a leaf."); + } + } + + private static IResult evaluateLiteral(IDiceASTNode leafNode) { + DiceLiteralType literalType = ((ILiteralDiceNode) leafNode) + .getLiteralType(); + + switch (literalType) { + case DICE: + int diceRoll = ((DiceLiteralNode) leafNode).getValue() + .roll(); + + return new IntegerResult(diceRoll); + case INTEGER: + int val = ((IntegerLiteralNode) leafNode).getValue(); + + return new IntegerResult(val); + default: + throw new UnsupportedOperationException("Literal value '" + + leafNode + "' is of a type (" + literalType + + ") not currently supported."); + } + } + + private static IPair<IResult, ITree<IDiceASTNode>> parseBinding( + IMap<String, ITree<IDiceASTNode>> enviroment, + IList<IPair<IResult, ITree<IDiceASTNode>>> nodes) { + if (nodes.getSize() != 2) { + throw new UnsupportedOperationException( + "Can only bind nodes with two children. Problem children are " + + nodes); + } + + IPair<IResult, ITree<IDiceASTNode>> nameNode = nodes.getByIndex(0); + IPair<IResult, ITree<IDiceASTNode>> valueNode = nodes + .getByIndex(1); + + return nameNode.bindRight((nameTree) -> { + return valueNode.bind((valueValue, valueTree) -> { + if (DiceASTUtils.containsSimpleVariable(nameTree)) { + String varName = nameTree.transformHead((nameNod) -> { + return ((VariableDiceNode) nameNod).getVariable(); + }); + + enviroment.put(varName, valueTree); + + return new Pair<>(valueValue, nameTree); + } else if (nameTree.getHead() == OperatorDiceNode.ARRAY) { + if (valueTree.getHead() == OperatorDiceNode.ARRAY) { + if (nameTree.getChildrenCount() != valueTree + .getChildrenCount()) { + throw new UnsupportedOperationException( + "Array assignment must be between two equal length arrays"); + } + + IHolder<Integer> childCount = new Identity<>(0); + + nameTree.doForChildren((child) -> { + doArrayAssign(enviroment, nameNode, nameTree, + valueTree, childCount, child); + + childCount.transform(val -> val + 1); + }); + + return new Pair<>(valueValue, nameTree); + } + + nameTree.doForChildren((child) -> { + String varName = child.transformHead((nameNod) -> { + return ((VariableDiceNode) nameNod) + .getVariable(); + }); + + enviroment.put(varName, valueTree); + }); + + return new Pair<>(valueValue, nameTree); + } + + throw new UnsupportedOperationException( + "Assigning to complex variables isn't supported. Problem node is " + + nameNode.getRight()); + }); + }); + } + + private static IPair<IResult, ITree<IDiceASTNode>> parseGroup( + IList<IPair<IResult, ITree<IDiceASTNode>>> nodes) { + if (nodes.getSize() != 2) { + throw new UnsupportedOperationException( + "Can only form a group from two dice"); + } + + IPair<IResult, ITree<IDiceASTNode>> numberDiceNode = nodes + .getByIndex(0); + IPair<IResult, ITree<IDiceASTNode>> diceTypeNode = nodes + .getByIndex(1); + + return numberDiceNode.bind((numberDiceValue, numberDiceTree) -> { + return diceTypeNode.bind((diceTypeValue, diceTypeTree) -> { + ComplexDice cDice = new ComplexDice( + ((IntegerResult) numberDiceValue).getValue(), + ((IntegerResult) diceTypeValue).getValue()); + + return new Pair<>(new IntegerResult(cDice.roll()), + new Tree<>(OperatorDiceNode.GROUP, numberDiceTree, + diceTypeTree)); + }); + }); + } + + private static IPair<IResult, ITree<IDiceASTNode>> parseLet( + IMap<String, ITree<IDiceASTNode>> enviroment, + IList<IPair<IResult, ITree<IDiceASTNode>>> nodes) { + if (nodes.getSize() != 2) { + throw new UnsupportedOperationException( + "Can only use let with two expressions."); + } + + ITree<IDiceASTNode> bindTree = nodes.getByIndex(0).getRight(); + ITree<IDiceASTNode> expressionTree = nodes.getByIndex(1) + .getRight(); + + IMap<String, ITree<IDiceASTNode>> letEnviroment = enviroment + .extend(); + + System.out.println("Evaluating tree for bound values"); + + evaluateAST(bindTree, letEnviroment); + + IResult exprResult = evaluateAST(expressionTree, letEnviroment); + + IList<ITree<IDiceASTNode>> childrn = nodes + .map((pair) -> pair.getRight()); + + return new Pair<>(exprResult, + new Tree<>(OperatorDiceNode.LET, childrn)); + } +} diff --git a/dice-lang/src/bjc/dicelang/ast/DiceASTInliner.java b/dice-lang/src/bjc/dicelang/ast/DiceASTInliner.java new file mode 100644 index 0000000..305c9af --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/DiceASTInliner.java @@ -0,0 +1,125 @@ +package bjc.dicelang.ast; + +import bjc.utils.funcdata.FunctionalList; +import bjc.utils.funcdata.IList; +import bjc.utils.funcdata.IMap; +import bjc.utils.funcdata.ITree; +import bjc.utils.funcdata.Tree; + +import bjc.dicelang.ast.nodes.DiceASTType; +import bjc.dicelang.ast.nodes.IDiceASTNode; +import bjc.dicelang.ast.nodes.VariableDiceNode; + +/** + * Inline variables in a dice AST + * + * @author ben + * + */ +public class DiceASTInliner { + /** + * Inline all the variables in the AST + * + * @param ast + * The AST to inline variables into + * @param enviroment + * The enviroment to inline from + * @return The inlined AST + */ + public static ITree<IDiceASTNode> inlineAll(ITree<IDiceASTNode> ast, + IMap<String, ITree<IDiceASTNode>> enviroment) { + // Tell the compiler that the null is for the entire varargs + // parameter, not a single one with a null value + return selectiveInline(ast, enviroment, (String[]) null); + } + + private static ITree<IDiceASTNode> inlineNode(IDiceASTNode node, + IMap<String, ITree<IDiceASTNode>> enviroment, + boolean specificInline, IList<String> variableNames) { + // Only variables get inlined + if (node.getType() != DiceASTType.VARIABLE) { + return new Tree<>(node); + } + + // Get the name of what we're inlining + String variableName = ((VariableDiceNode) node).getVariable(); + + // If we're inlining only certain variables, do so + if (specificInline) { + // Only inline the variable if we're supposed to + if (variableNames.contains(variableName)) { + // You can't inline non-existent variables + if (!enviroment.containsKey(variableName)) { + throw new UnsupportedOperationException( + "Attempted to inline non-existant variable " + + variableName); + } + + // Return the tree for the variable + return enviroment.get(variableName); + } + } else { + // You can't inline non-existent variables + if (!enviroment.containsKey(variableName)) { + throw new UnsupportedOperationException( + "Attempted to inline non-existant variable " + + variableName); + } + + // Return the tree for the variable + return enviroment.get(variableName); + } + + // return new Tree<>(node); + } + + /** + * Inline the specified variables in the AST + * + * @param ast + * The AST to inline variables into + * @param enviroment + * The enviroment to inline from + * @param variables + * The variables to inline + * @return The inlined AST + */ + public static ITree<IDiceASTNode> selectiveInline( + ITree<IDiceASTNode> ast, + IMap<String, ITree<IDiceASTNode>> enviroment, + IList<String> variables) { + return selectiveInline(ast, enviroment, + variables.toArray(new String[0])); + } + + /** + * Inline the specified variables in the AST + * + * @param ast + * The AST to inline variables into + * @param enviroment + * The enviroment to inline from + * @param variables + * The variables to inline + * @return The inlined AST + */ + public static ITree<IDiceASTNode> selectiveInline( + ITree<IDiceASTNode> ast, + IMap<String, ITree<IDiceASTNode>> enviroment, + String... variables) { + // If we're selectively inlining, do so + if (variables != null && variables.length > 0) { + IList<String> variableNames = new FunctionalList<>(variables); + + // Selectively inline each tree node + return ast.flatMapTree((node) -> { + return inlineNode(node, enviroment, true, variableNames); + }); + } + + // Inline everything in each node + return ast.flatMapTree((node) -> { + return inlineNode(node, enviroment, false, null); + }); + } +} diff --git a/dice-lang/src/bjc/dicelang/ast/DiceASTOptimizer.java b/dice-lang/src/bjc/dicelang/ast/DiceASTOptimizer.java new file mode 100644 index 0000000..d7fc23c --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/DiceASTOptimizer.java @@ -0,0 +1,60 @@ +package bjc.dicelang.ast; + +import bjc.utils.funcdata.FunctionalList; +import bjc.utils.funcdata.IList; +import bjc.utils.funcdata.IMap; +import bjc.utils.funcdata.ITree; + +import bjc.dicelang.ast.nodes.IDiceASTNode; +import bjc.dicelang.ast.optimization.IOptimizationPass; + +/** + * Contains optimizations appliable to a dice AST + * + * @author ben + * + */ +public class DiceASTOptimizer { + private IList<IOptimizationPass> passes; + + /** + * Create a new optimizer + */ + public DiceASTOptimizer() { + passes = new FunctionalList<>(); + } + + /** + * Add a pass to the list of optimization passes + * + * @param pass + * The pass to add + */ + public void addPass(IOptimizationPass pass) { + passes.add(pass); + } + + /** + * Optimize the passed in tree + * + * @param ast + * The tree to optimize + * @param enviroment + * The enviroment for variable references + * @return The optimized tree + */ + public ITree<IDiceASTNode> optimizeTree(ITree<IDiceASTNode> ast, + IMap<String, ITree<IDiceASTNode>> enviroment) { + ITree<IDiceASTNode> optimizedTree = passes.reduceAux(ast, + (currentPass, currentTree) -> { + return currentTree.collapse(currentPass::optimizeLeaf, + (operator) -> { + return (nodes) -> { + return currentPass.optimizeOperator( + operator, nodes); + }; + }, (tree) -> tree); + }, (tree) -> tree); + return optimizedTree; + } +} diff --git a/dice-lang/src/bjc/dicelang/ast/DiceASTParser.java b/dice-lang/src/bjc/dicelang/ast/DiceASTParser.java new file mode 100644 index 0000000..9a36951 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/DiceASTParser.java @@ -0,0 +1,160 @@ +package bjc.dicelang.ast; + +import java.util.Deque; +import java.util.InputMismatchException; +import java.util.function.Function; +import java.util.function.Predicate; + +import bjc.utils.funcdata.FunctionalList; +import bjc.utils.funcdata.FunctionalMap; +import bjc.utils.funcdata.IList; +import bjc.utils.funcdata.IMap; +import bjc.utils.funcdata.ITree; +import bjc.utils.funcdata.Tree; +import bjc.utils.funcutils.StringUtils; +import bjc.utils.parserutils.TreeConstructor; + +import bjc.dicelang.IDiceExpression; +import bjc.dicelang.ast.nodes.DiceLiteralNode; +import bjc.dicelang.ast.nodes.DiceLiteralType; +import bjc.dicelang.ast.nodes.IDiceASTNode; +import bjc.dicelang.ast.nodes.ILiteralDiceNode; +import bjc.dicelang.ast.nodes.IntegerLiteralNode; +import bjc.dicelang.ast.nodes.OperatorDiceNode; +import bjc.dicelang.ast.nodes.VariableDiceNode; + +/** + * Parse a string expression into AST form. Doesn't do anything else + * + * @author ben + * + */ +public class DiceASTParser { + private static IDiceASTNode convertLeafNode(String leafNode) { + DiceLiteralType literalType = ILiteralDiceNode + .getLiteralType(leafNode); + + if (literalType != null) { + switch (literalType) { + case DICE: + return new DiceLiteralNode( + IDiceExpression.toExpression(leafNode)); + case INTEGER: + return new IntegerLiteralNode( + Integer.parseInt(leafNode)); + default: + throw new InputMismatchException( + "Cannot convert string '" + leafNode + + "' into a literal."); + } + } + + if (leafNode.matches("[+-]?\\d*\\.\\d+")) { + throw new InputMismatchException( + "Floating point literals are not supported"); + } + + return new VariableDiceNode(leafNode); + } + + private static IDiceASTNode convertOperatorNode(String operatorNode) { + try { + return OperatorDiceNode.fromString(operatorNode); + } catch (IllegalArgumentException iaex) { + InputMismatchException imex = new InputMismatchException( + "Attempted to parse invalid operator " + operatorNode); + + imex.initCause(iaex); + + throw imex; + } + } + + /** + * Create an AST from a list of tokens + * + * @param tokens + * The list of tokens to convert + * @return An AST built from the tokens + */ + public static ITree<IDiceASTNode> createFromString( + IList<String> tokens) { + Predicate<String> specialPicker = (operator) -> { + if (StringUtils.containsOnly(operator, "\\[")) { + return true; + } else if (StringUtils.containsOnly(operator, "\\]")) { + return true; + } + + return false; + }; + + IMap<String, Function<Deque<ITree<String>>, ITree<String>>> operators = new FunctionalMap<>(); + + operators.put("[", (queuedTrees) -> { + Tree<String> openArray = new Tree<>("["); + + return openArray; + }); + + operators.put("]", (queuedTrees) -> { + return parseCloseArray(queuedTrees); + }); + + ITree<String> rawTokens = TreeConstructor.constructTree(tokens, + (token) -> { + return isOperatorNode(token); + }, specialPicker, operators::get); + + ITree<IDiceASTNode> tokenizedTree = rawTokens.rebuildTree( + DiceASTParser::convertLeafNode, + DiceASTParser::convertOperatorNode); + + return tokenizedTree; + } + + private static boolean isOperatorNode(String token) { + if (StringUtils.containsOnly(token, "\\[")) { + return true; + } else if (StringUtils.containsOnly(token, "\\]")) { + return true; + } + + if (token.equals("[]")) { + // This is a synthetic operator, constructed by [ and ] + return true; + } + + try { + OperatorDiceNode.fromString(token); + return true; + } catch (@SuppressWarnings("unused") IllegalArgumentException iaex) { + // We don't care about details + return false; + } + } + + private static ITree<String> parseCloseArray( + Deque<ITree<String>> queuedTrees) { + IList<ITree<String>> children = new FunctionalList<>(); + + while (shouldContinuePopping(queuedTrees)) { + children.add(queuedTrees.pop()); + } + + queuedTrees.pop(); + + children.reverse(); + + ITree<String> arrayTree = new Tree<>("[]", children); + + return arrayTree; + } + + private static boolean shouldContinuePopping( + Deque<ITree<String>> queuedTrees) { + String peekToken = queuedTrees.peek().getHead(); + + return !peekToken.equals("["); + } +} diff --git a/dice-lang/src/bjc/dicelang/ast/DiceASTReferenceChecker.java b/dice-lang/src/bjc/dicelang/ast/DiceASTReferenceChecker.java new file mode 100644 index 0000000..34414c5 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/DiceASTReferenceChecker.java @@ -0,0 +1,61 @@ +package bjc.dicelang.ast; + +import java.util.function.Consumer; + +import bjc.utils.data.IHolder; + +import bjc.dicelang.ast.nodes.DiceASTType; +import bjc.dicelang.ast.nodes.IDiceASTNode; +import bjc.dicelang.ast.nodes.VariableDiceNode; + +/** + * Check if the specified node references a particular variable + * + * @author ben + * + */ +public final class DiceASTReferenceChecker + implements Consumer<IDiceASTNode> { + /** + * This is true if the specified node references the set variable + */ + private IHolder<Boolean> 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<Boolean> 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); + } + + return false; + } +}
\ No newline at end of file diff --git a/dice-lang/src/bjc/dicelang/ast/DiceASTReferenceSanitizer.java b/dice-lang/src/bjc/dicelang/ast/DiceASTReferenceSanitizer.java new file mode 100644 index 0000000..d8f658e --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/DiceASTReferenceSanitizer.java @@ -0,0 +1,201 @@ +package bjc.dicelang.ast; + +import bjc.utils.data.IHolder; +import bjc.utils.data.Identity; +import bjc.utils.funcdata.IMap; +import bjc.utils.funcdata.ITree; +import bjc.utils.funcdata.TopDownTransformResult; +import bjc.utils.funcdata.Tree; + +import bjc.dicelang.ast.nodes.IDiceASTNode; +import bjc.dicelang.ast.nodes.OperatorDiceNode; +import bjc.dicelang.ast.nodes.VariableDiceNode; + +/** + * Sanitize the references in an AST so that a variable that refers to + * itself in its definition has the occurance of it replaced with its + * previous definition + * + * @author ben + * + */ +public class DiceASTReferenceSanitizer { + private static ITree<IDiceASTNode> doSanitize(ITree<IDiceASTNode> ast, + IMap<String, ITree<IDiceASTNode>> enviroment) { + if (ast.getChildrenCount() != 2) { + throw new UnsupportedOperationException( + "Assignment must have two arguments."); + } + + ITree<IDiceASTNode> nameTree = ast.getChild(0); + ITree<IDiceASTNode> valueTree = ast.getChild(1); + + if (!DiceASTUtils.containsSimpleVariable(nameTree)) { + if (nameTree.getHead() == OperatorDiceNode.ARRAY) { + IHolder<Boolean> allSimpleVariables = new Identity<>(true); + + nameTree.doForChildren((child) -> { + if (allSimpleVariables.getValue()) { + boolean isSimple = DiceASTUtils + .containsSimpleVariable(child); + + allSimpleVariables.replace(isSimple); + } + }); + + if (!allSimpleVariables.getValue()) { + throw new UnsupportedOperationException( + "Array assignment must be between variables and" + + " a expression/array of expressions"); + } + + if (valueTree.getHead() == OperatorDiceNode.ARRAY) { + if (nameTree.getChildrenCount() != valueTree + .getChildrenCount()) { + throw new UnsupportedOperationException( + "Array assignment between arrays must be" + + " between two arrays of equal length"); + } + } + } else { + throw new UnsupportedOperationException( + "Assignment must be between a variable and a expression"); + } + } + + if (nameTree.getHead() == OperatorDiceNode.ARRAY) { + if (valueTree.getHead() == OperatorDiceNode.ARRAY) { + IHolder<Integer> childCounter = new Identity<>(0); + + ITree<IDiceASTNode> returnTree = new Tree<>( + OperatorDiceNode.ARRAY); + + nameTree.doForChildren((child) -> { + String variableName = child.transformHead((node) -> { + return ((VariableDiceNode) node).getVariable(); + }); + + ITree<IDiceASTNode> currentValue = valueTree + .getChild(childCounter.getValue()); + + ITree<IDiceASTNode> sanitizedSubtree = doSingleSanitize( + ast, enviroment, child, currentValue, + variableName); + + if (sanitizedSubtree == null) { + ITree<IDiceASTNode> oldTree = new Tree<>( + ast.getHead(), child, currentValue); + + returnTree.addChild(oldTree); + } else { + returnTree.addChild(sanitizedSubtree); + } + + childCounter.transform((count) -> count + 1); + }); + + return returnTree; + } + + ITree<IDiceASTNode> returnTree = new Tree<>( + OperatorDiceNode.ARRAY); + + nameTree.doForChildren((child) -> { + String variableName = child.transformHead( + (node) -> ((VariableDiceNode) node).getVariable()); + + ITree<IDiceASTNode> sanitizedChild = doSingleSanitize(ast, + enviroment, child, valueTree, variableName); + if (sanitizedChild == null) { + ITree<IDiceASTNode> oldTree = new Tree<>(ast.getHead(), + child, valueTree); + + returnTree.addChild(oldTree); + } else { + returnTree.addChild(sanitizedChild); + } + }); + + return returnTree; + } + + String variableName = nameTree.transformHead( + (node) -> ((VariableDiceNode) node).getVariable()); + + ITree<IDiceASTNode> sanitizedTree = doSingleSanitize(ast, + enviroment, nameTree, valueTree, variableName); + + if (sanitizedTree == null) { + return ast; + } + + return sanitizedTree; + } + + private static ITree<IDiceASTNode> doSingleSanitize( + ITree<IDiceASTNode> ast, + IMap<String, ITree<IDiceASTNode>> enviroment, + ITree<IDiceASTNode> nameTree, ITree<IDiceASTNode> valueTree, + String variableName) { + if (enviroment.containsKey(variableName)) { + // @ is a meta-variable standing for the left side of an + // assignment + ITree<IDiceASTNode> oldVal = enviroment.put("@", + enviroment.get(variableName)); + + // We should always inline out references to last, because it + // will always change + ITree<IDiceASTNode> inlinedValue = DiceASTInliner + .selectiveInline(valueTree, enviroment, variableName, + "last", "@"); + + if (oldVal != null) { + enviroment.put("@", oldVal); + } else { + enviroment.remove("@"); + } + + return new Tree<>(ast.getHead(), nameTree, inlinedValue); + } + + return null; + } + + /** + * Sanitize the references in an AST + * + * @param ast + * @param enviroment + * @return The sanitized AST + */ + public static ITree<IDiceASTNode> sanitize(ITree<IDiceASTNode> ast, + IMap<String, ITree<IDiceASTNode>> enviroment) { + return ast.topDownTransform( + DiceASTReferenceSanitizer::shouldSanitize, (subTree) -> { + return doSanitize(subTree, enviroment); + }); + } + + private static TopDownTransformResult shouldSanitize( + IDiceASTNode node) { + if (!node.isOperator()) { + return TopDownTransformResult.SKIP; + } + + switch (((OperatorDiceNode) node)) { + case ASSIGN: + return TopDownTransformResult.TRANSFORM; + case ARRAY: + case LET: + return TopDownTransformResult.PASSTHROUGH; + case ADD: + case COMPOUND: + case DIVIDE: + case GROUP: + case MULTIPLY: + case SUBTRACT: + default: + return TopDownTransformResult.SKIP; + } + } +} diff --git a/dice-lang/src/bjc/dicelang/ast/DiceASTUtils.java b/dice-lang/src/bjc/dicelang/ast/DiceASTUtils.java new file mode 100644 index 0000000..d98c8fe --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/DiceASTUtils.java @@ -0,0 +1,86 @@ +package bjc.dicelang.ast; + +import bjc.utils.funcdata.ITree; + +import bjc.dicelang.IDiceExpression; +import bjc.dicelang.ScalarDie; +import bjc.dicelang.ast.nodes.DiceASTType; +import bjc.dicelang.ast.nodes.DiceLiteralNode; +import bjc.dicelang.ast.nodes.IDiceASTNode; +import bjc.dicelang.ast.nodes.ILiteralDiceNode; +import bjc.dicelang.ast.nodes.IntegerLiteralNode; + +/** + * Functions that are useful when dealing with dice ASTs + * + * @author ben + * + */ +public class DiceASTUtils { + /** + * Check if a dice AST contains a simple variable reference + * + * @param nameTree + * The tree to check for a reference in + * @return Whether or not a dice AST contains a simple variable + * reference + */ + public static boolean containsSimpleVariable( + ITree<IDiceASTNode> nameTree) { + return nameTree.transformHead((nameNode) -> { + if (nameNode.getType() != DiceASTType.VARIABLE) { + return false; + } + + return true; + }); + } + + /** + * Convert an literal AST node to a dice expression, if possible. + * + * @param tree + * The node to convert in tree form + * @return The tree as a dice expression + * + * @throws ClassCastException + * if the head of the tree is not a literal (implements + * {@link ILiteralDiceNode}) + * @throws UnsupportedOperationException + * if the head of the tree is not optimizable + */ + public static IDiceExpression literalToExpression( + ITree<IDiceASTNode> tree) { + ILiteralDiceNode literalNode = (ILiteralDiceNode) tree.getHead(); + + switch (literalNode.getLiteralType()) { + case DICE: + return ((DiceLiteralNode) literalNode).getValue(); + case INTEGER: + return new ScalarDie( + ((IntegerLiteralNode) literalNode).getValue()); + default: + throw new UnsupportedOperationException( + "This type of literal isn't convertable to an expression"); + } + } + + /** + * Convert an literal AST node to an integer, if possible. + * + * @param tree + * The literal node to convert, as a tree + * @return The node as an integer + * + * @throws ClassCastException + * if the head of the tree is not a literal (implements + * {@link ILiteralDiceNode}) + * @throws UnsupportedOperationException + * if the head of the tree is not optimizable + */ + public static int literalToInteger(ITree<IDiceASTNode> tree) { + return tree.transformHead((node) -> { + return ((ILiteralDiceNode) node).optimize(); + }); + } +} diff --git a/dice-lang/src/bjc/dicelang/ast/DummyResult.java b/dice-lang/src/bjc/dicelang/ast/DummyResult.java new file mode 100644 index 0000000..a84bb7c --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/DummyResult.java @@ -0,0 +1,31 @@ +package bjc.dicelang.ast; + +public class DummyResult implements IResult { + /* + * The reason why this result is a dummy + */ + private String dummyData; + + public DummyResult(String data) { + dummyData = data; + } + + /** + * Get the data in this dummy + * + * @return The reason why this result is a dummy + */ + public String getData() { + return dummyData; + } + + @Override + public ResultType getType() { + return ResultType.DUMMY; + } + + @Override + public String toString() { + return "Dummy with reason " + dummyData; + } +} diff --git a/dice-lang/src/bjc/dicelang/ast/IOperatorCollapser.java b/dice-lang/src/bjc/dicelang/ast/IOperatorCollapser.java new file mode 100644 index 0000000..0efaca9 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/IOperatorCollapser.java @@ -0,0 +1,20 @@ +package bjc.dicelang.ast; + +import java.util.function.Function; + +import bjc.utils.data.IPair; +import bjc.utils.funcdata.IList; +import bjc.utils.funcdata.ITree; + +import bjc.dicelang.ast.nodes.IDiceASTNode; + +/** + * Alias for operator collapsers. Because 68-char types are too long + * + * @author ben + * + */ +public interface IOperatorCollapser extends + Function<IList<IPair<IResult, ITree<IDiceASTNode>>>, IPair<IResult, ITree<IDiceASTNode>>> { + // Just an alias +} diff --git a/dice-lang/src/bjc/dicelang/ast/IResult.java b/dice-lang/src/bjc/dicelang/ast/IResult.java new file mode 100644 index 0000000..9a3f325 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/IResult.java @@ -0,0 +1,16 @@ +package bjc.dicelang.ast; + +/** + * Represents a result from an expression evaluation + * + * @author ben + * + */ +public interface IResult { + /** + * Get the type of this result + * + * @return The type of this result + */ + public ResultType getType(); +} diff --git a/dice-lang/src/bjc/dicelang/ast/IntegerResult.java b/dice-lang/src/bjc/dicelang/ast/IntegerResult.java new file mode 100644 index 0000000..ce61d38 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/IntegerResult.java @@ -0,0 +1,40 @@ +package bjc.dicelang.ast; + +/** + * Represents a integer-valued result + * + * @author ben + * + */ +public class IntegerResult implements IResult { + private int value; + + /** + * Create a new integer valued result + * + * @param val + * The value of the result + */ + public IntegerResult(int val) { + value = val; + } + + @Override + public ResultType getType() { + return ResultType.INTEGER; + } + + /** + * Get the value of this result + * + * @return The value of this result + */ + public int getValue() { + return value; + } + + @Override + public String toString() { + return Integer.toString(value); + } +} diff --git a/dice-lang/src/bjc/dicelang/ast/ResultType.java b/dice-lang/src/bjc/dicelang/ast/ResultType.java new file mode 100644 index 0000000..9e3b129 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/ResultType.java @@ -0,0 +1,22 @@ +package bjc.dicelang.ast; + +/** + * Represents the result of a computation + * + * @author ben + * + */ +public enum ResultType { + /** + * Represents a result that is equivalent to a single integer + */ + INTEGER, + /** + * Represents a result that is an array + */ + ARRAY, + /** + * Represents something not to poke at + */ + DUMMY +} diff --git a/dice-lang/src/bjc/dicelang/ast/nodes/DiceASTType.java b/dice-lang/src/bjc/dicelang/ast/nodes/DiceASTType.java new file mode 100644 index 0000000..9feb461 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/nodes/DiceASTType.java @@ -0,0 +1,27 @@ +package bjc.dicelang.ast.nodes; + +/** + * An enum to represent the type of node an AST node is + * + * @author ben + * + */ +public enum DiceASTType { + /** + * A node that contains a literal value + */ + LITERAL, + /** + * A node that contains an operator expression + */ + OPERATOR, + /** + * A node that contains a variable reference + */ + VARIABLE; + + @Override + public String toString() { + return this.name().toLowerCase(); + } +}
\ No newline at end of file diff --git a/dice-lang/src/bjc/dicelang/ast/nodes/DiceLiteralNode.java b/dice-lang/src/bjc/dicelang/ast/nodes/DiceLiteralNode.java new file mode 100644 index 0000000..b398ac6 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/nodes/DiceLiteralNode.java @@ -0,0 +1,52 @@ +package bjc.dicelang.ast.nodes; + +import bjc.dicelang.IDiceExpression; + +/** + * Represents a literal backed by a dice expression + * + * @author ben + * + */ +public class DiceLiteralNode implements ILiteralDiceNode { + private IDiceExpression expression; + + /** + * Create a new literal from an expression + * + * @param exp + * The expression to attempt to create a literal from + */ + public DiceLiteralNode(IDiceExpression exp) { + expression = exp; + } + + @Override + public boolean canOptimize() { + return expression.canOptimize(); + } + + @Override + public DiceLiteralType getLiteralType() { + return DiceLiteralType.DICE; + } + + /** + * Return the expression being represented + * + * @return The expression being represented + */ + public IDiceExpression getValue() { + return expression; + } + + @Override + public int optimize() { + return expression.optimize(); + } + + @Override + public String toString() { + return expression.toString(); + } +} diff --git a/dice-lang/src/bjc/dicelang/ast/nodes/DiceLiteralType.java b/dice-lang/src/bjc/dicelang/ast/nodes/DiceLiteralType.java new file mode 100644 index 0000000..41c6b05 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/nodes/DiceLiteralType.java @@ -0,0 +1,18 @@ +package bjc.dicelang.ast.nodes; + +/** + * Represents the type of literals that can be in an AST + * + * @author ben + * + */ +public enum DiceLiteralType { + /** + * Represents a integral constant + */ + INTEGER, + /** + * Represents a dice literal + */ + DICE; +} diff --git a/dice-lang/src/bjc/dicelang/ast/nodes/DiceOperatorType.java b/dice-lang/src/bjc/dicelang/ast/nodes/DiceOperatorType.java new file mode 100644 index 0000000..7cc1e42 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/nodes/DiceOperatorType.java @@ -0,0 +1,29 @@ +package bjc.dicelang.ast.nodes; + +/** + * Represents the different type of operators. + * + * Mostly, what distinguishes groups is that all the operators in a group + * have similiar precedence, and operate on similiar things + * + * @author ben + * + */ +public enum DiceOperatorType { + /** + * Represents operators that do math operations + */ + MATH, + /** + * Represents operators that do things with arrays + */ + ARRAY, + /** + * Represents operators that do things with dice + */ + DICE, + /** + * Represents operators that do things with expressions + */ + EXPRESSION; +} diff --git a/dice-lang/src/bjc/dicelang/ast/nodes/IDiceASTNode.java b/dice-lang/src/bjc/dicelang/ast/nodes/IDiceASTNode.java new file mode 100644 index 0000000..b7bf9a6 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/nodes/IDiceASTNode.java @@ -0,0 +1,23 @@ +package bjc.dicelang.ast.nodes; + +/** + * The interface for a node in a dice AST + * + * @author ben + * + */ +public interface IDiceASTNode { + /** + * Get the type of AST node this node is + * + * @return The type of AST node this AST node is + */ + public DiceASTType getType(); + + /** + * Check if this node represents an operator or not + * + * @return Whether or not this node represents an operator + */ + public boolean isOperator(); +}
\ No newline at end of file diff --git a/dice-lang/src/bjc/dicelang/ast/nodes/ILiteralDiceNode.java b/dice-lang/src/bjc/dicelang/ast/nodes/ILiteralDiceNode.java new file mode 100644 index 0000000..b94bcc8 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/nodes/ILiteralDiceNode.java @@ -0,0 +1,73 @@ +package bjc.dicelang.ast.nodes; + +/** + * Represents a literal of some type in the AST + * + * @author ben + * + */ +public interface ILiteralDiceNode extends IDiceASTNode { + /** + * Check if a token represents a literal, and if so, what type + * + * @param tok + * The token to check + * @return The type the literal would be if it is one, or null + * otherwise + */ + static DiceLiteralType getLiteralType(String tok) { + String diceGroupOrNumber = "[(?:\\d*d\\d+)(?:\\d+)]"; + + if (tok.matches("\\A" + diceGroupOrNumber + "?" + "c" + + diceGroupOrNumber + "\\Z")) { + return DiceLiteralType.DICE; + } + + String diceGroup = "\\d*d\\d+\\"; + + if (tok.matches("\\A" + diceGroup + "Z")) { + return DiceLiteralType.DICE; + } + + try { + Integer.parseInt(tok); + return DiceLiteralType.INTEGER; + } catch (@SuppressWarnings("unused") NumberFormatException nfex) { + // We don't care about details + return null; + } + } + + /** + * Check if this node can be optimized to a constant + * + * @return Whether or not this node can be optimized to a constant + * @see bjc.dicelang.IDiceExpression#canOptimize() + */ + boolean canOptimize(); + + /** + * Get the type of literal this node represents + * + * @return The type of literal this node represents + */ + DiceLiteralType getLiteralType(); + + @Override + default DiceASTType getType() { + return DiceASTType.LITERAL; + } + + @Override + default boolean isOperator() { + return false; + } + + /** + * Optimize this node to a constant if possible + * + * @return This node in constant form if possible + * @see bjc.dicelang.IDiceExpression#optimize() + */ + int optimize(); +} diff --git a/dice-lang/src/bjc/dicelang/ast/nodes/IntegerLiteralNode.java b/dice-lang/src/bjc/dicelang/ast/nodes/IntegerLiteralNode.java new file mode 100644 index 0000000..3d43bb1 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/nodes/IntegerLiteralNode.java @@ -0,0 +1,50 @@ +package bjc.dicelang.ast.nodes; + +/** + * Represents an integer literal of some kind + * + * @author ben + * + */ +public class IntegerLiteralNode implements ILiteralDiceNode { + private int value; + + /** + * Create a new integer literal from the given number + * + * @param val + * The value this node represents + */ + public IntegerLiteralNode(int val) { + value = val; + } + + @Override + public boolean canOptimize() { + return true; + } + + @Override + public DiceLiteralType getLiteralType() { + return DiceLiteralType.INTEGER; + } + + /** + * Get the value this node represents + * + * @return The integer value of this node + */ + public int getValue() { + return value; + } + + @Override + public int optimize() { + return value; + } + + @Override + public String toString() { + return Integer.toString(value); + } +} diff --git a/dice-lang/src/bjc/dicelang/ast/nodes/OperatorDiceNode.java b/dice-lang/src/bjc/dicelang/ast/nodes/OperatorDiceNode.java new file mode 100644 index 0000000..7c0a29d --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/nodes/OperatorDiceNode.java @@ -0,0 +1,110 @@ +package bjc.dicelang.ast.nodes; + +import static bjc.dicelang.ast.nodes.DiceOperatorType.DICE; +import static bjc.dicelang.ast.nodes.DiceOperatorType.EXPRESSION; +import static bjc.dicelang.ast.nodes.DiceOperatorType.MATH; + +/** + * A node that represents an operator + * + * @author ben + * + */ +public enum OperatorDiceNode implements IDiceASTNode { + /** + * Represents adding two nodes + */ + ADD(MATH), + /** + * Represents dividing two nodes + */ + DIVIDE(MATH), + /** + * Represents multiplying two nodes + */ + MULTIPLY(MATH), + /** + * Represents subtracting two nodes + */ + SUBTRACT(MATH), + /** + * Representings combining two node values together + */ + COMPOUND(DICE), + /** + * Represents using one node a variable number of times + */ + GROUP(DICE), + /** + * Represents constructing an array from a sequence of expressions + */ + ARRAY(DiceOperatorType.ARRAY), + /** + * Represents assigning one node to another + */ + ASSIGN(EXPRESSION), + /** + * Represents evaluating one expression in the context of another + */ + LET(EXPRESSION); + + /** + * Create a operator node from a string + * + * @param s + * The string to convert to a node + * @return The operator corresponding to the node + */ + public static OperatorDiceNode fromString(String s) { + switch (s) { + case ":=": + return ASSIGN; + case "+": + return ADD; + case "-": + return SUBTRACT; + case "*": + return MULTIPLY; + case "/": + return DIVIDE; + case "d": + case "group": + return GROUP; + case "c": + case "compound": + return COMPOUND; + case "=>": + return LET; + case "[]": + return ARRAY; + default: + throw new IllegalArgumentException( + s + " is not a valid operator node"); + } + } + + /** + * Represents the group of operator this operator is sorted into. + * + */ + public final DiceOperatorType type; + + private OperatorDiceNode(DiceOperatorType ty) { + type = ty; + } + + @Override + public DiceASTType getType() { + return DiceASTType.OPERATOR; + } + + /* + * (non-Javadoc) + * + * @see bjc.utils.dice.ast.IDiceASTNode#isOperator() + */ + @Override + public boolean isOperator() { + return true; + } +} diff --git a/dice-lang/src/bjc/dicelang/ast/nodes/VariableDiceNode.java b/dice-lang/src/bjc/dicelang/ast/nodes/VariableDiceNode.java new file mode 100644 index 0000000..da66608 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/nodes/VariableDiceNode.java @@ -0,0 +1,101 @@ +package bjc.dicelang.ast.nodes; + +/** + * A node that represents a reference to a variable + * + * @author ben + * + */ +public class VariableDiceNode implements IDiceASTNode { + /** + * The variable referenced by this node + */ + private String variableName; + + /** + * Create a new node representing the specified variable + * + * @param varName + * The name of the variable being referenced + */ + public VariableDiceNode(String varName) { + this.variableName = varName; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + // Handle special cases + if (this == obj) { + return true; + } else if (obj == null) { + return false; + } else if (getClass() != obj.getClass()) { + return false; + } else { + VariableDiceNode other = (VariableDiceNode) obj; + + if (variableName == null) { + if (other.variableName != null) { + return false; + } + } else if (!variableName.equals(other.variableName)) { + return false; + } + + return true; + } + } + + @Override + public DiceASTType getType() { + return DiceASTType.VARIABLE; + } + + /** + * Get the variable referenced by this AST node + * + * @return the variable referenced by this AST node + */ + public String getVariable() { + return variableName; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((variableName == null) ? 0 : variableName.hashCode()); + return result; + } + + /* + * (non-Javadoc) + * + * @see bjc.utils.dice.ast.IDiceASTNode#isOperator() + */ + @Override + public boolean isOperator() { + return false; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return variableName; + } +}
\ No newline at end of file diff --git a/dice-lang/src/bjc/dicelang/ast/nodes/package-info.java b/dice-lang/src/bjc/dicelang/ast/nodes/package-info.java new file mode 100644 index 0000000..f0f7366 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/nodes/package-info.java @@ -0,0 +1,7 @@ +/** + * Classes for nodes in the dice-lang AST + * + * @author ben + * + */ +package bjc.dicelang.ast.nodes;
\ No newline at end of file diff --git a/dice-lang/src/bjc/dicelang/ast/optimization/ArithmeticCollapser.java b/dice-lang/src/bjc/dicelang/ast/optimization/ArithmeticCollapser.java new file mode 100644 index 0000000..960fbf7 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/optimization/ArithmeticCollapser.java @@ -0,0 +1,50 @@ +package bjc.dicelang.ast.optimization; + +import java.util.function.BinaryOperator; + +import bjc.utils.funcdata.IList; +import bjc.utils.funcdata.ITree; +import bjc.utils.funcdata.Tree; + +import bjc.dicelang.ast.DiceASTUtils; +import bjc.dicelang.ast.nodes.DiceASTType; +import bjc.dicelang.ast.nodes.IDiceASTNode; +import bjc.dicelang.ast.nodes.ILiteralDiceNode; +import bjc.dicelang.ast.nodes.IntegerLiteralNode; +import bjc.dicelang.ast.nodes.OperatorDiceNode; + +class ArithmeticCollapser { + private BinaryOperator<Integer> reducer; + private OperatorDiceNode type; + + public ArithmeticCollapser(BinaryOperator<Integer> reducr, + OperatorDiceNode typ) { + reducer = reducr; + this.type = typ; + } + + public ITree<IDiceASTNode> collapse( + IList<ITree<IDiceASTNode>> children) { + boolean allConstant = children.allMatch((subtree) -> { + return subtree.transformHead((node) -> { + if (node.getType() == DiceASTType.LITERAL) { + return ((ILiteralDiceNode) node).canOptimize(); + } + + return false; + }); + }); + + if (!allConstant) { + return new Tree<>(type, children); + } + + int initState = DiceASTUtils.literalToInteger(children.first()); + + return children.tail().reduceAux(initState, + (currentNode, state) -> { + return reducer.apply(state, + DiceASTUtils.literalToInteger(currentNode)); + }, (state) -> new Tree<>(new IntegerLiteralNode(state))); + } +} diff --git a/dice-lang/src/bjc/dicelang/ast/optimization/ConstantCollapser.java b/dice-lang/src/bjc/dicelang/ast/optimization/ConstantCollapser.java new file mode 100644 index 0000000..95badd2 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/optimization/ConstantCollapser.java @@ -0,0 +1,91 @@ +package bjc.dicelang.ast.optimization; + +import bjc.utils.funcdata.IList; +import bjc.utils.funcdata.ITree; +import bjc.utils.funcdata.Tree; + +import bjc.dicelang.ComplexDice; +import bjc.dicelang.ast.DiceASTUtils; +import bjc.dicelang.ast.nodes.IDiceASTNode; +import bjc.dicelang.ast.nodes.IntegerLiteralNode; +import bjc.dicelang.ast.nodes.OperatorDiceNode; + +/** + * Collapses operations with constants to constants + * + * @author ben + * + */ +public class ConstantCollapser implements IOptimizationPass { + private static final ArithmeticCollapser additionCollapser = new ArithmeticCollapser( + (left, right) -> left + right, OperatorDiceNode.ADD); + + private static final ArithmeticCollapser divideCollapser = new ArithmeticCollapser( + (left, right) -> left / right, OperatorDiceNode.DIVIDE); + + private static final ArithmeticCollapser multiplyCollapser = new ArithmeticCollapser( + (left, right) -> left * right, OperatorDiceNode.MULTIPLY); + + private static final ArithmeticCollapser subtractCollapser = new ArithmeticCollapser( + (left, right) -> left - right, OperatorDiceNode.SUBTRACT); + + private static final ArithmeticCollapser compoundCollapser = new ArithmeticCollapser( + (left, right) -> Integer.parseInt( + Integer.toString(left) + Integer.toString(left)), + OperatorDiceNode.COMPOUND); + + @Override + public ITree<IDiceASTNode> optimizeLeaf(IDiceASTNode leafNode) { + // We don't do anything special here + return new Tree<>(leafNode); + } + + @Override + public ITree<IDiceASTNode> optimizeOperator(IDiceASTNode operator, + IList<ITree<IDiceASTNode>> children) { + if (!operator.isOperator()) { + return new Tree<>(operator, children); + } + + switch ((OperatorDiceNode) operator) { + case ADD: + return additionCollapser.collapse(children); + case DIVIDE: + return divideCollapser.collapse(children); + case MULTIPLY: + return multiplyCollapser.collapse(children); + case SUBTRACT: + return subtractCollapser.collapse(children); + case COMPOUND: + return compoundCollapser.collapse(children); + case GROUP: + if (children.getSize() != 2) { + return new Tree<>(operator, children); + } + + ComplexDice dice = new ComplexDice( + DiceASTUtils.literalToExpression( + children.getByIndex(0)), + DiceASTUtils.literalToExpression( + children.getByIndex(1))); + + if (dice.canOptimize()) { + return new Tree<>( + new IntegerLiteralNode(dice.optimize())); + } + + return new Tree<>(operator, children); + case ARRAY: + if (children.getSize() != 1) { + return new Tree<>(operator, children); + } + + return children.first(); + case ASSIGN: + case LET: + default: + // We don't optimize these operators + return new Tree<>(operator, children); + } + } +} diff --git a/dice-lang/src/bjc/dicelang/ast/optimization/IOptimizationPass.java b/dice-lang/src/bjc/dicelang/ast/optimization/IOptimizationPass.java new file mode 100644 index 0000000..36b03f1 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/optimization/IOptimizationPass.java @@ -0,0 +1,35 @@ +package bjc.dicelang.ast.optimization; + +import bjc.utils.funcdata.IList; +import bjc.utils.funcdata.ITree; + +import bjc.dicelang.ast.nodes.IDiceASTNode; + +/** + * Represents a pass of optimizations over a dice AST + * + * @author ben + * + */ +public interface IOptimizationPass { + /** + * Optimize a leaf in the tree + * + * @param leafNode + * The node to optimize + * @return The optimized node + */ + public ITree<IDiceASTNode> optimizeLeaf(IDiceASTNode leafNode); + + /** + * Optimize an operator in an AST node + * + * @param operator + * The operator being optimized + * @param children + * The children of the operator being optimized + * @return The optimized node + */ + public ITree<IDiceASTNode> optimizeOperator(IDiceASTNode operator, + IList<ITree<IDiceASTNode>> children); +} diff --git a/dice-lang/src/bjc/dicelang/ast/optimization/OperationCondenser.java b/dice-lang/src/bjc/dicelang/ast/optimization/OperationCondenser.java new file mode 100644 index 0000000..f646a17 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/optimization/OperationCondenser.java @@ -0,0 +1,107 @@ +package bjc.dicelang.ast.optimization; + +import bjc.utils.data.IHolder; +import bjc.utils.data.Identity; +import bjc.utils.funcdata.ITree; +import bjc.utils.funcdata.TopDownTransformResult; +import bjc.utils.funcdata.Tree; + +import bjc.dicelang.ast.nodes.DiceASTType; +import bjc.dicelang.ast.nodes.IDiceASTNode; +import bjc.dicelang.ast.nodes.OperatorDiceNode; + +/** + * Condenses chained operations into a single level + * + * @author ben + * + */ +public class OperationCondenser { + /** + * Condense chained similiar operations into a single level + * + * @param ast + * The AST to condense + * @return The condensed AST + */ + public static ITree<IDiceASTNode> condense(ITree<IDiceASTNode> ast) { + return ast.topDownTransform(OperationCondenser::pickNode, + OperationCondenser::doCondense); + } + + private static ITree<IDiceASTNode> doCondense( + ITree<IDiceASTNode> ast) { + OperatorDiceNode operation = ast + .transformHead((node) -> (OperatorDiceNode) node); + + IHolder<Boolean> canCondense = new Identity<>(true); + + ast.doForChildren((child) -> { + if (canCondense.getValue()) { + canCondense.replace(child.transformHead((node) -> { + if (node.getType() == DiceASTType.OPERATOR) { + if (operation.equals(node)) { + return true; + } + + return false; + } + + return true; + })); + } + }); + + if (!canCondense.getValue()) { + return ast; + } + + ITree<IDiceASTNode> condensedAST = new Tree<>(operation); + + ast.doForChildren((child) -> { + if (child.getHead().getType() == DiceASTType.OPERATOR) { + child.doForChildren((subChild) -> { + condensedAST.addChild(subChild); + }); + } else { + condensedAST.addChild(child); + } + }); + + return condensedAST; + } + + private static TopDownTransformResult pickNode(IDiceASTNode node) { + switch (node.getType()) { + case LITERAL: + return TopDownTransformResult.SKIP; + case OPERATOR: + return pickOperator((OperatorDiceNode) node); + case VARIABLE: + return TopDownTransformResult.SKIP; + default: + throw new UnsupportedOperationException( + "Attempted to traverse unknown node type " + node); + } + } + + private static TopDownTransformResult pickOperator( + OperatorDiceNode node) { + switch (node) { + case ADD: + case MULTIPLY: + case SUBTRACT: + case DIVIDE: + case COMPOUND: + return TopDownTransformResult.PUSHDOWN; + case ARRAY: + case ASSIGN: + case GROUP: + case LET: + return TopDownTransformResult.PASSTHROUGH; + default: + throw new UnsupportedOperationException( + "Attempted to traverse unknown operator " + node); + } + } +} diff --git a/dice-lang/src/bjc/dicelang/ast/optimization/package-info.java b/dice-lang/src/bjc/dicelang/ast/optimization/package-info.java new file mode 100644 index 0000000..6f75bf9 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/optimization/package-info.java @@ -0,0 +1,7 @@ +/** + * Contains optimizations on dice ASTs + * + * @author ben + * + */ +package bjc.dicelang.ast.optimization;
\ No newline at end of file diff --git a/dice-lang/src/bjc/dicelang/ast/package-info.java b/dice-lang/src/bjc/dicelang/ast/package-info.java new file mode 100644 index 0000000..f6352aa --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ast/package-info.java @@ -0,0 +1,7 @@ +/** + * New implementation of AST for dice-lang + * + * @author ben + * + */ +package bjc.dicelang.ast;
\ No newline at end of file |
