|
From: <cr...@us...> - 2009-08-20 11:24:49
|
Revision: 5656
http://jnode.svn.sourceforge.net/jnode/?rev=5656&view=rev
Author: crawley
Date: 2009-08-20 11:24:38 +0000 (Thu, 20 Aug 2009)
Log Message:
-----------
Working on support for $((expression)) ...
Modified Paths:
--------------
trunk/shell/src/shell/org/jnode/shell/bjorne/BjorneBuiltin.java
trunk/shell/src/shell/org/jnode/shell/bjorne/BjorneContext.java
Added Paths:
-----------
trunk/shell/src/shell/org/jnode/shell/bjorne/BjorneArithmeticEvaluator.java
trunk/shell/src/test/org/jnode/test/shell/bjorne/BjorneArithmeticEvaluatorTest.java
Added: trunk/shell/src/shell/org/jnode/shell/bjorne/BjorneArithmeticEvaluator.java
===================================================================
--- trunk/shell/src/shell/org/jnode/shell/bjorne/BjorneArithmeticEvaluator.java (rev 0)
+++ trunk/shell/src/shell/org/jnode/shell/bjorne/BjorneArithmeticEvaluator.java 2009-08-20 11:24:38 UTC (rev 5656)
@@ -0,0 +1,324 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2003-2009 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 java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+
+import org.jnode.shell.ShellException;
+import org.jnode.shell.ShellFailureException;
+import org.jnode.shell.ShellSyntaxException;
+
+/**
+ * This class parses and evaluates the bjorne shell's arithmetic expression sublanguage.
+ *
+ * @author cr...@jn...
+ */
+public class BjorneArithmeticEvaluator {
+
+ private static final int NONE = 1;
+ private static final int PERCENT = 2;
+ private static final int MINUS = 3;
+ private static final int PLUS = 4;
+ private static final int STAR = 5;
+ private static final int SLASH = 6;
+ private static final int PLUSPLUS = 7;
+ private static final int MINUSMINUS = 8;
+ private static final int PLING = 9;
+ private static final int TWIDDLE = 10;
+ private static final int STARSTAR = 11;
+
+ private static final int PREFIX = 16;
+
+ private static final HashMap<Integer, Integer> precedence = new HashMap<Integer, Integer>();
+ private static final HashSet<Integer> unaryOps;
+ static {
+ precedence.put(PLUSPLUS, 1);
+ precedence.put(MINUSMINUS, 1);
+ precedence.put(PLUSPLUS + PREFIX, 2);
+ precedence.put(MINUSMINUS + PREFIX, 2);
+ precedence.put(PLUS + PREFIX, 3);
+ precedence.put(MINUS + PREFIX, 3);
+ precedence.put(PLING + PREFIX, 4);
+ precedence.put(TWIDDLE + PREFIX, 4);
+ precedence.put(STARSTAR, 5);
+ precedence.put(STAR, 6);
+ precedence.put(SLASH, 6);
+ precedence.put(PERCENT, 6);
+ precedence.put(PLUS, 7);
+ precedence.put(MINUS, 7);
+ unaryOps = new HashSet<Integer>(Arrays.asList(new Integer[]{
+ PLUS + PREFIX, PLUSPLUS, PLUSPLUS + PREFIX,
+ MINUS + PREFIX, MINUSMINUS, MINUSMINUS + PREFIX}));
+ };
+
+
+ private class Primary {
+ private final String name;
+ private final long value;
+
+ public Primary(String name, long value) {
+ super();
+ this.name = name;
+ this.value = value;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public long getValue() throws ShellSyntaxException {
+ return name != null ? evalName(name) : value;
+ }
+ }
+
+ private final BjorneContext context;
+ private final Deque<Integer> opStack = new ArrayDeque<Integer>();
+ private final Deque<Primary> valStack = new ArrayDeque<Primary>();
+
+
+ public BjorneArithmeticEvaluator(BjorneContext context) {
+ super();
+ this.context = context;
+ }
+
+ protected synchronized String evaluateExpression(CharSequence source)
+ throws ShellException {
+ opStack.clear();
+ valStack.clear();
+ CharIterator ci = new CharIterator(source);
+ Primary res = evalExpression(ci);
+ return Long.toString(res.getValue());
+ }
+
+ private Primary evalExpression(CharIterator ci) throws ShellException {
+ int ch = skipWhiteSpace(ci);
+ while ((ch = skipWhiteSpace(ci)) != -1 && ch != ')') {
+ int prefixOp = parseExpressionOperator(ci);
+ switch (prefixOp) {
+ case NONE:
+ break;
+ case PLUS:
+ case MINUS:
+ case PLUSPLUS:
+ case MINUSMINUS:
+ prefixOp += PREFIX;
+ break;
+ default:
+ throw new ShellSyntaxException("Unexpected infix operator");
+ }
+ skipWhiteSpace(ci);
+ pushOperand(evalPrimary(ci));
+ skipWhiteSpace(ci);
+ int op = parseExpressionOperator(ci);
+ if (prefixOp != NONE) {
+ if (op == PLUSPLUS || op == MINUSMINUS) {
+ pushOperator(op);
+ skipWhiteSpace(ci);
+ op = parseExpressionOperator(ci);
+ }
+ pushOperator(prefixOp);
+ }
+ ch = skipWhiteSpace(ci);
+ if (op == NONE) {
+ if (ch != -1 && ch != ')') {
+ throw new ShellSyntaxException("Expected an infix operator in expression");
+ }
+ break;
+ } else if (op == PLUSPLUS || op == MINUSMINUS) {
+ throw new ShellSyntaxException("Expected an infix operator in expression");
+ } else if (ch == ')') {
+ throw new ShellSyntaxException("Expected a number or variable name in expression");
+ }
+ pushOperator(op);
+ }
+ if (valStack.size() == 0) {
+ throw new ShellSyntaxException("No expression within \"$((...))\"");
+ }
+ while (!opStack.isEmpty()) {
+ evalOperation();
+ }
+ return valStack.getFirst();
+ }
+
+ private void pushOperator(int op) throws ShellException {
+ while (!opStack.isEmpty() && opStack.getFirst() <= op) {
+ evalOperation();
+ }
+ opStack.addFirst(op);
+ }
+
+ private void pushOperand(Primary operand) {
+ valStack.addFirst(operand);
+ }
+
+ private void evalOperation() throws ShellException {
+ Integer op = opStack.removeFirst();
+ Primary operand1;
+ Primary operand2;
+ if (unaryOps.contains(op)) {
+ operand1 = valStack.removeFirst();
+ operand2 = null;
+ } else {
+ System.err.println(op);
+ operand2 = valStack.removeFirst();
+ operand1 = valStack.removeFirst();
+ }
+ long value;
+ Primary res;
+ switch (op) {
+ case PLUS + PREFIX:
+ res = new Primary(null, operand1.getValue());
+ break;
+ case MINUS + PREFIX:
+ res = new Primary(null, -operand1.getValue());
+ break;
+ case PLUSPLUS + PREFIX:
+ case MINUSMINUS + PREFIX:
+ if (operand1.name == null) {
+ throw new ShellSyntaxException("Cannot apply ++ or -- to a number or a subexpression");
+ }
+ value = evalName(operand1.name) + (op == PLUSPLUS ? 1 : -1);
+ context.setVariable(operand1.name, Long.toString(value));
+ res = new Primary(null, value);
+ break;
+ case PLUSPLUS:
+ case MINUSMINUS:
+ if (operand1.name == null) {
+ throw new ShellSyntaxException("Cannot apply ++ or -- to a number or a subexpression");
+ }
+ value = evalName(operand1.name) + (op == PLUSPLUS ? 1 : -1);
+ context.setVariable(operand1.name, Long.toString(value));
+ res = new Primary(null, value);
+ break;
+ case PLUS:
+ res = new Primary(null, operand1.getValue() + operand2.getValue());
+ break;
+ case MINUS:
+ res = new Primary(null, operand1.getValue() - operand2.getValue());
+ break;
+ case STAR:
+ res = new Primary(null, operand1.getValue() * operand2.getValue());
+ break;
+ case STARSTAR:
+ res = new Primary(null, Math.round(Math.pow(operand1.getValue(), operand2.getValue())));
+ break;
+ case SLASH:
+ value = operand2.getValue();
+ if (value == 0) {
+ throw new ShellException("Divide by zero in expression");
+ }
+ res = new Primary(null, operand1.getValue() / value);
+ break;
+ case PERCENT:
+ value = operand2.getValue();
+ if (value == 0) {
+ throw new ShellException("Divide by zero in expression");
+ }
+ res = new Primary(null, operand1.getValue() % value);
+ break;
+ default:
+ throw new ShellFailureException("operator not supported");
+ }
+ valStack.addFirst(res);
+ }
+
+ private Primary evalPrimary(CharIterator ci) throws ShellException {
+ int ch = ci.peekCh();
+ if (Character.isLetter(ch) || ch == '_') {
+ return new Primary(context.parseParameter(ci), 0L);
+ } else if (Character.isDigit(ch)) {
+ return new Primary(null, parseNumber(ci));
+ } else if (ch == '(') {
+ ci.nextCh();
+ Primary res = evalExpression(ci);
+ if (ci.nextCh() != ')') {
+ throw new ShellSyntaxException("Unmatched \"(\" (left parenthesis) in arithmetic expression");
+ }
+ return res;
+ } else {
+ throw new ShellSyntaxException("Expected a number or variable name");
+ }
+ }
+
+ private long evalName(String name) throws ShellSyntaxException {
+ try {
+ String value = context.variable(name);
+ return value == null ? 0L : Long.parseLong(value);
+ } catch (NumberFormatException ex) {
+ throw new ShellSyntaxException(
+ "expression syntax error: '" + context.variable(name) + "' is not an integer");
+ }
+ }
+
+ private int skipWhiteSpace(CharIterator ci) {
+ int ch = ci.peekCh();
+ while (ch == ' ' || ch == '\t' || ch == '\n') {
+ ci.nextCh();
+ ch = ci.peekCh();
+ }
+ return ch;
+ }
+
+ private int parseExpressionOperator(CharIterator ci) throws ShellSyntaxException {
+ int ch = ci.peekCh();
+ switch (ch) {
+ case '+':
+ ci.nextCh();
+ if (ci.peekCh() == '+') {
+ ci.nextCh();
+ return PLUSPLUS;
+ } else {
+ return PLUS;
+ }
+ case '-':
+ ci.nextCh();
+ if (ci.peekCh() == '-') {
+ ci.nextCh();
+ return MINUSMINUS;
+ } else {
+ return MINUS;
+ }
+ case '/':
+ ci.nextCh();
+ return SLASH;
+ case '*':
+ ci.nextCh();
+ return STAR;
+ case '%':
+ ci.nextCh();
+ return PERCENT;
+ default:
+ return NONE;
+ }
+ }
+
+ private long parseNumber(CharIterator ci) {
+ StringBuilder sb = new StringBuilder();
+ int ch;
+ while (Character.isDigit((char) (ch = ci.nextCh()))) {
+ sb.append((char) ch);
+ }
+ return Long.parseLong(sb.toString());
+ }
+}
Modified: trunk/shell/src/shell/org/jnode/shell/bjorne/BjorneBuiltin.java
===================================================================
--- trunk/shell/src/shell/org/jnode/shell/bjorne/BjorneBuiltin.java 2009-08-16 15:07:51 UTC (rev 5655)
+++ trunk/shell/src/shell/org/jnode/shell/bjorne/BjorneBuiltin.java 2009-08-20 11:24:38 UTC (rev 5656)
@@ -17,7 +17,6 @@
* 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 org.jnode.shell.AbstractCommand;
Modified: trunk/shell/src/shell/org/jnode/shell/bjorne/BjorneContext.java
===================================================================
--- trunk/shell/src/shell/org/jnode/shell/bjorne/BjorneContext.java 2009-08-16 15:07:51 UTC (rev 5655)
+++ trunk/shell/src/shell/org/jnode/shell/bjorne/BjorneContext.java 2009-08-20 11:24:38 UTC (rev 5656)
@@ -74,29 +74,17 @@
public class BjorneContext {
private static final int NONE = 1;
-
private static final int HASH = 2;
-
private static final int DHASH = 3;
-
private static final int PERCENT = 4;
-
private static final int DPERCENT = 5;
-
- private static final int HYPHEN = 6;
-
- private static final int COLONHYPHEN = 7;
-
+ private static final int MINUS = 6;
+ private static final int COLONMINUS = 7;
private static final int EQUALS = 8;
-
private static final int COLONEQUALS = 9;
-
private static final int PLUS = 10;
-
private static final int COLONPLUS = 11;
-
private static final int QUERY = 12;
-
private static final int COLONQUERY = 13;
private final BjorneInterpreter interpreter;
@@ -835,7 +823,7 @@
operator = COLONQUERY;
break;
case '-':
- operator = COLONHYPHEN;
+ operator = COLONMINUS;
break;
default:
throw new ShellSyntaxException("bad substitution operator");
@@ -852,7 +840,7 @@
operator = PLUS;
break;
case '-':
- operator = HYPHEN;
+ operator = MINUS;
break;
default:
throw new ShellSyntaxException("unrecognized substitution operator (\"" + (char) ch + "\")");
@@ -866,9 +854,9 @@
throw new ShellSyntaxException("Unmatched \"{\"");
}
switch (operator) {
- case HYPHEN:
+ case MINUS:
return (value == null) ? word : value;
- case COLONHYPHEN:
+ case COLONMINUS:
return (value == null || value.length() == 0) ? word : value;
case PLUS:
return (value == null) ? "" : word;
@@ -917,7 +905,7 @@
}
}
- private String parseParameter(CharIterator ci) throws ShellSyntaxException {
+ String parseParameter(CharIterator ci) throws ShellSyntaxException {
StringBuilder sb = new StringBuilder();
int ch = ci.peekCh();
while (Character.isLetterOrDigit((char) ch) || ch == '_') {
@@ -1040,82 +1028,45 @@
return runBacktickCommand(commandLine);
}
}
-
- private CharSequence dollarParenParenExpand(CharIterator ci) {
- // TODO Auto-generated method stub
- return null;
+
+ private CharSequence dollarParenParenExpand(CharIterator ci) throws ShellException {
+ // Different shells handle $(( ... )) differently, but dash seems to do what
+ // the POSIX spec seems to say. In the first phase, we look for the matching '))'
+ // keeping track of nested parentheses and performing any $ expansions. Double
+ // quotes should be treated as literal.
+ StringBuilder sb = new StringBuilder();
+ int nesting = 0;
+ boolean done = false;
+ do {
+ int ch = ci.peekCh();
+ switch (ch) {
+ case '(':
+ nesting++;
+ sb.append('(');
+ break;
+ case ')':
+ if (nesting > 0) {
+ nesting--;
+ sb.append(')');
+ } else if (ci.peekCh() == ')') {
+ ci.nextCh();
+ done = true;
+ } else {
+ sb.append(')');
+ }
+ break;
+ case '$':
+ sb.append(dollarExpand(ci, '\000'));
+ break;
+ case -1:
+ throw new ShellSyntaxException("Unmatched \"((\" (double left parenthesis)");
+ default:
+ sb.append((char) ch);
+ }
+ } while (!done);
+ return new BjorneArithmeticEvaluator(this).evaluateExpression(sb);
}
-// private String dollarParenExpand(CharIterator ci) throws ShellException {
-// StringBuilder sb = extractToMatchingParen(ci);
-// if (sb.length() > 0 && sb.charAt(sb.length()) == ')') {
-// throw new ShellSyntaxException(
-// "There should be a space between the two ')'s in '$(...))'");
-// }
-// return runBacktickCommand(sb.toString()).toString();
-// }
-//
-// private StringBuilder extractToMatchingParen(CharIterator ci) throws ShellSyntaxException {
-// StringBuilder sb = new StringBuilder(40);
-// Deque<Character> stack = new ArrayDeque<Character>();
-// int ch;
-// boolean more = true;
-// do {
-// ch = ci.nextCh();
-// switch (ch) {
-// case -1:
-// if (!stack.isEmpty()) {
-// throw new ShellSyntaxException("unmatched '('");
-// }
-// more = false;
-// break;
-// case ')':
-// if (stack.isEmpty()) {
-// more = false;
-// } else {
-// sb.append(')');
-// if (stack.peekFirst() == '(') {
-// stack.removeFirst();
-// }
-// }
-// break;
-// case '(':
-// if (stack.isEmpty() || stack.peekFirst() == '(') {
-// stack.addFirst('(');
-// }
-// sb.append('(');
-// break;
-// case '"':
-// case '\'':
-// case '`':
-// sb.append((char) ch);
-// if (stack.isEmpty()) {
-// stack.addFirst((char) ch);
-// } else {
-// char top = stack.peekFirst();
-// if (top != '"' && top != '\'' && top != '`') {
-// stack.addFirst('"');
-// } else if (top == ch) {
-// stack.removeFirst();
-// }
-// }
-// break;
-// case '\\':
-// sb.append('\\');
-// ch = ci.nextCh();
-// if (ch == -1) {
-// more = false;
-// } else {
-// sb.append((char) ch);
-// }
-// break;
-// default:
-// sb.append((char) ch);
-// }
-// } while (more);
-// return sb;
-// }
-
int execute(CommandLine command, CommandIO[] streams, boolean isBuiltin) throws ShellException {
if (isEchoExpansions()) {
StringBuilder sb = new StringBuilder();
Added: trunk/shell/src/test/org/jnode/test/shell/bjorne/BjorneArithmeticEvaluatorTest.java
===================================================================
--- trunk/shell/src/test/org/jnode/test/shell/bjorne/BjorneArithmeticEvaluatorTest.java (rev 0)
+++ trunk/shell/src/test/org/jnode/test/shell/bjorne/BjorneArithmeticEvaluatorTest.java 2009-08-20 11:24:38 UTC (rev 5656)
@@ -0,0 +1,100 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2003-2009 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.test.shell.bjorne;
+
+import junit.framework.TestCase;
+
+import org.jnode.shell.ShellException;
+import org.jnode.shell.bjorne.BjorneArithmeticEvaluator;
+import org.jnode.shell.bjorne.BjorneContext;
+import org.jnode.shell.io.CommandIOHolder;
+
+/**
+ * Some unit tests for the BjorneArithmeticEvaluator class.
+ *
+ * @author cr...@jn...
+ */
+public class BjorneArithmeticEvaluatorTest extends TestCase {
+
+ // This class simply allows us to call the setVariable method directly
+ private static class TestBjorneContext extends BjorneContext {
+ TestBjorneContext(CommandIOHolder[] holders) {
+ super(null, holders);
+ }
+
+ TestBjorneContext() {
+ super(null, null);
+ }
+
+ /**
+ * Expose method for testing
+ */
+ @Override
+ protected void setVariable(String name, String value) {
+ super.setVariable(name, value);
+ }
+ }
+
+ private static class TestBjorneArithmeticEvaluator extends BjorneArithmeticEvaluator {
+ public TestBjorneArithmeticEvaluator(BjorneContext context) {
+ super(context);
+ }
+
+ @Override
+ public synchronized String evaluateExpression(CharSequence source) throws ShellException {
+ return super.evaluateExpression(source);
+ }
+ }
+
+
+ public void testConstructor() {
+ new BjorneArithmeticEvaluator(new TestBjorneContext());
+ }
+
+ public void testLiterals() throws ShellException {
+ TestBjorneArithmeticEvaluator ev = new TestBjorneArithmeticEvaluator(new TestBjorneContext());
+ assertEquals("1", ev.evaluateExpression("1"));
+ assertEquals("1", ev.evaluateExpression(" 1 "));
+ assertEquals("42", ev.evaluateExpression("42"));
+ }
+
+ public void testVariable() throws ShellException {
+ TestBjorneContext context = new TestBjorneContext();
+ context.setVariable("A", "1");
+ TestBjorneArithmeticEvaluator ev = new TestBjorneArithmeticEvaluator(context);
+ assertEquals("1", ev.evaluateExpression("A"));
+ assertEquals("1", ev.evaluateExpression(" A "));
+ assertEquals("0", ev.evaluateExpression(" B"));
+ }
+
+ public void testUnaryPlusMinus() throws ShellException {
+ TestBjorneContext context = new TestBjorneContext();
+ context.setVariable("A", "1");
+ TestBjorneArithmeticEvaluator ev = new TestBjorneArithmeticEvaluator(context);
+ assertEquals("1", ev.evaluateExpression("+A"));
+ assertEquals("1", ev.evaluateExpression(" + A "));
+ assertEquals("0", ev.evaluateExpression(" + B"));
+ assertEquals("-1", ev.evaluateExpression("-A"));
+ assertEquals("-1", ev.evaluateExpression(" - A "));
+ assertEquals("0", ev.evaluateExpression(" - B"));
+ }
+
+}
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|