//============================================================================== // 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")* * *
* 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),
* 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}),
* 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