/*
* $Id$
*
* Copyright (C) 2003-2015 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.shell.bjorne;
import static org.jnode.shell.bjorne.BjorneInterpreter.CMD_BRACE_GROUP;
import static org.jnode.shell.bjorne.BjorneInterpreter.CMD_COMMAND;
import static org.jnode.shell.bjorne.BjorneInterpreter.CMD_ELIF;
import static org.jnode.shell.bjorne.BjorneInterpreter.CMD_IF;
import static org.jnode.shell.bjorne.BjorneInterpreter.CMD_LIST;
import static org.jnode.shell.bjorne.BjorneInterpreter.CMD_SUBSHELL;
import static org.jnode.shell.bjorne.BjorneInterpreter.CMD_UNTIL;
import static org.jnode.shell.bjorne.BjorneInterpreter.CMD_WHILE;
import static org.jnode.shell.bjorne.BjorneInterpreter.FLAG_AND_IF;
import static org.jnode.shell.bjorne.BjorneInterpreter.FLAG_ASYNC;
import static org.jnode.shell.bjorne.BjorneInterpreter.FLAG_BANG;
import static org.jnode.shell.bjorne.BjorneInterpreter.FLAG_OR_IF;
import static org.jnode.shell.bjorne.BjorneInterpreter.FLAG_PIPE;
import static org.jnode.shell.bjorne.BjorneToken.RULE_1_CONTEXT;
import static org.jnode.shell.bjorne.BjorneToken.RULE_5_CONTEXT;
import static org.jnode.shell.bjorne.BjorneToken.RULE_6_CONTEXT;
import static org.jnode.shell.bjorne.BjorneToken.RULE_7a_CONTEXT;
import static org.jnode.shell.bjorne.BjorneToken.RULE_7b_CONTEXT;
import static org.jnode.shell.bjorne.BjorneToken.RULE_8_CONTEXT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_AMP;
import static org.jnode.shell.bjorne.BjorneToken.TOK_AMP_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_AND_IF;
import static org.jnode.shell.bjorne.BjorneToken.TOK_AND_IF_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_ASSIGNMENT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_ASSIGNMENT_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_BANG_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_BAR_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_CASE;
import static org.jnode.shell.bjorne.BjorneToken.TOK_CASE_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_CASE_WORD_BITS;
import static org.jnode.shell.bjorne.BjorneToken.TOK_CLOBBER_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_COMMAND_NAME_BITS;
import static org.jnode.shell.bjorne.BjorneToken.TOK_COMMAND_WORD_BITS;
import static org.jnode.shell.bjorne.BjorneToken.TOK_DGREAT_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_DLESS;
import static org.jnode.shell.bjorne.BjorneToken.TOK_DLESSDASH;
import static org.jnode.shell.bjorne.BjorneToken.TOK_DLESSDASH_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_DLESS_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_DONE_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_DO_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_DSEMI_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_ELIF;
import static org.jnode.shell.bjorne.BjorneToken.TOK_ELIF_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_ELSE_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_END_OF_LINE;
import static org.jnode.shell.bjorne.BjorneToken.TOK_END_OF_LINE_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_END_OF_STREAM;
import static org.jnode.shell.bjorne.BjorneToken.TOK_END_OF_STREAM_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_ESAC_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_FILE_NAME_BITS;
import static org.jnode.shell.bjorne.BjorneToken.TOK_FI_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_FOR;
import static org.jnode.shell.bjorne.BjorneToken.TOK_FOR_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_FOR_NAME_BITS;
import static org.jnode.shell.bjorne.BjorneToken.TOK_FOR_WORD_BITS;
import static org.jnode.shell.bjorne.BjorneToken.TOK_FUNCTION_NAME_BITS;
import static org.jnode.shell.bjorne.BjorneToken.TOK_GREATAND_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_GREAT_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_HERE_END_BITS;
import static org.jnode.shell.bjorne.BjorneToken.TOK_IF;
import static org.jnode.shell.bjorne.BjorneToken.TOK_IF_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_IN_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_IO_NUMBER_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_LBRACE;
import static org.jnode.shell.bjorne.BjorneToken.TOK_LBRACE_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_LESSAND_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_LESSGREAT_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_LESS_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_LPAREN;
import static org.jnode.shell.bjorne.BjorneToken.TOK_LPAREN_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_OR_IF_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_PATTERN_BITS;
import static org.jnode.shell.bjorne.BjorneToken.TOK_RBRACE_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_RPAREN_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_SEMI;
import static org.jnode.shell.bjorne.BjorneToken.TOK_SEMI_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_THEN_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_UNTIL;
import static org.jnode.shell.bjorne.BjorneToken.TOK_UNTIL_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_WHILE;
import static org.jnode.shell.bjorne.BjorneToken.TOK_WHILE_BIT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_WORD;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.jnode.shell.ShellSyntaxException;
/**
* The BjorneBarser is a simple recursive descent parser/tree builder for the "posix shell"
* language with the "twist" that it can capture completion information. This done by
* causing all token 'expectations' to be expressed as 'expectedSets', and capturing the
* expected sets that apply when the end-of-stream token and the one before it are parsed.
*
* @author crawley@jnode.org
*/
public class BjorneParser {
private final BjorneTokenizer tokens;
private BjorneCompleter completer;
private final List<RedirectionNode> hereRedirections = new ArrayList<RedirectionNode>();
private boolean allowLineBreaks;
public BjorneParser(BjorneTokenizer tokens) {
this.tokens = tokens;
}
/**
* Parse a 'complete_command'.
* <p>
* Parse 'complete_command ::= list separator | list'
*
* @return the CommandNode representing the complete command.
* @throws ShellSyntaxException
*/
public CommandNode parse() throws ShellSyntaxException {
hereRedirections.clear();
CommandNode command = parseOptList();
if (command != null) {
allowLineBreaks();
captureHereDocuments();
} else {
noLineBreaks();
if (optNext(TOK_END_OF_LINE_BIT) != null) {
command = new SimpleCommandNode(CMD_COMMAND, new BjorneToken[0]);
}
}
return command;
}
/**
* Parse a 'complete_command' capturing completions.
*
* @param completer holder object for capturing completion information.
* @return the CommandNode representing the complete command.
* @throws ShellSyntaxException
*/
public CommandNode parse(BjorneCompleter completer) throws ShellSyntaxException {
this.completer = completer;
try {
return parse();
} finally {
// Make sure this is nulled ...
this.completer = null;
}
}
/**
* Parse 'list ::= list separator_op and_or | and_or' or empty
*
* @return the CommandNode representing the list.
* @throws ShellSyntaxException
*/
private CommandNode parseOptList() throws ShellSyntaxException {
List<CommandNode> commands = new LinkedList<CommandNode>();
CommandNode command = parseOptAndOr();
while (command != null) {
commands.add(command);
BjorneToken token = expectPeek(
TOK_SEMI_BIT | TOK_AMP_BIT | TOK_END_OF_LINE_BIT | TOK_END_OF_STREAM_BIT);
switch (token.getTokenType()) {
case TOK_SEMI:
next();
command = parseOptAndOr();
break;
case TOK_AMP:
command.setFlag(FLAG_ASYNC);
next();
command = parseOptAndOr();
break;
case TOK_END_OF_LINE:
case TOK_END_OF_STREAM:
command = null;
break;
}
}
return listToNode(commands);
}
/**
* Parse 'and_or ::= pipeline | and_or AND_IF linebreak pipeline | and_or
* OR_IF linebreak pipeline'
*
* @return the CommandNode representing the and_or.
* @throws ShellSyntaxException
*/
private CommandNode parseAndOr() throws ShellSyntaxException {
List<CommandNode> commands = new LinkedList<CommandNode>();
int flag = 0;
CommandNode command = parsePipeline();
commands.add(command);
BjorneToken token;
while ((token = optNext(TOK_AND_IF_BIT | TOK_OR_IF_BIT)) != null) {
flag = (token.getTokenType() == TOK_AND_IF) ? FLAG_AND_IF : FLAG_OR_IF;
skipLineBreaks();
command = parsePipeline();
command.setFlag(flag);
commands.add(command);
}
return listToNode(commands);
}
private CommandNode parseOptAndOr() throws ShellSyntaxException {
// allowLineBreaks();
if (optPeek(TOK_LBRACE_BIT | TOK_LPAREN_BIT | TOK_COMMAND_NAME_BITS | TOK_FUNCTION_NAME_BITS |
TOK_IF_BIT | TOK_WHILE_BIT | TOK_UNTIL_BIT | TOK_CASE_BIT | TOK_FOR_BIT |
TOK_IO_NUMBER_BIT | TOK_LESS_BIT | TOK_GREAT_BIT | TOK_DLESS_BIT |
TOK_DGREAT_BIT | TOK_LESSAND_BIT | TOK_GREATAND_BIT | TOK_LESSGREAT_BIT |
TOK_CLOBBER_BIT, RULE_1_CONTEXT) != null) {
return parseAndOr();
} else {
return null;
}
}
/**
* Parse 'pipeline ::= '!' pipe_sequence | pipe_sequence'
*
* @return the CommandNode representing the pipeline.
*/
private CommandNode parsePipeline() throws ShellSyntaxException {
peekEager();
boolean bang = optNext(TOK_BANG_BIT) != null;
CommandNode pipeSeq = parsePipeSequence();
if (bang) {
pipeSeq.setFlag(FLAG_BANG);
}
return pipeSeq;
}
/**
* Parse 'pipe_sequence ::= command | pipe_sequence '|' linebreak command'
*
* @return the CommandNode representing the pipe_sequence.
*/
private CommandNode parsePipeSequence() throws ShellSyntaxException {
List<CommandNode> commands = new LinkedList<CommandNode>();
commands.add(parseCommand());
while (optNext(TOK_BAR_BIT) != null) {
skipLineBreaks();
commands.add(parseCommand());
}
boolean pipe = commands.size() > 1;
CommandNode res = listToNode(commands);
if (pipe) {
res.setFlag(FLAG_PIPE);
}
return res;
}
/**
* Parse 'command ::= simple_command_or_function_definition |
* compound_command_with_opt_redirects '
*
* @return the CommandNode representing the command.
* @throws ShellSyntaxException
*/
private CommandNode parseCommand() throws ShellSyntaxException {
if (optPeek(TOK_IF_BIT | TOK_WHILE_BIT | TOK_UNTIL_BIT | TOK_FOR_BIT |
TOK_CASE_BIT | TOK_LBRACE_BIT | TOK_LPAREN_BIT, RULE_7a_CONTEXT) != null) {
return parseCompoundCommand();
} else {
CommandNode tmp = parseFunctionDefinition();
return (tmp != null) ? tmp : parseSimpleCommand();
}
}
private CommandNode parseSimpleCommand() throws ShellSyntaxException {
List<BjorneToken> assignments = new LinkedList<BjorneToken>();
List<RedirectionNode> redirects = new LinkedList<RedirectionNode>();
List<BjorneToken> words = new LinkedList<BjorneToken>();
// Deal with cmd_prefix'es before the command name; i.e. assignments and
// redirections
BjorneToken token;
for (int i = 0; /**/; i++) {
token = optPeek(TOK_ASSIGNMENT_BIT | TOK_IO_NUMBER_BIT | TOK_LESS_BIT |
TOK_GREAT_BIT | TOK_DLESS_BIT | TOK_DGREAT_BIT | TOK_LESSAND_BIT |
TOK_GREATAND_BIT | TOK_LESSGREAT_BIT | TOK_CLOBBER_BIT,
i == 0 ? RULE_7a_CONTEXT : RULE_7b_CONTEXT);
if (token == null) {
break;
}
if (token.getTokenType() == TOK_ASSIGNMENT) {
assignments.add(token);
next();
} else {
redirects.add(parseRedirect());
}
}
if (assignments.isEmpty() && redirects.isEmpty()) {
// An empty command without assignments or redirections is illegal ...
// but the real reason to call expectNext is to ensure that we don't
// get stuck on a line-break.
token = expectNext(TOK_COMMAND_NAME_BITS);
} else {
// An empty command with assignments and/or redirections is legal
token = optNext(TOK_COMMAND_NAME_BITS);
}
try {
if (token != null) {
// This is the command name.
words.add(token);
// Deal with any command arguments and embedded / trailing
// redirections.
while ((token = optPeek(TOK_COMMAND_WORD_BITS | TOK_IO_NUMBER_BIT | TOK_LESS_BIT |
TOK_GREAT_BIT | TOK_DLESS_BIT | TOK_DGREAT_BIT | TOK_LESSAND_BIT |
TOK_GREATAND_BIT | TOK_LESSGREAT_BIT | TOK_CLOBBER_BIT)) != null) {
if (token.getTokenType() == TOK_WORD) {
words.add(token);
next();
} else {
redirects.add(parseRedirect());
}
}
}
} catch (ShellSyntaxException ex) {
if (completer != null) {
completer.setCommand(words.size() == 0 ? null :
new SimpleCommandNode(CMD_COMMAND,
words.toArray(new BjorneToken[words.size()])));
}
throw ex;
}
SimpleCommandNode res = new SimpleCommandNode(CMD_COMMAND,
words.toArray(new BjorneToken[words.size()]));
if (completer != null) {
completer.setCommand(words.size() == 0 ? null : res);
}
if (!redirects.isEmpty()) {
res.setRedirects(redirects.toArray(new RedirectionNode[redirects.size()]));
}
if (!assignments.isEmpty()) {
res.setAssignments(assignments.toArray(new BjorneToken[assignments.size()]));
}
return res;
}
private FunctionDefinitionNode parseFunctionDefinition() throws ShellSyntaxException {
BjorneToken fname = optNext(TOK_FUNCTION_NAME_BITS, RULE_8_CONTEXT);
if (fname == null) {
return null;
}
if (optPeek(TOK_LPAREN_BIT) == null) {
tokens.backup();
return null;
} else {
next();
}
expectNext(TOK_RPAREN_BIT);
skipLineBreaks();
return new FunctionDefinitionNode(fname, parseFunctionBody());
}
private CommandNode parseFunctionBody() throws ShellSyntaxException {
CommandNode body = parseCompoundCommand();
body.setRedirects(parseOptRedirects());
return body;
}
/**
* Parse 'compound_command_with_opt_redirects ::= ( if_command |
* while_command | until_command | for_command | case_command | brace_group |
* subshell ) [ redirects ]
*
* @return the CommandNode representing the compound command.
* @throws ShellSyntaxException
*/
private CommandNode parseCompoundCommand() throws ShellSyntaxException {
CommandNode command = null;
BjorneToken token = expectPeek(TOK_IF_BIT | TOK_WHILE_BIT | TOK_UNTIL_BIT | TOK_FOR_BIT |
TOK_CASE_BIT | TOK_LBRACE_BIT | TOK_LPAREN_BIT, RULE_1_CONTEXT);
switch (token.getTokenType()) {
case TOK_IF:
command = parseIfCommand();
break;
case TOK_WHILE:
command = parseWhileCommand();
break;
case TOK_UNTIL:
command = parseUntilCommand();
break;
case TOK_FOR:
command = parseForCommand();
break;
case TOK_CASE:
command = parseCaseCommand();
break;
case TOK_LBRACE:
command = parseBraceGroup();
break;
case TOK_LPAREN:
command = parseSubshell();
break;
}
command.setRedirects(parseOptRedirects());
return command;
}
private RedirectionNode parseRedirect() throws ShellSyntaxException {
BjorneToken io = optNext(TOK_IO_NUMBER_BIT);
BjorneToken arg = null;
BjorneToken token = expectNext(TOK_LESS_BIT | TOK_GREAT_BIT | TOK_DGREAT_BIT |
TOK_LESSAND_BIT | TOK_GREATAND_BIT | TOK_LESSGREAT_BIT | TOK_CLOBBER_BIT |
TOK_DLESS_BIT | TOK_DLESSDASH_BIT);
int tt = token.getTokenType();
if (tt == TOK_DLESS || tt == TOK_DLESSDASH) {
arg = expectNext(TOK_HERE_END_BITS);
RedirectionNode res = new RedirectionNode(tt, io, arg);
// (HERE document capture will start when we reach the next
// real (i.e. not '\' escaped) line break ... see processLineBreaks())
hereRedirections.add(res);
return res;
} else {
arg = expectNext(TOK_FILE_NAME_BITS);
// (Corresponding token type and redirection type values are the same)
return new RedirectionNode(tt, io, arg);
}
}
private RedirectionNode[] parseOptRedirects() throws ShellSyntaxException {
List<RedirectionNode> redirects = new LinkedList<RedirectionNode>();
while (optPeek(TOK_LESS_BIT | TOK_GREAT_BIT | TOK_DGREAT_BIT |
TOK_LESSAND_BIT | TOK_GREATAND_BIT | TOK_LESSGREAT_BIT | TOK_CLOBBER_BIT |
TOK_DLESS_BIT | TOK_DLESSDASH_BIT | TOK_IO_NUMBER_BIT) != null) {
redirects.add(parseRedirect());
}
if (redirects.isEmpty()) {
return null;
}
return redirects.toArray(new RedirectionNode[redirects.size()]);
}
private CommandNode parseSubshell() throws ShellSyntaxException {
next();
CommandNode compoundList = parseCompoundList(TOK_RPAREN_BIT);
expectNext(TOK_RPAREN_BIT);
compoundList.setNodeType(CMD_SUBSHELL);
return compoundList;
}
private CommandNode parseCompoundList(long terminatorSet) throws ShellSyntaxException {
List<CommandNode> commands = new LinkedList<CommandNode>();
skipLineBreaks();
CommandNode command = parseAndOr();
while (command != null) {
commands.add(command);
BjorneToken token = optNext(TOK_SEMI_BIT | TOK_AMP_BIT | TOK_END_OF_LINE_BIT);
if (token == null) {
break;
} else if (token.getTokenType() == TOK_AMP) {
command.setFlag(FLAG_ASYNC);
}
skipLineBreaks();
// (This helps the completer figure out alternative completions for a word
// in the command name position.)
token = optPeek(terminatorSet);
command = parseOptAndOr();
}
return listToNode(commands);
}
private CommandNode parseBraceGroup() throws ShellSyntaxException {
next();
CommandNode compoundList = parseCompoundList(TOK_RBRACE_BIT);
expectNext(TOK_RBRACE_BIT, RULE_1_CONTEXT);
compoundList.setNodeType(CMD_BRACE_GROUP);
return compoundList;
}
private CaseCommandNode parseCaseCommand() throws ShellSyntaxException {
next();
BjorneToken word = expectNext(TOK_CASE_WORD_BITS);
List<CaseItemNode> caseItems = new LinkedList<CaseItemNode>();
allowLineBreaks();
expectNext(TOK_IN_BIT, RULE_6_CONTEXT);
skipLineBreaks();
while (optNext(TOK_ESAC_BIT, RULE_1_CONTEXT) == null) {
caseItems.add(parseCaseItem());
skipLineBreaks();
if (optNext(TOK_DSEMI_BIT, RULE_1_CONTEXT) != null) {
skipLineBreaks();
}
}
return new CaseCommandNode(word, caseItems.toArray(new CaseItemNode[caseItems.size()]));
}
private CaseItemNode parseCaseItem() throws ShellSyntaxException {
// Note: we've already ascertained that the first token of the case_item
// will not be an 'esac', so there's no point applying rule 4
optNext(TOK_LPAREN_BIT);
BjorneToken[] pattern = parsePattern();
expectNext(TOK_RPAREN_BIT);
CommandNode body = null;
skipLineBreaks();
if (optPeek(TOK_DSEMI_BIT | TOK_ESAC_BIT, RULE_1_CONTEXT) == null) {
body = parseCompoundList(0L);
skipLineBreaks();
}
return new CaseItemNode(pattern, body);
}
private BjorneToken[] parsePattern() throws ShellSyntaxException {
List<BjorneToken> pattern = new LinkedList<BjorneToken>();
while (true) {
BjorneToken token = expectNext(TOK_PATTERN_BITS);
pattern.add(token);
if (optNext(TOK_BAR_BIT) == null) {
break;
}
}
return pattern.toArray(new BjorneToken[pattern.size()]);
}
private ForCommandNode parseForCommand() throws ShellSyntaxException {
next();
BjorneToken var = expectNext(TOK_FOR_NAME_BITS, RULE_5_CONTEXT);
skipLineBreaks();
List<BjorneToken> words = new LinkedList<BjorneToken>();
if (optNext(TOK_IN_BIT, RULE_6_CONTEXT) != null) {
BjorneToken word = expectNext(TOK_FOR_WORD_BITS);
do {
words.add(word);
word = optNext(TOK_FOR_WORD_BITS);
} while (word != null);
expectNext(TOK_SEMI_BIT | TOK_END_OF_LINE_BIT);
}
return new ForCommandNode(var,
words.toArray(new BjorneToken[words.size()]), parseDoGroup());
}
private CommandNode parseDoGroup() throws ShellSyntaxException {
allowLineBreaks();
expectNext(TOK_DO_BIT, RULE_1_CONTEXT);
CommandNode body = parseCompoundList(TOK_DONE_BIT);
allowLineBreaks();
expectNext(TOK_DONE_BIT, RULE_1_CONTEXT);
return body;
}
private LoopCommandNode parseUntilCommand() throws ShellSyntaxException {
next();
CommandNode cond = parseCompoundList(TOK_DO_BIT);
CommandNode body = parseDoGroup();
return new LoopCommandNode(CMD_UNTIL, cond, body);
}
private LoopCommandNode parseWhileCommand() throws ShellSyntaxException {
next();
CommandNode cond = parseCompoundList(TOK_DO_BIT);
CommandNode body = parseDoGroup();
return new LoopCommandNode(CMD_WHILE, cond, body);
}
private IfCommandNode parseIfCommand() throws ShellSyntaxException {
next();
CommandNode cond = parseCompoundList(TOK_THEN_BIT);
allowLineBreaks();
expectNext(TOK_THEN_BIT, RULE_1_CONTEXT);
CommandNode thenPart = parseCompoundList(TOK_ELIF_BIT | TOK_ELSE_BIT | TOK_FI_BIT);
CommandNode elsePart = parseOptElsePart();
allowLineBreaks();
expectNext(TOK_FI_BIT, RULE_1_CONTEXT);
return new IfCommandNode(CMD_IF, cond, thenPart, elsePart);
}
private CommandNode parseOptElsePart() throws ShellSyntaxException {
skipLineBreaks();
BjorneToken token = optNext(TOK_ELIF_BIT | TOK_ELSE_BIT, RULE_1_CONTEXT);
if (token == null) {
return null;
} else if (token.getTokenType() == TOK_ELIF) {
CommandNode cond = parseCompoundList(TOK_THEN_BIT);
allowLineBreaks();
expectNext(TOK_THEN_BIT, RULE_1_CONTEXT);
return new IfCommandNode(CMD_ELIF, cond,
parseCompoundList(TOK_ELIF_BIT | TOK_ELSE_BIT | TOK_FI_BIT), parseOptElsePart());
} else {
return parseCompoundList(TOK_FI_BIT);
}
}
private BjorneToken optNext(long expectedSet, int context) throws ShellSyntaxException {
if (allowLineBreaks) {
doLineBreaks(expectedSet, false);
}
BjorneToken token = tokens.next(context);
if (expect(token, expectedSet, false)) {
return token;
} else {
tokens.backup();
return null;
}
}
private BjorneToken optNext(long expectedSet) throws ShellSyntaxException {
if (allowLineBreaks) {
doLineBreaks(expectedSet, false);
}
BjorneToken token = next();
if (expect(token, expectedSet, false)) {
return token;
} else {
tokens.backup();
return null;
}
}
private BjorneToken optPeek(long expectedSet, int context) throws ShellSyntaxException {
if (allowLineBreaks) {
doLineBreaks(expectedSet, false);
}
BjorneToken token = tokens.peek(context);
return expect(token, expectedSet, false) ? token : null;
}
private BjorneToken optPeek(long expectedSet) throws ShellSyntaxException {
if (allowLineBreaks) {
doLineBreaks(expectedSet, false);
}
BjorneToken token = tokens.peek();
return expect(token, expectedSet, false) ? token : null;
}
private BjorneToken expectNext(long expectedSet, int context) throws ShellSyntaxException {
if (allowLineBreaks) {
doLineBreaks(expectedSet, true);
}
BjorneToken token = tokens.next(context);
expect(token, expectedSet, true);
return token;
}
private BjorneToken expectNext(long expectedSet) throws ShellSyntaxException {
if (allowLineBreaks) {
doLineBreaks(expectedSet, true);
}
BjorneToken token = tokens.next();
expect(token, expectedSet, true);
return token;
}
private BjorneToken expectPeek(long expectedSet, int context) throws ShellSyntaxException {
if (allowLineBreaks) {
doLineBreaks(expectedSet, true);
}
BjorneToken token = tokens.peek(context);
expect(token, expectedSet, true);
return token;
}
private BjorneToken expectPeek(long expectedSet) throws ShellSyntaxException {
BjorneToken token = peekEager();
expect(token, expectedSet, true);
return token;
}
private BjorneToken next() throws ShellSyntaxException {
if (allowLineBreaks) {
doLineBreaks(0L, false);
}
return tokens.next();
}
private BjorneToken peekEager() throws ShellSyntaxException {
if (allowLineBreaks) {
doLineBreaks(0L, true);
}
return tokens.peek();
}
private boolean expect(BjorneToken token, long expectedSet, boolean mandatory)
throws ShellSyntaxException {
captureCompletions(token, expectedSet);
int tt = token.getTokenType();
if (((1L << tt) & expectedSet) == 0L) {
if (mandatory) {
if (tt == TOK_END_OF_STREAM) {
throw new ShellSyntaxException(
"EOF reached while looking for " + BjorneToken.formatExpectedSet(expectedSet));
} else {
throw new ShellSyntaxException(
"expected " + BjorneToken.formatExpectedSet(expectedSet) + " but got " + token);
}
} else {
return false;
}
} else {
return true;
}
}
private void captureCompletions(BjorneToken token, long expectedSet) {
if (completer != null) {
// Capture tokens and expectedSets for later use in determining what
// completions to allow.
BjorneToken et = completer.getEndToken();
BjorneToken pt = completer.getPenultimateToken();
int tt = token.getTokenType();
if (tt == TOK_END_OF_STREAM) {
if (et == null) {
completer.setEndToken(token);
completer.setEndExpectedSet(expectedSet);
} else {
completer.addToEndExpectedSet(expectedSet);
}
} else {
if (pt == null || pt.start != token.start) {
completer.setPenultimateToken(token);
if (pt != null && pt.getTokenType() == TOK_END_OF_LINE && pt.start < token.start) {
completer.addToPenultimateExpectedSet(expectedSet);
} else {
completer.setPenultimateExpectedSet(expectedSet);
}
} else {
completer.addToPenultimateExpectedSet(expectedSet);
}
}
}
}
private void skipLineBreaks() throws ShellSyntaxException {
this.allowLineBreaks = true;
doLineBreaks(0L, false);
}
private void allowLineBreaks() throws ShellSyntaxException {
this.allowLineBreaks = true;
}
private void noLineBreaks() throws ShellSyntaxException {
this.allowLineBreaks = false;
}
/**
* Skip optional linebreaks.
* @param expectedSet the tokens expected after the line breaks
* @param needMore if {@code true} we need a token after the line breaks,
* otherwise, it is OK to have no more tokens.
*/
private void doLineBreaks(long expectedSet, boolean needMore) throws ShellSyntaxException {
// NB: use tokens.peek() / next() rather than the wrappers here!!
this.allowLineBreaks = false;
BjorneToken token = tokens.peek();
int tt = token.getTokenType();
if (tt == TOK_END_OF_STREAM) {
captureCompletions(token, expectedSet);
if (needMore) {
throw new ShellSyntaxException(
"EOF reached while looking for optional linebreak(s)");
}
} else if (tt == TOK_END_OF_LINE) {
tokens.next();
captureHereDocuments();
while (true) {
token = tokens.peek();
tt = token.getTokenType();
if (tt == TOK_END_OF_LINE) {
tokens.next();
} else if (tt == TOK_END_OF_STREAM) {
captureCompletions(token, expectedSet);
if (needMore) {
throw new ShellSyntaxException(
"EOF reached while dealing with optional linebreak(s)");
} else {
break;
}
} else {
break;
}
}
}
}
/**
* Capture all current HERE documents, dealing with TAB stripping (if required)
* and recording on the relevant redirection object. We don't do expansions at
* this point, but we set the flag to say if expansion is required.
*
* @throws ShellSyntaxException
*/
private void captureHereDocuments() throws ShellSyntaxException {
for (RedirectionNode redirection : hereRedirections) {
StringBuilder sb = new StringBuilder();
String rawMarker = redirection.getArg().getText();
String marker = BjorneContext.dequote(rawMarker).toString();
boolean trimTabs = redirection.getRedirectionType() == TOK_DLESSDASH;
while (true) {
String line = tokens.readRawLine();
if (line == null) {
throw new ShellSyntaxException("EOF reached while looking for '" +
marker + "' to end a HERE document");
}
if (trimTabs) {
int len = line.length();
int i;
for (i = 0; i < len && line.charAt(i) == '\t'; i++) { /**/ }
if (i > 0) {
line = line.substring(i);
}
}
if (line.equals(marker)) {
break;
}
sb.append(line).append('\n');
}
redirection.setHereDocument(sb.toString());
redirection.setHereDocumentExpandable(marker.equals(rawMarker));
}
hereRedirections.clear();
}
private CommandNode listToNode(List<? extends CommandNode> commands) {
int len = commands.size();
if (len == 0) {
return null;
} else if (len == 1) {
return commands.get(0);
} else {
return new ListCommandNode(CMD_LIST, commands);
}
}
public String getContinuationPrompt() {
// FIXME ... use PS2, PS4 or whatever as appropriate.
return "> ";
}
}