/*
* $Id$
*
* Copyright (C) 2003-2014 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.BjorneToken.TOK_CLOBBER;
import static org.jnode.shell.bjorne.BjorneToken.TOK_DGREAT;
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_GREAT;
import static org.jnode.shell.bjorne.BjorneToken.TOK_GREATAND;
import static org.jnode.shell.bjorne.BjorneToken.TOK_LESS;
import static org.jnode.shell.bjorne.BjorneToken.TOK_LESSAND;
import static org.jnode.shell.bjorne.BjorneToken.TOK_LESSGREAT;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.jnode.shell.CommandInterpreter;
import org.jnode.shell.CommandLine;
import org.jnode.shell.CommandShell;
import org.jnode.shell.Completable;
import org.jnode.shell.ShellException;
import org.jnode.shell.ShellFailureException;
import org.jnode.shell.ShellSyntaxException;
import org.jnode.shell.io.CommandIO;
import org.jnode.shell.io.CommandOutput;
import org.jnode.vm.VmExit;
/**
* This is the JNode implementation of the Bourne Shell language. The long term
* goal is to faithfully implement the POSIX Shell specification.
*
* @author crawley@jnode.org
*/
public class BjorneInterpreter implements CommandInterpreter {
public static final int CMD_EMPTY = 0;
public static final int CMD_COMMAND = 1;
public static final int CMD_LIST = 2;
public static final int CMD_FOR = 3;
public static final int CMD_WHILE = 4;
public static final int CMD_UNTIL = 5;
public static final int CMD_IF = 6;
public static final int CMD_ELIF = 7;
public static final int CMD_ELSE = 8;
public static final int CMD_CASE = 9;
public static final int CMD_SUBSHELL = 10;
public static final int CMD_BRACE_GROUP = 11;
public static final int CMD_FUNCTION_DEF = 12;
public static final int BRANCH_BREAK = 1;
public static final int BRANCH_CONTINUE = 2;
public static final int BRANCH_EXIT = 3;
public static final int BRANCH_RETURN = 4;
public static final int REDIR_LESS = TOK_LESS;
public static final int REDIR_GREAT = TOK_GREAT;
public static final int REDIR_DLESS = TOK_DLESS;
public static final int REDIR_DLESSDASH = TOK_DLESSDASH;
public static final int REDIR_DGREAT = TOK_DGREAT;
public static final int REDIR_LESSAND = TOK_LESSAND;
public static final int REDIR_GREATAND = TOK_GREATAND;
public static final int REDIR_LESSGREAT = TOK_LESSGREAT;
public static final int REDIR_CLOBBER = TOK_CLOBBER;
public static final int FLAG_ASYNC = 0x0001;
public static final int FLAG_AND_IF = 0x0002;
public static final int FLAG_OR_IF = 0x0004;
public static final int FLAG_BANG = 0x0008;
public static final int FLAG_PIPE = 0x0010;
public static final CommandNode EMPTY =
new SimpleCommandNode(CMD_EMPTY, new BjorneToken[0]);
static HashMap<String, BjorneBuiltin.Factory> BUILTINS =
new HashMap<String, BjorneBuiltin.Factory>();
private static boolean DEBUG = false;
private static long subshellCount;
static {
BUILTINS.put("alias", AliasBuiltin.FACTORY);
BUILTINS.put("break", BreakBuiltin.FACTORY);
BUILTINS.put("continue", ContinueBuiltin.FACTORY);
BUILTINS.put("exit", ExitBuiltin.FACTORY);
BUILTINS.put("export", ExportBuiltin.FACTORY);
BUILTINS.put("read", ReadBuiltin.FACTORY);
BUILTINS.put("readonly", ReadonlyBuiltin.FACTORY);
BUILTINS.put("return", ReturnBuiltin.FACTORY);
BUILTINS.put("set", SetBuiltin.FACTORY);
BUILTINS.put("shift", ShiftBuiltin.FACTORY);
BUILTINS.put("source", SourceBuiltin.FACTORY);
BUILTINS.put("unalias", UnaliasBuiltin.FACTORY);
BUILTINS.put("unset", UnsetBuiltin.FACTORY);
BUILTINS.put(".", SourceBuiltin.FACTORY);
BUILTINS.put(":", ColonBuiltin.FACTORY);
}
private CommandShell shell;
private BjorneContext context;
private BjorneParser parser;
private Reader reader;
public BjorneInterpreter() {
this.context = new BjorneContext(this);
}
@Override
public String getName() {
return "bjorne";
}
@Override
public synchronized Completable parsePartial(CommandShell shell, String partial) throws ShellSyntaxException {
bindShell(shell);
BjorneTokenizer tokens = new BjorneTokenizer(partial);
BjorneCompleter completer = new BjorneCompleter(context);
try {
parser = new BjorneParser(tokens);
parser.parse(completer);
} catch (ShellSyntaxException ex) {
if (DEBUG) {
System.err.println("exception in parsePartial: " + ex);
ex.printStackTrace();
}
} finally {
parser = null;
}
return completer;
}
@Override
public synchronized boolean help(CommandShell shell, String partial, PrintWriter pw) throws ShellException {
// TODO Auto-generated method stub
return false;
}
@Override
public String escapeWord(String word) {
// FIXME ... do this properly
if (word.indexOf(' ') == -1 && word.indexOf('\t') == -1) {
return word;
} else {
return "'" + word + "'";
}
}
synchronized int interpret(CommandShell shell, Reader reader, boolean script,
StringWriter capture, boolean source)
throws ShellException {
BjorneContext myContext;
if (capture == null) {
bindShell(shell);
myContext = this.context;
} else {
myContext = new BjorneContext(this);
myContext.setIO(1, new CommandOutput(capture), true);
}
BjorneTokenizer tokens = new BjorneTokenizer(reader);
// (Save the current parser and reader objects in the case where we are called
// recursively ... to interpret a back-tick command.)
BjorneParser savedParser = this.parser;
Reader savedReader = this.reader;
this.reader = reader;
parser = new BjorneParser(tokens);
try {
do {
CommandNode tree = this.parser.parse();
if (tree == null) {
break;
}
if (DEBUG) {
System.err.println(tree);
}
tree.execute((BjorneContext) myContext);
} while (script);
return myContext.getLastReturnCode();
} finally {
this.parser = savedParser;
this.reader = savedReader;
}
}
@Override
public int interpret(CommandShell shell, Reader reader, boolean script, String alias, String[] args)
throws ShellException {
context.setCommand(alias == null ? "" : alias);
context.setArgs(args == null ? new String[0] : args);
try {
return interpret(shell, reader, script, null, false);
} catch (BjorneControlException ex) {
switch (ex.getControl()) {
case BjorneInterpreter.BRANCH_EXIT:
// The script will exit immediately
return ex.getCount();
case BjorneInterpreter.BRANCH_BREAK:
throw new ShellSyntaxException(
"'break' has been executed in an inappropriate context");
case BjorneInterpreter.BRANCH_CONTINUE:
throw new ShellSyntaxException(
"'continue' has been executed in an inappropriate context");
case BjorneInterpreter.BRANCH_RETURN:
throw new ShellSyntaxException(
"'return' has been executed in an inappropriate context");
default:
throw new ShellFailureException(
"unknown 'control' in BjorneControlException");
}
} catch (VmExit ex) {
return ex.getStatus();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ex) {
// ignore
}
}
}
}
@Override
public String getPrompt(CommandShell shell, boolean continuation) {
try {
String res = context.variable(continuation ? "PS2" : "PS1");
return (res == null) ? "$ " : expandPrompt(res);
} catch (ShellSyntaxException ex) {
return "$ ";
}
}
private String expandPrompt(String prompt) {
// FIXME implement
return prompt;
}
@Override
public boolean supportsMultiline() {
return true;
}
Reader getReader() {
return this.reader;
}
private void bindShell(CommandShell shell) {
if (this.shell != shell) {
if (this.shell != null) {
throw new ShellFailureException("my shell changed");
}
this.shell = shell;
}
}
static boolean isBuiltin(String commandWord) {
return BUILTINS.containsKey(commandWord);
}
int executeCommand(CommandLine cmdLine, BjorneContext context, CommandIO[] streams,
Properties sysProps, Map<String, String> env)
throws ShellException {
String commandName = cmdLine.getCommandName();
if (isBuiltin(commandName)) {
BjorneBuiltinCommandInfo builtin = BUILTINS.get(commandName).buildCommandInfo(context);
cmdLine.setCommandInfo(builtin);
} else {
CommandNode body = context.getFunction(commandName);
if (body != null) {
context.evaluateRedirectionsAndPushHolders(body.getRedirects(), streams);
String[] savedArgs = context.getArgs();
try {
context.setArgs(cmdLine.getArguments());
return body.execute(context);
} finally {
context.popHolders();
context.setArgs(savedArgs);
}
}
}
cmdLine.setStreams(streams);
return shell.invoke(cmdLine, sysProps, env);
}
BjorneContext createContext() throws ShellFailureException {
return new BjorneContext(this);
}
CommandShell getShell() {
return shell;
}
PrintStream resolvePrintStream(CommandIO commandIOIF) {
return shell.resolvePrintStream(commandIOIF);
}
InputStream resolveInputStream(CommandIO stream) {
return shell.resolveInputStream(stream);
}
private static synchronized long getSubshellNumber() {
return subshellCount++;
}
String getUniqueName() {
return getName() + "-" + getSubshellNumber();
}
}