package bjc.dicelang.ast; import bjc.utils.data.IHolder; import bjc.utils.data.Identity; import bjc.utils.funcdata.IFunctionalMap; 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 { /** * Sanitize the references in an AST * * @param ast * @param enviroment * @return The sanitized AST */ public static ITree sanitize(ITree ast, IFunctionalMap> 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 CALL: case COMPOUND: case DIVIDE: case GROUP: case MULTIPLY: case SUBTRACT: default: return TopDownTransformResult.SKIP; } } private static ITree doSanitize(ITree ast, IFunctionalMap> 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, IFunctionalMap> 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; } }