diff options
Diffstat (limited to 'dice-lang/src/main/java')
21 files changed, 1758 insertions, 0 deletions
diff --git a/dice-lang/src/main/java/bjc/dicelang/BindingDiceExpression.java b/dice-lang/src/main/java/bjc/dicelang/BindingDiceExpression.java new file mode 100644 index 0000000..94dd761 --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/BindingDiceExpression.java @@ -0,0 +1,77 @@ +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 exp; + + /** + * The name to bind the expression to + */ + private String name; + + /** + * 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 env + * The enviroment to bind into + */ + public BindingDiceExpression(IDiceExpression left, + IDiceExpression right, Map<String, IDiceExpression> env) { + this(((ReferenceDiceExpression) left).getName(), right, env); + } + + /** + * Create a new dice expression binder + * + * @param name + * The name of the variable to bind + * @param exp + * The expression to bind to the variable + * @param env + * The enviroment to bind it in + */ + public BindingDiceExpression(String name, IDiceExpression exp, + Map<String, IDiceExpression> env) { + this.name = name; + this.exp = exp; + + env.put(name, exp); + } + + /* + * (non-Javadoc) + * + * @see bjc.utils.dice.IDiceExpression#roll() + */ + @Override + public int roll() { + return exp.roll(); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "assign[n=" + name + ", exp=" + exp.toString() + "]"; + } +} diff --git a/dice-lang/src/main/java/bjc/dicelang/ComplexDice.java b/dice-lang/src/main/java/bjc/dicelang/ComplexDice.java new file mode 100644 index 0000000..5d88001 --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/ComplexDice.java @@ -0,0 +1,113 @@ +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 dice + * The string to parse the dice from + * @return A dice group parsed from the string + */ + public static ComplexDice fromString(String dice) { + /* + * Split it on the dice type marker + */ + String[] strangs = dice.split("d"); + + try { + /* + * Create the actual dice + */ + return new ComplexDice( + new ScalarDie(Integer.parseInt(strangs[0])), + new Die(Integer.parseInt(strangs[1]))); + } catch (NumberFormatException nfex) { + /* + * Tell the user the expression is invalid + */ + throw new IllegalArgumentException( + "Attempted to create a dice using something that's not" + + " an integer: " + strangs[0] + " and " + + strangs[1] + " are likely culprits."); + } + } + + /** + * The die being rolled + */ + private IDiceExpression die; + + /** + * The number of the specified 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); + } + + /* + * (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(); + + for (int i = 0; i < nRoll; i++) { + res += die.roll(); + } + + return res; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + if (nDice instanceof ScalarDie && die instanceof Die) { + return nDice.toString() + die.toString(); + } else { + return "complex[n=" + nDice.toString() + ", d=" + + die.toString() + "]"; + } + } +} diff --git a/dice-lang/src/main/java/bjc/dicelang/CompoundDice.java b/dice-lang/src/main/java/bjc/dicelang/CompoundDice.java new file mode 100644 index 0000000..dcde650 --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/CompoundDice.java @@ -0,0 +1,80 @@ +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 l; + + /** + * The right die of the expression + */ + private IDiceExpression r; + + /** + * Create a new compound dice using the specified dice + * + * @param l + * The die to use on the left + * @param r + * The die to use on the right + */ + public CompoundDice(IDiceExpression l, IDiceExpression r) { + this.l = l; + this.r = r; + } + + /** + * Create a new compound dice from two dice strings + * + * @param l + * The left side dice + * @param r + * The right side dice + */ + public CompoundDice(String l, String r) { + this(ComplexDice.fromString(l), ComplexDice.fromString(r)); + } + + /** + * Create a new compound dice from an array of dice strings + * + * @param exps + * An array of dice strings + */ + public CompoundDice(String[] exps) { + this(exps[0], exps[1]); + } + + /* + * (non-Javadoc) + * + * @see bjc.utils.dice.IDiceExpression#roll() + */ + @Override + public int roll() { + /* + * Make the combination of the two dice + */ + return Integer.parseInt(l.roll() + "" + r.roll()); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "compound[l=" + l.toString() + ", r=" + r.toString() + "]"; + } +} diff --git a/dice-lang/src/main/java/bjc/dicelang/CompoundDiceExpression.java b/dice-lang/src/main/java/bjc/dicelang/CompoundDiceExpression.java new file mode 100644 index 0000000..162317b --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/CompoundDiceExpression.java @@ -0,0 +1,83 @@ +package bjc.dicelang; + +/** + * Implements a class for combining two dice with an operator + * + * @author ben + * + */ +public class CompoundDiceExpression implements IDiceExpression { + /** + * The operator to use for combining the dice + */ + private DiceExpressionType det; + + /** + * The dice on the left side of the expression + */ + private IDiceExpression left; + + /** + * The dice on the right side of the expression + */ + private IDiceExpression right; + + /** + * 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 det + * The operator to use for combining the dices + */ + public CompoundDiceExpression(IDiceExpression right, + IDiceExpression left, DiceExpressionType det) { + this.right = right; + this.left = left; + this.det = det; + } + + /* + * (non-Javadoc) + * + * @see bjc.utils.dice.IDiceExpression#roll() + */ + @Override + public int roll() { + /* + * Handle each operator + */ + switch (det) { + case ADD: + return right.roll() + left.roll(); + case SUBTRACT: + return right.roll() - left.roll(); + case MULTIPLY: + return right.roll() * left.roll(); + case DIVIDE: + /* + * Round to keep results as integers. We don't really have + * any need for floating-point dice + */ + return Math.round(right.roll() / left.roll()); + default: + throw new IllegalArgumentException( + "Got passed a invalid ScalarExpressionType " + det + + ". WAT"); + + } + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "dice-exp[type=" + det + ", l=" + left.toString() + ", r=" + + right.toString() + "]"; + } +} diff --git a/dice-lang/src/main/java/bjc/dicelang/DiceExpressionBuilder.java b/dice-lang/src/main/java/bjc/dicelang/DiceExpressionBuilder.java new file mode 100644 index 0000000..5ced36d --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/DiceExpressionBuilder.java @@ -0,0 +1,153 @@ +package bjc.dicelang; + +import static bjc.dicelang.DiceExpressionType.*; + +/** + * Build a dice expression piece by piece + * + * @author ben + * + */ +public class DiceExpressionBuilder { + /** + * The dice expression we are building + */ + private IDiceExpression baking; + + /** + * Build a dice expresssion from a seed dice + * + * @param firstDice + * The dice to use as a seed + */ + public DiceExpressionBuilder(ComplexDice firstDice) { + baking = firstDice; + } + + /** + * 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 CompoundDiceExpression(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 ScalarDiceExpression(baking, 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 CompoundDiceExpression(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 ScalarDiceExpression(baking, 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 CompoundDiceExpression(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 ScalarDiceExpression(baking, 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 CompoundDiceExpression(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 ScalarDiceExpression(baking, num, SUBTRACT); + return this; + } +} diff --git a/dice-lang/src/main/java/bjc/dicelang/DiceExpressionParser.java b/dice-lang/src/main/java/bjc/dicelang/DiceExpressionParser.java new file mode 100644 index 0000000..9f96835 --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/DiceExpressionParser.java @@ -0,0 +1,152 @@ +package bjc.dicelang; + +import java.util.Map; +import java.util.Stack; + +import org.apache.commons.lang3.StringUtils; + +import bjc.utils.funcdata.FunctionalList; +import bjc.utils.funcdata.FunctionalStringTokenizer; +import bjc.utils.parserutils.ShuntingYard; + +/** + * Parse a dice expression from a string + * + * @author ben + * + */ +public class DiceExpressionParser { + /** + * Parse a dice expression from a string + * + * @param exp + * The string to parse an expression from + * @param env + * The enviroment to use when parsing expressions + * @return The parsed dice expression + */ + public IDiceExpression parse(String exp, + Map<String, IDiceExpression> env) { + /* + * Create a tokenizer over the strings + */ + FunctionalStringTokenizer fst = new FunctionalStringTokenizer(exp); + + /* + * Create a shunter to rewrite the expression + */ + ShuntingYard<String> yard = new ShuntingYard<>(); + + /* + * 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 + */ + FunctionalList<String> ls = + yard.postfix(fst.toList(s -> s), s -> s); + + /* + * Create a stack for building an expression from parts + */ + Stack<IDiceExpression> dexps = new Stack<>(); + + /* + * Create the expression from parts + */ + ls.forEach((tok) -> { + /* + * Handle compound dice + */ + if (StringUtils.countMatches(tok, 'c') == 1 + && !tok.equalsIgnoreCase("c")) { + String[] strangs = tok.split("c"); + + dexps.push(new CompoundDice(strangs)); + } else if (StringUtils.countMatches(tok, 'd') == 1 + && !tok.equalsIgnoreCase("d")) { + /* + * Handle dice groups + */ + dexps.push(ComplexDice.fromString(tok)); + } else { + try { + /* + * Handle scalar numbers + */ + dexps.push(new ScalarDie(Integer.parseInt(tok))); + } catch (NumberFormatException nfex) { + + if (dexps.size() >= 2) { + /* + * Apply an operation to two dice + */ + IDiceExpression r = dexps.pop(); + IDiceExpression l = dexps.pop(); + switch (tok) { + case ":=": + dexps.push(new BindingDiceExpression(l, r, + env)); + break; + case "+": + dexps.push(new CompoundDiceExpression(r, l, + DiceExpressionType.ADD)); + break; + case "-": + dexps.push(new CompoundDiceExpression(r, l, + DiceExpressionType.SUBTRACT)); + break; + case "*": + dexps.push(new CompoundDiceExpression(r, l, + DiceExpressionType.MULTIPLY)); + break; + case "/": + dexps.push(new CompoundDiceExpression(r, l, + DiceExpressionType.DIVIDE)); + break; + case "c": + dexps.push(new CompoundDice(l, r)); + break; + case "d": + dexps.push(new ComplexDice(l, r)); + break; + default: + /* + * Parse it as a variable reference + * + * Make sure to restore popped variables + */ + dexps.push(l); + dexps.push(r); + + dexps.push(new ReferenceDiceExpression(tok, + env)); + } + } else { + /* + * Parse it as a variable reference + */ + dexps.push(new ReferenceDiceExpression(tok, env)); + } + } + } + }); + + if (dexps.size() != 1) { + System.err.println( + "WARNING: Leftovers found on dice expression stack. Remember, := is assignment."); + } + + /* + * Return the built expression + */ + return dexps.pop(); + } +} diff --git a/dice-lang/src/main/java/bjc/dicelang/DiceExpressionType.java b/dice-lang/src/main/java/bjc/dicelang/DiceExpressionType.java new file mode 100644 index 0000000..296d751 --- /dev/null +++ b/dice-lang/src/main/java/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/main/java/bjc/dicelang/Die.java b/dice-lang/src/main/java/bjc/dicelang/Die.java new file mode 100644 index 0000000..addc2af --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/Die.java @@ -0,0 +1,51 @@ +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) { + this.nSides = nSides; + } + + /* + * (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/main/java/bjc/dicelang/IDiceExpression.java b/dice-lang/src/main/java/bjc/dicelang/IDiceExpression.java new file mode 100644 index 0000000..93a75b9 --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/IDiceExpression.java @@ -0,0 +1,17 @@ +package bjc.dicelang; + +/** + * An expression for something that can be rolled like a polyhedral die + * + * @author ben + * + */ +@FunctionalInterface +public interface IDiceExpression { + /** + * Roll the dice once + * + * @return The result of rowing the dice + */ + public int roll(); +} diff --git a/dice-lang/src/main/java/bjc/dicelang/PolyhedralDice.java b/dice-lang/src/main/java/bjc/dicelang/PolyhedralDice.java new file mode 100644 index 0000000..e0bef64 --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/PolyhedralDice.java @@ -0,0 +1,86 @@ +package bjc.dicelang; + +/** + * Utility class that produces common polyhedral dice + * + * @author ben + * + */ +public class PolyhedralDice { + /** + * 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 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 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 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 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 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 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/main/java/bjc/dicelang/ReferenceDiceExpression.java b/dice-lang/src/main/java/bjc/dicelang/ReferenceDiceExpression.java new file mode 100644 index 0000000..c1a4a79 --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/ReferenceDiceExpression.java @@ -0,0 +1,70 @@ +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> env; + + /** + * The name of the bound variable + */ + private String name; + + /** + * 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.name = name; + this.env = env; + } + + /** + * Get the name of the referenced variable + * + * @return the name of the referenced variable + */ + public String getName() { + return name; + } + + /* + * (non-Javadoc) + * + * @see bjc.utils.dice.IDiceExpression#roll() + */ + @Override + public int roll() { + return env.get(name).roll(); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + if (env.containsKey(name)) { + return env.get(name).toString(); + } else { + return name; + } + } +} diff --git a/dice-lang/src/main/java/bjc/dicelang/ScalarDiceExpression.java b/dice-lang/src/main/java/bjc/dicelang/ScalarDiceExpression.java new file mode 100644 index 0000000..a7aae83 --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/ScalarDiceExpression.java @@ -0,0 +1,75 @@ +package bjc.dicelang; + +/** + * A dice expression that combines a scalar and a dice + * + * @author ben + * + */ +public class ScalarDiceExpression implements IDiceExpression { + /** + * The operation to combine with + */ + private DiceExpressionType det; + + /** + * The expression to be combined + */ + private IDiceExpression exp; + + /** + * The scalar to be combined + */ + private int scalar; + + /** + * Create a dice expression with a scalar + * + * @param dex + * The dice to use + * @param scalr + * The scalar to use + * @param dt + * The operation to combine with + */ + public ScalarDiceExpression(IDiceExpression dex, int scalr, + DiceExpressionType dt) { + exp = dex; + scalar = scalr; + det = dt; + } + + /* + * (non-Javadoc) + * + * @see bjc.utils.dice.IDiceExpression#roll() + */ + @Override + public int roll() { + switch (det) { + case ADD: + return exp.roll() + scalar; + case SUBTRACT: + return exp.roll() - scalar; + case MULTIPLY: + return exp.roll() * scalar; + case DIVIDE: + return Math.round(exp.roll() / scalar); + default: + throw new IllegalStateException( + "Got passed a invalid ScalarExpressionType " + + det); + } + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "scalar-exp[type=" + det + ", l=" + scalar + ", r=" + + exp.toString() + "]"; + } +} diff --git a/dice-lang/src/main/java/bjc/dicelang/ScalarDie.java b/dice-lang/src/main/java/bjc/dicelang/ScalarDie.java new file mode 100644 index 0000000..8078e8b --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/ScalarDie.java @@ -0,0 +1,45 @@ +package bjc.dicelang; + +/** + * A die that represents a static number + * + * @author ben + * + */ +public class ScalarDie implements IDiceExpression { + /** + * The represented number + */ + private int num; + + /** + * Create a dice with the specified number + * + * @param num + * The number used for the dice + */ + public ScalarDie(int num) { + this.num = num; + } + + /* + * (non-Javadoc) + * + * @see bjc.utils.dice.IDiceExpression#roll() + */ + @Override + public int roll() { + return num; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return Integer.toString(num); + } + +} diff --git a/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTExpression.java b/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTExpression.java new file mode 100644 index 0000000..6e9b836 --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTExpression.java @@ -0,0 +1,195 @@ +package bjc.dicelang.ast; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BinaryOperator; + +import org.apache.commons.lang3.StringUtils; + +import bjc.dicelang.ComplexDice; +import bjc.dicelang.CompoundDice; +import bjc.dicelang.IDiceExpression; +import bjc.utils.data.Pair; +import bjc.utils.parserutils.AST; + +/** + * An implementation of {@link IDiceExpression} backed by an AST of + * {@link IDiceASTNode}s + * + * @author ben + * + */ +public class DiceASTExpression implements IDiceExpression { + + /** + * Build the map of operations to use when collapsing the AST + * + * @param env + * The enviroment to evaluate bindings and such against + * @return The operations to use when collapsing the AST + */ + private static + Map<IDiceASTNode, BinaryOperator<Pair<Integer, AST<IDiceASTNode>>>> + buildOperations(Map<String, DiceASTExpression> env) { + Map<IDiceASTNode, BinaryOperator<Pair<Integer, AST<IDiceASTNode>>>> opCollapsers = + new HashMap<>(); + + opCollapsers.put(OperatorDiceNode.ADD, (left, right) -> { + return left.merge((lval, last) -> right.merge((rval, rast) -> { + return new Pair<>(lval + rval, + new AST<>(OperatorDiceNode.ADD, last, rast)); + })); + + }); + opCollapsers.put(OperatorDiceNode.SUBTRACT, (left, right) -> { + return left.merge((lval, last) -> right.merge((rval, rast) -> { + return new Pair<>(lval - rval, + new AST<>(OperatorDiceNode.SUBTRACT, last, rast)); + })); + + }); + opCollapsers.put(OperatorDiceNode.MULTIPLY, (left, right) -> { + return left.merge((lval, last) -> right.merge((rval, rast) -> { + return new Pair<>(lval * rval, + new AST<>(OperatorDiceNode.MULTIPLY, last, rast)); + })); + + }); + opCollapsers.put(OperatorDiceNode.DIVIDE, (left, right) -> { + return left.merge((lval, last) -> right.merge((rval, rast) -> { + return new Pair<>(lval / rval, + new AST<>(OperatorDiceNode.DIVIDE, last, rast)); + })); + }); + + opCollapsers.put(OperatorDiceNode.ASSIGN, (left, right) -> { + return left.merge((lval, last) -> right.merge((rval, rast) -> { + String nam = last.collapse((nod) -> { + return ((VariableDiceNode) nod).getVariable(); + } , (v) -> (lv, rv) -> null, (r) -> r); + + env.put(nam, new DiceASTExpression(rast, env)); + + return new Pair<>(rval, + new AST<>(OperatorDiceNode.ASSIGN, last, rast)); + })); + }); + + opCollapsers.put(OperatorDiceNode.COMPOUND, (left, right) -> { + return left.merge((lval, last) -> right.merge((rval, rast) -> { + int ival = Integer.parseInt( + Integer.toString(lval) + Integer.toString(rval)); + + return new Pair<>(ival, + new AST<>(OperatorDiceNode.COMPOUND, last, rast)); + })); + }); + opCollapsers.put(OperatorDiceNode.GROUP, (left, right) -> { + return left.merge((lval, last) -> right.merge((rval, rast) -> { + + return new Pair<>(new ComplexDice(lval, rval).roll(), + new AST<>(OperatorDiceNode.GROUP, last, rast)); + })); + }); + + return opCollapsers; + } + + /** + * The AST this expression will evaluate + */ + private AST<IDiceASTNode> ast; + + /** + * The enviroment to evaluate bindings and such against + */ + private Map<String, DiceASTExpression> env; + + /** + * Create a new dice expression backed by an AST + * + * @param ast + * The AST backing this expression + * @param env + * The enviroment to evaluate bindings against + */ + public DiceASTExpression(AST<IDiceASTNode> ast, + Map<String, DiceASTExpression> env) { + this.ast = ast; + this.env = env; + } + + /** + * Expand a leaf AST token into a pair for evaluation + * + * @param tokn + * The token to evaluate + * @return A pair consisting of the token's value and the AST it + * represents + */ + private Pair<Integer, AST<IDiceASTNode>> evalLeaf(IDiceASTNode tokn) { + if (tokn instanceof VariableDiceNode) { + String varName = ((VariableDiceNode) tokn).getVariable(); + + if (env.containsKey(varName)) { + return new Pair<>(env.get(varName).roll(), + new AST<>(tokn)); + } else { + // Handle special case for defining variables + return new Pair<>(0, new AST<>(tokn)); + } + } else { + LiteralDiceNode lnod = (LiteralDiceNode) tokn; + String dat = lnod.getData(); + + if (StringUtils.countMatches(dat, 'c') == 1 + && !dat.equalsIgnoreCase("c")) { + String[] strangs = dat.split("c"); + return new Pair<>(new CompoundDice(strangs).roll(), + new AST<>(tokn)); + } else if (StringUtils.countMatches(dat, 'd') == 1 + && !dat.equalsIgnoreCase("d")) { + /* + * Handle dice groups + */ + return new Pair<>(ComplexDice.fromString(dat).roll(), + new AST<>(tokn)); + } else { + return new Pair<>(Integer.parseInt(dat), new AST<>(tokn)); + } + } + } + + /** + * Get the AST bound to this expression + * + * @return the ast + */ + public AST<IDiceASTNode> getAst() { + return ast; + } + + /* + * (non-Javadoc) + * + * @see bjc.utils.dice.IDiceExpression#roll() + */ + @Override + public int roll() { + Map<IDiceASTNode, BinaryOperator<Pair<Integer, AST<IDiceASTNode>>>> operations = + buildOperations(env); + + return ast.collapse(this::evalLeaf, operations::get, + (r) -> r.merge((left, right) -> left)); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return ast.toString(); + } +} diff --git a/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTFlattener.java b/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTFlattener.java new file mode 100644 index 0000000..aa15c4d --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTFlattener.java @@ -0,0 +1,114 @@ +package bjc.dicelang.ast; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BinaryOperator; + +import org.apache.commons.lang3.StringUtils; + +import bjc.dicelang.BindingDiceExpression; +import bjc.dicelang.ComplexDice; +import bjc.dicelang.CompoundDice; +import bjc.dicelang.CompoundDiceExpression; +import bjc.dicelang.DiceExpressionType; +import bjc.dicelang.IDiceExpression; +import bjc.dicelang.ReferenceDiceExpression; +import bjc.dicelang.ScalarDie; +import bjc.utils.parserutils.AST; + +/** + * Flatten an {@link AST} of {@link IDiceASTNode} into a + * {@link IDiceExpression} + * + * @author ben + * + */ +public class DiceASTFlattener { + /** + * Build the operations to use for tree flattening + * + * @param env + * The enviroment the tree will be flattened against + * @return The operations needed for tree flattening + */ + private static Map<IDiceASTNode, BinaryOperator<IDiceExpression>> + buildOperations(Map<String, IDiceExpression> env) { + Map<IDiceASTNode, BinaryOperator<IDiceExpression>> opCollapsers = + new HashMap<>(); + opCollapsers.put(OperatorDiceNode.ADD, (left, right) -> { + return new CompoundDiceExpression(right, left, + DiceExpressionType.ADD); + }); + opCollapsers.put(OperatorDiceNode.SUBTRACT, (left, right) -> { + return new CompoundDiceExpression(right, left, + DiceExpressionType.SUBTRACT); + }); + opCollapsers.put(OperatorDiceNode.MULTIPLY, (left, right) -> { + return new CompoundDiceExpression(right, left, + DiceExpressionType.MULTIPLY); + }); + opCollapsers.put(OperatorDiceNode.DIVIDE, (left, right) -> { + return new CompoundDiceExpression(right, left, + DiceExpressionType.DIVIDE); + }); + opCollapsers.put(OperatorDiceNode.ASSIGN, (left, right) -> { + return new BindingDiceExpression(left, right, env); + }); + opCollapsers.put(OperatorDiceNode.COMPOUND, (left, right) -> { + return new CompoundDice(left, right); + }); + opCollapsers.put(OperatorDiceNode.GROUP, (left, right) -> { + return new ComplexDice(left, right); + }); + + return opCollapsers; + } + + /** + * Create a dice expression from a literal token + * + * @param tok + * The token to convert to an expression + * @return The dice expression represented by the token + */ + private static IDiceExpression expFromLiteral(LiteralDiceNode tok) { + String data = tok.getData(); + + if (StringUtils.countMatches(data, 'c') == 1 + && !data.equalsIgnoreCase("c")) { + String[] strangs = data.split("c"); + + return new CompoundDice(ComplexDice.fromString(strangs[0]), + ComplexDice.fromString(strangs[1])); + } else if (StringUtils.countMatches(data, 'd') == 1 + && !data.equalsIgnoreCase("d")) { + return ComplexDice.fromString(data); + } else { + return new ScalarDie(Integer.parseInt(data)); + } + } + + /** + * Flatten a AST into a dice expression + * + * @param ast + * The AST to flatten + * @param env + * The enviroment to flatten against + * @return The AST, flattened into a dice expression + */ + public static IDiceExpression flatten(AST<IDiceASTNode> ast, + Map<String, IDiceExpression> env) { + Map<IDiceASTNode, BinaryOperator<IDiceExpression>> opCollapsers = + buildOperations(env); + + return ast.collapse((nod) -> { + if (nod instanceof LiteralDiceNode) { + return expFromLiteral((LiteralDiceNode) nod); + } else { + return new ReferenceDiceExpression( + ((VariableDiceNode) nod).getVariable(), env); + } + } , opCollapsers::get, (r) -> r); + } +} diff --git a/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTFreezer.java b/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTFreezer.java new file mode 100644 index 0000000..0e2134b --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTFreezer.java @@ -0,0 +1,92 @@ +package bjc.dicelang.ast; + +import java.util.Map; + +import bjc.utils.parserutils.AST; + +/** + * Freeze references in a dice AST, replacing variable references with what + * the variables refer to + * + * @author ben + * + */ +public class DiceASTFreezer { + /** + * Expand a reference + * + * @param vnode + * The node containing the reference to expand + * @param env + * The enviroment to expand against + * @return The expanded reference + */ + private static AST<IDiceASTNode> expandNode(VariableDiceNode vnode, + Map<String, AST<IDiceASTNode>> env) { + return env.get(vnode.getVariable()); + } + + /** + * Expand a reference + * + * @param vnode + * The node containing the reference to expand + * @param env + * The enviroment to expand against + * @return The expanded reference + */ + private static AST<IDiceASTNode> expandNode2(VariableDiceNode vnode, + Map<String, DiceASTExpression> env) { + return env.get(vnode.getVariable()).getAst(); + } + + /** + * Freeze the references in an AST + * + * @param tree + * The tree to freeze references in + * @param env + * The enviroment to get reference values from + * @return The tree with references frozen + */ + @SuppressWarnings("unused") + public static AST<IDiceASTNode> freezeAST(AST<IDiceASTNode> tree, + Map<String, AST<IDiceASTNode>> env) { + return tree.collapse((nod) -> { + if (nod instanceof VariableDiceNode) { + return expandNode((VariableDiceNode) nod, env); + } else { + // Type is specified here so compiler can know the type + // we're using + return new AST<IDiceASTNode>(nod); + } + } , (op) -> (left, right) -> { + return new AST<IDiceASTNode>(op, left, right); + } , (r) -> r); + } + + /** + * Freeze the references in an expression backed by an AST + * + * @param tree + * The tree-backed expression to freeze references in + * @param env + * The enviroment to get reference values from + * @return The tree with references frozen + */ + @SuppressWarnings("unused") + public static AST<IDiceASTNode> freezeAST(DiceASTExpression tree, + Map<String, DiceASTExpression> env) { + return tree.getAst().collapse((nod) -> { + if (nod instanceof VariableDiceNode) { + return expandNode2((VariableDiceNode) nod, env); + } else { + // Type is specified here so compiler can know the type + // we're using + return new AST<IDiceASTNode>(nod); + } + } , (op) -> (left, right) -> { + return new AST<IDiceASTNode>(op, left, right); + } , (r) -> r); + } +} diff --git a/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTParser.java b/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTParser.java new file mode 100644 index 0000000..5f16554 --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/ast/DiceASTParser.java @@ -0,0 +1,108 @@ +package bjc.dicelang.ast; + +import org.apache.commons.lang3.StringUtils; + +import bjc.utils.funcdata.FunctionalList; +import bjc.utils.funcdata.FunctionalStringTokenizer; +import bjc.utils.parserutils.AST; +import bjc.utils.parserutils.ShuntingYard; +import bjc.utils.parserutils.TreeConstructor; + +/** + * Create an AST from a string expression + * + * @author ben + * + */ +public class DiceASTParser { + /** + * The yard to use for shunting expressions + */ + private static ShuntingYard<String> yard; + + static { + yard = new ShuntingYard<>(); + + 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 + } + + /** + * Build an AST from a string expression + * + * @param exp + * The string to build from + * @return An AST built from the passed in string + */ + public AST<IDiceASTNode> buildAST(String exp) { + FunctionalList<String> tokens = FunctionalStringTokenizer + .fromString(exp).toList((s) -> s); + + FunctionalList<String> shunted = yard.postfix(tokens, (s) -> s); + + AST<String> rawAST = TreeConstructor.constructTree(shunted, + this::isOperator, (op) -> false, null); + + AST<IDiceASTNode> bakedAST = rawAST.transmuteAST((tok) -> { + if (isOperator(tok)) { + return OperatorDiceNode.fromString(tok); + } else if (isLiteral(tok)) { + return new LiteralDiceNode(tok); + } else { + return new VariableDiceNode(tok); + } + }); + + return bakedAST; + } + + /** + * Check if a token represents a literal + * + * @param tok + * The token to check + * @return Whether or not the token represents a literal + */ + private static boolean isLiteral(String tok) { + if (StringUtils.countMatches(tok, 'c') == 1 + && !tok.equalsIgnoreCase("c")) { + return true; + } else if (StringUtils.countMatches(tok, 'd') == 1 + && !tok.equalsIgnoreCase("d")) { + return true; + } else { + try { + Integer.parseInt(tok); + return true; + } catch (NumberFormatException nfx) { + return false; + } + } + } + + /** + * Check if a token represents an operator + * + * @param tok + * The token to check if it represents an operator + * @return Whether or not the token represents an operator + */ + private boolean isOperator(String tok) { + switch (tok) { + case ":=": + case "+": + case "-": + case "*": + case "/": + case "c": + case "d": + return true; + default: + return false; + } + } +} diff --git a/dice-lang/src/main/java/bjc/dicelang/ast/IDiceASTNode.java b/dice-lang/src/main/java/bjc/dicelang/ast/IDiceASTNode.java new file mode 100644 index 0000000..439bdac --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/ast/IDiceASTNode.java @@ -0,0 +1,16 @@ +package bjc.dicelang.ast; + +/** + * The interface for a node in a dice AST + * + * @author ben + * + */ +public interface IDiceASTNode { + /** + * Check if this node represents an operator or not + * + * @return Whether or not this node represents an operator + */ + public boolean isOperator(); +} diff --git a/dice-lang/src/main/java/bjc/dicelang/ast/LiteralDiceNode.java b/dice-lang/src/main/java/bjc/dicelang/ast/LiteralDiceNode.java new file mode 100644 index 0000000..8157844 --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/ast/LiteralDiceNode.java @@ -0,0 +1,48 @@ +package bjc.dicelang.ast; + +/** + * A AST node that represents a literal value + * + * @author ben + * + */ +public class LiteralDiceNode implements IDiceASTNode { + /** + * The value contained by this node + */ + private String data; + + /** + * Create a new node with the given value + * + * @param data + * The value to be in this node + */ + public LiteralDiceNode(String data) { + this.data = data; + } + + @Override + public boolean isOperator() { + return false; + } + + /** + * Get the data stored in this AST node + * + * @return the data stored in this AST node + */ + public String getData() { + return data; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return data; + } +} diff --git a/dice-lang/src/main/java/bjc/dicelang/ast/OperatorDiceNode.java b/dice-lang/src/main/java/bjc/dicelang/ast/OperatorDiceNode.java new file mode 100644 index 0000000..45c3d58 --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/ast/OperatorDiceNode.java @@ -0,0 +1,81 @@ +package bjc.dicelang.ast; + +// The following classes need to be changed upon addition of a new operator +// 1. DiceASTExpression +// 2. DiceASTFlattener +// 3. DiceASTParser +/** + * A node that represents an operator + * + * @author ben + * + */ +public enum OperatorDiceNode implements IDiceASTNode { + /** + * Represents adding two nodes + */ + ADD, + /** + * Represents assigning one node to another + */ + ASSIGN, + /** + * Representings combining two node values together + */ + COMPOUND, + /** + * Represents dividing two nodes + */ + DIVIDE, + /** + * Represents using one node a variable number of times + */ + GROUP, + /** + * Represents multiplying two nodes + */ + MULTIPLY, + /** + * Represents subtracting two nodes + */ + SUBTRACT; + + /** + * 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": + return GROUP; + case "c": + return COMPOUND; + default: + throw new IllegalArgumentException( + s + " is not a valid operator node"); + } + } + + /* + * (non-Javadoc) + * + * @see bjc.utils.dice.ast.IDiceASTNode#isOperator() + */ + @Override + public boolean isOperator() { + return true; + } +} diff --git a/dice-lang/src/main/java/bjc/dicelang/ast/VariableDiceNode.java b/dice-lang/src/main/java/bjc/dicelang/ast/VariableDiceNode.java new file mode 100644 index 0000000..e02952d --- /dev/null +++ b/dice-lang/src/main/java/bjc/dicelang/ast/VariableDiceNode.java @@ -0,0 +1,53 @@ +package bjc.dicelang.ast; + +/** + * A node that represents a variable reference + * + * @author ben + * + */ +public class VariableDiceNode implements IDiceASTNode { + /** + * The variable referenced by this node + */ + private String var; + + /** + * Create a new node representing the specified variable + * + * @param data + * The name of the variable being referenced + */ + public VariableDiceNode(String data) { + this.var = data; + } + + /** + * Get the variable referenced by this AST node + * + * @return the variable referenced by this AST node + */ + public String getVariable() { + return var; + } + + /* + * (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 var; + } +} |
