//============================================================================== // CommandParser.java //============================================================================== package tribble.net.ftp.shell; import java.io.FileReader; import java.io.InputStreamReader; import java.io.IOException; import java.io.PrintWriter; import java.io.Reader; import java.io.Writer; import java.lang.Character; import java.lang.Exception; import java.lang.Integer; import java.lang.NullPointerException; import java.lang.String; import java.lang.System; import java.text.ParseException; /******************************************************************************* * FTP command script parser. * Parses a command script text input stream containing FTP commands into a * parse tree. * *

* The parser uses a recursive-descent LL(1) parsing algorithm. * *

* See the package summary for details about * syntax and lexicon. * * *

* Example * *

*  Loop1:
*    foreach *.tar
*    {
*        get $F
*        if ($Error != 0)
*            continue Loop1
*        del $F
*        !tar -xf $F
*        !del $F
*    }
* *

* The example script above parses into the following command parse tree: *

*  1  (block "file")
*  2    (filename "foo.ftp"
*  3    (label "loop1")
*  4    (foreach &1 "remote" "*.tar" 0 nil)
*  5      (get &1 "$F")
*  6      (if (!= "$Error" "0"))
*  7        (continue "loop1")
*  8      (del &1 "$F")
*  9      (! "tar" "-xf" "$F")
* 10      (! "del" "$F")
* * *
*
Source code:
*
Available at: * http://david.tribble.com/src/java/tribble/net/ftp/shell/CommandParser.java *
*
Documentation:
*
Available at: * http://david.tribble.com/docs/tribble/net/ftp/shell/CommandParser.html *
*
* * * @version $Revision: 1.58 $ $Date: 2010/07/12 17:56:42 $ * @since API 1.0, 2007-03-09 * @author David R. Tribble (david@tribble.com). *

* Copyright ©2007-2010 by David R. Tribble, all rights reserved.
* Permission is granted to any person or entity except those designated by * by the United States Department of State as a terrorist, or terrorist * government or agency, to use and distribute this source code provided * that the original copyright notice remains present and unaltered. * * @see CommandLexer * @see CommandTokens * @see CommandNode * @see BlockDef */ class CommandParser implements CommandCodes, CommandTokens, VarNames { /** Revision information. */ static final String REV = "@(#)tribble/net/ftp/shell/CommandParser.java $Revision: 1.58 $ $Date: 2010/07/12 17:56:42 $\n"; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Constants /** Maximum var name length (64). */ private static final int MAX_VAR_LEN = 64; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Variables /** Source stream name (typically a filename). */ private String m_srcName = "-"; /** Command source input stream. */ private CommandLexer m_lexer; /** Error/warning message output stream. */ private PrintWriter m_out; /** Current (innermost) begin/end or func block. */ private BlockDef m_scope; /** Var name buffer. */ private char[] m_vbuf = new char[MAX_VAR_LEN]; /** Last non-newline token read. */ private String m_lastTok = "?"; /** Last source line number number. */ private int m_lastLine = 1; /** Source line number of the last token read. */ int m_lineNo = 1; /** Errors produced. */ int m_nErrors; /** Warnings produced. */ int m_nWarnings; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Constructors /*************************************************************************** * Constructor. * * @param in * Command script input stream. * * @since 1.1, 2007-03-09 */ CommandParser(Reader in) { // Initialize this(in, new PrintWriter(System.out, true)); } /*************************************************************************** * Constructor. * * @param in * Command script input stream. * * @param out * Error/warning message output stream. * * @since 1.4, 2007-03-14 */ CommandParser(Reader in, Writer out) { // Initialize if (out instanceof PrintWriter) m_out = (PrintWriter) out; else m_out = new PrintWriter(out, true); m_lexer = new CommandLexer(in, m_out); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Methods /*************************************************************************** * Establish the name of the source stream (the command script). * * @param name * Name of the input stream, typically a filename. * * @since 1.10, 2007-03-20 */ void setSourceName(String name) { m_srcName = name; m_lexer.setSourceName(name); } /*************************************************************************** * Parse a command script. * * @param in * Input stream containing the contents of the command script. * * @return * Execution parse tree. * * @since 1.1, 2007-03-09 */ CommandNode parse() throws ParseException, IOException { CommandNode top; CommandNode cmds; String tok; // Sanity check if (m_lexer == null) throw new NullPointerException("Command input stream not open"); // Set up m_nErrors = 0; m_nWarnings = 0; top = new CommandNode(CMD_BLOCK, 0); m_scope = new BlockDef(".file", 0, null, null); m_scope.m_type = CMD_BLOCK; m_scope.addVarDef(VAR_ERROR, top); m_scope.addVarDef(VAR_ERRORCMD, top); m_scope.addVarDef(VAR_ERRORCNT, top); m_scope.addVarDef(VAR_ERRORMSG, top); m_scope.addVarDef(VAR_HOST, top); m_scope.addVarDef(VAR_LOCALDIR, top); m_scope.addVarDef(VAR_PASSWORD, top); m_scope.addVarDef(VAR_PORT, top); m_scope.addVarDef(VAR_RANDOM, top); m_scope.addVarDef(VAR_REMOTEDIR, top); m_scope.addVarDef(VAR_TIME, top); m_scope.addVarDef(VAR_USER, top); m_scope.addVarDef(VAR_FILE, top); m_scope.addVarDef(VAR_FILE_DIRSEP, top); m_scope.addVarDef(VAR_FILE_DIR, top); m_scope.addVarDef(VAR_FILE_EXT, top); m_scope.addVarDef(VAR_FILE_FILE, top); m_scope.addVarDef(VAR_FILE_GROUP, top); m_scope.addVarDef(VAR_FILE_HOST, top); m_scope.addVarDef(VAR_FILE_PERMS, top); m_scope.addVarDef(VAR_FILE_NAME, top); m_scope.addVarDef(VAR_FILE_OWNER, top); m_scope.addVarDef(VAR_FILE_PATH, top); m_scope.addVarDef(VAR_FILE_SIZE, top); m_scope.addVarDef(VAR_FILE_TYPE, top); // Parse the command script cmds = new CommandNode(CMD_FILENAME, 0); cmds.addArg(m_srcName); tok = readToken(); cmds.m_next = parseStmts(tok); if (cmds.m_next == null) warning(m_lineNo, "Empty script, no commands"); top.addArg(m_scope); top.addArg(cmds); m_scope.setCmds(cmds); // Check for the expected end of the source file tok = readToken(); if (tok != null) error(m_lineNo, "Incomplete command script"); else { // Post-process the parse tree postprocess(top); } // Done m_lexer = null; m_scope = null; if (m_nErrors > 0) return null; return top; } /*************************************************************************** * Postprocess a command parse tree. * This reduces (simplifies) the parse tree, fixing up CMD_TOK and * CMD_GOTO commands. * * @since 1.10, 2007-03-21 (1.2, 2007-03-13) */ private void postprocess(CommandNode cmds) { CommandNode cmd; // Simplify the parse tree, eliminating CMD_TOK nodes for (cmd = cmds; cmd != null; cmd = cmd.m_next) { BlockDef prev; // Establish the current block scope prev = m_scope; if (cmd.m_cmd == CMD_BLOCK) m_scope = (BlockDef) cmd.m_args[0]; // Simplify the arguments of this node if (cmd.m_args != null) { for (int i = 0; i < cmd.m_args.length; i++) { Object arg; // Simplify the next command argument arg = cmd.m_args[i]; if (arg instanceof CommandNode) { CommandNode c; c = (CommandNode) arg; if (c.m_cmd == CMD_TOK) { String s; s = (String) c.m_args[0]; cmd.m_args[i] = prepareString(s, 0); } else postprocess(c); } } } // Fix up labels if (cmd.m_cmd == CMD_BREAK || cmd.m_cmd == CMD_CONTINUE || cmd.m_cmd == CMD_GOTO) { // Verify and fix up jump (goto/break/continue) labels postprocessJump(cmd); } else if (cmd.m_cmd == CMD_LABEL) { CommandNode stmt; String name; // Check for the label hiding an outer block label if (m_scope.m_outer != null) { name = (String) cmd.m_args[0]; stmt = m_scope.m_outer.findLabelDef(name); if (stmt != null) { warning(cmd.m_lineNo, "Label hides outer definition: '" + name + "' (" + stmt.m_lineNo + ")"); } } } // Restore the previous block scope if (cmd.m_cmd == CMD_BLOCK) m_scope.scrub(); m_scope = prev; } } /*************************************************************************** * Postprocess a "goto"/"break"/"continue" jump command. * * @param cmd * Command to fix up, which is a CMD_GOTO, CMD_BREAK, or * CMD_CONTINUE command. * * @since 1.50, 2007-08-03 */ private void postprocessJump(CommandNode cmd) { String name; BlockDef blk; CommandNode lcmd; // Fix up a jump command name = (String) cmd.m_args[0]; if (name == null) return; // Find the block containing the target command blk = m_scope.findLabelBlock(name); if (blk == null) { error(cmd.m_lineNo, "No such label for '" + cmd.m_cmd + "': '" + name + "'"); return; } // Verify that the label names a valid target command lcmd = blk.findLabelDef(name); if (cmd.m_cmd != CMD_GOTO) { BlockDef scope; // Verify that the label names a loop command for ( ; lcmd != null; lcmd = lcmd.m_next) if (lcmd.m_cmd != CMD_LABEL) break; if (lcmd == null) { error(cmd.m_lineNo, "Label does not precede a loop command: '" + name + "'"); return; } // Verify that the target loop encloses the jump command for (scope = m_scope; scope != null; scope = scope.m_outer) if (scope.m_parent == lcmd) break; if (scope == null) { error(cmd.m_lineNo, "Misplaced label for '" + cmd.m_cmd + "': '" + name + "'"); return; } } // The jump label names a valid target command cmd.m_args[1] = lcmd; cmd.m_args[2] = blk; } /*************************************************************************** * Issue an error message to the output stream. * * @param lineNo * Source line number associated with the message. * * @param msg * Message to display. * * @since 1.10, 2007-03-20 */ private void error(int lineNo, String msg) { // Display the error message m_nErrors++; m_out.println(m_srcName + ":" + lineNo + ": error: " + msg); m_out.flush(); } /*************************************************************************** * Issue an error message to the output stream. * * @param msg * Message to display. * * @since 1.10, 2007-03-20 */ private void error(String msg) { error(m_lastLine, msg); } /*************************************************************************** * Issue a warning message to the output stream. * * @param lineNo * Source line number associated with the message. * * @param msg * Message to display. * * @since 1.11, 2007-03-23 */ private void warning(int lineNo, String msg) { // Display the warning message m_nWarnings++; m_out.println(m_srcName + ":" + lineNo + ": warning: " + msg); m_out.flush(); } /*************************************************************************** * Issue a warning message to the output stream. * * @param msg * Message to display. * * @since 1.10, 2007-03-20 */ private void warning(String msg) { warning(m_lastLine, msg); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Command parsing methods /*************************************************************************** * Parse a sequence of command statements. * *

* Syntax *

    *    Stmt...
    *        : (empty)
    *        : Stmt Stmt...
* * @return * List of commands (statements), or null if there are no commands. * * @since 1.1, 2007-03-09 */ private CommandNode parseStmts(String tok) throws ParseException, IOException { CommandNode stmts = null; CommandNode last = null; int nStmts; // Parse: Stmt... -> [Stmt...] nStmts = 0; for (;;) { CommandNode stmt; // Skip blank lines while (tok == TOK__NL) tok = readToken(); if (tok == null || tok.equals(TOK_RC)) break; // Parse: Stmt... -> [Stmt [Stmt...]] stmt = parseStmt(tok); if (stmt == null) break; // Add the parsed command to the parse tree if (last != null) { last.m_next = stmt; if (last.m_cmd == CMD_EXIT || last.m_cmd == CMD_GOTO || last.m_cmd == CMD_BREAK || last.m_cmd == CMD_CONTINUE) warning(stmt.m_lineNo, "Unreachable command: '" + stmt.m_cmd + "'"); } last = stmt; if (stmts == null) stmts = stmt; nStmts++; // Read the next command tok = readToken(); } if (tok != null && tok.equals(TOK_RC)) unReadToken(tok); return stmts; } /*************************************************************************** * Parse a command statement. * *

* Syntax *

    *    Stmt
    *        : name ':'
    *        : BlockStmt
    *        : AppendStmt
    *        : AsciiStmt
    *        : BinaryStmt
    *        : BreakStmt
    *        : CallStmt
    *        : CdupStmt
    *        : ChdirStmt
    *        : CloseStmt
    *        : ContinueStmt
    *        : DeleteStmt
    *        : DirStmt
    *        : EchoStmt
    *        : ExitStmt
    *        : ForeachStmt
    *        : FuncDefStmt
    *        : GetStmt
    *        : GlobStmt
    *        : GotoStmt
    *        : IfStmt
    *        : LocalChdirStmt
    *        : MgetStmt      (remove)
    *        : MkdirStmt
    *        : MputStmt      (remove)
    *        : NopStmt
    *        : OpenStmt
    *        : PrintStmt
    *        : PutStmt
    *        : PwdStmt
    *        : ReadStmt
    *        : RenameStmt
    *        : RepeatStmt
    *        : ReturnStmt
    *        : RmdirStmt
    *        : SetStmt
    *        : ShellStmt
    *        : SleepStmt
    *        : TimeoutStmt
    *        : UserStmt
    *        : VarStmt
    *        : WhileStmt
    * 
* * @since 1.1, 2007-03-09 */ private CommandNode parseStmt(String tok) throws ParseException, IOException { CommandNode stmt; // Skip blank lines while (tok == TOK__NL) tok = readToken(); // Parse: Stmt -> (empty) if (tok == null) return null; // Parse: Stmt -> XxxStmt if (tok.equals(TOK_BANG)) stmt = parseShellStmt(tok); else if (isLabelName(tok)) stmt = parseStmtLabel(tok); else if (isFunc(tok)) stmt = parseCallStmt(tok); else if (tok.equals(TOK_LC)) stmt = parseBlockStmt(tok); else if (tok.equalsIgnoreCase(TOK_APP) || tok.equalsIgnoreCase(TOK_APPEND)) stmt = parseUnknownStmt(tok); else if (tok.equalsIgnoreCase(TOK_ASCII) || tok.equalsIgnoreCase(TOK_ASC)) stmt = parseAsciiStmt(tok); else if (tok.equalsIgnoreCase(TOK_BINARY) || tok.equalsIgnoreCase(TOK_BIN)) stmt = parseBinaryStmt(tok); else if (tok.equalsIgnoreCase(TOK_BREAK)) stmt = parseBreakStmt(tok); else if (tok.equalsIgnoreCase(TOK_CDUP) || tok.equalsIgnoreCase(TOK_UP)) stmt = parseCdupStmt(tok); else if (tok.equalsIgnoreCase(TOK_CD) || tok.equalsIgnoreCase(TOK_CHDIR)) stmt = parseChdirStmt(tok); else if (tok.equalsIgnoreCase(TOK_CLOSE) || tok.equalsIgnoreCase(TOK_DISC) || tok.equalsIgnoreCase(TOK_DISCONNECT)) stmt = parseCloseStmt(tok); else if (tok.equalsIgnoreCase(TOK_CONTINUE)) stmt = parseContinueStmt(tok); else if (tok.equalsIgnoreCase(TOK_DEL) || tok.equalsIgnoreCase(TOK_DELETE)) stmt = parseDeleteStmt(tok); else if (tok.equalsIgnoreCase(TOK_DIR) || tok.equalsIgnoreCase(TOK_LS)) stmt = parseDirStmt(tok); else if (tok.equalsIgnoreCase(TOK_ECHO)) stmt = parseEchoStmt(tok); else if (tok.equalsIgnoreCase(TOK_EXIT) || tok.equalsIgnoreCase(TOK_QUIT) || tok.equalsIgnoreCase(TOK_BYE)) stmt = parseExitStmt(tok); else if (tok.equalsIgnoreCase(TOK_FOREACH)) stmt = parseForeachStmt(tok); else if (tok.equalsIgnoreCase(TOK_FUNC)) stmt = parseFuncDefStmt(tok); else if (tok.equalsIgnoreCase(TOK_GET)) stmt = parseGetStmt(tok); else if (tok.equalsIgnoreCase(TOK_GLOB)) stmt = parseUnknownStmt(tok); //+FIXME else if (tok.equalsIgnoreCase(TOK_GOTO)) stmt = parseGotoStmt(tok); else if (tok.equalsIgnoreCase(TOK_IF)) stmt = parseIfStmt(tok); else if (tok.equalsIgnoreCase(TOK_LCD) || tok.equalsIgnoreCase(TOK_LCHDIR)) stmt = parseLocalChdirStmt(tok); else if (tok.equalsIgnoreCase(TOK_MGET)) stmt = parseUnknownStmt(tok); //+UNSUPPORTED else if (tok.equalsIgnoreCase(TOK_MKDIR)) stmt = parseMkdirStmt(tok); else if (tok.equalsIgnoreCase(TOK_MPUT)) stmt = parseUnknownStmt(tok); //+UNSUPPORTED else if (tok.equalsIgnoreCase(TOK_NOP)) stmt = parseNopStmt(tok); else if (tok.equalsIgnoreCase(TOK_OPEN)) stmt = parseOpenStmt(tok); else if (tok.equalsIgnoreCase(TOK_PRINT)) stmt = parsePrintStmt(tok); else if (tok.equalsIgnoreCase(TOK_PROMPT)) stmt = parseUnknownStmt(tok); else if (tok.equalsIgnoreCase(TOK_PUT)) stmt = parsePutStmt(tok); else if (tok.equalsIgnoreCase(TOK_PWD)) stmt = parsePwdStmt(tok); else if (tok.equalsIgnoreCase(TOK_READ)) stmt = parseUnknownStmt(tok); //+FIXME else if (tok.equalsIgnoreCase(TOK_REN) || tok.equalsIgnoreCase(TOK_RENAME)) stmt = parseRenameStmt(tok); else if (tok.equalsIgnoreCase(TOK_REPEAT)) stmt = parseRepeatStmt(tok); else if (tok.equalsIgnoreCase(TOK_RETURN)) stmt = parseReturnStmt(tok); else if (tok.equalsIgnoreCase(TOK_RMDIR)) stmt = parseRmdirStmt(tok); else if (tok.equalsIgnoreCase(TOK_SET)) stmt = parseSetStmt(tok); else if (tok.equalsIgnoreCase(TOK_SLEEP)) stmt = parseSleepStmt(tok); else if (tok.equalsIgnoreCase(TOK_TIMEOUT)) stmt = parseUnknownStmt(tok); //+FIXME else if (tok.equalsIgnoreCase(TOK_USER)) stmt = parseUserStmt(tok); else if (tok.equalsIgnoreCase(TOK_VAR)) stmt = parseVarStmt(tok); else if (tok.equalsIgnoreCase(TOK_WHILE)) stmt = parseWhileStmt(tok); else if (tok.equalsIgnoreCase(TOK_X_INTERRUPT)) { stmt = new CommandNode(CMD_X_INTERRUPT, m_lineNo); skipLine(tok); } else if (tok.equalsIgnoreCase(TOK_X_SESSIONS)) { stmt = new CommandNode(CMD_X_SESSIONS, m_lineNo); skipLine(tok); } else if (tok.equalsIgnoreCase(TOK_X_STACK)) { stmt = new CommandNode(CMD_X_STACK, m_lineNo); skipLine(tok); } else if (tok.equalsIgnoreCase(TOK_X_STOP)) { // Stop compiling stmt = new CommandNode(CMD_X_STOP, m_lineNo); m_lexer = null; } else if (tok.equalsIgnoreCase(TOK_X_VARS)) { stmt = new CommandNode(CMD_X_VARS, m_lineNo); skipLine(tok); } else if (tok.equalsIgnoreCase(TOK_X_VERBOSE)) { stmt = parseXVerboseStmt(tok); } else { error("Unrecognized command at: '" + m_lastTok + "'"); return skipLine(tok); } return stmt; } ///+TEMPORARY private CommandNode parseUnknownStmt(String tok) throws ParseException, IOException { error("Unsupported command at: '" + tok + "'"); return skipLine(tok); } /*************************************************************************** * Parse a command label. * *

* Syntax *

    *    LabelStmt
    *        : name: nl
* * @return * (CMD_LABEL, name). * * @since 1.10, 2007-03-20 */ private CommandNode parseStmtLabel(String tok) throws ParseException, IOException { String stmtTok; String name; CommandNode stmt; // Parse: Stmt -> name: name = tok.substring(0, tok.length()-1).toLowerCase(); stmt = new CommandNode(CMD_LABEL, m_lineNo); stmt.addArg(name); if (!m_scope.addLabelDef(name, stmt)) { CommandNode def; def = m_scope.findLabelDef(name); error("Label already defined: '" + name + "' (" + def.m_lineNo + ")"); } tok = readToken(); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse a block command. * *

* Syntax *

    *    BlockStmt
    *        : '{' nl [Stmt...] '}' nl
    *        : '{' '}' nl
* * @return * (CMD_BLOCK, {@link BlockDef}, Stmts). * * @since 1.7, 2007-03-15 */ private CommandNode parseBlockStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode stmts; BlockDef def; BlockDef prevDef; // Parse: Block -> '{' nl [Stmt...] '}' nl def = new BlockDef("." + m_lineNo, m_lineNo, m_scope, null); def.m_type = CMD_BLOCK; stmt = new CommandNode(CMD_BLOCK, m_lineNo); stmt.addArg(def); tok = readToken(); if (tok.equals(TOK_RC)) { // Parse: Block -> '{' '}' nl stmt.addArg(null); tok = readToken(); if (tok == TOK__NL) return stmt; error("Missing newline following: '" + TOK_RC + "'"); return skipLine(tok); } else if (tok != TOK__NL) { error("Missing newline following: '" + TOK_LC + "'"); return skipLine(tok); } // Parse: Block -> '{' nl [Stmt...] '}' nl prevDef = m_scope; m_scope = def; stmts = parseStmts(tok); m_scope = prevDef; def.setCmds(stmts); stmt.addArg(stmts); tok = readToken(); if (tok == null || !tok.equals(TOK_RC)) { error("Missing block (" + def.m_lineNo + ") closing '" + TOK_RC + "' at: '" + m_lastTok + "'"); return skipLine(tok); } tok = readToken(); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse an "ascii" command. * *

* Syntax *

    *    AsciiStmt
    *        : asc[ii] [Session] nl
* * @return * (CMD_TEXT, sess). * * @since 1.52, 2007-08-06 */ private CommandNode parseAsciiStmt(String tok) throws ParseException, IOException { return parseModeStmt(tok, CMD_TEXT); } /*************************************************************************** * Parse a "binary" command. * *

* Syntax *

    *    BinaryStmt
    *        : bin[ary] [Session] nl
* * @return * (CMD_BINARY, sess). * * @since 1.52, 2007-08-06 */ private CommandNode parseBinaryStmt(String tok) throws ParseException, IOException { return parseModeStmt(tok, CMD_BINARY); } /*************************************************************************** * Parse an "ascii" or "binary" mode command. * *

* Syntax *

    *    AsciiStmt
    *        : asc[ii] [Session] nl
    *
    *    BinaryStmt
    *        : bin[ary] [Session] nl
* * @param cmdOp * Command code {@link #CMD_BINARY} or {@link #CMD_TEXT}. * * @return * (CMD_TEXT, sess),
* (CMD_BINARY, sess). * * @since 1.52, 2007-08-06 */ private CommandNode parseModeStmt(String tok, String cmdOp) throws ParseException, IOException { CommandNode stmt; CommandNode expr; // Parse: ModeStmt -> asc[ii]|bin[ary] ... nl stmt = new CommandNode(cmdOp, m_lineNo); // Parse: ModeStmt -> ... ['&' sess] nl tok = readToken(); expr = parseSessionTerm(tok); stmt.addArg(expr); tok = readToken(); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse a "break" command. * *

* Syntax *

    *    BreakStmt
    *        : break [name] nl
* * @return * (CMD_BREAK, [name], [stmt], {@link BlockDef}). * * @since 1.11, 2007-03-23 */ private CommandNode parseBreakStmt(String tok) throws ParseException, IOException { return parseBreakContinueStmt(tok, CMD_BREAK); } /*************************************************************************** * Parse a "break" or "continue" command. * *

* Syntax *

    *    BreakStmt
    *        : break [name] nl
    *
    *    ContinueStmt
    *        : continue [name] nl
* * @param cmdOp * Command code {@link #CMD_BREAK} or {@link #CMD_CONTINUE}. * * @return * (CMD_BREAK, [name], [stmt], {@link BlockDef}),
* (CMD_CONTINUE, [name], [stmt], {@link BlockDef}). * * @since 1.50, 2008-08-02 */ private CommandNode parseBreakContinueStmt(String tok, String cmdOp) throws ParseException, IOException { String cmd; CommandNode stmt; String name; // Parse: BreakStmt|ContinueStmt stmt = new CommandNode(cmdOp, m_lineNo); stmt.addArg(null); // [0] label stmt.addArg(null); // [1] loop stmt, filled in later stmt.addArg(null); // [2] block, filled in later // Parse: BreakStmt -> break [name] nl // Parse: CommandNode -> continue [name] nl cmd = tok; tok = readToken(); if (tok != TOK__NL) { // Parse: BreakStmt -> break name nl // Parse: ContinueStmt -> continue name nl if (!isVarName(tok)) error("Not a proper command label: '" + tok + "'"); name = tok.toLowerCase(); stmt.m_args[0] = name; tok = readToken(); } // Check that this command is within a looping command scope if (!isInALoop()) error("Not within a loop command: '" + cmd + "'"); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse a subroutine call command. * *

* Syntax *

    *    CallStmt
    *        : FuncCallExpr nl
* * @return * (CMD_CALL, name, Expr, ...). * * @since 1.7, 2007-03-15 */ private CommandNode parseCallStmt(String tok) throws ParseException, IOException { CommandNode stmt; // Parse: CallStmt -> FuncCallExpr nl stmt = parseFuncCallExpr(tok); tok = readToken(); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse a "cdup" (or "up") command. * *

* Syntax *

    *    CdupStmt
    *        : cdup [Session] nl
* * @return * (CMD_CDUP, sess). * * @since 1.35, 2007-04-16 */ private CommandNode parseCdupStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode expr; // Parse: CdupStmt stmt = new CommandNode(CMD_CDUP, m_lineNo); // Parse: CdupStmt -> cdup|up ['&' sess] nl tok = readToken(); expr = parseSessionTerm(tok); stmt.addArg(expr); tok = readToken(); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse a "cd" ("chdir") command. * *

* Syntax *

    *    ChdirStmt
    *        : ch|chdir ['&' Term] Term nl
* * @return * (CMD_CHDIR, sess, dir). * * @since 1.33, 2007-04-13 */ private CommandNode parseChdirStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode expr; // Parse: ChdirStmt -> cd|chdir ['&' sess] Term nl stmt = new CommandNode(CMD_CHDIR, m_lineNo); // Parse: ChdirStmt -> cd|chdir ['&' sess] ... tok = readToken(); expr = parseSessionTerm(tok); stmt.addArg(expr); // Parse: ChdirStmt -> cd|chdir ... Term nl tok = readToken(); expr = parseTerm(tok); stmt.addArg(expr); tok = readToken(); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse a "close" command. * *

* Syntax *

    *    CloseStmt
    *        : close ['&' Term] nl
* * @return * (CMD_CLOSE, sess). * * @since 1.18, 2007-04-01 */ private CommandNode parseCloseStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode expr; // Parse: CloseStmt -> close ['&' sess] nl stmt = new CommandNode(CMD_CLOSE, m_lineNo); // Parse: CloseStmt -> close ['&' sess] tok = readToken(); expr = parseSessionTerm(tok); stmt.addArg(expr); tok = readToken(); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse a "continue" command. * *

* Syntax *

    *    ContinueStmt
    *        : continue [name] nl
* * @return * (CMD_CONTINUE, [name], [stmt], {@link BlockDef}). * * @since 1.14, 2007-03-26 */ private CommandNode parseContinueStmt(String tok) throws ParseException, IOException { return parseBreakContinueStmt(tok, CMD_CONTINUE); } /*************************************************************************** * Parse a "delete" command. * *

* Syntax *

    *    DeleteStmt
    *        : del[ete] [Session] Term... nl
* * @return * (CMD_DELETE, sess, file...). * * @since 1.43, 2007-05-11 */ private CommandNode parseDeleteStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode expr; int nArgs; // Parse: DeleteStmt -> del[ete] ['&' sess] Term... nl stmt = new CommandNode(CMD_DELETE, m_lineNo); // Parse: DeleteStmt -> del[ete] ['&' sess] ... tok = readToken(); expr = parseSessionTerm(tok); stmt.addArg(expr); // Parse: DeleteStmt -> del[ete] ... Term... nl nArgs = 0; for (;;) { tok = readToken(); if (tok.equals(TOK_COMMA)) tok = readToken(); if (tok == TOK__NL) break; expr = parseTerm(tok); stmt.addArg(expr); nArgs++; } // Syntax check if (nArgs < 1) error(m_lineNo-1, "Missing arguments for: '" + TOK_DELETE + "'"); return stmt; } /*************************************************************************** * Parse a "dir" command. * *

* Syntax *

    *    DirStmt
    *        : dir|ls ['&' Term] [max Term] [Term...]
    *            nl
* * @return * (CMD_DIR, sess, "full"|"terse", [max], * [path, ...]). * * @since 1.10, 2007-03-21 */ private CommandNode parseDirStmt(String tok) throws ParseException, IOException { String cmd; CommandNode stmt; CommandNode expr; CommandNode path; // Parse: DirStmt stmt = new CommandNode(CMD_DIR, m_lineNo); // Parse: DirStmt -> dir|ls ['&' Term] ... cmd = tok; tok = readToken(); expr = parseSessionTerm(tok); stmt.addArg(expr); if (cmd.equalsIgnoreCase(TOK_DIR)) stmt.addArg("full"); else // cmd is TOK_LS stmt.addArg("terse"); // Parse: DirStmt -> dir|ls ... [max Term] ... tok = readToken(); if (tok.equalsIgnoreCase(TOK_MAX)) { tok = readToken(); expr = parseTerm(tok); stmt.addArg(expr); tok = readToken(); } else stmt.addArg(VAL_ZERO); // Parse: DirStmt -> dir|ls ... [Term...] while (tok != TOK__NL) { path = parseTerm(tok); stmt.addArg(path); tok = readToken(); } if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse an "echo" command. * *

* Syntax *

    *    EchoStmt
    *        : echo ['-n'] ['>'|'>>' Term]
    *            Term [[','] Term]... nl
* * @return * (CMD_ECHO, ['-n'], '>'|'>>', file, * Term, ...). * * @since 1.8, 2007-03-16 */ private CommandNode parseEchoStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode expr; int nArgs; // Parse: EchoStmt stmt = new CommandNode(CMD_ECHO, m_lineNo); // Parse: EchoStmt -> echo ['-n'] ['>'|'>>' Term] Term [',' Term]... nl tok = readToken(); if (tok.equals(TOK_NO_N)) { // Parse: EchoStmt -> echo '-n' ... stmt.addArg(CMD_NO_N); tok = readToken(); } else stmt.addArg(null); // Parse: EchoStmt -> echo ... ['>'|'>>' Term] ... if (tok.equals(TOK_GT) || tok.equals(TOK_GT2)) { // Parse: EchoStmt -> echo '>'|'>>' Term ... stmt.addArg(tok); tok = readToken(); expr = parseTerm(tok); stmt.addArg(expr); } else { unReadToken(tok); stmt.addArg(TOK_GT2); stmt.addArg(null); } // Parse: EchoStmt -> echo ... Term [',' Term]... nl nArgs = 0; for (;;) { tok = readToken(); if (tok.equals(TOK_COMMA)) tok = readToken(); if (tok == TOK__NL) break; expr = parseTerm(tok); stmt.addArg(expr); nArgs++; } // Syntax check if (nArgs < 1) error(m_lineNo-1, "Missing arguments for: '" + TOK_ECHO + "'"); return stmt; } /*************************************************************************** * Parse an "exit" command. * *

* Syntax *

    *    ExitStmt
    *        : bye  [Term] nl
    *        : exit [Term] nl
    *        : quit [Term] nl
* * @return * (CMD_EXIT, [Term]). * * @since 1.1, 2007-03-11 */ private CommandNode parseExitStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode expr; // Parse: ExitStmt stmt = new CommandNode(CMD_EXIT, m_lineNo); // Parse: ExitStmt -> bye|exit|quit nl tok = readToken(); if (tok == TOK__NL) { stmt.addArg(null); return stmt; } // Parse: ExitStmt -> bye|exit|quit Term nl expr = parseTerm(tok); stmt.addArg(expr); tok = readToken(); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse a "foreach" command. * *

* Syntax *

    *    ForeachStmt
    *        : foreach ['&' Term] Term...
    *            [in Term] [max Term] nl
    *            DoBlock
* * @return * (CMD_FOREACH, sess, {@link BlockDef}, stmt, * in, max, pattern...). * * @since 1.9, 2007-03-19 */ private CommandNode parseForeachStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode expr; CommandNode body; BlockDef def; BlockDef prevDef; // Parse: ForeachStmt stmt = new CommandNode(CMD_FOREACH, m_lineNo); // Parse: ForeachStmt -> foreach ['&' Term] ... tok = readToken(); expr = parseSessionTerm(tok); stmt.addArg(expr); // [0] sess stmt.addArg(null); // [1] block stmt.addArg(null); // [2] stmt stmt.addArg(VAL_EMPTY); // [3] in stmt.addArg(VAL_ZERO); // [4] max // Parse: ForeachStmt -> foreach ... Term... ... tok = readToken(); if (tok == TOK__NL) { error("Missing pattern argument for: '" + TOK_FOREACH + "'"); return skipLine(tok); } while (tok != TOK__NL && !tok.equals(TOK_LC)) { // Parse: ForeachStmt -> foreach ... Term... ... if (tok.equalsIgnoreCase(TOK_IN)) break; if (tok.equalsIgnoreCase(TOK_MAX)) break; expr = parseTerm(tok); stmt.addArg(expr); tok = readToken(); } if (tok.equalsIgnoreCase(TOK_IN)) { // Parse: ForeachStmt -> foreach ... [in Term] ... tok = readToken(); if (tok == TOK__NL) { error("Missing '" + TOK_IN + "' argument for: '" + TOK_FOREACH + "'"); return skipLine(tok); } expr = parseTerm(tok); stmt.m_args[3] = expr; tok = readToken(); } if (tok.equalsIgnoreCase(TOK_MAX)) { // Parse: ForeachStmt -> foreach ... [max Term] ... tok = readToken(); if (tok == TOK__NL) { error("Missing '" + TOK_MAX + "' argument for: '" + TOK_FOREACH + "'"); return skipLine(tok); } expr = parseTerm(tok); stmt.m_args[4] = expr; tok = readToken(); } // Parse: ForeachStmt -> foreach ... nl Stmt if (tok != TOK__NL) { error("Extraneous words following command: '" + tok + "'"); return skipLine(tok); } // Parse: ForeachStmt -> foreach ... Stmt def = new BlockDef("." + stmt.m_lineNo, stmt.m_lineNo, m_scope, stmt); def.m_type = CMD_FOREACH; prevDef = m_scope; m_scope = def; tok = readToken(); body = parseStmt(tok); m_scope = prevDef; def.setCmds(body); stmt.m_args[1] = def; stmt.m_args[2] = body; return stmt; } /*************************************************************************** * Parse a "func" definition command. * *

* Syntax *

    *    FuncDefStmt
    *        : func '@'name '(' [name [',' name]...] ')' nl
    *            Stmt
* * @return * (CMD_FUNC, @name, {@link BlockDef}, stmt, parm...). * * @since 1.56, 2007-09-15 */ private CommandNode parseFuncDefStmt(String tok) throws ParseException, IOException { CommandNode stmt; String func; CommandNode body; BlockDef def; BlockDef prevDef; // Parse: FuncDefStmt -> func @name ['(' [parm...] ')'] nl Stmt stmt = new CommandNode(CMD_FUNC, m_lineNo); m_lexer.splitWordTokens(true); // Parse: FuncDefStmt -> func @name ... tok = readToken(); func = tok.toLowerCase(); if (!isFunc(tok)) { error("Not a proper func name: '" + tok + "'"); return skipLine(tok); } stmt.addArg(func); // [0] @name stmt.addArg(null); // [1] BlockDef stmt.addArg(null); // [2] stmt // Parse: FuncDefStmt -> func @name ['(' [parm...] ')'] nl tok = readToken(); if (tok.equals(TOK_LP)) { // Parse: FuncDefStmt -> func @name '(' [parm...] ')' nl tok = readToken(); while (tok != TOK__NL && !tok.equals(TOK_RP)) { // Parse: ExprList -> func ... name [',' name]... ')' stmt.addArg(tok); tok = readToken(); if (tok.equals(TOK_COMMA)) { tok = readToken(); if (tok.equals(TOK_RP)) error("Missing func parameter at: '" + m_lastTok + "'"); } } if (!tok.equals(TOK_RP)) { error("Missing func ',' or ')' at: '" + m_lastTok + "'"); return skipLine(tok); } tok = readToken(); } // Parse: FuncDefStmt -> func ... nl ... if (tok != TOK__NL) { error("Extraneous words following func parameters: '" + tok + "'"); skipLine(tok); } // Parse: FuncDefStmt -> func ... Stmt def = new BlockDef("." + stmt.m_lineNo, stmt.m_lineNo, m_scope, stmt); def.m_type = CMD_FUNC; // Add func parameters as local vars, checking for duplicates for (int i = 3; i < stmt.m_args.length; i++) { String var; var = (String) stmt.m_args[i]; if (def.findVarBlock(var) == def) error("Duplicate func parameter: '" + var + "'"); else def.addVarDef(var, stmt); } prevDef = m_scope; m_scope = def; tok = readToken(); body = parseStmt(tok); stmt.m_args[1] = def; stmt.m_args[2] = body; m_scope = prevDef; def.setCmds(body); // Check for a duplicate func if (m_scope.findFuncBlock(func) == m_scope) { error(stmt.m_lineNo, "Duplicate func defined: '" + func + "' (" + m_scope.findFuncDef(func).m_lineNo + ")"); } else m_scope.addFuncDef(func, stmt); return stmt; } /*************************************************************************** * Parse a "get" command. * *

* Syntax *

    *    GetStmt
    *        : get ['&' Term] Term [Term]
    *            nl
* * @return * (CMD_GET, sess, path, [path]). * * @since 1.7, 2007-03-15 */ private CommandNode parseGetStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode expr; CommandNode path; // Parse: GetStmt -> get ['&' Term] Term [Term] nl stmt = new CommandNode(CMD_GET, m_lineNo); // Parse: GetStmt -> get ['&' Term] ... tok = readToken(); expr = parseSessionTerm(tok); stmt.addArg(expr); // Parse: GetStmt -> get ... Term [Term] nl tok = readToken(); if (tok == TOK__NL) { error("Missing file argument for: '" + TOK_GET + "'"); return skipLine(tok); } path = parseTerm(tok); stmt.addArg(path); tok = readToken(); if (tok == TOK__NL) { stmt.addArg(null); return stmt; } path = parseTerm(tok); stmt.addArg(path); tok = readToken(); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse a "goto" command. * *

* Syntax *

    *    GotoStmt
    *        : goto name nl
* * @return * (CMD_GOTO, name, stmt, block). * * @since 1.7, 2007-03-15 */ private CommandNode parseGotoStmt(String tok) throws ParseException, IOException { CommandNode stmt; String name; // Parse: GotoStmt -> goto name nl stmt = new CommandNode(CMD_GOTO, m_lineNo); tok = readToken(); if (tok == TOK__NL) { error("Missing label for: '" + TOK_GOTO + "'"); return skipLine(tok); } if (!isVarName(tok)) { error("Not a proper command label: '" + tok + "'"); return skipLine(tok); } name = tok.toLowerCase(); stmt.addArg(name); // [0] label stmt.addArg(null); // [1] stmt, filled in later stmt.addArg(null); // [2] block, filled in later tok = readToken(); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse an "if" command. * *

* Syntax *

    *    IfStmt
    *        : if Term nl Stmt
    *            [else nl Stmt]
    *        : if Term nl Stmt
    *            [else IfStmt]
* * @return * (CMD_IF, Expr, Stmt, [Stmt]). * * @since 1.13, 2007-03-25 */ private CommandNode parseIfStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode expr; CommandNode body; // Parse: IfStmt stmt = new CommandNode(CMD_IF, m_lineNo); // Parse: IfStmt -> if Term nl Stmt [else nl Stmt] tok = readToken(); if (tok == TOK__NL) { error("Missing expression for: '" + TOK_IF + "'"); return stmt; } expr = parseTerm(tok); stmt.addArg(expr); tok = readToken(); if (tok != TOK__NL) { error("Extraneous words following expression: '" + tok + "'"); return skipLine(tok); } // Parse: IfStmt -> if ... Stmt ... tok = readToken(); body = parseStmt(tok); stmt.addArg(body); tok = readToken(); if (tok == null || !tok.equalsIgnoreCase(TOK_ELSE)) { if (tok != null) unReadToken(tok); stmt.addArg(null); return stmt; } // Parse: IfStmt -> if ... else nl Stmt | if ... else IfStmt tok = readToken(); if (tok == TOK__NL) { tok = readToken(); body = parseStmt(tok); stmt.addArg(body); return stmt; } else if (tok != null && tok.equalsIgnoreCase(TOK_IF)) { body = parseIfStmt(tok); stmt.addArg(body); return stmt; } error("Extraneous words following '" + TOK_ELSE + "': '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse an "lcd" ("lchdir") command. * *

* Syntax *

    *    ChdirStmt
    *        : lch|lchdir Term nl
* * @return * (CMD_LCHDIR, sess, dir). * * @since 1.57, 2008-09-14 */ private CommandNode parseLocalChdirStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode expr; // Parse: LocalChdirStmt -> lcd|lchdir ['&' sess] Term nl stmt = new CommandNode(CMD_LCHDIR, m_lineNo); // Parse: LocalChdirStmt -> lcd|lchdir ['&' sess] ... tok = readToken(); expr = parseSessionTerm(tok); stmt.addArg(expr); // Parse: LocalChdirStmt -> lcd|lchdir ... Term nl tok = readToken(); expr = parseTerm(tok); stmt.addArg(expr); tok = readToken(); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse a "mkdir" command. * *

* Syntax *

    *    MkdirStmt
    *        : mkdir [Session] Term nl
* * @return * (CMD_MKDIR, sess, dir). * * @since 1.33, 2007-04-13 */ private CommandNode parseMkdirStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode expr; // Parse: MkdirStmt -> mkdir ['&' sess] Term nl stmt = new CommandNode(CMD_MKDIR, m_lineNo); // Parse: MkdirStmt -> mkdir ['&' sess] ... tok = readToken(); expr = parseSessionTerm(tok); stmt.addArg(expr); // Parse: MkdirStmt -> mkdir ... Term nl tok = readToken(); expr = parseTerm(tok); stmt.addArg(expr); tok = readToken(); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse a "nop" command. * *

* Syntax *

    *    NopStmt
    *        : nop nl
* * @return * (CMD_NOP). * * @since 1.28, 2007-04-09 */ private CommandNode parseNopStmt(String tok) throws ParseException, IOException { CommandNode stmt; // Parse: NopStmt -> nop nl stmt = new CommandNode(CMD_NOP, m_lineNo); tok = readToken(); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse an "open" command. * *

* Syntax *

    *    OpenStmt
    *        : open ['&' Term] Term [Term]
    *            nl
* * @return * (CMD_OPEN, sess, host, [port]). * * @since 1.1, 2007-03-11 */ private CommandNode parseOpenStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode expr; CommandNode path; // Parse: OpenStmt -> open ['&' sess] host [port] nl stmt = new CommandNode(CMD_OPEN, m_lineNo); // Parse: OpenStmt -> open ['&' sess] ... tok = readToken(); expr = parseSessionTerm(tok); stmt.addArg(expr); // Parse: OpenStmt -> open ... host [port] nl tok = readToken(); if (tok == TOK__NL) { error("Missing host argument for: '" + TOK_OPEN + "'"); return skipLine(tok); } path = parseTerm(tok); stmt.addArg(path); tok = readToken(); if (tok == TOK__NL) { stmt.addArg(null); return stmt; } path = parseTerm(tok); stmt.addArg(path); tok = readToken(); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse a "print" command. * *

* Syntax *

    *    PrintStmt
    *        : print ['>'|'>>' Term] Term...
    *            nl
* * @return * (CMD_PRINT, '>'|'>>', file, Term, ...). * * @since 1.29, 2007-04-11 */ private CommandNode parsePrintStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode expr; int nArgs; // Parse: PrintStmt stmt = new CommandNode(CMD_PRINT, m_lineNo); // Parse: PrintStmt -> print ['>'|'>>' Term] Term... nl tok = readToken(); if (tok.equals(TOK_GT) || tok.equals(TOK_GT2)) { // Parse: PrintStmt -> print '>'|'>>' Term ... stmt.addArg(tok); tok = readToken(); expr = parseTerm(tok); stmt.addArg(expr); } else { unReadToken(tok); stmt.addArg(TOK_GT2); stmt.addArg(null); } // Parse: PrintStmt -> print ... Term... nl nArgs = 0; for (;;) { tok = readToken(); if (tok.equals(TOK_COMMA)) tok = readToken(); if (tok == TOK__NL) break; expr = parseTerm(tok); stmt.addArg(expr); nArgs++; } // Syntax check if (nArgs < 1) error(m_lineNo-1, "Missing arguments for: '" + TOK_PRINT + "'"); return stmt; } /*************************************************************************** * Parse a "put" command. * *

* Syntax *

    *    PutStmt
    *        : put ['&' Term] Term [Term]
    *            nl
* * @return * (CMD_PUT, sess, path, [path]). * * @since 1.7, 2007-03-15 */ private CommandNode parsePutStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode expr; CommandNode path; // Parse: PutStmt -> put ['&' Term] Term [Term] nl stmt = new CommandNode(CMD_PUT, m_lineNo); // Parse: PutStmt -> put ['&' sess] ... tok = readToken(); expr = parseSessionTerm(tok); stmt.addArg(expr); // Parse: PutStmt -> put ... Term [Term] nl tok = readToken(); if (tok == TOK__NL) { error("Missing filename argument for: '" + TOK_PUT + "'"); return skipLine(tok); } path = parseTerm(tok); stmt.addArg(path); tok = readToken(); if (tok == TOK__NL) { stmt.addArg(null); return stmt; } path = parseTerm(tok); stmt.addArg(path); tok = readToken(); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse a "pwd" command. * *

* Syntax *

    *    PwdStmt
    *        : pwd ['&' Term] nl
* * @return * (CMD_PWD, sess). * * @since 1.11, 2007-03-22 */ private CommandNode parsePwdStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode expr; // Parse: PwdStmt -> pwd nl stmt = new CommandNode(CMD_PWD, m_lineNo); // Parse: PwdStmt -> pwd ['&' sess] nl tok = readToken(); expr = parseSessionTerm(tok); stmt.addArg(expr); tok = readToken(); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse a "rename" command. * *

* Syntax *

    *    RenameStmt
    *        : ren[ame] [Session] Term Term nl
* * @return * (CMD_RENAME, sess, old, new). * * @since 1.44, 2007-05-14 */ private CommandNode parseRenameStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode expr; // Parse: RenameStmt -> ren[ame] ['&' sess] Term Term nl stmt = new CommandNode(CMD_RENAME, m_lineNo); // Parse: RenameStmt -> ren[ame] ['&' sess] ... tok = readToken(); expr = parseSessionTerm(tok); stmt.addArg(expr); // Parse: RenameStmt -> ren[ame] ... Term Term nl tok = readToken(); if (tok == TOK__NL) { error("Missing 'from' name for: '" + TOK_RENAME + "'"); return skipLine(tok); } expr = parseTerm(tok); stmt.addArg(expr); tok = readToken(); if (tok == TOK__NL) { error("Missing 'to' name for: '" + TOK_RENAME + "'"); return skipLine(tok); } expr = parseTerm(tok); stmt.addArg(expr); tok = readToken(); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse a "return" command. * *

* Syntax *

    *    ReturnStmt
    *        : return [Term]
* * @return * (CMD_RETURN, expr). * * @since 1.56, 2007-09-16 */ private CommandNode parseReturnStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode expr; // Parse: ReturnStmt -> return [Term] nl stmt = new CommandNode(CMD_RETURN, m_lineNo); stmt.addArg(null); // Parse: ReturnStmt -> return [Term] nl tok = readToken(); if (tok == TOK__NL) return stmt; // Parse: ReturnStmt -> return Term nl expr = parseTerm(tok); stmt.m_args[0] = expr; // Parse: ReturnStmt -> return ... nl tok = readToken(); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); // Treat a 'return' command at file scope like an 'exit' command if (m_scope.m_parent == null) warning("Command '" + TOK_RETURN + "' is treated like '" + TOK_EXIT + "'"); return stmt; } /*************************************************************************** * Parse a "repeat" command. * *

* Syntax *

    *    RepeatStmt
    *        : repeat nl Stmt
* * @return * (CMD_REPEAT, {@link BlockDef}, Stmt). * * @since 1.11, 2007-03-22 */ private CommandNode parseRepeatStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode body; BlockDef def; BlockDef prevDef; // Parse: RepeatStmt stmt = new CommandNode(CMD_REPEAT, m_lineNo); // Parse: RepeatStmt -> repeat nl Stmt tok = readToken(); if (tok != TOK__NL) { error("Extraneous words following '" + TOK_REPEAT + "': '" + tok + "'"); return skipLine(tok); } // Parse: RepeatStmt -> repeat nl Stmt def = new BlockDef("." + stmt.m_lineNo, stmt.m_lineNo, m_scope, stmt); def.m_type = CMD_REPEAT; prevDef = m_scope; m_scope = def; tok = readToken(); body = parseStmt(tok); m_scope = prevDef; def.setCmds(body); stmt.addArg(def); stmt.addArg(body); return stmt; } /*************************************************************************** * Parse a "rmdir" command. * *

* Syntax *

    *    RmdirStmt
    *        : rmdir [Session] Term... nl
* * @return * (CMD_RMDIR, sess, dir...). * * @since 1.33, 2007-04-13 */ private CommandNode parseRmdirStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode expr; int nArgs; // Parse: RmdirStmt -> rmdir ['&' sess] Term nl stmt = new CommandNode(CMD_RMDIR, m_lineNo); // Parse: RmdirStmt -> rmdir ['&' sess] ... tok = readToken(); expr = parseSessionTerm(tok); stmt.addArg(expr); // Parse: RmdirStmt -> rmdir ... Term... nl nArgs = 0; for (;;) { tok = readToken(); if (tok.equals(TOK_COMMA)) tok = readToken(); if (tok == TOK__NL) break; expr = parseTerm(tok); stmt.addArg(expr); nArgs++; } // Syntax check if (nArgs < 1) error(m_lineNo-1, "Missing arguments for: '" + TOK_RMDIR + "'"); return stmt; } /*************************************************************************** * Parse a "set" command. * *

* Syntax *

    *    SetStmt
    *        : set name = Term nl
    *        : set name '[' Expr ']' = Term nl
* * @return * (CMD_SET, name, {@link BlockDef}, index, expr). * * @since 1.7, 2007-03-15 */ private CommandNode parseSetStmt(String tok) throws ParseException, IOException { CommandNode stmt; String name; String var; BlockDef dcl; CommandNode expr; // Parse: SetStmt stmt = new CommandNode(CMD_SET, m_lineNo); // Parse: SetStmt -> set name ... m_lexer.splitWordTokens(true); tok = readToken(); if (!isVarName(tok)) { error("Missing var name for: '" + TOK_SET + "'"); return skipLine(tok); } name = tok; if (name.length() > MAX_VAR_LEN) { name = name.substring(0, MAX_VAR_LEN); warning("Var name truncated: '" + name + "'"); } var = name.charAt(0) + name.substring(1).toLowerCase(); stmt.addArg(var); dcl = m_scope.findVarBlock(var); if (dcl == null) { error("Var is undefined: '" + name + "'"); return skipLine(tok); } stmt.addArg(dcl); if (Character.isUpperCase(var.charAt(0)) && !var.equals(VAR_ERROR)) { error("Cannot set builtin var: '" + var + "'"); return skipLine(tok); } // Parse: SetStmt -> set name ['[' Expr ']'] ... tok = readToken(); if (tok.equals(TOK_LB)) { // Parse: SetStmt -> set name '[' Expr ']' ... tok = readToken(); expr = parseExpr(tok); stmt.addArg(expr); tok = readToken(); if (!tok.equals(TOK_RB)) { error("Missing closing ']' at: '" + m_lastTok + "'"); return skipLine(tok); } } else { unReadToken(tok); stmt.addArg(null); } // Parse: SetStmt -> set name ... = Term nl tok = readToken(); m_lexer.splitWordTokens(false); if (!tok.equals(TOK_EQ)) { error("Missing '=' for: set '" + name + "'"); return skipLine(tok); } tok = readToken(); expr = parseTerm(tok); stmt.addArg(expr); tok = readToken(); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse a "!" shell command. * *

* Syntax *

    *    ShellStmt
    *        : '!' Term... nl
* * @return * (CMD_SHELL, expr...). * * @since 1.36, 2007-04-18 */ private CommandNode parseShellStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode expr; // Parse: ShellStmt stmt = new CommandNode(CMD_SHELL, m_lineNo); // Parse: ShellStmt -> '!' Term... nl tok = readToken(); while (tok != TOK__NL) { expr = parseTerm(tok); stmt.addArg(expr); tok = readToken(); } if (stmt.m_args.length < 1) error(stmt.m_lineNo, "Missing command line for: '" + TOK_BANG + "'"); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse a "sleep" command. * *

* Syntax *

    *    SleepStmt
    *        : sleep Term [Term] nl
* * @return * (CMD_SLEEP, expr). * * @since 1.33, 2007-04-13 */ private CommandNode parseSleepStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode expr; // Parse: SleepStmt -> sleep Term nl stmt = new CommandNode(CMD_SLEEP, m_lineNo); // Parse: SleepStmt -> sleep Term nl tok = readToken(); expr = parseTerm(tok); stmt.addArg(expr); tok = readToken(); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse a "user" command. * *

* Syntax *

    *    UserStmt
    *        : user ['&' Term] Term [Term]
    *            nl
* * @return * (CMD_USER, sess, name, [password]). * * @since 1.10, 2007-03-21 */ private CommandNode parseUserStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode expr; // Parse: UserStmt -> user ['&' sess] name [password] nl stmt = new CommandNode(CMD_USER, m_lineNo); // Parse: UserStmt -> user ['&' sess] ... tok = readToken(); expr = parseSessionTerm(tok); stmt.addArg(expr); // Parse: UserStmt -> user ... name [password] nl tok = readToken(); if (tok == TOK__NL) { error("Missing user argument for: '" + TOK_USER + "'"); return skipLine(tok); } expr = parseTerm(tok); stmt.addArg(expr); tok = readToken(); if (tok == TOK__NL) { stmt.addArg(null); return stmt; } expr = parseTerm(tok); stmt.addArg(expr); tok = readToken(); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse a "var" command. * *

* Syntax *

    *    VarStmt
    *        : var name ['=' Term ] nl
* * @return * (CMD_VAR, name, {@link BlockDef}, expr). * * @since 1.7, 2007-03-15 */ private CommandNode parseVarStmt(String tok) throws ParseException, IOException { CommandNode stmt; String name; String var; BlockDef dcl; CommandNode def; CommandNode expr; // Parse: VarStmt stmt = new CommandNode(CMD_VAR, m_lineNo); tok = readToken(); if (!isVarName(tok)) { error("Missing var name for: '" + TOK_VAR + "'"); return skipLine(tok); } name = tok; if (name.length() > MAX_VAR_LEN) { name = name.substring(0, MAX_VAR_LEN); warning("Var name truncated: '" + name + "'"); } var = name.charAt(0) + name.substring(1).toLowerCase(); stmt.addArg(var); stmt.addArg(m_scope); if (Character.isUpperCase(var.charAt(0))) { error("Cannot define builtin var: '" + var + "'"); return skipLine(tok); } // Parse: VarStmt -> var name nl tok = readToken(); if (tok.equals(TOK_EQ)) { // Parse: VarStmt -> var name ['=' Term] nl tok = readToken(); expr = parseTerm(tok); stmt.addArg(expr); } else { unReadToken(tok); stmt.addArg(null); } // Verify the var definition dcl = m_scope.findVarBlock(var); def = m_scope.findVarDef(var); if (!m_scope.addVarDef(var, stmt)) error("Var is already defined: '" + name + "' (" + def.m_lineNo + ")"); if (dcl != null && m_scope.m_depth - dcl.m_depth > 0) warning("Var hides outer definition: '" + name + "' (" + def.m_lineNo + ")"); tok = readToken(); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Parse a "while" command. * *

* Syntax *

    *    WhileStmt
    *        : while Term nl Stmt
* * @return * (CMD_WHILE, Expr, {@link BlockDef}, Stmt). * * @since 1.29, 2007-04-11 */ private CommandNode parseWhileStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode expr; CommandNode body; BlockDef def; BlockDef prevDef; // Parse: WhileStmt -> while Term nl Stmt stmt = new CommandNode(CMD_WHILE, m_lineNo); // Parse: WhileStmt -> while Term ... tok = readToken(); if (tok == TOK__NL) { error("Missing expression for: '" + TOK_WHILE + "'"); return stmt; } expr = parseTerm(tok); stmt.addArg(expr); // Parse: WhileStmt -> while ... nl Stmt tok = readToken(); if (tok != TOK__NL) { error("Extraneous words following expression: '" + tok + "'"); return skipLine(tok); } // Parse: WhileStmt -> while ... Stmt def = new BlockDef("." + stmt.m_lineNo, stmt.m_lineNo, m_scope, stmt); def.m_type = CMD_WHILE; prevDef = m_scope; m_scope = def; tok = readToken(); body = parseStmt(tok); m_scope = prevDef; def.setCmds(body); stmt.addArg(def); stmt.addArg(body); return stmt; } /*************************************************************************** * Parse a "%verbose" command. * *

* Syntax *

    *    XVerboseStmt
    *        : '%verbose' Term nl
* * @return * (CMD_X_VERBOSE, Expr). * * @since 1.37, 2007-04-27 */ private CommandNode parseXVerboseStmt(String tok) throws ParseException, IOException { CommandNode stmt; CommandNode expr; // Parse: XVerboseStmt -> '%verbose' Term nl stmt = new CommandNode(CMD_X_VERBOSE, m_lineNo); // Parse: XVerboseStmt -> '%verbose' Term ... tok = readToken(); if (tok == TOK__NL) { error("Missing expression for: '" + TOK_X_VERBOSE + "'"); return stmt; } expr = parseTerm(tok); stmt.addArg(expr); // Parse: XVerboseStmt -> '%verbose' ... nl tok = readToken(); if (tok == TOK__NL) return stmt; error("Extraneous words following command: '" + tok + "'"); skipLine(tok); return stmt; } /*************************************************************************** * Skip tokens up to the end of the current source line (newline). * * @return * (CMD_NOP). * * @since 1.10, 2007-03-20 */ private CommandNode skipLine(String tok) throws IOException { int lineNo; // Skip tokens up to the next newline lineNo = m_lineNo; while (tok != TOK__NL && tok != null) { lineNo = m_lineNo; tok = readToken(); } if (tok != null) unReadToken(tok); // Return a dummy 'NOP' command node return (new CommandNode(CMD_NOP, lineNo)); } /*************************************************************************** * Determine if a break/continue command occurs within the scope of a looping * command. * * @since 1.31, 2007-04-12 */ private boolean isInALoop() { BlockDef scope; // Check that this command is within a looping command scope for (scope = m_scope; scope != null; scope = scope.m_outer) { String type; // Check the enclosing scope for a looping command type = scope.m_type; if (type == CMD_FOREACH || type == CMD_REPEAT || type == CMD_WHILE) return true; // Search outer block scope, but not outside a func block if (scope.isFunc()) break; } return false; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Expression parsing methods /*************************************************************************** * Parse an expression. * Note: This is a helper method meant only for testing. * * @since 1.7, 2007-03-15 */ CommandNode parseAnExpr() throws ParseException, IOException { String tok; CommandNode expr; // Parse an expression tok = readToken(); expr = parseExpr(tok); // Check for a completely read source script tok = readToken(); if (tok != null) error(m_lineNo, "Incomplete expression at: '" + tok + "'"); // Simplify the parse tree postprocess(expr); return expr; } /*************************************************************************** * Parse an expression. * *

* Syntax *

    *    Expr
    *        : IfExpr
* * @since 1.2, 2007-03-13 */ private CommandNode parseExpr(String tok) throws ParseException, IOException { CommandNode expr; // Parse: Expr -> IfExpr expr = parseIfExpr(tok); return expr; } /*************************************************************************** * Parse a '?:' conditional expression. * *

* Syntax *

    *    IfExpr
    *        : OrExpr
    *        : OrExpr '?' IfExpr ':' IfExpr
* * @return * (CMD_COND, expr, expr, expr). * * @since 1.55, 2007-08-10 */ private CommandNode parseIfExpr(String tok) throws ParseException, IOException { CommandNode expr; // Parse: IfExpr -> OrExpr ['?' IfExpr ':' IfExpr] expr = parseOrExpr(tok); tok = readToken(); if (tok.equals(TOK_QUES)) { CommandNode exp2; // Parse: IfExpr -> OrExpr '?' IfExpr ':' ... exp2 = expr; expr = new CommandNode(CMD_COND, m_lineNo); expr.addArg(exp2); tok = readToken(); exp2 = parseIfExpr(tok); expr.addArg(exp2); tok = readToken(); if (tok.equals(TOK_COLON)) { // Parse: IfExpr -> OrExpr '?' IfExpr ':' IfExpr tok = readToken(); exp2 = parseIfExpr(tok); expr.addArg(exp2); } else { error("Missing expected ':' for '?' expression at: '" + m_lastTok + "'"); expr.addArg(null); unReadToken(tok); } } else { // Parse: IfExpr -> OrExpr unReadToken(tok); } return expr; } /*************************************************************************** * Parse an 'or' expression. * *

* Syntax *

    *    OrExpr
    *        : AndExpr [or AndExpr]...
* * @return * (CMD_OR, expr, expr). * * @since 1.27, 2007-04-07 */ private CommandNode parseOrExpr(String tok) throws ParseException, IOException { CommandNode expr; // Parse: OrExpr expr = parseAndExpr(tok); tok = readToken(); while (tok.equalsIgnoreCase(TOK_OR)) { CommandNode exp2; // Parse: OrExpr -> AndExpr {'or' AndExpr}... exp2 = expr; expr = new CommandNode(CMD_OR, m_lineNo); expr.addArg(exp2); tok = readToken(); exp2 = parseAndExpr(tok); expr.addArg(exp2); tok = readToken(); } unReadToken(tok); return expr; } /*************************************************************************** * Parse an 'and' expression. * *

* Syntax *

    *    AndExpr
    *        : NotExpr [and NotExpr]...
* * @return * (CMD_AND, expr, expr). * * @since 1.2, 2007-03-13 */ private CommandNode parseAndExpr(String tok) throws ParseException, IOException { CommandNode expr; // Parse: AndExpr expr = parseNotExpr(tok); tok = readToken(); while (tok.equalsIgnoreCase(TOK_AND)) { CommandNode exp2; // Parse: AndExpr -> NotExpr {'and' NotExpr}... exp2 = expr; expr = new CommandNode(CMD_AND, m_lineNo); expr.addArg(exp2); tok = readToken(); exp2 = parseNotExpr(tok); expr.addArg(exp2); tok = readToken(); } unReadToken(tok); return expr; } /*************************************************************************** * Parse a 'not' expression. * *

* Syntax *

    *    NotExpr
    *        : RelExpr
    *        : not NotExpr
* * @return * (CMD_NOT, expr). * * @since 1.2, 2007-03-13 */ private CommandNode parseNotExpr(String tok) throws ParseException, IOException { CommandNode expr; // Parse: NotExpr if (tok.equalsIgnoreCase(TOK_NOT)) { CommandNode exp2; // Parse: NotExpr -> 'not' NotExpr expr = new CommandNode(CMD_NOT, m_lineNo); tok = readToken(); exp2 = parseNotExpr(tok); expr.addArg(exp2); } else { // Parse: NotExpr -> RelExpr expr = parseRelExpr(tok); } return expr; } /*************************************************************************** * Parse a relational (comparison) expression. * *

* Syntax *

    *    RelExpr
    *        : TestExpr
    *        : TestExpr '='  TestExpr
    *        : TestExpr '==' TestExpr
    *        : TestExpr '!=' TestExpr
    *        : TestExpr '<'  TestExpr
    *        : TestExpr '<=' TestExpr
    *        : TestExpr '>'  TestExpr
    *        : TestExpr '>=' TestExpr
    *        : TestExpr '~'  TestExpr
    *        : TestExpr '!~' TestExpr
* * @return * (CMD_RELOP, expr, expr). * * @since 1.2, 2007-03-13 */ private CommandNode parseRelExpr(String tok) throws ParseException, IOException { CommandNode expr; String op; // Parse: RelExpr expr = parseTestExpr(tok); tok = readToken(); if (tok.equals(TOK_EQ) || tok.equals(TOK_EQ2)) op = CMD_EQ; else if (tok.equals(TOK_NE)) op = CMD_NE; else if (tok.equals(TOK_LT)) op = CMD_LT; else if (tok.equals(TOK_LE)) op = CMD_LE; else if (tok.equals(TOK_GT)) op = CMD_GT; else if (tok.equals(TOK_GE)) op = CMD_GE; else if (tok.equals(TOK_MATCH)) op = CMD_MATCH; else if (tok.equals(TOK_NMATCH)) op = CMD_NMATCH; else op = null; if (op != null) { CommandNode exp2; // Parse: RelExpr -> TestExpr RelOp TestExpr exp2 = expr; expr = new CommandNode(op, m_lineNo); expr.addArg(exp2); tok = readToken(); exp2 = parseTestExpr(tok); expr.addArg(exp2); } else { // Parse: RelExpr -> TestExpr unReadToken(tok); } return expr; } /*************************************************************************** * Parse a unary predicate expression. * *

* Syntax *

    *    TestExpr
    *        : StringExpr
    *        : ':dir'    StringExpr
    *        : ':exists' StringExpr
    *        : ':file'   StringExpr
    *        : ':mtime'  StringExpr
    *        : ':read'   StringExpr
    *        : ':size'   StringExpr
    *        : ':write'  StringExpr
* * @return * (CMD_TEST, optype, expr). * * @since 1.21, 2007-04-04 */ private CommandNode parseTestExpr(String tok) throws ParseException, IOException { CommandNode expr; CommandNode exp2; // Parse: TestExpr if (isPredicateOp(tok)) { String op; // Parse: TestExpr -> TestOp StringExpr expr = new CommandNode(CMD_TEST, m_lineNo); op = "?"; if (tok.equalsIgnoreCase(TOK_TEST_DIR) || tok.equals(TOK_TEST_D)) op = CMD_TEST_DIR; /*+++INCOMPLETE else if (tok.equalsIgnoreCase(TOK_TEST_EXEC) || tok.equals(TOK_TEST_X)) op = CMD_TEST_EXEC; +++*/ else if (tok.equalsIgnoreCase(TOK_TEST_EXISTS) || tok.equals(TOK_TEST_E)) op = CMD_TEST_EXISTS; else if (tok.equalsIgnoreCase(TOK_TEST_FILE) || tok.equals(TOK_TEST_F)) op = CMD_TEST_FILE; else if (tok.equalsIgnoreCase(TOK_TEST_MTIME) || tok.equals(TOK_TEST_M)) op = CMD_TEST_MODTIME; else if (tok.equalsIgnoreCase(TOK_TEST_READ) || tok.equals(TOK_TEST_R)) op = CMD_TEST_READ; else if (tok.equalsIgnoreCase(TOK_TEST_SIZE) || tok.equals(TOK_TEST_SIZE)) op = CMD_TEST_SIZE; else if (tok.equalsIgnoreCase(TOK_TEST_WRITE) || tok.equals(TOK_TEST_W)) op = CMD_TEST_WRITE; else error("Unknown file test operator: '" + tok + "'"); expr.addArg(op); tok = readToken(); exp2 = parseStringExpr(tok); expr.addArg(exp2); } else { // Parse: TestExpr -> StringExpr expr = parseStringExpr(tok); } return expr; } /*************************************************************************** * Parse a string expression. * *

* Syntax *

    *    StringExpr
    *        : AddExpr
    *        : AddExpr ['.' AddExpr]...
* * @return * (CMD_CONCAT, expr, expr). * * @since 1.2, 2007-03-13 */ private CommandNode parseStringExpr(String tok) throws ParseException, IOException { CommandNode expr; // Parse: StringExpr -> AddExpr ['.' AddExpr]... expr = parseAddExpr(tok); tok = readToken(); while (tok.equals(TOK_CONCAT)) { CommandNode exp2; // Parse: StringExpr -> AddExpr {'.' AddExpr}... exp2 = expr; expr = new CommandNode(CMD_CONCAT, m_lineNo); expr.addArg(exp2); tok = readToken(); exp2 = parseAddExpr(tok); expr.addArg(exp2); tok = readToken(); } unReadToken(tok); return expr; } /*************************************************************************** * Parse an addition expression. * *

* Syntax *

    *    AddExpr
    *        : MulExpr
    *        : MulExpr ['+' MulExpr]...
    *        : MulExpr ['-' MulExpr]...
* * @return * (CMD_ADDOP, expr, expr). * * @since 1.2, 2007-03-13 */ private CommandNode parseAddExpr(String tok) throws ParseException, IOException { CommandNode expr; // Parse: AddExpr expr = parseMulExpr(tok); tok = readToken(); for (;;) { String op; CommandNode exp2; if (tok.equals(TOK_ADD)) op = CMD_ADD; else if (tok.equals(TOK_SUB)) op = CMD_SUB; else break; // Parse: AddExpr -> MulExpr {'+'|'-' MulExpr}... exp2 = expr; expr = new CommandNode(op, m_lineNo); expr.addArg(exp2); tok = readToken(); exp2 = parseMulExpr(tok); expr.addArg(exp2); tok = readToken(); } unReadToken(tok); return expr; } /*************************************************************************** * Parse a multiplication expression. * *

* Syntax *

    *    MulExpr
    *        : UnaryExpr
    *        : UnaryExpr ['*' UnaryExpr]...
    *        : UnaryExpr ['/' UnaryExpr]...
    *        : UnaryExpr ['%' UnaryExpr]...
* * @return * (CMD_MULOP, expr, expr). * * @since 1.2, 2007-03-13 */ private CommandNode parseMulExpr(String tok) throws ParseException, IOException { CommandNode expr; // Parse: MulExpr expr = parseUnaryExpr(tok); tok = readToken(); for (;;) { String op; CommandNode exp2; if (tok.equals(TOK_MUL)) op = CMD_MUL; else if (tok.equals(TOK_DIV)) op = CMD_DIV; else if (tok.equals(TOK_MOD)) op = CMD_MOD; else break; // Parse: MulExpr -> UnaryExpr {'*'|'/'|'%' UnaryExpr}... exp2 = expr; expr = new CommandNode(op, m_lineNo); expr.addArg(exp2); tok = readToken(); exp2 = parseUnaryExpr(tok); expr.addArg(exp2); tok = readToken(); } unReadToken(tok); return expr; } /*************************************************************************** * Parse a unary expression. * *

* Syntax *

    *    UnaryExpr
    *        : CallExpr
    *        : '+' UnaryExpr
    *        : '-' UnaryExpr
* * @return * (CMD_UNARYOP, expr, expr). * * @since 1.2, 2007-03-13 */ private CommandNode parseUnaryExpr(String tok) throws ParseException, IOException { CommandNode expr; CommandNode exp2; // Parse: UnaryExpr if (tok.equals(TOK_PLUS)) { // Parse: UnaryExpr -> '+' UnaryExpr expr = new CommandNode(CMD_POS, m_lineNo); tok = readToken(); exp2 = parseUnaryExpr(tok); expr.addArg(exp2); } else if (tok.equals(TOK_NEG)) { // Parse: UnaryExpr -> '-' UnaryExpr expr = new CommandNode(CMD_NEG, m_lineNo); tok = readToken(); exp2 = parseUnaryExpr(tok); expr.addArg(exp2); } else { // Parse: UnaryExpr expr = parseCallExpr(tok); } return expr; } /*************************************************************************** * Parse a subroutine call expression. * *

* Syntax *

    *    CallExpr
    *        : BuiltinExpr
    *        : FuncCallExpr
    *        : SubscriptExpr
* * @since 1.2, 2007-03-13 */ private CommandNode parseCallExpr(String tok) throws ParseException, IOException { CommandNode expr; // Parse: CallExpr if (isFunc(tok)) { // Parse: CallExpr -> FuncCallExpr expr = parseFuncCallExpr(tok); } else { expr = parseBuiltinExpr(tok); if (expr != null) { // Parse: CallExpr -> BuiltinExpr } else { // Parse: CallExpr -> SubscriptExpr expr = parseSubscriptExpr(tok); } } return expr; } /*************************************************************************** * Parse a built-in function call expression. * *

* Syntax *

    *    BuiltinExpr
    *        : format '(' Expr ',' Expr ')'
    *        : index  '(' Expr ',' Expr ')'
    *        : lc     '(' Expr ')'
    *        : len    '(' Expr ')'
    *        : norm   '(' Expr ')'
    *        : repl   '(' Expr ',' Expr ',' Expr ')'
    *        : rindex '(' Expr ',' Expr ')'
    *        : sub    '(' Expr ',' Expr [',' Expr] ')'
    *        : trim   '(' Expr [',' Expr] ')'
    *        : uc     '(' Expr ')'
* * @return * (CMD_BUILTIN, CMD_FUNCOP, expr, ...). * * @since 1.21, 2007-04-04 */ private CommandNode parseBuiltinExpr(String tok) throws ParseException, IOException { CommandNode expr; String exprTok; String cmd; // Parse: BuiltinExpr -> format|...|trim '(' Expr [',' Expr]... ')' if (tok.equals(TOK_FORMAT)) cmd = CMD_FUNC_FORMAT; else if (tok.equals(TOK_INDEX)) cmd = CMD_FUNC_INDEX; else if (tok.equals(TOK_LCASE)) cmd = CMD_FUNC_LCASE; else if (tok.equals(TOK_LEN)) cmd = CMD_FUNC_LEN; else if (tok.equals(TOK_NORM)) cmd = CMD_FUNC_NORM; else if (tok.equals(TOK_REPL)) cmd = CMD_FUNC_REPL; else if (tok.equals(TOK_RINDEX)) cmd = CMD_FUNC_RINDEX; else if (tok.equals(TOK_SUBSTR)) cmd = CMD_FUNC_SUB; else if (tok.equals(TOK_TRIM)) cmd = CMD_FUNC_TRIM; else if (tok.equals(TOK_UCASE)) cmd = CMD_FUNC_UCASE; else return null; // Parse: BuiltinExpr -> format|...|trim '(' ... expr = new CommandNode(CMD_BUILTIN, m_lineNo); expr.addArg(cmd); exprTok = tok; tok = readToken(); if (!tok.equals(TOK_LP)) { error("Missing '(' after func name: '" + exprTok + "'"); return (new CommandNode(CMD_NOP, m_lineNo)); } // Parse: BuiltinExpr -> ... '(' Expr... ')' tok = readToken(); while (tok != TOK__NL && !tok.equals(TOK_RP)) { CommandNode arg; // Parse: ExprList -> Expr [',' Expr]... ')' arg = parseExpr(tok); expr.addArg(arg); tok = readToken(); if (tok.equals(TOK_COMMA)) { tok = readToken(); if (tok.equals(TOK_RP)) error("Missing func argument at: '" + m_lastTok + "'"); } } if (!tok.equals(TOK_RP)) { error("Missing func ',' or ')' at: '" + m_lastTok + "'"); return skipLine(tok); } //+++INCOMPLETE, check arg count return expr; } /*************************************************************************** * Parse a subroutine call expression. * *

* Syntax *

    *    FuncCallExpr
    *        : '@'name ['(' [ExprList] ')']
    *
    *    ExprList
    *        : Expr
    *        : Expr [',' Expr]...
* * @return * (CMD_CALL, name, expr, ...). * * @since 1.7, 2007-03-15 */ private CommandNode parseFuncCallExpr(String tok) throws ParseException, IOException { CommandNode expr; // Parse: FuncCallExpr expr = new CommandNode(CMD_CALL, m_lineNo); expr.addArg(tok); warning("Unsupported operation: func call: '" + tok + "'"); tok = readToken(); if (tok.equals(TOK_LP)) { // Parse: FuncCallExpr -> @name '(' [ExprList] ')' tok = readToken(); while (tok != TOK__NL && !tok.equals(TOK_RP)) { CommandNode arg; // Parse: ExprList -> Expr [',' Expr]... ')' arg = parseExpr(tok); expr.addArg(arg); tok = readToken(); if (tok.equals(TOK_COMMA)) { tok = readToken(); if (tok.equals(TOK_RP)) error("Missing func argument at: '" + m_lastTok + "'"); } } if (!tok.equals(TOK_RP)) { error("Missing func ',' or ')' at: '" + m_lastTok + "'"); return skipLine(tok); } } else { // Parse: FuncCallExpr -> @name unReadToken(tok); } return expr; } /*************************************************************************** * Parse an array subscript expression. * *

* Syntax *

    *    SubscriptExpr
    *        : Var
    *        : Var '[' Expr ']'
    *        : Term
* * @return * (CMD_SUBSCR, var, expr). * * @since 1.46, 2007-05-29 */ private CommandNode parseSubscriptExpr(String tok) throws ParseException, IOException { CommandNode expr; // Parse: SubscriptExpr if (isVarExpr(tok)) { String var; // Parse: SubscriptExpr -> var ['[' Expr ']'] expr = new CommandNode(CMD_TOK, m_lineNo); prepareString(tok, m_lineNo); expr.addArg(tok); var = tok.substring(1); if (var.charAt(0) == '{') var = var.substring(1, var.length() - 1); if (var.length() > MAX_VAR_LEN) { var = var.substring(0, MAX_VAR_LEN); warning("Var name truncated: '" + var + "'"); } var = var.charAt(0) + var.substring(1).toLowerCase(); tok = readToken(); if (tok.equals(TOK_LB)) { CommandNode exp2; // Parse: SubscriptExpr -> var '[' Expr ']' expr = new CommandNode(CMD_SUBSCR, m_lineNo); tok = readToken(); exp2 = parseExpr(tok); expr.addArg(var); expr.addArg(exp2); tok = readToken(); if (!tok.equals(TOK_RB)) { error("Missing closing ']' at: '" + m_lastTok + "'"); return skipLine(tok); } } else unReadToken(tok); return expr; } // Parse: SubscriptExpr -> Term expr = parseTerm(tok); return expr; } /*************************************************************************** * Parse a term expression. * *

* Syntax *

    *    Term
    *        : true
    *        : false
    *        : word
    *        : number
    *        : string
    *        : var
    *        : '(' Expr ')'
* * @since 1.2, 2007-03-13 */ private CommandNode parseTerm(String tok) throws ParseException, IOException { CommandNode expr; // Check if (tok == TOK__NL) { error("Missing expression term at: '" + m_lastTok + "'"); return skipLine(tok); } // Parse: Term if (tok.equals(TOK_LP)) { // Parse: Term -> '(' Expr ')' tok = readToken(); expr = parseExpr(tok); tok = readToken(); if (!tok.equals(TOK_RP)) { error("Missing closing ')' at: '" + m_lastTok + "'"); return skipLine(tok); } } else { expr = new CommandNode(CMD_TOK, m_lineNo); if (tok.equalsIgnoreCase(TOK_TRUE)) { // Parse: Term -> true expr.addArg(CMD_TRUE); } else if (tok.equalsIgnoreCase(TOK_FALSE)) { // Parse: Term -> false expr.addArg(CMD_FALSE); } else { // Parse: Term -> word | string prepareString(tok, m_lineNo); expr.addArg(tok); } } return expr; } /*************************************************************************** * Parse a session specifier clause. * *

* Syntax *

    *    Session
    *        : '&' Term
    *        : '§' Term
* * @return * Term or (CMD_TOK, '1'). * * @since 1.35, 2007-04-16 */ private CommandNode parseSessionTerm(String tok) throws ParseException, IOException { CommandNode expr; // Parse: Session -> ['&' Term] if (tok.equals(TOK_AMPER) || tok.equals(TOK_SECT)) { // Parse: Session -> '&' Term tok = readToken(); expr = parseTerm(tok); } else { // Parse: Session -> (empty) unReadToken(tok); expr = new CommandNode(CMD_TOK, m_lineNo); expr.addArg(VAL_ONE); } return expr; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Parsing helper methods /*************************************************************************** * Determine if a token is a variable name. * * @since 1.4, 2007-03-14 */ private boolean isVarName(String tok) { char ch; // Scan the name for (int i = tok.length() - 1; i >= 0; i--) { ch = tok.charAt(i); if (!Character.isLetterOrDigit(ch) && ch != '_') return false; } return true; } /*************************************************************************** * Determine if a token is a variable expression. * * @since 1.4, 2007-03-14 */ private boolean isVarExpr(String tok) { int len; // Check the token word if (tok.length() < 2 || tok.charAt(0) != '$') return false; tok = tok.substring(1); if (tok.charAt(0) == '{') tok = tok.substring(1, tok.length() - 1); return isVarName(tok); } /*************************************************************************** * Determine if a token is a command label. * * @since 1.54, 2007-08-10 */ private boolean isLabelName(String tok) { char ch; int len; // Scan the name len = tok.length(); if (len < 2) return false; if (tok.charAt(len-1) != ':') return false; for (int i = 0; i < len-2; i++) { ch = tok.charAt(i); if (!Character.isLetterOrDigit(ch) && ch != '_') return false; } return true; } /*************************************************************************** * Determine if a token is a subroutine name. * * @since 1.7, 2007-03-15 */ private boolean isFunc(String tok) { return (tok.charAt(0) == '@'); } /*************************************************************************** * Determine if a token is a quoted string literal. * * @since 1.2, 2007-03-13 */ private boolean isString(String tok) { char ch; ch = tok.charAt(0); if (ch == '\'' && tok.endsWith("'")) return true; if (ch == '"' && tok.endsWith("\"")) return true; return false; } /*************************************************************************** * Determine if a token is a numeric literal. * * @since 1.2, 2007-03-13 */ private boolean isNumber(String tok) { char ch; ch = tok.charAt(0); if (ch >= '0' && ch <= '9') return true; return false; } /*************************************************************************** * Determine if a token is a file predicate operator. * * @since 1.40, 2007-05-04 (1.2, 2007-03-13) */ private boolean isPredicateOp(String tok) { int len; char ch; // Check for a token of the form ":xxx" len = tok.length(); if (len < 2) return false; ch = tok.charAt(0); if (ch != ':') return false; for (int i = 1; i < len; i++) { ch = tok.charAt(i); if (!Character.isLetter(ch)) return false; } return true; } /*************************************************************************** * Validate and simplify a string literal. * *

* Verifies that any embedded var names ($foo, ${foo}) * within the string are defined within the current scope, and canonicalizes * them (to the form ${foo}). * *

* The enclosing quotes are stripped from string literal tokens, converting * them into word tokens (e.g., '/bin/$file' becomes * /bin/${file}). * *

* Integer tokens, composed of decimal digits, optionally preceded with a * leading sign, are tagged as being numeric with a 'n' prefix. * All other tokens are tagged as being string tokens with a 's' * prefix. For example, '+123' is converted into n123 * (numeric), and 12$foo is converted into s12${foo} * (string). * * @param s * String literal to validate. * * @param lineNo * Source line number of the string. If this is greater than zero, error * messages are displayed if the string is malformed, otherwise no messages * are issued. * * @return * Canonicalized string literal, or null if the string is malformed. * * @since 1.12, 2007-03-23 */ private String prepareString(String s, int lineNo) { StringBuffer buf; boolean isNum; int len; int i; char ch; // Validate and canonicalize the string len = s.length(); if (len < 1) return VAL_EMPTY; // Strip any enclosing quotes i = 0; if (s.charAt(0) == '"') { i++; len--; } if (i >= len) return VAL_EMPTY; // Interpret the string contents, replacing vars and escape sequences buf = new StringBuffer(len+10); buf.append('s'); isNum = true; if (i < len) { // Check for a leading numeric sign ch = s.charAt(i++); if (ch == '+' || ch == '-') buf.append(ch); else i--; } while (i < len) { // Interpret the next portion of the string ch = s.charAt(i++); if (ch == '$' && i < len) { String name; String val; char quote; int bi; // Escape sequence or var name isNum = false; ch = s.charAt(i++); quote = 'x'; switch (ch) { case '$': buf.append('$'); case '`': case '"': case '\'': // Escape sequence, '$x' buf.append(ch); break; case '{': quote = '{'; if (i < len) ch = s.charAt(i++); default: // Variable name, '$abc' or '${abc}' or '${$abc}' i--; bi = 0; if (ch == '$' && i+1 < len) { // Handle '${$abc}' environment variable name m_vbuf[bi++] = ch; i++; ch = s.charAt(i); } while (Character.isLetterOrDigit(ch) || ch == '_') { if (bi < MAX_VAR_LEN) m_vbuf[bi++] = ch; i++; if (i >= len) break; ch = s.charAt(i); } if (quote == '{' && ch == '}') i++; // Canonicalize the var name if (bi > MAX_VAR_LEN) { bi = MAX_VAR_LEN; name = new String(m_vbuf, 0, bi); if (lineNo > 0) warning(lineNo, "Var name truncated: '" + name + "'"); } else if (bi < 1) { if (lineNo > 0) error(lineNo, "Missing var name after '$': '" + ch + "'"); name = null; } else name = new String(m_vbuf, 0, bi); // Check for a properly defined var name if (name != null) { String var; char ch0; var = name; ch0 = name.charAt(0); if (ch0 != '$') var = ch0 + name.substring(1).toLowerCase(); if (quote == '{' && ch != '}' && lineNo > 0) error(lineNo, "Missing '}' after var name: '" + name + "'"); if (ch0 != '$' && m_scope.findVarDef(var) == null && lineNo > 0) error(lineNo, "Var is not defined: '" + name + "'"); buf.append("${"); buf.append(var); buf.append('}'); } break; } } else if (ch == '`' && i < len) { // Character escape sequence isNum = false; ch = s.charAt(i++); if (ch == 'n') { buf.append(Interp.LOCAL_NL); } else { switch (ch) { case 'a': ch = (char) 0x07; break; case 'b': ch = (char) '\b'; break; case 'd': ch = (char) 0x7F; break; case 'e': ch = (char) 0x1B; break; case 'f': ch = (char) '\f'; break; case 'l': ch = (char) 0x0A; break; case 'r': ch = (char) '\r'; break; case 's': ch = (char) ' '; break; case 't': ch = (char) '\t'; break; case 'v': ch = (char) 0x0B; break; case 'z': ch = (char) 0x1A; break; ///+REDO case 'u': break; ///+REDO case 'x': break; case '$': buf.append('$'); case '`': case '"': case '\'': break; default: if (lineNo > 0) warning(lineNo, "Unknown character escape sequence: '`" + ch + "'"); break; } buf.append(ch); } } else if (ch >= '0' && ch <= '9') { buf.append(ch); } else { isNum = false; buf.append(ch); } } // Done if (isNum) buf.setCharAt(0, 'n'); if (lineNo == 0) { String res; res = buf.toString(); if (res.equalsIgnoreCase(VAL_TRUE_WD)) return VAL_TRUE_WD; if (res.equalsIgnoreCase(VAL_FALSE_WD)) return VAL_FALSE_WD; return res; } else return s; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Input stream I/O methods /*************************************************************************** * Read the next input token from the source script. * This also updates {@link #m_lineNo} to reflect the source line number of * the returned token. * *

* Tokens are composed of simple keywords (e.g., get), or quoted * literals (e.g., "*.txt", 'get.*'). Tokens may contain * embedded variable sequences (e.g., "${name}"). * *

* Blank lines are ignored. Comments start with # and end at the * end of the line (newline), and are ignored. * *

* Tokens cannot be longer than 2,000 characters. * * @return * The next token text, or {@link #TOK__NL} if an end-of-line (newline) was * read, or null if the end of the source stream was reached. Note that the * stream is not closed after the end is reached. * * @since 1.1, 2007-03-11 */ String readToken() throws IOException { String tok; // Check the input stream if (m_lexer == null) return null; // Read the next token from the source stream tok = m_lexer.readToken(); m_lineNo = m_lexer.m_lineNo; if (tok != null && tok != TOK__NL) { m_lastLine = m_lineNo; m_lastTok = tok; } return tok; } /*************************************************************************** * Push back the last token read from the input source. * * @see #readToken readToken() * * @since 1.2, 2007-03-13 */ void unReadToken(String tok) { if (tok != null) m_lexer.unReadToken(tok); } } // End CommandParser.java