//============================================================================== // 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