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 | |
| parent | e13a6981bd278c2cfc3b5ecb2517367b117f7a52 (diff) | |
Moved examples
Diffstat (limited to 'dice-lang/src/bjc')
48 files changed, 3982 insertions, 0 deletions
diff --git a/dice-lang/src/bjc/dicelang/BindingDiceExpression.java b/dice-lang/src/bjc/dicelang/BindingDiceExpression.java new file mode 100644 index 0000000..6a030e3 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/BindingDiceExpression.java @@ -0,0 +1,93 @@ +package bjc.dicelang; + +import java.util.Map; + +/** + * A variable expression that represents binding a variable to a name in an + * enviroment + * + * @author ben + * + */ +public class BindingDiceExpression implements IDiceExpression { + /** + * The expression being bound to a name + */ + private IDiceExpression expression; + + /** + * The name to bind the expression to + */ + private String variableName; + + /** + * Create a new dice expression binder from two expressions and an + * enviroment + * + * @param left + * The left side expression to get a name from. Must be a + * ReferenceDiceExpression + * @param right + * The right side to bind to the name + * @param enviroment + * The enviroment to bind into + */ + public BindingDiceExpression(IDiceExpression left, + IDiceExpression right, + Map<String, IDiceExpression> enviroment) { + if (!(left instanceof ReferenceDiceExpression)) { + throw new UnsupportedOperationException( + "Error: Binding an expression to something that is not a variable reference," + + " or array thereof. is unsupported." + + " Problematic expression is " + left); + } + + String varName = ((ReferenceDiceExpression) left).getName(); + + initialize(varName, right, enviroment); + } + + /** + * Create a new dice expression binder + * + * @param name + * The name of the variable to bind + * @param expression + * The expression to bind to the variable + * @param enviroment + * The enviroment to bind it in + */ + public BindingDiceExpression(String name, IDiceExpression expression, + Map<String, IDiceExpression> enviroment) { + initialize(name, expression, enviroment); + } + + private void initialize(String name, IDiceExpression expr, + Map<String, IDiceExpression> enviroment) { + this.variableName = name; + this.expression = expr; + + enviroment.put(name, expr); + } + + /* + * (non-Javadoc) + * + * @see bjc.utils.dice.IDiceExpression#roll() + */ + @Override + public int roll() { + return expression.roll(); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "assign[n=" + variableName + ", exp=" + + expression.toString() + "]"; + } +} diff --git a/dice-lang/src/bjc/dicelang/ComplexDice.java b/dice-lang/src/bjc/dicelang/ComplexDice.java new file mode 100644 index 0000000..9bf191f --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ComplexDice.java @@ -0,0 +1,147 @@ +package bjc.dicelang; + +/** + * Implements a collection of one or more of a particular die, where the + * number of dice in the group is variable. + * + * @author ben + * + */ +public class ComplexDice implements IDiceExpression { + /** + * Create a dice from a string expression + * + * @param expression + * The string to parse the dice from + * @return A dice group parsed from the string + */ + public static IDiceExpression fromString(String expression) { + // Handle the case where someone passes us a simple expression + // containing a single die + if (!expression.contains("d")) { + return new Die(Integer.parseInt(expression)); + } + // Split it on the dice type marker + + String[] strangs = expression.split("d"); + + try { + // Create the actual group of dice + return new ComplexDice( + new ScalarDie(Integer.parseInt(strangs[0])), + new Die(Integer.parseInt(strangs[1]))); + } catch (@SuppressWarnings("unused") NumberFormatException nfex) { + // We don't care about details + + // Tell the user the expression is invalid + throw new IllegalArgumentException( + "Attempted to create a set of dice using invalid arguments." + + " They must be integers. " + strangs[0] + + " and " + strangs[1] + + " are likely culprits."); + } + } + + /** + * The die being rolled + */ + private IDiceExpression die; + + /** + * The number of the particular die to roll + */ + private IDiceExpression nDice; + + /** + * Create a new collection of dice + * + * @param nDce + * The number of dice in the collection + * @param de + * The type of dice the collection is composed of + */ + public ComplexDice(IDiceExpression nDce, IDiceExpression de) { + nDice = nDce; + die = de; + } + + /** + * Create a new collection of dice + * + * @param nSides + * The number of dice in the collection + * @param de + * The type of dice the collection is composed of + */ + public ComplexDice(int nSides, int de) { + nDice = new ScalarDie(nSides); + die = new Die(de); + } + + @Override + public boolean canOptimize() { + // Can only optimize this dice group if both components can be + // optimized and the die itself has only one value + if (nDice.canOptimize() && die.canOptimize()) { + return die.optimize() == 1; + } + + return false; + } + + @Override + public int optimize() { + if (!canOptimize()) { + throw new UnsupportedOperationException( + "This complex dice cannot be optimized. " + + "Both the dice to be rolled and the number of" + + " dice must be optimizable."); + } + + return nDice.optimize(); + } + + /* + * (non-Javadoc) + * + * @see bjc.utils.dice.IDiceExpression#roll() + */ + @Override + public int roll() { + int res = 0; + + /* + * Add the results of rolling each die + */ + int nRoll = nDice.roll(); + + if (nRoll < 0) { + throw new UnsupportedOperationException( + "Attempted to roll a negative number of dice. " + + "The problematic expression is " + nDice); + } + + // Roll all the dice and combine them + for (int i = 0; i < nRoll; i++) { + res += die.roll(); + } + + return res; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + // Print simple dice groups in a much clearer manner + if (nDice instanceof ScalarDie && die instanceof Die) { + return nDice.toString() + die.toString(); + } + + return "complex[n=" + nDice.toString() + ", d=" + die.toString() + + "]"; + } +}
\ No newline at end of file diff --git a/dice-lang/src/bjc/dicelang/CompoundDice.java b/dice-lang/src/bjc/dicelang/CompoundDice.java new file mode 100644 index 0000000..704e4cd --- /dev/null +++ b/dice-lang/src/bjc/dicelang/CompoundDice.java @@ -0,0 +1,100 @@ +package bjc.dicelang; + +/** + * Implements a "compound dice" + * + * To explain, a compound dice is something like a d100 composed from two + * d10s instead of a hundred sided die. + * + * @author ben + * + */ +public class CompoundDice implements IDiceExpression { + /** + * The left die of the expression + */ + private IDiceExpression leftDice; + + /** + * The right die of the expression + */ + private IDiceExpression rightDice; + + /** + * Create a new compound dice using the specified dice + * + * @param left + * The die to use on the left + * @param right + * The die to use on the right + */ + public CompoundDice(IDiceExpression left, IDiceExpression right) { + this.leftDice = left; + this.rightDice = right; + } + + /** + * Create a new compound dice from two dice strings + * + * @param leftExp + * The left side dice as a string + * @param rightExp + * The right side dice as a string + */ + public CompoundDice(String leftExp, String rightExp) { + this(ComplexDice.fromString(leftExp), + ComplexDice.fromString(rightExp)); + } + + /** + * Create a new compound dice from an array of dice strings + * + * @param exps + * An array of two dice strings + */ + public CompoundDice(String[] exps) { + this(exps[0], exps[1]); + } + + @Override + public boolean canOptimize() { + return leftDice.canOptimize() && rightDice.canOptimize(); + } + + @Override + public int optimize() { + if (!canOptimize()) { + throw new UnsupportedOperationException( + "Cannot optimize this compound dice. " + + "Both component dice must be optimizable" + + " to optimize a compound dice"); + } + + return Integer + .parseInt(leftDice.optimize() + "" + rightDice.optimize()); + } + + /* + * (non-Javadoc) + * + * @see bjc.utils.dice.IDiceExpression#roll() + */ + @Override + public int roll() { + /* + * Make the combination of the two dice + */ + return Integer.parseInt(leftDice.roll() + "" + rightDice.roll()); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "compound[l=" + leftDice.toString() + ", r=" + + rightDice.toString() + "]"; + } +}
\ No newline at end of file diff --git a/dice-lang/src/bjc/dicelang/DiceExpressionBuilder.java b/dice-lang/src/bjc/dicelang/DiceExpressionBuilder.java new file mode 100644 index 0000000..af856a6 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/DiceExpressionBuilder.java @@ -0,0 +1,150 @@ +package bjc.dicelang; + +import static bjc.dicelang.DiceExpressionType.ADD; +import static bjc.dicelang.DiceExpressionType.DIVIDE; +import static bjc.dicelang.DiceExpressionType.MULTIPLY; +import static bjc.dicelang.DiceExpressionType.SUBTRACT; + +/** + * Build a dice expression piece by piece + * + * @author ben + * + */ +public class DiceExpressionBuilder { + /** + * The dice expression we are building + */ + private IDiceExpression baking; + + /** + * Build a dice expression from a seed dice expression + * + * @param seed + * The dice expression to use as a seed + */ + public DiceExpressionBuilder(IDiceExpression seed) { + baking = seed; + } + + /** + * Build a dice expression from a seed dice + * + * @param nSides + * The number of sides in the dice + * @param nDice + * The number of dice in the group + */ + public DiceExpressionBuilder(int nSides, int nDice) { + baking = new ComplexDice(nSides, nDice); + } + + /** + * Add a term to this dice expression + * + * @param exp + * The expression to use on the left + * @return A new expression adding the two dice + */ + public DiceExpressionBuilder add(IDiceExpression exp) { + baking = new OperatorDiceExpression(baking, exp, ADD); + return this; + } + + /** + * Add a scalar to this dice + * + * @param num + * The scalar to add to the dice + * @return A dice expression adding a scalar to this + */ + public DiceExpressionBuilder add(int num) { + baking = new OperatorDiceExpression(baking, new ScalarDie(num), + ADD); + return this; + } + + /** + * Bake the expression being built to completion + * + * @return A usable dice expression + */ + public IDiceExpression bake() { + return baking; + } + + /** + * Divide a term from dice expression + * + * @param exp + * The expression to use on the left + * @return A new expression dividing the two dice + */ + public DiceExpressionBuilder divide(IDiceExpression exp) { + baking = new OperatorDiceExpression(baking, exp, DIVIDE); + return this; + } + + /** + * Divide a scalar from this dice + * + * @param num + * The scalar to add to the dice + * @return A dice expression dividing a scalar from this + */ + public DiceExpressionBuilder divide(int num) { + baking = new OperatorDiceExpression(baking, new ScalarDie(num), + DIVIDE); + return this; + } + + /** + * Multiply a term by this dice expression + * + * @param exp + * The expression to use on the left + * @return A new expression multiplying the two dice + */ + public DiceExpressionBuilder multiply(IDiceExpression exp) { + baking = new OperatorDiceExpression(baking, exp, MULTIPLY); + return this; + } + + /** + * Multiply a scalar by this dice + * + * @param num + * The scalar to multiply to the dice + * @return A dice expression multiplying a scalar to this + */ + public DiceExpressionBuilder multiply(int num) { + baking = new OperatorDiceExpression(baking, new ScalarDie(num), + MULTIPLY); + return this; + } + + /** + * Add a term to this dice expression + * + * @param exp + * The expression to use on the left + * @return A new expression adding the two dice + */ + public DiceExpressionBuilder subtract(IDiceExpression exp) { + baking = new OperatorDiceExpression(baking, exp, SUBTRACT); + return this; + } + + /** + * Add a scalar to this dice + * + * @param num + * The scalar to add to the dice + * @return A dice expression adding a scalar to this + */ + public DiceExpressionBuilder subtract(int num) { + baking = new OperatorDiceExpression(baking, new ScalarDie(num), + SUBTRACT); + return this; + } +} diff --git a/dice-lang/src/bjc/dicelang/DiceExpressionParser.java b/dice-lang/src/bjc/dicelang/DiceExpressionParser.java new file mode 100644 index 0000000..8a1552a --- /dev/null +++ b/dice-lang/src/bjc/dicelang/DiceExpressionParser.java @@ -0,0 +1,173 @@ +package bjc.dicelang; + +import java.util.Map; +import java.util.Stack; + +import bjc.utils.funcdata.FunctionalStringTokenizer; +import bjc.utils.funcdata.IList; +import bjc.utils.parserutils.ShuntingYard; + +import org.apache.commons.lang3.StringUtils; + +/** + * Parse a dice expression from a string + * + * @author ben + * + */ +public class DiceExpressionParser { + /** + * Parse a dice expression from a string + * + * @param expression + * The string to parse an expression from + * @param enviroment + * The enviroment to use when parsing expressions + * @return The parsed dice expression + */ + public static IDiceExpression parse(String expression, + Map<String, IDiceExpression> enviroment) { + /* + * Create a tokenizer over the strings + */ + FunctionalStringTokenizer tokenizer = new FunctionalStringTokenizer( + expression); + + /* + * Create a shunter to rewrite the expression + */ + ShuntingYard<String> yard = new ShuntingYard<>(true); + + /* + * Add our custom operators to the yard + */ + 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 + + /* + * Shunt the expression to postfix form + */ + IList<String> list = yard.postfix(tokenizer.toList(), s -> s); + + /* + * Create a stack for building an expression from parts + */ + Stack<IDiceExpression> expressions = new Stack<>(); + + /* + * Create the expression from parts + */ + list.forEach((expressionPart) -> { + /* + * Handle compound dice + */ + if (StringUtils.countMatches(expressionPart, 'c') == 1 + && !expressionPart.equalsIgnoreCase("c")) { + String[] strangs = expressionPart.split("c"); + + expressions.push(new CompoundDice(strangs)); + } else if (StringUtils.countMatches(expressionPart, 'd') == 1 + && !expressionPart.equalsIgnoreCase("d")) { + /* + * Handle dice groups + */ + expressions.push(ComplexDice.fromString(expressionPart)); + } else { + try { + /* + * Handle scalar numbers + */ + expressions.push(new ScalarDie( + Integer.parseInt(expressionPart))); + } catch (@SuppressWarnings("unused") NumberFormatException nfex) { + // We don't care about details, just that it failed + if (expressions.size() >= 2) { + /* + * Apply an operation to two dice + */ + IDiceExpression rightExpression = expressions + .pop(); + IDiceExpression leftExpression = expressions.pop(); + + switch (expressionPart) { + case ":=": + expressions.push(new BindingDiceExpression( + leftExpression, rightExpression, + enviroment)); + break; + case "+": + expressions + .push(new OperatorDiceExpression( + rightExpression, + leftExpression, + DiceExpressionType.ADD)); + break; + case "-": + expressions + .push(new OperatorDiceExpression( + rightExpression, + leftExpression, + DiceExpressionType.SUBTRACT)); + break; + case "*": + expressions + .push(new OperatorDiceExpression( + rightExpression, + leftExpression, + DiceExpressionType.MULTIPLY)); + break; + case "/": + expressions + .push(new OperatorDiceExpression( + rightExpression, + leftExpression, + DiceExpressionType.DIVIDE)); + break; + case "c": + expressions.push(new CompoundDice( + leftExpression, rightExpression)); + break; + case "d": + expressions.push(new ComplexDice( + leftExpression, rightExpression)); + break; + default: + /* + * Parse it as a variable reference + * + * Make sure to restore popped variables + */ + expressions.push(leftExpression); + expressions.push(rightExpression); + + expressions + .push(new ReferenceDiceExpression( + expressionPart, + enviroment)); + } + } else { + /* + * Parse it as a variable reference + */ + expressions.push(new ReferenceDiceExpression( + expressionPart, enviroment)); + } + } + } + }); + + if (expressions.size() != 1) { + System.err.println( + "WARNING: Leftovers found on dice expression stack. Remember, := is assignment."); + } + + /* + * Return the built expression + */ + return expressions.pop(); + } +} diff --git a/dice-lang/src/bjc/dicelang/DiceExpressionType.java b/dice-lang/src/bjc/dicelang/DiceExpressionType.java new file mode 100644 index 0000000..296d751 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/DiceExpressionType.java @@ -0,0 +1,49 @@ +package bjc.dicelang; + +/** + * Enumeration for basic dice expression operators + */ +public enum DiceExpressionType { + /** + * Add two expressions + */ + ADD, + + /** + * Divide two expressions + */ + DIVIDE, + + /** + * Multiply two expressions + */ + MULTIPLY, + + /** + * Subtract two expressions + */ + SUBTRACT; + + /* + * (non-Javadoc) + * + * @see java.lang.Enum#toString() + */ + @Override + public String toString() { + switch (this) { + case ADD: + return "+"; + case DIVIDE: + return "/"; + case MULTIPLY: + return "*"; + case SUBTRACT: + return "-"; + default: + throw new IllegalArgumentException( + "Got passed a invalid ScalarExpressionType " + + this + ". WAT"); + } + }; +}
\ No newline at end of file diff --git a/dice-lang/src/bjc/dicelang/Die.java b/dice-lang/src/bjc/dicelang/Die.java new file mode 100644 index 0000000..192daaa --- /dev/null +++ b/dice-lang/src/bjc/dicelang/Die.java @@ -0,0 +1,71 @@ +package bjc.dicelang; + +import java.util.Random; + +/** + * A single polyhedral dice + * + * @author ben + * + */ +public class Die implements IDiceExpression { + /** + * Random # gen to use for dice + */ + private static Random rng = new Random(); + + /** + * Number of sides this die has + */ + private int nSides; + + /** + * Create a die with the specified number of sides + * + * @param nSides + * The number of sides this dice has + */ + public Die(int nSides) { + if (nSides < 1) { + throw new UnsupportedOperationException( + "Dice with less than 1 side are not supported"); + } + + this.nSides = nSides; + } + + @Override + public boolean canOptimize() { + return nSides == 1; + } + + @Override + public int optimize() { + if (nSides != 1) { + throw new UnsupportedOperationException( + "Can't optimize " + nSides + "-sided dice"); + } + + return 1; + } + + /* + * (non-Javadoc) + * + * @see bjc.utils.dice.IDiceExpression#roll() + */ + @Override + public int roll() { + return rng.nextInt(nSides) + 1; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "d" + nSides; + } +} diff --git a/dice-lang/src/bjc/dicelang/IDiceExpression.java b/dice-lang/src/bjc/dicelang/IDiceExpression.java new file mode 100644 index 0000000..acb1d4d --- /dev/null +++ b/dice-lang/src/bjc/dicelang/IDiceExpression.java @@ -0,0 +1,91 @@ +package bjc.dicelang; + +import bjc.utils.funcutils.StringUtils; + +/** + * An expression for something that can be rolled like a polyhedral die + * + * @author ben + * + */ +@FunctionalInterface +public interface IDiceExpression { + /** + * Parse a string into an expression. + * + * It can accept the following types of expressions + * <ul> + * <li>Simple integers - '2'</li> + * <li>Simple dice - 'd6'</li> + * <li>Groups of simple dice - '2d6'</li> + * <li>Number concatenation - '2c6'</li> + * <li>Dice concatenation - '1d10c1d10</li> + * </ul> + * + * Dice concatenation is like using 2 d10s to emulate a d100, so + * instead of adding them, it reads them side by side. + * + * @param expression + * The string to convert to an expression + * + * @return The string, converted into expression form + */ + static IDiceExpression toExpression(String expression) { + String literalData = expression; + + if (StringUtils.containsInfixOperator(literalData, "c")) { + // Parse a compound die + String[] strangs = literalData.split("c"); + + return new CompoundDice(strangs); + } else if (StringUtils.containsInfixOperator(literalData, "d")) { + // Handle groups of similiar dice + return ComplexDice.fromString(literalData); + } else if (literalData.matches("\\Ad\\d+\\Z")) { + // Handle people who put 'd6' instead of '1d6' + return new Die(Integer.parseInt(literalData.substring(1))); + } else { + // Parse a scalar number + try { + return new ScalarDie(Integer.parseInt(literalData)); + } catch (NumberFormatException nfex) { + UnsupportedOperationException usex = new UnsupportedOperationException( + "Found malformed leaf token " + expression); + + usex.initCause(nfex); + + throw usex; + } + } + } + + /** + * Check if this expression can be optimized to a scalar value + * + * @return Whether or not this expression can be optimized to a scalar + * value + */ + public default boolean canOptimize() { + return false; + } + + /** + * Optimize this expression to a scalar value + * + * @return This expression, optimized to a scalar value + * + * @throws UnsupportedOperationException + * if this type of expression can't be optimized + */ + public default int optimize() { + throw new UnsupportedOperationException( + "Can't optimize this type of expression"); + } + + /** + * Roll the dice once + * + * @return The result of rowing the dice + */ + public int roll(); +}
\ No newline at end of file diff --git a/dice-lang/src/bjc/dicelang/OperatorDiceExpression.java b/dice-lang/src/bjc/dicelang/OperatorDiceExpression.java new file mode 100644 index 0000000..f86773d --- /dev/null +++ b/dice-lang/src/bjc/dicelang/OperatorDiceExpression.java @@ -0,0 +1,96 @@ +package bjc.dicelang; + +/** + * Implements a class for combining two dice with an operator + * + * @author ben + * + */ +public class OperatorDiceExpression implements IDiceExpression { + /** + * The operator to use for combining the dice + */ + private DiceExpressionType expressionType; + + /** + * The dice on the left side of the expression + */ + private IDiceExpression leftExpression; + + /** + * The dice on the right side of the expression + */ + private IDiceExpression rightExpression; + + /** + * Create a new compound expression using the specified parameters + * + * @param right + * The die on the right side of the expression + * @param left + * The die on the left side of the expression + * @param type + * The operator to use for combining the dices + */ + public OperatorDiceExpression(IDiceExpression right, + IDiceExpression left, DiceExpressionType type) { + this.rightExpression = right; + this.leftExpression = left; + this.expressionType = type; + } + + /* + * (non-Javadoc) + * + * @see bjc.utils.dice.IDiceExpression#roll() + */ + @Override + public int roll() { + /* + * Handle each operator + */ + switch (expressionType) { + case ADD: + return rightExpression.roll() + leftExpression.roll(); + case SUBTRACT: + return rightExpression.roll() - leftExpression.roll(); + case MULTIPLY: + return rightExpression.roll() * leftExpression.roll(); + case DIVIDE: + /* + * Round to keep results as integers. We don't really have + * any need for floating-point dice, and continuous + * probability is a pain + */ + try { + return rightExpression.roll() / leftExpression.roll(); + } catch (ArithmeticException aex) { + UnsupportedOperationException usex = new UnsupportedOperationException( + "Attempted to divide by zero." + + " Problematic expression is " + + leftExpression); + + usex.initCause(aex); + + throw usex; + } + default: + throw new IllegalArgumentException( + "Got passed a invalid ScalarExpressionType (" + + expressionType + "). WAT"); + + } + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "dice-exp[type=" + expressionType + ", l=" + + leftExpression.toString() + ", r=" + + rightExpression.toString() + "]"; + } +}
\ No newline at end of file diff --git a/dice-lang/src/bjc/dicelang/PolyhedralDice.java b/dice-lang/src/bjc/dicelang/PolyhedralDice.java new file mode 100644 index 0000000..3ac9420 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/PolyhedralDice.java @@ -0,0 +1,149 @@ +package bjc.dicelang; + +/** + * Utility class that produces common polyhedral dice + * + * @author ben + * + */ +public class PolyhedralDice { + /** + * Produce a single d10 + * + * @return A single d10 + */ + public static IDiceExpression d10() { + return d10(1); + } + + /** + * Produce the specified number of 10-sided dice + * + * @param nDice + * The number of ten-sided dice to produce + * @return A group of ten-sided dice of the specified size + */ + public static IDiceExpression d10(int nDice) { + return new ComplexDice(nDice, 10); + } + + /** + * Produce a single d100 + * + * @return A single d100 + */ + public static IDiceExpression d100() { + return d100(1); + } + + /** + * Produce the specified number of 100-sided dice + * + * @param nDice + * The number of hundred-sided dice to produce + * @return A group of hundred-sided dice of the specified size + */ + public static IDiceExpression d100(int nDice) { + return new ComplexDice(nDice, 100); + } + + /** + * Produce a single d12 + * + * @return A single d12 + */ + public static IDiceExpression d12() { + return d12(1); + } + + /** + * Produce the specified number of 12-sided dice + * + * @param nDice + * The number of twelve-sided dice to produce + * @return A group of twelve-sided dice of the specified size + */ + public static IDiceExpression d12(int nDice) { + return new ComplexDice(nDice, 12); + } + + /** + * Produce a single d20 + * + * @return A single d20 + */ + public static IDiceExpression d20() { + return d20(1); + } + + /** + * Produce the specified number of 20-sided dice + * + * @param nDice + * The number of twenty-sided dice to produce + * @return A group of twenty-sided dice of the specified size + */ + public static IDiceExpression d20(int nDice) { + return new ComplexDice(nDice, 20); + } + + /** + * Produce a single d4 + * + * @return A single d4 + */ + public static IDiceExpression d4() { + return d4(1); + } + + /** + * Produce the specified number of 4-sided dice + * + * @param nDice + * The number of four-sided dice to produce + * @return A group of four-sided dice of the specified size + */ + public static IDiceExpression d4(int nDice) { + return new ComplexDice(nDice, 4); + } + + /** + * Produce a single d6 + * + * @return A single d6 + */ + public static IDiceExpression d6() { + return d6(1); + } + + /** + * Produce the specified number of 6-sided dice + * + * @param nDice + * The number of six-sided dice to produce + * @return A group of six-sided dice of the specified size + */ + public static IDiceExpression d6(int nDice) { + return new ComplexDice(nDice, 6); + } + + /** + * Produce a single d8 + * + * @return A single d8 + */ + public static IDiceExpression d8() { + return d8(1); + } + + /** + * Produce the specified number of 8-sided dice + * + * @param nDice + * The number of eight-sided dice to produce + * @return A group of eight-sided dice of the specified size + */ + public static IDiceExpression d8(int nDice) { + return new ComplexDice(nDice, 8); + } +} diff --git a/dice-lang/src/bjc/dicelang/ReferenceDiceExpression.java b/dice-lang/src/bjc/dicelang/ReferenceDiceExpression.java new file mode 100644 index 0000000..483545b --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ReferenceDiceExpression.java @@ -0,0 +1,76 @@ +package bjc.dicelang; + +import java.util.Map; + +/** + * A dice expression that refers to a variable bound in a mutable + * enviroment + * + * @author ben + * + */ +public class ReferenceDiceExpression implements IDiceExpression { + /** + * The enviroment to do variable dereferencing against + */ + private Map<String, IDiceExpression> enviroment; + + /** + * The name of the bound variable + */ + private String variableName; + + /** + * Create a new reference dice expression referring to the given name + * in an enviroment + * + * @param name + * The name of the bound variable + * @param env + * The enviroment to resolve the variable against + */ + public ReferenceDiceExpression(String name, + Map<String, IDiceExpression> env) { + this.variableName = name; + this.enviroment = env; + } + + /** + * Get the name of the referenced variable + * + * @return the name of the referenced variable + */ + public String getName() { + return variableName; + } + + /* + * (non-Javadoc) + * + * @see bjc.utils.dice.IDiceExpression#roll() + */ + @Override + public int roll() { + if (!enviroment.containsKey(variableName)) { + throw new UnsupportedOperationException( + "Attempted to reference undefined variable " + + variableName); + } + + return enviroment.get(variableName).roll(); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + if (enviroment.containsKey(variableName)) { + return enviroment.get(variableName).toString(); + } + + return variableName; + } +}
\ No newline at end of file diff --git a/dice-lang/src/bjc/dicelang/ScalarDie.java b/dice-lang/src/bjc/dicelang/ScalarDie.java new file mode 100644 index 0000000..8d31d15 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/ScalarDie.java @@ -0,0 +1,54 @@ +package bjc.dicelang; + +/** + * A die that represents a static number + * + * @author ben + * + */ +public class ScalarDie implements IDiceExpression { + /** + * The represented number + */ + private int number; + + /** + * Create a dice with the specified number + * + * @param num + * The number used for the dice + */ + public ScalarDie(int num) { + this.number = num; + } + + @Override + public boolean canOptimize() { + return true; + } + + @Override + public int optimize() { + return number; + } + + /* + * (non-Javadoc) + * + * @see bjc.utils.dice.IDiceExpression#roll() + */ + @Override + public int roll() { + return number; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return Integer.toString(number); + } +}
\ No newline at end of file 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 diff --git a/dice-lang/src/bjc/dicelang/examples/DiceASTLanguageTest.java b/dice-lang/src/bjc/dicelang/examples/DiceASTLanguageTest.java new file mode 100644 index 0000000..fa92fd2 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/examples/DiceASTLanguageTest.java @@ -0,0 +1,261 @@ +package bjc.dicelang.examples; + +import java.util.InputMismatchException; +import java.util.Scanner; + +import bjc.utils.data.ITree; +import bjc.utils.funcdata.FunctionalMap; +import bjc.utils.funcdata.FunctionalStringTokenizer; +import bjc.utils.funcdata.IList; +import bjc.utils.funcdata.IMap; + +import bjc.dicelang.ast.DiceASTEvaluator; +import bjc.dicelang.ast.DiceASTInliner; +import bjc.dicelang.ast.DiceASTOptimizer; +import bjc.dicelang.ast.DiceASTParser; +import bjc.dicelang.ast.DiceASTReferenceSanitizer; +import bjc.dicelang.ast.IResult; +import bjc.dicelang.ast.nodes.IDiceASTNode; +import bjc.dicelang.ast.optimization.ConstantCollapser; +import bjc.dicelang.ast.optimization.OperationCondenser; + +/** + * Test interface for AST-based dice language + * + * @author ben + * + */ +public class DiceASTLanguageTest { + private static IMap<String, DiceASTPragma> actions; + + private static DiceASTOptimizer optimizer; + + // Set up things that need to be configured + static { + actions = new FunctionalMap<>(); + + // Inline all the variables in a given expression + actions.put("inline", DiceASTLanguageTest::handleInlineAction); + + // Print out the enviroment + actions.put("env", (tokenizer, enviroment) -> { + enviroment.forEach((varName, varValue) -> { + System.out.println(varName + " is bound to " + varValue); + }); + }); + + // Create and configure the optimizer + optimizer = new DiceASTOptimizer(); + + optimizer.addPass(new ConstantCollapser()); + } + + // Read in a command + private static String getNextCommand(Scanner inputSource, + int commandNumber) { + // Print a prompt using the current command number + System.out.print("\ndice-lang-" + commandNumber + "> "); + + // Read in the next command + return inputSource.nextLine(); + } + + private static void handleInlineAction( + FunctionalStringTokenizer tokenizer, + IMap<String, ITree<IDiceASTNode>> enviroment) { + // Skip the pragma name + tokenizer.nextToken(); + + // Get the pragma arguments + IList<String> pragmaArgs = tokenizer.toList(); + + if (pragmaArgs.getSize() < 3) { + // Complain about pragma arguments not being valid + System.err.println( + "ERROR: Inline requires at least 3 parameters. They are:" + + "\n\t1. The name of the expression to inline." + + "\n\t2. The name of the variable to bind the result to." + + "\n\t3 and onwards. Names of variables to inline in the expression."); + } else { + // Get arguments + String inlineExpression = pragmaArgs.getByIndex(0); + String variableName = pragmaArgs.getByIndex(1); + + // Grab the variables we want to inline + IList<String> inlinedVariables = pragmaArgs.tail().tail(); + + // Actually inline the variable + ITree<IDiceASTNode> inlinedExpression = DiceASTInliner + .selectiveInline(enviroment.get(inlineExpression), + enviroment, inlinedVariables); + + // Stick the inlined variable into the enviroment + enviroment.put(variableName, inlinedExpression); + } + } + + /** + * Main method of class + * + * @param args + * Unused CLI args + */ + public static void main(String[] args) { + // Prepare the things we need for input + Scanner inputSource = new Scanner(System.in); + int commandNumber = 0; + + // Grab the initial command + String currentLine = getNextCommand(inputSource, commandNumber); + + // The enviroment for variables + IMap<String, ITree<IDiceASTNode>> enviroment = new FunctionalMap<>(); + + // Handle commands + while (!currentLine.equalsIgnoreCase("quit")) { + // Get the name of a possible action + String possibleActionName = currentLine.split(" ")[0]; + + // Check and see if we're executing an action + if (actions.containsKey(possibleActionName)) { + // Execute action + FunctionalStringTokenizer tokenizer = new FunctionalStringTokenizer( + currentLine); + + // Execute the action + actions.get(possibleActionName).accept(tokenizer, + enviroment); + + // Get the next command + currentLine = getNextCommand(inputSource, commandNumber); + + continue; + } + + // The AST we are going to build + ITree<IDiceASTNode> builtAST; + + // Time command preparation + long time = System.nanoTime(); + + // Prepare the command + IList<String> preparedTokens = DiceExpressionPreparer + .prepareCommand(currentLine); + + System.out.println("Command prepared in " + + (double) (System.nanoTime() - time) / 1000000000 + + " s"); + + try { + // Time the AST creation + time = System.nanoTime(); + + // Create the AST + builtAST = DiceASTParser.createFromString(preparedTokens); + + System.out + .println( + "Command parsed in " + + (double) (System.nanoTime() + - time) / 1000000000 + + " s"); + } catch (InputMismatchException | IllegalStateException + | UnsupportedOperationException ex) { + // Tell the user there was an error in parsing + System.out.println("PARSING ERROR: " + ex.getLocalizedMessage()); + + // Move onto the next command + currentLine = getNextCommand(inputSource, commandNumber); + + continue; + } + + // Print out parsed AST + System.out.println("\tParsed: " + builtAST.toString()); + + // Time AST transformation + time = System.nanoTime(); + + // Transform the AST + ITree<IDiceASTNode> transformedAST = transformAST(builtAST, + enviroment); + + System.out.println("Command transformed in " + + (double) (System.nanoTime() - time) / 1000000000 + + " s"); + + // Print out the transformed AST + System.out + .println("\tTransformed: " + transformedAST.toString()); + + + try { + // Time the evaluation + time = System.nanoTime(); + + // Evaluate the expression once + IResult sampleResult = DiceASTEvaluator.evaluateAST(transformedAST, + enviroment); + + System.out + .println( + "Command evaluated in " + + (double) (System.nanoTime() + - time) / 1000000000 + + " s"); + + // Print out the result of evaluating the expression + System.out.println("\t\tSample Result: " + sampleResult); + + // Update the 'last' meta-variable + enviroment.put("last", transformedAST); + } catch (UnsupportedOperationException usex) { + // Tell the user there was an error in evaluation + System.out.println("EVALUATION ERROR: " + usex.getLocalizedMessage()); + + // Get the next command + currentLine = getNextCommand(inputSource, commandNumber); + + // Process it + continue; + } + + + // Increase the number of commands + commandNumber++; + + // Get the next command + currentLine = getNextCommand(inputSource, commandNumber); + } + + System.out.println("Bye."); + + // Cleanup after ourselves + inputSource.close(); + } + + // Transform a parsed AST + private static ITree<IDiceASTNode> transformAST( + ITree<IDiceASTNode> builtAST, + IMap<String, ITree<IDiceASTNode>> enviroment) { + // Optimize the tree first + ITree<IDiceASTNode> optimizedTree = optimizer + .optimizeTree(builtAST, enviroment); + + // Then, condense unnecessary operations + ITree<IDiceASTNode> condensedTree = OperationCondenser + .condense(optimizedTree); + + // Next, sanitize references + ITree<IDiceASTNode> sanitizedTree = DiceASTReferenceSanitizer + .sanitize(condensedTree, enviroment); + + // Re-optimize the sanitized & condensed tree + optimizedTree = optimizer.optimizeTree(sanitizedTree, enviroment); + + // Re-condense the newly optimized tree + condensedTree = OperationCondenser.condense(optimizedTree); + + return condensedTree; + } +} diff --git a/dice-lang/src/bjc/dicelang/examples/DiceASTPragma.java b/dice-lang/src/bjc/dicelang/examples/DiceASTPragma.java new file mode 100644 index 0000000..4900e46 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/examples/DiceASTPragma.java @@ -0,0 +1,22 @@ +package bjc.dicelang.examples; + +import java.util.function.BiConsumer; + +import bjc.utils.funcdata.FunctionalStringTokenizer; +import bjc.utils.funcdata.IMap; +import bjc.utils.funcdata.ITree; + +import bjc.dicelang.ast.nodes.IDiceASTNode; + +/** + * Alias for the type of a 'pragma' or special language command + * + * To explain it, a pragma is a function that takes a tokenizer with the rest + * of the line, and an enviroment that contains variable bindings + * @author ben + * + */ +public interface DiceASTPragma extends + BiConsumer<FunctionalStringTokenizer, IMap<String, ITree<IDiceASTNode>>> { + // Just an alias +} diff --git a/dice-lang/src/bjc/dicelang/examples/DiceExpressionParserTest.java b/dice-lang/src/bjc/dicelang/examples/DiceExpressionParserTest.java new file mode 100644 index 0000000..b31f3a0 --- /dev/null +++ b/dice-lang/src/bjc/dicelang/examples/DiceExpressionParserTest.java @@ -0,0 +1,59 @@ +package bjc.dicelang.examples; + +import java.util.HashMap; +import java.util.Scanner; + +import bjc.dicelang.DiceExpressionParser; +import bjc.dicelang.IDiceExpression; + +/** + * Driver class for testing expression parser + * + * @author ben + * + */ +public class DiceExpressionParserTest { + /** + * Run the parser test + * + * @param args + * Unused CLI arguments + */ + public static void main(String[] args) { + /* + * Get a scanner for input + */ + Scanner scn = new Scanner(System.in); + + /* + * Ask to enter a expression + */ + System.out.print("Enter dice expression: "); + + String exp = scn.nextLine(); + + /* + * Enter amount of times to roll an expression + */ + System.out.print("Enter number of times to roll: "); + + int nTimes = Integer.parseInt(scn.nextLine()); + + IDiceExpression dexp = DiceExpressionParser.parse(exp, + new HashMap<>()); + + /* + * Roll the dice a specified amount of times + */ + for (int i = 1; i <= nTimes; i++) { + int roll = dexp.roll(); + + System.out.println("Rolled " + roll); + } + + /* + * Clean up after ourselves + */ + scn.close(); + } +} diff --git a/dice-lang/src/bjc/dicelang/examples/DiceExpressionPreparer.java b/dice-lang/src/bjc/dicelang/examples/DiceExpressionPreparer.java new file mode 100644 index 0000000..5488b5d --- /dev/null +++ b/dice-lang/src/bjc/dicelang/examples/DiceExpressionPreparer.java @@ -0,0 +1,93 @@ +package bjc.dicelang.examples; + +import java.util.Deque; +import java.util.LinkedList; + +import bjc.utils.data.IPair; +import bjc.utils.data.Pair; +import bjc.utils.funcdata.FunctionalStringTokenizer; +import bjc.utils.funcdata.IList; +import bjc.utils.funcutils.ListUtils; +import bjc.utils.parserutils.ShuntingYard; + +/** + * Prepare a dice expression to be parsed + * + * @author ben + * + */ +public class DiceExpressionPreparer { + /** + * The yard to use for shunting expressions + */ + private static ShuntingYard<String> yard; + + private static final int MATH_PREC = 20; + private static final int DICE_PREC = 10; + private static final int EXPR_PREC = 0; + + // Do initialization for all parsers + static { + // The shunter we're going to use + yard = new ShuntingYard<>(false); + + // Configure the shunters operators + // Basic mathematical operators + yard.addOp("+", 0 + MATH_PREC); + yard.addOp("-", 0 + MATH_PREC); + + yard.addOp("*", 1 + MATH_PREC); + yard.addOp("/", 1 + MATH_PREC); + + yard.addOp("d", 0 + DICE_PREC); // dice operator: use for creating + // variable size dice groups + yard.addOp("c", 1 + DICE_PREC); // compound operator: use for + // creating compound dice from expressions + + yard.addOp("=>", 0 + EXPR_PREC); // let operator: evaluate an + // expression in the context of another + yard.addOp(":=", 1 + EXPR_PREC); // binding operator: Bind a name + // to a variable expression + } + + // Prepare a command, turning raw tokens into input for the tree builder + public static IList<String> prepareCommand(String currentLine) { + // Split the command into tokens + IList<String> tokens = FunctionalStringTokenizer + .fromString(currentLine).toList(); + + // The linked list to use for handling tokens + Deque<IPair<String, String>> ops = new LinkedList<>(); + + // Prepare the list for operator expansion + ops.add(new Pair<>("+", "\\+")); + ops.add(new Pair<>("-", "-")); + ops.add(new Pair<>("*", "\\*")); + ops.add(new Pair<>("/", "/")); + ops.add(new Pair<>(":=", ":=")); + ops.add(new Pair<>("=>", "=>")); + + // Expand infix single tokens to multiple infix tokens + IList<String> semiExpandedTokens = ListUtils.splitTokens(tokens, + ops); + + // Reinitialize the list + ops = new LinkedList<>(); + + // Prepare the list for deaffixation + ops.add(new Pair<>("(", "\\(")); + ops.add(new Pair<>(")", "\\)")); + ops.add(new Pair<>("[", "\\[")); + ops.add(new Pair<>("]", "\\]")); + + // Deaffix ('s and ['s from tokens + IList<String> fullyExpandedTokens = ListUtils + .deAffixTokens(semiExpandedTokens, ops); + + // Remove blank tokens + fullyExpandedTokens.removeIf((strang) -> strang.equals("")); + + // Shunt the tokens, and hand them back + return yard.postfix(fullyExpandedTokens, (token) -> token); + } +} diff --git a/dice-lang/src/bjc/dicelang/examples/DiceLanguageState.java b/dice-lang/src/bjc/dicelang/examples/DiceLanguageState.java new file mode 100644 index 0000000..fbb103f --- /dev/null +++ b/dice-lang/src/bjc/dicelang/examples/DiceLanguageState.java @@ -0,0 +1,37 @@ +package bjc.dicelang.examples; + +import java.util.Map; + +import bjc.utils.data.Pair; + +import bjc.dicelang.DiceExpressionParser; +import bjc.dicelang.IDiceExpression; + +/** + * Internal state of dice language + * + * @author ben + * + */ +public class DiceLanguageState + extends Pair<DiceExpressionParser, Map<String, IDiceExpression>> { + + /** + * Create a new state + */ + public DiceLanguageState() { + } + + /** + * Create a new state with the desired parameters + * + * @param left + * The parser to use + * @param right + * The enviroment to use + */ + public DiceLanguageState(DiceExpressionParser left, + Map<String, IDiceExpression> right) { + super(left, right); + } +} diff --git a/dice-lang/src/bjc/dicelang/examples/DiceLanguageTest.java b/dice-lang/src/bjc/dicelang/examples/DiceLanguageTest.java new file mode 100644 index 0000000..bd6270e --- /dev/null +++ b/dice-lang/src/bjc/dicelang/examples/DiceLanguageTest.java @@ -0,0 +1,96 @@ +package bjc.dicelang.examples; + +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; +import java.util.function.BiConsumer; + +import bjc.dicelang.DiceExpressionParser; +import bjc.dicelang.IDiceExpression; + +/** + * Test of dice language + * + * @author ben + * + */ +public class DiceLanguageTest { + private static Map<String, BiConsumer<String, DiceLanguageState>> acts; + + static { + acts = new HashMap<>(); + + acts.put("roll", DiceLanguageTest::rollReference); + acts.put("env", DiceLanguageTest::printEnv); + } + + /** + * Main method + * + * @param args + * Unused CLI args + */ + public static void main(String[] args) { + Scanner scn = new Scanner(System.in); + int i = 0; + + System.out.print("dice-lang-" + i + "> "); + String ln = scn.nextLine(); + + DiceExpressionParser dep = new DiceExpressionParser(); + Map<String, IDiceExpression> env = new HashMap<>(); + DiceLanguageState state = new DiceLanguageState(dep, env); + + while (!ln.equalsIgnoreCase("quit")) { + String header = ln.split(" ")[0]; + + if (acts.containsKey(header)) { + acts.get(header).accept(ln, state); + } else { + IDiceExpression exp = DiceExpressionParser.parse(ln, env); + + System.out.println("\tParsed: " + exp.toString()); + System.out.println("\tSample Roll: " + exp.roll()); + + env.put("last", exp); + } + + i++; + + System.out.print("dice-lang-" + i + "> "); + ln = scn.nextLine(); + } + + System.out.println("Bye."); + scn.close(); + } + + /** + * @param ln + * Unused parameter, kept to comply with expected type sig + */ + private static void printEnv(String ln, DiceLanguageState stat) { + System.out.println("Printing enviroment for debugging purposes."); + + stat.doWith((dep, env) -> env.forEach((key, exp) -> System.out + .println("\tKey: " + key + "\tExp: " + exp.toString()))); + } + + private static void rollReference(String ln, DiceLanguageState stat) { + String[] strangs = ln.split(" "); + + System.out.println("\tRolling dice expression " + strangs[1] + " " + + strangs[2] + " times."); + + int nRolls = Integer.parseInt(strangs[2]); + + IDiceExpression dexp = stat + .merge((dep, env) -> env.get(strangs[1])); + + for (int i = 1; i <= nRolls; i++) { + int roll = dexp.roll(); + + System.out.println("\tRolled " + roll); + } + } +} |
