Package org.jnode.shell.bjorne

Source Code of org.jnode.shell.bjorne.BjorneArithmeticEvaluator$Primary

/*
* $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 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 crawley@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;
        }

        @Override
        public String toString() {
            try {
                return Long.toString(getValue());
            } catch (ShellException ex) {
                return "OOPS";
            }
        }
    }
   
    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 mark = opStack.size();
        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));
            if (prefixOp != NONE) {
                pushOperator(prefixOp, mark);
            }
            skipWhiteSpace(ci);
            int op = parseExpressionOperator(ci);
            if (op == PLUSPLUS || op == MINUSMINUS) {
                pushOperator(op, mark);
                skipWhiteSpace(ci);
                op = parseExpressionOperator(ci);
            }
           
            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, mark);
        }
        if (valStack.size() == 0) {
            throw new ShellSyntaxException("No expression within \"$((...))\"");
        }
        while (opStack.size() > mark) {
            evalOperation();
        }
        return valStack.removeFirst();
    }
   
    private void pushOperator(int op, int mark) throws ShellException {
        while (opStack.size() > mark && precedence.get(opStack.getFirst()) <= precedence.get(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 {
            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 + PREFIX ? 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);
                context.setVariable(operand1.name, Long.toString(value + (op == PLUSPLUS ? 1 : -1)));
                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("Remainder 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);
            skipWhiteSpace(ci);
            if ((ch = 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();
                if (ci.peekCh() == '*') {
                    ci.nextCh();
                    return STARSTAR;
                } else {
                    return STAR;
                }
            case '%':
                ci.nextCh();
                return PERCENT;
            default:
                return NONE;
        }
    }

    private long parseNumber(CharIterator ci) {
        StringBuilder sb = new StringBuilder();
        int ch = ci.peekCh();
        while (ch != -1 && Character.isDigit((char) ch)) {
            ci.nextCh();
            sb.append((char) ch);
            ch = ci.peekCh();
        }
        return Long.parseLong(sb.toString());
    }
}
TOP

Related Classes of org.jnode.shell.bjorne.BjorneArithmeticEvaluator$Primary

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.