summaryrefslogtreecommitdiff
path: root/dice-lang/src/bjc
diff options
context:
space:
mode:
authorbculkin2442 <bjculkin@mix.wvu.edu>2016-10-27 21:56:18 -0400
committerbculkin2442 <bjculkin@mix.wvu.edu>2016-10-27 22:12:47 -0400
commite7413128ff4e376997de6e94e4bea5eca14811ef (patch)
tree0749e270fdb754d04dc223abd95d47436508047f /dice-lang/src/bjc
parente13a6981bd278c2cfc3b5ecb2517367b117f7a52 (diff)
Moved examples
Diffstat (limited to 'dice-lang/src/bjc')
-rw-r--r--dice-lang/src/bjc/dicelang/BindingDiceExpression.java93
-rw-r--r--dice-lang/src/bjc/dicelang/ComplexDice.java147
-rw-r--r--dice-lang/src/bjc/dicelang/CompoundDice.java100
-rw-r--r--dice-lang/src/bjc/dicelang/DiceExpressionBuilder.java150
-rw-r--r--dice-lang/src/bjc/dicelang/DiceExpressionParser.java173
-rw-r--r--dice-lang/src/bjc/dicelang/DiceExpressionType.java49
-rw-r--r--dice-lang/src/bjc/dicelang/Die.java71
-rw-r--r--dice-lang/src/bjc/dicelang/IDiceExpression.java91
-rw-r--r--dice-lang/src/bjc/dicelang/OperatorDiceExpression.java96
-rw-r--r--dice-lang/src/bjc/dicelang/PolyhedralDice.java149
-rw-r--r--dice-lang/src/bjc/dicelang/ReferenceDiceExpression.java76
-rw-r--r--dice-lang/src/bjc/dicelang/ScalarDie.java54
-rw-r--r--dice-lang/src/bjc/dicelang/ast/ArithmeticCollapser.java192
-rw-r--r--dice-lang/src/bjc/dicelang/ast/ArrayResult.java43
-rw-r--r--dice-lang/src/bjc/dicelang/ast/DiceASTEvaluator.java321
-rw-r--r--dice-lang/src/bjc/dicelang/ast/DiceASTInliner.java125
-rw-r--r--dice-lang/src/bjc/dicelang/ast/DiceASTOptimizer.java60
-rw-r--r--dice-lang/src/bjc/dicelang/ast/DiceASTParser.java160
-rw-r--r--dice-lang/src/bjc/dicelang/ast/DiceASTReferenceChecker.java61
-rw-r--r--dice-lang/src/bjc/dicelang/ast/DiceASTReferenceSanitizer.java201
-rw-r--r--dice-lang/src/bjc/dicelang/ast/DiceASTUtils.java86
-rw-r--r--dice-lang/src/bjc/dicelang/ast/DummyResult.java31
-rw-r--r--dice-lang/src/bjc/dicelang/ast/IOperatorCollapser.java20
-rw-r--r--dice-lang/src/bjc/dicelang/ast/IResult.java16
-rw-r--r--dice-lang/src/bjc/dicelang/ast/IntegerResult.java40
-rw-r--r--dice-lang/src/bjc/dicelang/ast/ResultType.java22
-rw-r--r--dice-lang/src/bjc/dicelang/ast/nodes/DiceASTType.java27
-rw-r--r--dice-lang/src/bjc/dicelang/ast/nodes/DiceLiteralNode.java52
-rw-r--r--dice-lang/src/bjc/dicelang/ast/nodes/DiceLiteralType.java18
-rw-r--r--dice-lang/src/bjc/dicelang/ast/nodes/DiceOperatorType.java29
-rw-r--r--dice-lang/src/bjc/dicelang/ast/nodes/IDiceASTNode.java23
-rw-r--r--dice-lang/src/bjc/dicelang/ast/nodes/ILiteralDiceNode.java73
-rw-r--r--dice-lang/src/bjc/dicelang/ast/nodes/IntegerLiteralNode.java50
-rw-r--r--dice-lang/src/bjc/dicelang/ast/nodes/OperatorDiceNode.java110
-rw-r--r--dice-lang/src/bjc/dicelang/ast/nodes/VariableDiceNode.java101
-rw-r--r--dice-lang/src/bjc/dicelang/ast/nodes/package-info.java7
-rw-r--r--dice-lang/src/bjc/dicelang/ast/optimization/ArithmeticCollapser.java50
-rw-r--r--dice-lang/src/bjc/dicelang/ast/optimization/ConstantCollapser.java91
-rw-r--r--dice-lang/src/bjc/dicelang/ast/optimization/IOptimizationPass.java35
-rw-r--r--dice-lang/src/bjc/dicelang/ast/optimization/OperationCondenser.java107
-rw-r--r--dice-lang/src/bjc/dicelang/ast/optimization/package-info.java7
-rw-r--r--dice-lang/src/bjc/dicelang/ast/package-info.java7
-rw-r--r--dice-lang/src/bjc/dicelang/examples/DiceASTLanguageTest.java261
-rw-r--r--dice-lang/src/bjc/dicelang/examples/DiceASTPragma.java22
-rw-r--r--dice-lang/src/bjc/dicelang/examples/DiceExpressionParserTest.java59
-rw-r--r--dice-lang/src/bjc/dicelang/examples/DiceExpressionPreparer.java93
-rw-r--r--dice-lang/src/bjc/dicelang/examples/DiceLanguageState.java37
-rw-r--r--dice-lang/src/bjc/dicelang/examples/DiceLanguageTest.java96
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);
+ }
+ }
+}