package bjc.dicelang.v2; import bjc.utils.data.IPair; import bjc.utils.data.Pair; import bjc.utils.funcdata.FunctionalList; import bjc.utils.funcdata.FunctionalMap; import bjc.utils.funcdata.FunctionalStringTokenizer; import bjc.utils.funcdata.IList; import bjc.utils.funcdata.IMap; import bjc.utils.funcutils.ListUtils; import bjc.utils.funcutils.StringUtils; import java.util.Arrays; import java.util.Deque; import java.util.LinkedList; import java.util.regex.Matcher; import java.util.regex.Pattern; import static bjc.dicelang.v2.Token.Type.*; public class DiceLangEngine { // Input rules for processing tokens private Deque> opExpansionTokens; private Deque> deaffixationTokens; // ID for generation private int nextLiteral; private int nextSym; // Debug indicator private boolean debugMode; // Shunter for token postfixing private Shunter shunt; private IMap symTable; private IMap stringLits; private final int MATH_PREC = 20; private final int DICE_PREC = 10; private final int EXPR_PREC = 0; public DiceLangEngine() { opExpansionTokens = new LinkedList<>(); opExpansionTokens.add(new Pair<>("+", "\\+")); opExpansionTokens.add(new Pair<>("-", "-")); opExpansionTokens.add(new Pair<>("*", "\\*")); opExpansionTokens.add(new Pair<>("//", "//")); opExpansionTokens.add(new Pair<>("/", "/")); opExpansionTokens.add(new Pair<>(":=", ":=")); opExpansionTokens.add(new Pair<>("=>", "=>")); deaffixationTokens = new LinkedList<>(); deaffixationTokens.add(new Pair<>("(", "\\(")); deaffixationTokens.add(new Pair<>(")", "\\)")); deaffixationTokens.add(new Pair<>("[", "\\[")); deaffixationTokens.add(new Pair<>("]", "\\]")); nextLiteral = 1; // @TODO make configurable debugMode = true; shunt = new Shunter(); } public boolean runCommand(String command) { // Split the command into tokens IList tokens = FunctionalStringTokenizer .fromString(command) .toList(); // Will hold tokens with string literals removed IList destringed = new FunctionalList<>(); // Where we keep the string literals // @TODO put these in the sym-table early instead // once there is a sym-table IMap stringLiterals = new FunctionalMap<>(); boolean success = destringTokens(tokens, stringLiterals, destringed); if(!success) return success; if(debugMode) { System.out.println("\tCommand after destringing: " + destringed.toString()); System.out.println("\tString literals in table"); stringLiterals.forEach((key, val) -> { System.out.printf("\t\tName: (%s)\tValue: (%s)\n", key, val); }); } IList semiExpandedTokens = ListUtils.deAffixTokens(destringed, deaffixationTokens); IList fullyExpandedTokens = ListUtils.splitTokens(semiExpandedTokens, opExpansionTokens); if(debugMode) { System.out.printf("\tCommand after token deaffixation: " + semiExpandedTokens.toString() + "\n"); System.out.printf("\tCommand after token expansion: " + fullyExpandedTokens.toString() + "\n"); } IList lexedTokens = new FunctionalList<>(); for(String token : fullyExpandedTokens.toIterable()) { Token tk = lexToken(token, stringLiterals); if(tk == null) continue; else if(tk == Token.NIL_TOKEN) return false; else lexedTokens.add(tk); } if(debugMode) System.out.printf("\tCommand after tokenization: %s\n", lexedTokens.toString()); IList shuntedTokens = new FunctionalList<>(); success = shunt.shuntTokens(lexedTokens, shuntedTokens); if(!success) return false; if(debugMode) System.out.printf("\tCommand after shunting: %s\n", shuntedTokens.toString()); return true; } private Token lexToken(String token, IMap stringLts) { if(token.equals("")) return null; Token tk = Token.NIL_TOKEN; switch(token) { case "+": tk = new Token(ADD); break; case "-": tk = new Token(SUBTRACT); break; case "*": tk = new Token(MULTIPLY); break; case "/": tk = new Token(DIVIDE); break; case "//": tk = new Token(IDIVIDE); break; case "dg": tk = new Token(DICEGROUP); break; case "dc": tk = new Token(DICECONCAT); break; case "dl": tk = new Token(DICELIST); break; case "=>": tk = new Token(LET); break; case ":=": tk = new Token(BIND); break; case "(": case ")": case "[": case "]": tk = tokenizeGrouping(token); break; default: tk = tokenizeLiteral(token, stringLts); } return tk; } private Token tokenizeGrouping(String token) { Token tk = Token.NIL_TOKEN; if(StringUtils.containsOnly(token, "\\" + token.charAt(0))) { switch(token) { case "(": tk = new Token(OPAREN, token.length()); break; case ")": tk = new Token(CPAREN, token.length()); break; case "[": tk = new Token(OBRACKET, token.length()); break; case "]": tk = new Token(CBRACKET, token.length()); break; } } return tk; } private Pattern intMatcher = Pattern.compile("\\A[\\-\\+]?\\d+\\Z"); private Pattern stringLitMatcher = Pattern.compile("\\AstringLiteral\\d+\\Z"); private Token tokenizeLiteral(String token, IMap stringLts) { Token tk = Token.NIL_TOKEN; if(intMatcher.matcher(token).matches()) { tk = new Token(INT_LIT, Integer.parseInt(token)); } else if(DoubleMatcher.floatingLiteral.matcher(token).matches()) { tk = new Token(FLOAT_LIT, Double.parseDouble(token)); } else if(DiceBox.isValidExpression(token)) { tk = new Token(DICE_LIT, DiceBox.parseExpression(token)); if(debugMode) System.out.println("\tDEBUG: Parsed dice expression" + ", evaluated as: " + tk.diceValue.value()); } else { Matcher stringLit = stringLitMatcher.matcher(token); if(stringLit.matches()) { int litNum = Integer.parseInt(stringLit.group()); stringLits.put(litNum, stringLts.get(token)); tk = new Token(STRING_LIT, litNum); } else { // @TODO define what a valid identifier is symTable.put(nextSym++, token); tk = new Token(VREF, nextSym - 1); } // @TODO uncomment when we have a defn. for var names // System.out.printf("\tERROR: Unrecognized token:" // + "%s\n", token); } return tk; } private boolean destringTokens(IList tokens, IMap stringLiterals, IList destringed) { // Are we parsing a string literal? boolean stringMode = false; // The current string literal StringBuilder currentLiteral = new StringBuilder(); String literalName = "stringLiteral"; for(String token : tokens.toIterable()) { if(token.startsWith("\"")) { if(token.endsWith("\"")) { String litName = literalName + nextLiteral++; stringLiterals.put(litName, token.substring(1, token.length() - 1)); destringed.add(litName); continue; } if(stringMode) { // @TODO make this not an error System.out.printf("\tPARSER ERROR: Initial" +" quotes can only start strings\n"); } else { currentLiteral.append(token.substring(1) + " "); stringMode = true; } } else if (token.endsWith("\"")) { if(!stringMode) { // @TODO make this not an error System.out.printf("\tPARSER ERROR: Terminal" +" quotes can only end strings\n"); return false; } else { currentLiteral.append( token.substring(0, token.length() - 1)); String litName = literalName + nextLiteral++; stringLiterals.put(litName, currentLiteral.toString()); destringed.add(litName); currentLiteral = new StringBuilder(); stringMode = false; } } else if (token.contains("\"")) { if(token.contains("\\\"")) { if(stringMode) { currentLiteral.append(token + " "); } else { System.out.printf("\tERROR: Escaped quote " + " outside of string literal\n"); return false; } } else { // @TODO make this not an error System.out.printf("\tPARSER ERROR: A string" + " literal must be delimited by spaces" + " for now.\n"); return false; } } else { if(stringMode) { currentLiteral.append(token + " "); } else { destringed.add(token); } } } if(stringMode) { System.out.printf("\tERROR: Unclosed string literal (%s" + ").\n", currentLiteral.toString()); return false; } return true; } }