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> 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 buildOperations( IMap> enviroment) { IMap 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 resultSupplier = () -> { IList resultList = new FunctionalList<>(); nodes.forEach((node) -> { resultList.add(node.getLeft()); }); return new ArrayResult(resultList); }; Supplier> treeSupplier = () -> { ITree 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> enviroment, IPair> nameNode, ITree nameTree, ITree valueTree, IHolder childCount, ITree 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 expression, IMap> enviroment) { IMap collapsers = buildOperations( enviroment); return expression.collapse( (node) -> evaluateLeaf(node, enviroment), collapsers::get, (pair) -> pair.getLeft()); } private static IPair> evaluateLeaf( IDiceASTNode leafNode, IMap> enviroment) { ITree 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> parseBinding( IMap> enviroment, IList>> nodes) { if (nodes.getSize() != 2) { throw new UnsupportedOperationException( "Can only bind nodes with two children. Problem children are " + nodes); } IPair> nameNode = nodes.getByIndex(0); IPair> 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 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> parseGroup( IList>> nodes) { if (nodes.getSize() != 2) { throw new UnsupportedOperationException( "Can only form a group from two dice"); } IPair> numberDiceNode = nodes .getByIndex(0); IPair> 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> parseLet( IMap> enviroment, IList>> nodes) { if (nodes.getSize() != 2) { throw new UnsupportedOperationException( "Can only use let with two expressions."); } ITree bindTree = nodes.getByIndex(0).getRight(); ITree expressionTree = nodes.getByIndex(1) .getRight(); IMap> letEnviroment = enviroment .extend(); System.out.println("Evaluating tree for bound values"); evaluateAST(bindTree, letEnviroment); IResult exprResult = evaluateAST(expressionTree, letEnviroment); IList> childrn = nodes .map((pair) -> pair.getRight()); return new Pair<>(exprResult, new Tree<>(OperatorDiceNode.LET, childrn)); } }