summaryrefslogtreecommitdiff
path: root/base/src/bjc/dicelang/scl
diff options
context:
space:
mode:
Diffstat (limited to 'base/src/bjc/dicelang/scl')
-rw-r--r--base/src/bjc/dicelang/scl/StreamControlConsole.java75
-rw-r--r--base/src/bjc/dicelang/scl/StreamControlEngine.java546
-rw-r--r--base/src/bjc/dicelang/scl/StreamEngine.java266
3 files changed, 887 insertions, 0 deletions
diff --git a/base/src/bjc/dicelang/scl/StreamControlConsole.java b/base/src/bjc/dicelang/scl/StreamControlConsole.java
new file mode 100644
index 0000000..649c6fa
--- /dev/null
+++ b/base/src/bjc/dicelang/scl/StreamControlConsole.java
@@ -0,0 +1,75 @@
+package bjc.dicelang.scl;
+
+import bjc.utils.funcdata.FunctionalList;
+import bjc.utils.funcdata.IList;
+
+import java.util.Iterator;
+import java.util.Scanner;
+
+import java.util.function.Supplier;
+
+/**
+ * Implement a SCL REPL
+ *
+ * @author Ben Culkin
+ */
+public class StreamControlConsole {
+ /*
+ * @TODO 10/08/17 :SCLArgs
+ * Do something useful with the CLI args.
+ *
+ */
+ /**
+ * Main method
+ *
+ * @param args
+ * Unused CLI args.
+ */
+ public static void main(String[] args) {
+ /*
+ * Initialize vars.
+ *
+ * We can get away with passing the null, because StreamEngine
+ * doesn't reference any parts of DiceLangEngine.
+ */
+ StreamEngine sengine = new StreamEngine(null);
+ StreamControlEngine sclengine = new StreamControlEngine(sengine);
+ Scanner scn = new Scanner(System.in);
+
+ /* Get input from the user. */
+ System.out.print("Enter a SCL command string (blank to exit): ");
+
+ /* Process it. */
+ while (scn.hasNextLine()) {
+ String ln = scn.nextLine().trim();
+
+ if (ln.equals("")) {
+ /* Ignore empty lines. */
+ break;
+ }
+
+ /* Break the token into strings. */
+ IList<String> res = new FunctionalList<>();
+ String[] tokens = ln.split(" ");
+
+ /* Run the stream engine on the tokens. */
+ boolean succ = sengine.doStreams(tokens, res);
+ if (!succ) {
+ System.out.printf("ERROR: Stream engine failed for line '%s'\n", ln);
+ continue;
+ }
+
+ /* Run the command through SCL. */
+ tokens = res.toArray(new String[res.getSize()]);
+ succ = sclengine.runProgram(tokens);
+ if (!succ) {
+ System.out.printf("ERROR: SCL engine failed for line '%s'\n", ln);
+ continue;
+ }
+
+ /* Prompt again. */
+ System.out.print("Command string executed succesfully.\n\n");
+ System.out.print("Enter a SCL command string (blank to exit): ");
+ }
+ }
+}
diff --git a/base/src/bjc/dicelang/scl/StreamControlEngine.java b/base/src/bjc/dicelang/scl/StreamControlEngine.java
new file mode 100644
index 0000000..d5e8b72
--- /dev/null
+++ b/base/src/bjc/dicelang/scl/StreamControlEngine.java
@@ -0,0 +1,546 @@
+package bjc.dicelang.scl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import bjc.dicelang.Errors;
+import bjc.utils.esodata.SimpleStack;
+import bjc.utils.esodata.Stack;
+import bjc.utils.funcdata.FunctionalList;
+import bjc.utils.funcdata.IList;
+import bjc.utils.parserutils.TokenUtils;
+
+import static bjc.dicelang.Errors.ErrorKey.*;
+import static bjc.dicelang.scl.StreamControlEngine.Token.Type.*;
+
+/*
+ * @TODO 10/08/17 Ben Culkin :SCLReorg
+ * This is a large enough class that it should maybe be split into
+ * subclasses.
+ */
+/**
+ * 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.
+ *
+ * @author Ben Culkin
+ */
+public class StreamControlEngine {
+ /*
+ * @TODO 10/08/17 Ben Culkin :TokenSplit
+ * Again with the multiple subclasses in one class. Split it so
+ * that each subclass only has the fields it needs.
+ */
+ public 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 */
+ /*
+ * @NOTE
+ * These should really be in their own enum.
+ */
+ /* 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<Token> tokenVals;
+
+ /* Create a new token. */
+ public Token(final Type typ) {
+ type = typ;
+ }
+
+ /* Create a new token. */
+ public Token(final Type typ, final long iVal) {
+ this(typ);
+
+ intVal = iVal;
+ }
+
+ /* Create a new token. */
+ public Token(final Type typ, final double dVal) {
+ this(typ);
+
+ floatVal = dVal;
+ }
+
+ /* Create a new token. */
+ public Token(final Type typ, final boolean bVal) {
+ this(typ);
+
+ boolVal = bVal;
+ }
+
+ /* Create a new token. */
+ public Token(final Type typ, final String sVal) {
+ this(typ);
+
+ stringVal = sVal;
+ }
+
+ /* Create a new token. */
+ public Token(final Type typ, final Token tVal) {
+ this(typ);
+
+ tokenVal = tVal;
+ }
+
+ /* Create a new token. */
+ public Token(final Type typ, final Token.Type tVal) {
+ this(typ, new Token(tVal));
+ }
+
+ /* Create a new token. */
+ public Token(final Type typ, final IList<Token> tVals) {
+ this(typ);
+
+ tokenVals = tVals;
+ }
+
+ /* Convert a string into a token. */
+ public static Token tokenizeString(final 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 (TokenUtils.isInt(token)) {
+ return new Token(ILIT, Long.parseLong(token));
+ } else if (TokenUtils.isDouble(token)) {
+ return new Token(FLIT, Double.parseDouble(token));
+ } else {
+ Errors.inst.printError(EK_SCL_INVTOKEN, token);
+ return null;
+ }
+ }
+
+ /* The literal tokens. */
+ private static final Map<String, Token.Type> litTokens;
+ /* The builtin words. */
+ private static final Map<String, Token.Type> builtinWords;
+
+ static {
+ /* Init literal tokens. */
+ litTokens = new HashMap<>();
+
+ litTokens.put("'", SQUOTE);
+ litTokens.put("\"", DQUOTE);
+ litTokens.put("[", OBRACKET);
+ litTokens.put("{", OBRACE);
+
+ /* Init builtin words. */
+ builtinWords = new HashMap<>();
+
+ builtinWords.put("makearray", MAKEARRAY);
+ builtinWords.put("+stream", NEWSTREAM);
+ builtinWords.put(">stream", LEFTSTREAM);
+ builtinWords.put("<stream", RIGHTSTREAM);
+ builtinWords.put("-stream", DELETESTREAM);
+ builtinWords.put("<-stream", MERGESTREAM);
+ builtinWords.put("cvx", MAKEEXEC);
+ builtinWords.put("cvux", MAKEUNEXEC);
+ builtinWords.put("#", STACKCOUNT);
+ builtinWords.put("empty?", STACKEMPTY);
+ builtinWords.put("drop", DROP);
+ builtinWords.put("ndrop", NDROP);
+ builtinWords.put("nip", NIP);
+ builtinWords.put("nnip", NNIP);
+ }
+ }
+
+ /* The stream engine we're hooked to. */
+ private final StreamEngine eng;
+
+ /* The current stack state. */
+ private final Stack<Token> curStack;
+
+ /* Map of user defined words. */
+ private final Map<String, Token> words;
+
+ /**
+ * Create a new stream control engine.
+ *
+ * @param engine
+ * The engine to control.
+ */
+ public StreamControlEngine(final StreamEngine engine) {
+ eng = engine;
+
+ words = new HashMap<>();
+ curStack = new SimpleStack<>();
+ }
+
+ /**
+ * Run a SCL program.
+ *
+ * @param tokens
+ * The program to run.
+ *
+ * @return Whether the program executed successfully.
+ */
+ public boolean runProgram(final String[] tokens) {
+ for (int i = 0; i < tokens.length; i++) {
+ /* Tokenize each token. */
+ final String token = tokens[i];
+ final Token tok = Token.tokenizeString(token);
+
+ if (tok == null) {
+ System.out.printf("ERROR: Tokenization failed for '%s'\n", token);
+ return false;
+ }
+
+ /* Handle token types. */
+ switch (tok.type) {
+ case SQUOTE:
+ /* Handle single-quotes. */
+ i = handleSingleQuote(i, tokens);
+ if (i == -1) {
+ return false;
+ }
+ break;
+ case OBRACKET:
+ /* Handle delimited brackets. */
+ i = handleDelim(i, tokens, "]");
+ if (i == -1) {
+ return false;
+ }
+ break;
+ case OBRACE:
+ /* Handle delimited braces. */
+ i = handleDelim(i, tokens, "}");
+ if (i == -1) {
+ return false;
+ }
+ final Token brak = curStack.pop();
+ curStack.push(new Token(ARRAY, brak.tokenVals));
+ break;
+
+ case WORD:
+ /* Handle words. */
+ if(!handleWord(tok)) {
+ System.out.printf("WARNING: Execution of word '%s' failed\n", tok);
+ }
+ break;
+ default:
+ /* Put it onto the stack. */
+ curStack.push(tok);
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ private boolean handleWord(final Token tk) {
+ boolean succ = true;
+
+ /* Handle each type of word. */
+ /*
+ * @NOTE
+ * This should probably use something other than a switch
+ * statement.
+ */
+ 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;
+ }
+
+ /* Handle nipping a specified number of items. */
+ private boolean handleNNip() {
+ final Token num = curStack.pop();
+
+ if (num.type != ILIT) {
+ Errors.inst.printError(EK_SCL_INVARG, num.type.toString());
+ return false;
+ }
+
+ final int n = (int) num.intVal;
+
+ if (curStack.size() < n) {
+ Errors.inst.printError(EK_SCL_SUNDERFLOW, NNIP.toString());
+ return false;
+ }
+
+ curStack.nip(n);
+ return true;
+ }
+
+ /* Handle dropping a specified number of items. */
+ private boolean handleNDrop() {
+ final Token num = curStack.pop();
+
+ if (num.type != ILIT) {
+ Errors.inst.printError(EK_SCL_INVARG, num.type.toString());
+ return false;
+ }
+
+ final int n = (int) num.intVal;
+
+ if (curStack.size() < n) {
+ Errors.inst.printError(EK_SCL_SUNDERFLOW, NDROP.toString());
+ return false;
+ }
+
+ curStack.drop(n);
+ return true;
+ }
+
+ /* Handle toggling the executable flag on an array. */
+ private boolean toggleExec(final boolean exec) {
+ final 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;
+ }
+
+ /* Handle creating an array. */
+ private boolean makeArray() {
+ final Token num = curStack.pop();
+
+ if (num.type != ILIT) {
+ Errors.inst.printError(EK_SCL_INVARG, num.type.toString());
+ }
+
+ final IList<Token> arr = new FunctionalList<>();
+
+ for (int i = 0; i < num.intVal; i++) {
+ arr.add(curStack.pop());
+ }
+
+ curStack.push(new Token(ARRAY, arr));
+
+ return true;
+ }
+
+ /* Handle a delimited series of tokens. */
+ private int handleDelim(final int i, final String[] tokens, final String delim) {
+ final IList<Token> 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)) {
+ final 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:
+ n = handleDelim(i, tokens, "}");
+ if (n == -1) {
+ return -1;
+ }
+ final 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;
+
+ /* @NOTE
+ * Instead of being hardcoded, this should be a parameter.
+ */
+ curStack.push(new Token(WORDS, toks));
+
+ return n;
+ }
+
+ /* Handle a single-quoted string. */
+ private int handleSingleQuote(final int i, final String[] tokens) {
+ final 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, TokenUtils.descapeString(sb.toString())));
+
+ return n;
+ }
+}
diff --git a/base/src/bjc/dicelang/scl/StreamEngine.java b/base/src/bjc/dicelang/scl/StreamEngine.java
new file mode 100644
index 0000000..6e970b7
--- /dev/null
+++ b/base/src/bjc/dicelang/scl/StreamEngine.java
@@ -0,0 +1,266 @@
+package bjc.dicelang.scl;
+
+import static bjc.dicelang.Errors.ErrorKey.EK_STRM_INVCOM;
+import static bjc.dicelang.Errors.ErrorKey.EK_STRM_LAST;
+import static bjc.dicelang.Errors.ErrorKey.EK_STRM_NONEX;
+
+import static java.util.logging.Level.*;
+
+import bjc.dicelang.DiceLangEngine;
+import bjc.dicelang.Errors;
+import bjc.utils.esodata.SingleTape;
+import bjc.utils.esodata.Tape;
+import bjc.utils.esodata.TapeLibrary;
+import bjc.utils.funcdata.FunctionalList;
+import bjc.utils.funcdata.IList;
+import bjc.utils.funcutils.ListUtils;
+
+import java.util.Arrays;
+
+/**
+ * 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 &amp;
+ * Murphy, but the command language was my own idea.
+ *
+ * @author Ben Culkin
+ */
+public class StreamEngine {
+ /* Whether or not we're doing debugging. */
+ public final boolean debug = true;
+
+ /* The engine we're attached to. */
+ DiceLangEngine eng;
+
+ /* Our streams. */
+ Tape<IList<String>> streams;
+ IList<String> currStream;
+
+ /* Saved streams */
+ TapeLibrary<IList<String>> savedStreams;
+
+ /* Handler for SCL programs */
+ private final StreamControlEngine scleng;
+
+ /**
+ * Create a new stream engine.
+ *
+ * @param engine
+ * The dice engine we're attached to.
+ */
+ public StreamEngine(final DiceLangEngine engine) {
+ eng = engine;
+
+ savedStreams = new TapeLibrary<>();
+ scleng = new StreamControlEngine(this);
+ }
+
+ /* Do pre-run (re)initialization. */
+ 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.
+ *
+ * @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(final String[] toks, final IList<String> dest) {
+ return doStreams(Arrays.asList(toks), dest);
+ }
+
+ /**
+ * Process a possibly interleaved set of streams.
+ *
+ * @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(final Iterable<String> toks, final IList<String> dest) {
+ /* Initialize per-run state. */
+ init();
+
+ /* Are we currently quoting things? */
+ boolean quoteMode = false;
+
+ /* Process each token. */
+ for (final String tk : toks) {
+ /* Process stream commands. */
+ if (tk.startsWith("{@S") && !quoteMode) {
+ if (tk.equals("{@SQ}")) {
+ /* Start quoting. */
+ quoteMode = true;
+ } else if (!processCommand(tk)) {
+ return false;
+ }
+ } else {
+ if (tk.equals("{@SU}")) {
+ /* Stop quoting. */
+ quoteMode = false;
+ } else if (tk.startsWith("\\") && tk.endsWith("{@SU}")) {
+ /* Unquote quoted end. */
+ currStream.add(tk.substring(1));
+ } else {
+ currStream.add(tk);
+ }
+ }
+ }
+
+ for (final String tk : currStream) {
+ /* Collect tokens from the current stream. */
+ dest.add(tk);
+ }
+
+ return true;
+ }
+
+ /** Create a new stream. */
+ public void newStream() {
+ streams.insertAfter(new FunctionalList<>());
+ }
+
+ /**
+ * Move to a stream to the right.
+ *
+ * @return Whether or not the move was successful.
+ */
+ public boolean rightStream() {
+ if (!streams.right()) {
+ Errors.inst.printError(EK_STRM_NONEX);
+ return false;
+ }
+
+ currStream = streams.item();
+ return true;
+ }
+
+ /**
+ * Move to a stream to the left.
+ *
+ * @return Whether or not the move was successful.
+ */
+ public boolean leftStream() {
+ if (!streams.left()) {
+ Errors.inst.printError(EK_STRM_NONEX);
+ return false;
+ }
+
+ currStream = streams.item();
+ return true;
+ }
+
+ /**
+ * Delete the current stream.
+ *
+ * @return Whether or not the delete succeeded.
+ */
+ public boolean deleteStream() {
+ if (streams.size() == 1) {
+ Errors.inst.printError(EK_STRM_LAST);
+ return false;
+ }
+
+ streams.remove();
+ currStream = streams.item();
+
+ return true;
+ }
+
+ /**
+ * Merge the current stream into the previous stream.
+ *
+ * @return Whether or not the merge succeded.
+ */
+ public boolean mergeStream() {
+ if (streams.size() == 1) {
+ Errors.inst.printError(EK_STRM_LAST);
+ return false;
+ }
+
+ final IList<String> stringLit = streams.remove();
+ currStream = streams.item();
+ currStream.add(ListUtils.collapseTokens(stringLit, " "));
+
+ return true;
+ }
+
+ private boolean processCommand(final String tk) {
+ char[] comms = null;
+
+ if (tk.length() > 5) {
+ /* Pull off {@S and closing } */
+ comms = tk.substring(3, tk.length() - 1).toCharArray();
+ } else {
+ /* Its a single char. command. */
+ comms = new char[1];
+ comms[0] = tk.charAt(3);
+ }
+
+ boolean succ;
+
+ /* Process each command. */
+ /*
+ * @TODO 10/09/17 Ben Culkin :StreamCommands
+ * This should probably be refactored in some way, so as to
+ * make it easier to add new commands.
+ */
+ for (final 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;
+ }
+}