From d15bcca6cd2a18850a0d109c4a8292ef7f20ac22 Mon Sep 17 00:00:00 2001 From: bculkin2442 Date: Wed, 8 Mar 2017 14:15:40 -0500 Subject: SCL is now partially implemented --- dice-lang/src/bjc/dicelang/CLIArgsParser.java | 5 + dice-lang/src/bjc/dicelang/DiceLangEngine.java | 7 +- dice-lang/src/bjc/dicelang/Errors.java | 23 + dice-lang/src/bjc/dicelang/Evaluator.java | 4 +- dice-lang/src/bjc/dicelang/StreamEngine.java | 174 ------- .../src/bjc/dicelang/scl/StreamControlEngine.java | 501 +++++++++++++++++++++ dice-lang/src/bjc/dicelang/scl/StreamEngine.java | 219 +++++++++ 7 files changed, 754 insertions(+), 179 deletions(-) create mode 100644 dice-lang/src/bjc/dicelang/CLIArgsParser.java delete mode 100644 dice-lang/src/bjc/dicelang/StreamEngine.java create mode 100644 dice-lang/src/bjc/dicelang/scl/StreamControlEngine.java create mode 100644 dice-lang/src/bjc/dicelang/scl/StreamEngine.java diff --git a/dice-lang/src/bjc/dicelang/CLIArgsParser.java b/dice-lang/src/bjc/dicelang/CLIArgsParser.java new file mode 100644 index 0000000..b8e0baf --- /dev/null +++ b/dice-lang/src/bjc/dicelang/CLIArgsParser.java @@ -0,0 +1,5 @@ +package bjc.dicelang; + +public class CLIArgsParser { + +} diff --git a/dice-lang/src/bjc/dicelang/DiceLangEngine.java b/dice-lang/src/bjc/dicelang/DiceLangEngine.java index 98e42fd..86a9aa6 100644 --- a/dice-lang/src/bjc/dicelang/DiceLangEngine.java +++ b/dice-lang/src/bjc/dicelang/DiceLangEngine.java @@ -1,5 +1,7 @@ package bjc.dicelang; +import bjc.dicelang.scl.StreamEngine; + import bjc.utils.data.IPair; import bjc.utils.data.ITree; import bjc.utils.data.Pair; @@ -10,9 +12,6 @@ import bjc.utils.funcdata.IList; import bjc.utils.funcdata.IMap; import bjc.utils.funcutils.ListUtils; -import static bjc.dicelang.Errors.ErrorKey.*; -import static bjc.dicelang.Token.Type.*; - import java.util.Comparator; import java.util.Deque; import java.util.Iterator; @@ -21,6 +20,8 @@ import java.util.LinkedList; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static bjc.dicelang.Errors.ErrorKey.*; +import static bjc.dicelang.Token.Type.*; /** * Implements the orchestration necessary for processing DiceLang commands * diff --git a/dice-lang/src/bjc/dicelang/Errors.java b/dice-lang/src/bjc/dicelang/Errors.java index 8c2b1d5..201a6c4 100644 --- a/dice-lang/src/bjc/dicelang/Errors.java +++ b/dice-lang/src/bjc/dicelang/Errors.java @@ -94,6 +94,17 @@ public class Errors { // Unknown stream command EK_STRM_INVCOM, + // SCL Errors + // Unknown SCL token + EK_SCL_INVTOKEN, + // Mismatched quote in SCL command + EK_SCL_MMQUOTE, + // Stack underflow in SCL command + EK_SCL_SUNDERFLOW, + // Unknown word in SCL command + EK_SCL_UNWORD, + // Invalid argument to SCL command + EK_SCL_INVARG, } public static enum ErrorMode { @@ -236,6 +247,18 @@ public class Errors { case EK_STRM_INVCOM: System.out.printf("\tERROR: Unknown stream control command %s\n", args[0]); break; + case EK_SCL_INVTOKEN: + System.out.printf("\tERROR: Unknown SCL token %s\n", args[0]); + break; + case EK_SCL_MMQUOTE: + System.out.printf("\tERROR: Mismatched delimiter in SCL command\n"); + break; + case EK_SCL_SUNDERFLOW: + System.out.printf("\tERROR: Not enough items in stack for word %s\n", args[0]); + break; + case EK_SCL_UNWORD: + System.out.printf("\tERROR: Unknown word %s\n", args[0]); + break; default: System.out.printf("\tERROR ERROR: Unknown error key %s\n", key); } diff --git a/dice-lang/src/bjc/dicelang/Evaluator.java b/dice-lang/src/bjc/dicelang/Evaluator.java index 0fadd60..87980d4 100644 --- a/dice-lang/src/bjc/dicelang/Evaluator.java +++ b/dice-lang/src/bjc/dicelang/Evaluator.java @@ -172,7 +172,7 @@ public class Evaluator { EvaluatorResult opr = ast.getChild(0).getHead().resultVal; if(opr.type != INT) { - Errors.inst.printError(EK_EVAL_INVDCREATE, opr.type); + Errors.inst.printError(EK_EVAL_INVDCREATE, opr.type.toString()); } return new Tree<>(new Node(Node.Type.RESULT, @@ -182,7 +182,7 @@ public class Evaluator { EvaluatorResult oprn = ast.getChild(0).getHead().resultVal; if(oprn.type != INT) { - Errors.inst.printError(EK_EVAL_INVDCREATE, oprn.type); + Errors.inst.printError(EK_EVAL_INVDCREATE, oprn.type.toString()); } return new Tree<>(new Node(Node.Type.RESULT, diff --git a/dice-lang/src/bjc/dicelang/StreamEngine.java b/dice-lang/src/bjc/dicelang/StreamEngine.java deleted file mode 100644 index 5debe00..0000000 --- a/dice-lang/src/bjc/dicelang/StreamEngine.java +++ /dev/null @@ -1,174 +0,0 @@ -package bjc.dicelang; - -import bjc.utils.funcdata.FunctionalList; -import bjc.utils.funcdata.IList; -import bjc.utils.funcutils.ListUtils; - -import static bjc.dicelang.Errors.ErrorKey.*; - -import bjc.utils.esodata.SingleTape; -import bjc.utils.esodata.Tape; -import bjc.utils.esodata.TapeLibrary; - -/** - * Implements multiple interleaved parse streams, as well as a command language - * for the streams. - * - * The idea for the interleaved streams came from the language Oozylbub & - * Murphy, but the command language was my own idea. - * - * @author Ben Culkin - */ -public class StreamEngine { - /* - * The engine we're attached to. - */ - private DiceLangEngine eng; - - /* - * Our streams. - */ - private Tape> streams; - private IList currStream; - - /* - * Saved streams - */ - private TapeLibrary> savedStreams; - - /** - * Create a new stream engine. - * - * @param engine The dice engine we're attached to. - */ - public StreamEngine(DiceLangEngine engine) { - eng = engine; - - savedStreams = new TapeLibrary<>(); - } - - private void init() { - /* - * Reinitialize our list of streams. - */ - streams = new SingleTape<>(); - - /* - * Create an initial stream. - */ - currStream = new FunctionalList<>(); - streams.insertBefore(currStream); - } - - /** - * Process a possibly interleaved set of streams from toks into dest. - * - * @param toks The raw token to read streams from. - * @param dest The list to write the final stream to. - * - * @return Whether or not the streams were successfully processed. - */ - public boolean doStreams(String[] toks, IList dest) { - /* - * Initialize per-run state. - */ - init(); - - /* - * Are we currently quoting things? - */ - boolean quoteMode = false; - - /* - * Process each token. - */ - for(String tk : toks) { - /* - * Process stream commands. - */ - if(tk.startsWith("{@S") && !quoteMode) { - if(tk.equals("{@SQ}")) { - quoteMode = true; - } else if(!processCommand(tk)) { - return false; - } - /* - * Command ran correctly, continue - */ - } else { - if(tk.equals("{@SU}")) { - quoteMode = false; - } else if(tk.startsWith("\\") && tk.endsWith("{@SU}")) { - currStream.add(tk.substring(1)); - } else { - currStream.add(tk); - } - } - } - - for(String tk : currStream) { - dest.add(tk); - } - - return true; - } - - private boolean processCommand(String tk) { - char[] comms = null; - - if(tk.length() > 5) { - comms = tk.substring(3, tk.length() - 1).toCharArray(); - } else { - comms = new char[1]; - comms[0] = tk.charAt(3); - } - - for(char comm : comms) { - switch(comm) { - case '+': - streams.insertAfter(new FunctionalList<>()); - break; - case '>': - if(!streams.right()) { - Errors.inst.printError(EK_STRM_NONEX); - return false; - } - - currStream = streams.item(); - break; - case '<': - if(!streams.left()) { - Errors.inst.printError(EK_STRM_NONEX); - return false; - } - - currStream = streams.item(); - break; - case '-': - if(streams.size() == 1) { - Errors.inst.printError(EK_STRM_LAST); - return false; - } else { - streams.remove(); - currStream = streams.item(); - } - break; - case 'S': - if(streams.size() == 1) { - Errors.inst.printError(EK_STRM_LAST); - return false; - } else { - IList stringLit = streams.remove(); - currStream = streams.item(); - currStream.add(ListUtils.collapseTokens(stringLit, " ")); - } - break; - default: - Errors.inst.printError(EK_STRM_INVCOM, tk); - return false; - } - } - - return true; - } -} diff --git a/dice-lang/src/bjc/dicelang/scl/StreamControlEngine.java b/dice-lang/src/bjc/dicelang/scl/StreamControlEngine.java new file mode 100644 index 0000000..fff8b3e --- /dev/null +++ b/dice-lang/src/bjc/dicelang/scl/StreamControlEngine.java @@ -0,0 +1,501 @@ +package bjc.dicelang.scl; + +import bjc.dicelang.DoubleMatcher; +import bjc.dicelang.Errors; + +import bjc.utils.esodata.Stack; +import bjc.utils.esodata.Tape; +import bjc.utils.funcdata.IList; +import bjc.utils.funcdata.FunctionalList; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +import static bjc.dicelang.Errors.ErrorKey.*; +import static bjc.dicelang.scl.StreamControlEngine.Token.Type.*; + +/** + * Runs a Stream Control Language (SCL) program. + * + * SCL is a stack-based concatenative language based mostly off of Postscript + * and Factor, with inspiration from various other languages. + */ +public class StreamControlEngine { + static class Token { + public static enum Type { + /* + * Natural tokens. + * These come directly from strings + */ + ILIT, FLIT, BLIT, + SQUOTE, DQUOTE, + OBRACKET, OBRACE, + SYMBOL, WORD, + + /* + * Synthetic tokens. + * These are produced from special tokens. + */ + SLIT, + WORDS, ARRAY, + + /* + * Word tokens + * These are subordinate to WORD tokens + */ + /* + * Array manipulation + */ + MAKEARRAY, + MAKEEXEC, MAKEUNEXEC, + /* + * Stream manipulation + */ + NEWSTREAM, + LEFTSTREAM, RIGHTSTREAM, + DELETESTREAM, MERGESTREAM, + /* + * Stack manipulation + */ + STACKCOUNT, STACKEMPTY, + DROP, NDROP, + NIP, NNIP, + } + + /* + * The type of this token + */ + public Type type; + + /* + * Used for + * ILIT + */ + public long intVal; + /* + * Used for + * FLIT + */ + public double floatVal; + /* + * Used for + * BLIT + */ + public boolean boolVal; + /* + * Used for + * SYMBOL + * SLIT + */ + public String stringVal; + /* + * Used for + * WORD + */ + public Token tokenVal; + /* + * Used for + * WORDS + * ARRAY + */ + public IList tokenVals; + + public Token(Type typ) { + type = typ; + } + + public Token(Type typ, long iVal) { + this(typ); + + intVal = iVal; + } + + public Token(Type typ, double dVal) { + this(typ); + + floatVal = dVal; + } + + public Token(Type typ, boolean bVal) { + this(typ); + + boolVal = bVal; + } + + public Token(Type typ, String sVal) { + this(typ); + + stringVal = sVal; + } + + public Token(Type typ, Token tVal) { + this(typ); + + tokenVal = tVal; + } + + public Token(Type typ, Token.Type tVal) { + this(typ, new Token(tVal)); + } + + + public Token(Type typ, IList tVals) { + this(typ); + + tokenVals = tVals; + } + + public static Token tokenizeString(String token) { + if(litTokens.containsKey(token)) { + return new Token(litTokens.get(token)); + } else if(token.startsWith("\\")) { + return new Token(SYMBOL, token.substring(1)); + } else if(builtinWords.containsKey(token)) { + return new Token(WORD, builtinWords.get(token)); + } else if(token.equals("true")) { + return new Token(BLIT, true); + } else if(token.equals("false")) { + return new Token(BLIT, false); + } else if(intLitPattern.matcher(token).matches()) { + return new Token(ILIT, Long.parseLong(token)); + } else if(DoubleMatcher.floatingLiteral.matcher(token).matches()) { + return new Token(FLIT, Double.parseDouble(token)); + } else { + Errors.inst.printError(EK_SCL_INVTOKEN, token); + return null; + } + } + + private static final Pattern intLitPattern = Pattern.compile("\\A[+\\-]?\\d+\\Z"); + + private static final Map litTokens; + private static final Map builtinWords; + + static { + litTokens = new HashMap<>(); + + litTokens.put("'", SQUOTE); + litTokens.put("\"", DQUOTE); + litTokens.put("[", OBRACKET); + litTokens.put("{", OBRACE); + + builtinWords = new HashMap<>(); + + builtinWords.put("makearray", MAKEARRAY); + builtinWords.put("+stream", NEWSTREAM); + builtinWords.put(">stream", LEFTSTREAM); + builtinWords.put(" curStack; + + private Map words; + + public StreamControlEngine(StreamEngine eng) { + this.eng = eng; + + words = new HashMap<>(); + } + + /** + * Run a SCL program. + * + * @param tokens The program to run + * + * @return Whether the program executed succesfully + */ + public boolean runProgram(String[] tokens) { + for(int i = 0; i < tokens.length; i++) { + String token = tokens[i]; + Token tok = Token.tokenizeString(token); + + if(tok == null) { + return false; + } + + switch(tok.type) { + case SQUOTE: + i = handleSingleQuote(i, tokens); + if(i == -1) return false; + break; + case OBRACKET: + i = handleDelim(i, tokens, "]"); + if(i == -1) return false; + break; + case OBRACE: + i = handleDelim(i, tokens, "}"); + if(i == -1) return false; + Token brak = curStack.pop(); + curStack.push(new Token(ARRAY, brak.tokenVals)); + break; + case WORD: + handleWord(tok); + break; + default: + curStack.push(tok); + break; + } + } + + return true; + } + + private boolean handleWord(Token tk) { + boolean succ = true; + + switch(tk.tokenVal.type) { + case NEWSTREAM: + eng.newStream(); + break; + case LEFTSTREAM: + succ = eng.leftStream(); + if(!succ) return false; + break; + case RIGHTSTREAM: + succ = eng.rightStream(); + if(!succ) return false; + break; + case DELETESTREAM: + succ = eng.deleteStream(); + if(!succ) return false; + break; + case MERGESTREAM: + succ = eng.mergeStream(); + if(!succ) return false; + break; + case MAKEARRAY: + succ = makeArray(); + if(!succ) return false; + break; + case MAKEEXEC: + succ = toggleExec(true); + if(!succ) return false; + break; + case MAKEUNEXEC: + succ = toggleExec(false); + if(!succ) return false; + break; + case STACKCOUNT: + curStack.push(new Token(ILIT, curStack.size())); + break; + case STACKEMPTY: + curStack.push(new Token(BLIT, curStack.empty())); + break; + case DROP: + if(curStack.size() == 0) { + Errors.inst.printError(EK_SCL_SUNDERFLOW, tk.tokenVal.type.toString()); + return false; + } + curStack.drop(); + break; + case NDROP: + succ = handleNDrop(); + if(!succ) return false; + break; + case NIP: + if(curStack.size() < 2) { + Errors.inst.printError(EK_SCL_SUNDERFLOW, tk.tokenVal.type.toString()); + return false; + } + + curStack.nip(); + break; + case NNIP: + succ = handleNNip(); + if(!succ) return false; + break; + default: + Errors.inst.printError(EK_SCL_UNWORD, tk.tokenVal.type.toString()); + return false; + } + + return true; + } + + private boolean handleNNip() { + Token num = curStack.pop(); + + if(num.type != ILIT) { + Errors.inst.printError(EK_SCL_INVARG, num.type.toString()); + return false; + } + + int n = (int)num.intVal; + + if(curStack.size() < n) { + Errors.inst.printError(EK_SCL_SUNDERFLOW, NNIP.toString()); + return false; + } + + curStack.nip(n); + return true; + } + + private boolean handleNDrop() { + Token num = curStack.pop(); + + if(num.type != ILIT) { + Errors.inst.printError(EK_SCL_INVARG, num.type.toString()); + return false; + } + + int n = (int)num.intVal; + + if(curStack.size() < n) { + Errors.inst.printError(EK_SCL_SUNDERFLOW, NDROP.toString()); + return false; + } + + curStack.drop(n); + return true; + } + + private boolean toggleExec(boolean exec) { + Token top = curStack.top(); + + if(exec) { + if(top.type != ARRAY) { + Errors.inst.printError(EK_SCL_INVARG, top.toString()); + return false; + } + + top.type = WORDS; + } else { + if(top.type != WORDS) { + Errors.inst.printError(EK_SCL_INVARG, top.toString()); + return false; + } + + top.type = ARRAY; + } + + return true; + } + + private boolean makeArray() { + Token num = curStack.pop(); + + if(num.type != ILIT) { + Errors.inst.printError(EK_SCL_INVARG, num.type.toString()); + } + + IList arr = new FunctionalList<>(); + + for(int i = 0; i < num.intVal; i++) { + arr.add(curStack.pop()); + } + + curStack.push(new Token(ARRAY, arr)); + + return true; + } + + private int handleDelim(int i, String[] tokens, String delim) { + IList toks = new FunctionalList<>(); + + int n = i + 1; + if(n >= tokens.length) { + Errors.inst.printError(EK_SCL_MMQUOTE); + return -1; + } + String tok = tokens[n]; + + while(!tok.equals(delim)) { + Token ntok = Token.tokenizeString(tok); + + switch(ntok.type) { + case SQUOTE: + n = handleSingleQuote(n, tokens); + if(n == -1) return -1; + toks.add(curStack.pop()); + break; + case OBRACKET: + n = handleDelim(n, tokens, "]"); + if(n == -1) return -1; + toks.add(curStack.pop()); + break; + case OBRACE: + i = handleDelim(i, tokens, "}"); + if(i == -1) return -1; + Token brak = curStack.pop(); + toks.add(new Token(ARRAY, brak.tokenVals)); + break; + default: + toks.add(ntok); + } + /* + * Move to the next token + */ + n += 1; + if(n >= tokens.length) { + Errors.inst.printError(EK_SCL_MMQUOTE); + return -1; + } + tok = tokens[n]; + } + + /* + * Skip the closing bracket + */ + n += 1; + + curStack.push(new Token(WORDS, toks)); + + return n; + } + + private int handleSingleQuote(int i, String[] tokens) { + StringBuilder sb = new StringBuilder(); + + int n = i + 1; + if(n >= tokens.length) { + Errors.inst.printError(EK_SCL_MMQUOTE); + return -1; + } + String tok = tokens[n]; + + while(!tok.equals("'")) { + if(tok.matches("\\\\+'")) { + /* + * Handle escaped quotes. + */ + sb.append(tok.substring(1)); + } else { + sb.append(tok); + } + + /* + * Move to the next token + */ + n += 1; + if(n >= tokens.length) { + Errors.inst.printError(EK_SCL_MMQUOTE); + return -1; + } + tok = tokens[n]; + } + + /* + * Skip the single quote + */ + n += 1; + + curStack.push(new Token(SLIT, sb.toString())); + + return n; + } +} diff --git a/dice-lang/src/bjc/dicelang/scl/StreamEngine.java b/dice-lang/src/bjc/dicelang/scl/StreamEngine.java new file mode 100644 index 0000000..65e566d --- /dev/null +++ b/dice-lang/src/bjc/dicelang/scl/StreamEngine.java @@ -0,0 +1,219 @@ +package bjc.dicelang.scl; + +import bjc.dicelang.DiceLangEngine; +import bjc.dicelang.Errors; + +import bjc.utils.funcdata.FunctionalList; +import bjc.utils.funcdata.IList; +import bjc.utils.funcutils.ListUtils; + +import static bjc.dicelang.Errors.ErrorKey.*; + +import bjc.utils.esodata.SingleTape; +import bjc.utils.esodata.Tape; +import bjc.utils.esodata.TapeLibrary; + +/** + * Implements multiple interleaved parse streams, as well as a command language + * for the streams. + * + * The idea for the interleaved streams came from the language Oozylbub & + * Murphy, but the command language was my own idea. + * + * @author Ben Culkin + */ +public class StreamEngine { + /* + * The engine we're attached to. + */ + DiceLangEngine eng; + + /* + * Our streams. + */ + Tape> streams; + IList currStream; + + /* + * Saved streams + */ + TapeLibrary> savedStreams; + + /* + * Handler for SCL programs + */ + private StreamControlEngine scleng; + + /** + * Create a new stream engine. + * + * @param engine The dice engine we're attached to. + */ + public StreamEngine(DiceLangEngine engine) { + eng = engine; + + savedStreams = new TapeLibrary<>(); + scleng = new StreamControlEngine(this); + } + + private void init() { + /* + * Reinitialize our list of streams. + */ + streams = new SingleTape<>(); + + /* + * Create an initial stream. + */ + currStream = new FunctionalList<>(); + streams.insertBefore(currStream); + } + + /** + * Process a possibly interleaved set of streams from toks into dest. + * + * @param toks The raw token to read streams from. + * @param dest The list to write the final stream to. + * + * @return Whether or not the streams were successfully processed. + */ + public boolean doStreams(String[] toks, IList dest) { + /* + * Initialize per-run state. + */ + init(); + + /* + * Are we currently quoting things? + */ + boolean quoteMode = false; + + /* + * Process each token. + */ + for(String tk : toks) { + /* + * Process stream commands. + */ + if(tk.startsWith("{@S") && !quoteMode) { + if(tk.equals("{@SQ}")) { + quoteMode = true; + } else if(!processCommand(tk)) { + return false; + } + /* + * Command ran correctly, continue + */ + } else { + if(tk.equals("{@SU}")) { + quoteMode = false; + } else if(tk.startsWith("\\") && tk.endsWith("{@SU}")) { + currStream.add(tk.substring(1)); + } else { + currStream.add(tk); + } + } + } + + for(String tk : currStream) { + dest.add(tk); + } + + return true; + } + + public void newStream() { + streams.insertAfter(new FunctionalList<>()); + } + + public boolean rightStream() { + if(!streams.right()) { + Errors.inst.printError(EK_STRM_NONEX); + return false; + } + + currStream = streams.item(); + return true; + } + + public boolean leftStream() { + if(!streams.left()) { + Errors.inst.printError(EK_STRM_NONEX); + return false; + } + + currStream = streams.item(); + return true; + } + + public boolean deleteStream() { + if(streams.size() == 1) { + Errors.inst.printError(EK_STRM_LAST); + return false; + } else { + streams.remove(); + currStream = streams.item(); + } + + return true; + } + + public boolean mergeStream() { + if(streams.size() == 1) { + Errors.inst.printError(EK_STRM_LAST); + return false; + } else { + IList stringLit = streams.remove(); + currStream = streams.item(); + currStream.add(ListUtils.collapseTokens(stringLit, " ")); + } + + return true; + } + + private boolean processCommand(String tk) { + char[] comms = null; + + if(tk.length() > 5) { + comms = tk.substring(3, tk.length() - 1).toCharArray(); + } else { + comms = new char[1]; + comms[0] = tk.charAt(3); + } + + boolean succ; + + for(char comm : comms) { + switch(comm) { + case '+': + newStream(); + break; + case '>': + succ = rightStream(); + if(!succ) return false; + break; + case '<': + succ = leftStream(); + if(!succ) return false; + break; + case '-': + succ = deleteStream(); + if(!succ) return false; + break; + case 'M': + succ = mergeStream(); + if(!succ) return false; + break; + case 'L': + succ = scleng.runProgram(currStream.toArray(new String[0])); + if(!succ) return false; + break; + default: + Errors.inst.printError(EK_STRM_INVCOM, tk); + return false; + } + } + + return true; + } +} -- cgit v1.2.3