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 craw...@jnode.org
+ */
+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 craw...@jnode.org
+ */
+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.

------------------------------------------------------------------------------
Let Crystal Reports handle the reporting - Free Crystal Reports 2008 30-Day 
trial. Simplify your report design, integration and deployment - and focus on 
what you do best, core application coding. Discover what's new with 
Crystal Reports now.  http://p.sf.net/sfu/bobj-july
_______________________________________________
Jnode-svn-commits mailing list
Jnode-svn-commits@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/jnode-svn-commits

Reply via email to