summaryrefslogtreecommitdiff
path: root/dice-lang/src/bjc/utils/dice/ast
diff options
context:
space:
mode:
Diffstat (limited to 'dice-lang/src/bjc/utils/dice/ast')
-rw-r--r--dice-lang/src/bjc/utils/dice/ast/DiceASTExpression.java195
-rw-r--r--dice-lang/src/bjc/utils/dice/ast/DiceASTFlattener.java114
-rw-r--r--dice-lang/src/bjc/utils/dice/ast/DiceASTFreezer.java92
-rw-r--r--dice-lang/src/bjc/utils/dice/ast/DiceASTParser.java108
-rw-r--r--dice-lang/src/bjc/utils/dice/ast/IDiceASTNode.java16
-rw-r--r--dice-lang/src/bjc/utils/dice/ast/LiteralDiceNode.java48
-rw-r--r--dice-lang/src/bjc/utils/dice/ast/OperatorDiceNode.java81
-rw-r--r--dice-lang/src/bjc/utils/dice/ast/VariableDiceNode.java53
8 files changed, 707 insertions, 0 deletions
diff --git a/dice-lang/src/bjc/utils/dice/ast/DiceASTExpression.java b/dice-lang/src/bjc/utils/dice/ast/DiceASTExpression.java
new file mode 100644
index 0000000..89adb65
--- /dev/null
+++ b/dice-lang/src/bjc/utils/dice/ast/DiceASTExpression.java
@@ -0,0 +1,195 @@
+package bjc.utils.dice.ast;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.BinaryOperator;
+
+import org.apache.commons.lang3.StringUtils;
+
+import bjc.utils.data.Pair;
+import bjc.utils.dice.ComplexDice;
+import bjc.utils.dice.CompoundDice;
+import bjc.utils.dice.IDiceExpression;
+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/bjc/utils/dice/ast/DiceASTFlattener.java b/dice-lang/src/bjc/utils/dice/ast/DiceASTFlattener.java
new file mode 100644
index 0000000..70465a5
--- /dev/null
+++ b/dice-lang/src/bjc/utils/dice/ast/DiceASTFlattener.java
@@ -0,0 +1,114 @@
+package bjc.utils.dice.ast;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.BinaryOperator;
+
+import org.apache.commons.lang3.StringUtils;
+
+import bjc.utils.dice.BindingDiceExpression;
+import bjc.utils.dice.ComplexDice;
+import bjc.utils.dice.CompoundDice;
+import bjc.utils.dice.CompoundDiceExpression;
+import bjc.utils.dice.DiceExpressionType;
+import bjc.utils.dice.IDiceExpression;
+import bjc.utils.dice.ReferenceDiceExpression;
+import bjc.utils.dice.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/bjc/utils/dice/ast/DiceASTFreezer.java b/dice-lang/src/bjc/utils/dice/ast/DiceASTFreezer.java
new file mode 100644
index 0000000..efe37c0
--- /dev/null
+++ b/dice-lang/src/bjc/utils/dice/ast/DiceASTFreezer.java
@@ -0,0 +1,92 @@
+package bjc.utils.dice.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/bjc/utils/dice/ast/DiceASTParser.java b/dice-lang/src/bjc/utils/dice/ast/DiceASTParser.java
new file mode 100644
index 0000000..38c514a
--- /dev/null
+++ b/dice-lang/src/bjc/utils/dice/ast/DiceASTParser.java
@@ -0,0 +1,108 @@
+package bjc.utils.dice.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/bjc/utils/dice/ast/IDiceASTNode.java b/dice-lang/src/bjc/utils/dice/ast/IDiceASTNode.java
new file mode 100644
index 0000000..073da89
--- /dev/null
+++ b/dice-lang/src/bjc/utils/dice/ast/IDiceASTNode.java
@@ -0,0 +1,16 @@
+package bjc.utils.dice.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/bjc/utils/dice/ast/LiteralDiceNode.java b/dice-lang/src/bjc/utils/dice/ast/LiteralDiceNode.java
new file mode 100644
index 0000000..b0c1400
--- /dev/null
+++ b/dice-lang/src/bjc/utils/dice/ast/LiteralDiceNode.java
@@ -0,0 +1,48 @@
+package bjc.utils.dice.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/bjc/utils/dice/ast/OperatorDiceNode.java b/dice-lang/src/bjc/utils/dice/ast/OperatorDiceNode.java
new file mode 100644
index 0000000..c4f7763
--- /dev/null
+++ b/dice-lang/src/bjc/utils/dice/ast/OperatorDiceNode.java
@@ -0,0 +1,81 @@
+package bjc.utils.dice.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/bjc/utils/dice/ast/VariableDiceNode.java b/dice-lang/src/bjc/utils/dice/ast/VariableDiceNode.java
new file mode 100644
index 0000000..43a09b2
--- /dev/null
+++ b/dice-lang/src/bjc/utils/dice/ast/VariableDiceNode.java
@@ -0,0 +1,53 @@
+package bjc.utils.dice.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;
+ }
+}