package bjc.dicelang.v1.ast; import bjc.dicelang.v1.ast.nodes.IDiceASTNode; import bjc.dicelang.v1.ast.nodes.OperatorDiceNode; import bjc.dicelang.v1.ast.nodes.VariableDiceNode; import bjc.utils.data.IHolder; import bjc.utils.data.ITree; import bjc.utils.data.Identity; import bjc.utils.data.TopDownTransformResult; import bjc.utils.data.Tree; import bjc.utils.funcdata.IMap; /** * 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 doSanitize(ITree ast, IMap> enviroment) { if(ast.getChildrenCount() != 2) throw new UnsupportedOperationException("Assignment must have two arguments."); ITree nameTree = ast.getChild(0); ITree valueTree = ast.getChild(1); if(!DiceASTUtils.containsSimpleVariable(nameTree)) { if(nameTree.getHead() == OperatorDiceNode.ARRAY) { IHolder 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 childCounter = new Identity<>(0); ITree returnTree = new Tree<>(OperatorDiceNode.ARRAY); nameTree.doForChildren((child) -> { String variableName = child.transformHead((node) -> { return ((VariableDiceNode) node).getVariable(); }); ITree currentValue = valueTree.getChild(childCounter.getValue()); ITree sanitizedSubtree = doSingleSanitize(ast, enviroment, child, currentValue, variableName); if(sanitizedSubtree == null) { ITree oldTree = new Tree<>(ast.getHead(), child, currentValue); returnTree.addChild(oldTree); } else { returnTree.addChild(sanitizedSubtree); } childCounter.transform((count) -> count + 1); }); return returnTree; } ITree returnTree = new Tree<>(OperatorDiceNode.ARRAY); nameTree.doForChildren((child) -> { String variableName = child .transformHead((node) -> ((VariableDiceNode) node).getVariable()); ITree sanitizedChild = doSingleSanitize(ast, enviroment, child, valueTree, variableName); if(sanitizedChild == null) { ITree 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 sanitizedTree = doSingleSanitize(ast, enviroment, nameTree, valueTree, variableName); if(sanitizedTree == null) return ast; return sanitizedTree; } private static ITree doSingleSanitize(ITree ast, IMap> enviroment, ITree nameTree, ITree valueTree, String variableName) { if(enviroment.containsKey(variableName)) { // @ is a meta-variable standing for the left side of an // assignment ITree oldVal = enviroment.put("@", enviroment.get(variableName)); // We should always inline out references to last, // because it // will always change ITree 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 sanitize(ITree ast, IMap> 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; } } }