All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.qedeq.kernel.bo.parser.MathParser Maven / Gradle / Ivy

/* This file is part of the project "Hilbert II" - http://www.qedeq.org
 *
 * Copyright 2000-2013,  Michael Meyling .
 *
 * "Hilbert II" is free software; you can redistribute
 * it and/or modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 */

package org.qedeq.kernel.bo.parser;

import java.util.ArrayList;
import java.util.List;

import org.qedeq.base.io.TextInput;
import org.qedeq.base.trace.Trace;

/**
 * Parse term or formula data into {@link org.qedeq.kernel.bo.parser.Term}s.
 *
 * @author  Michael Meyling
 */
public abstract class MathParser {

    /** This class. */
    private static final Class CLASS = MathParser.class;

    /** Input source to parse. */
    private MementoTextInput input;

    /** List of operators. */
    private List operators;

    /**
     * Constructor.
     */
    public MathParser() {
        // nothing to do
    }

    /**
     * Set input source and defined parameters.
     *
     * @param   input       Input source to parse.
     * @param   operators   Operator list.
     */
    public void setParameters(final MementoTextInput input, final List operators) {
        this.input = input;
        this.operators = operators;
    }

    /**
     * Set input source and defined parameters.
     *
     * @param   input       Input source to parse.
     * @param   operators   Operator list.
     */
    public void setParameters(final TextInput input, final List operators) {
        setParameters(new MementoTextInput(input), operators);
    }

    /**
     * Set input source and defined parameters.
     *
     * @param   input       Input source to parse.
     * @param   operators   Operator list.
     */
    public void setParameters(final StringBuffer input, final List operators) {
        setParameters(new TextInput(input), operators);
    }

    /**
     * Set input source and defined parameters.
     *
     * @param   input       Input source to parse.
     * @param   operators   Operator list.
     */
    public void setParameters(final String input, final List operators) {
        setParameters(new TextInput(input), operators);
    }

    protected final List getOperators() {
        return operators;
    }

    /**
     * Reads (maximal possible) Term from input.
     *
     * @return  Read term.
     * @throws  ParserException Parsing failed.
     */
    public final Term readTerm() throws ParserException {
        final String method = "Term readTerm()";
        Trace.begin(CLASS, this, method);
        try {
            final Term term = readMaximalTerm(0);
            if (eot(getToken())) {
                readToken();
            }
            return term;
        } finally {
            Trace.end(CLASS, this, method);
        }
    }

    /**
     * Reads next "maximal" term from input. The following input doesn't make the the term more
     * complete. Respects the priority of coming operators by comparing it
     * to the given value.
     *
     * @param priority  next formula will be the last, if
     *                  connecting operator has higher priority
     * @return  Read term.
     * @throws  ParserException Parsing failed.
     */
    private final Term readMaximalTerm(final int priority) throws ParserException {
        final String method = "readMaximalTerm(int)";
        Trace.begin(CLASS, this, method);
        Term term = null;
        try {
            if (eot(getToken())) {
                Trace.param(CLASS, this, method, "return term", "null");
                return null;
            }
            term = readPrefixTerm();
            term = addNextInfixTerms(priority, term);
            Trace.param(CLASS, this, method,
                "return term", (term != null ? term.getQedeq() : "null"));
            return term;
        } finally {
            Trace.end(CLASS, this, method);
        }
    }

    private Term addNextInfixTerms(final int priority, final Term initialTerm)
            throws ParserException {
        final String method = "Term addNextInfixTerms(int, Term)";
        Trace.begin(CLASS, this, method);
        Term term = initialTerm;
        try {
            Operator newOperator = null;
            Operator oldOperator = null;

            do {
                markPosition();
                newOperator = readOperator();  // we expect an unique infix operator
                Trace.param(CLASS, this, method,
                    "newOperator", (newOperator != null ? newOperator.getQedeq() : "null"));
                if (newOperator == null || newOperator.getPriority() <= priority) {
                    Trace.trace(CLASS, this, method, "newOperator is null or of less priority");
                    rewindPosition();
                    Trace.param(CLASS, this, method,
                        "read term", (term != null ? term.getQedeq() : "null"));
                    return term;
                }
                if (newOperator.isPrefix()) {
                    Trace.trace(CLASS, this, method, "newOperator is prefix");
                    // TODO mime 20060313: try to read further arguments
                    rewindPosition();
                    Trace.param(CLASS, this, method,
                        "read term", (term != null ? term.getQedeq() : "null"));
                    return term;
                }
                if (newOperator.isPostfix()) {
                    rewindPosition();
                    throw new IllegalArgumentException("Postfix Operators not yet supported");
                }
                clearMark();
                if (oldOperator == null || oldOperator.getPriority() >= newOperator.getPriority()) {
                    Trace.trace(CLASS, this, method,
                        "oldOperator is null or has more priority than new");
                    Term term2 = readMaximalTerm(newOperator.getPriority());
                    if (term2 == null) {
    //                      TODO mime 20060313: 2 could be false if newOperator == oldOperator
                        throw new TooFewArgumentsException(getPosition(), 2);
                    }
                    if (oldOperator == newOperator) {
                        if (oldOperator.getMax() != -1 && term.size() + 1 >= oldOperator.getMax()) {
                            throw new TooMuchArgumentsException(getPosition(), oldOperator,
                                oldOperator.getMax());
                        }
                        Trace.trace(CLASS, this, method, "new term is added to old term");
                        term.addArgument(term2);
                    } else {
                        // old term is first argument of new operator
                        Trace.trace(CLASS, this, method,
                            "old term is first argument of new operator");
                        term = new Term(newOperator, term);
                        term.addArgument(term2);
                    }
                } else {
                    Trace.trace(CLASS, this,
                        method, "oldOperator is not null or has less priority than new");
                    Term term2 = readMaximalTerm(newOperator.getPriority());
                    if (term2 == null) {
    //                      TODO mime 20060313: 2 could be false if newOperator == oldOperator
                        throw new TooFewArgumentsException(getPosition(), 2);
                    }
                    term = new Term(newOperator, term);
                    term.addArgument(term2);
                }
                oldOperator = newOperator;
            } while (true);
        } finally {
            Trace.end(CLASS, this, method);
        }
    }

    /**
     * Read next following term. This is a complete term but some infix operator
     * or some terms for an infix operator might follow.
     *
     * @return  Read term.
     * @throws  ParserException Parsing failed.
     */
    private final Term readPrefixTerm() throws ParserException {
        final String method = "readPrefixTerm()";
        Trace.begin(CLASS, this, method);
        Term term = null;
        try {
            final List readOperators = readOperators();   // there might be several prefix operators
            if (readOperators != null && readOperators.size() > 0) {
                Trace.trace(CLASS, this, method, "operators found");
                term = readPrefixOperator(readOperators);
            } else { // no operator found
                Trace.trace(CLASS, this, method, "no operators found");
                final String token;
                token = getToken();
                if (token == null) {
                    Trace.param(CLASS, this, method, "read term", "null");
                    return null;
                }
                if ("(".equals(token)) {
                    readToken();
                    Trace.trace(CLASS, this, method, "start bracket found: " + token);
                    term = readMaximalTerm(0);
                    final String lastToken = readToken();
                    if (!")".equals(lastToken)) {
                        throw new ClosingBracketMissingException(getPosition(), "(", lastToken);
                    }

                } else if ("[".equals(token)) {
                    readToken();
                    Trace.trace(CLASS, this, method, "start bracket found: " + token);
                    term = readMaximalTerm(0);
                    final String lastToken = readToken();
                    if (!"]".equals(lastToken)) {
                        throw new ClosingBracketMissingException(getPosition(), "[", lastToken);
                    }
                } else {
                    readToken();
                    Trace.param(CLASS, this, method, "atom", token);
                    term = new Term(new TermAtom(token));
                }
            }
            Trace.param(CLASS, this, method,
                "read term", (term != null ? term.getQedeq() : "null"));
            return term;
        } finally {
            Trace.end(CLASS, this, method);
        }
    }

    /**
     * Try to parse an prefix operator and its operands from the input. Tries first operator,
     * second operator and so on. If the last one fails an appropriate exception is thrown.
     *
     * @param   operators   Prefix operator list.
     * @return  Resulting term.
     * @throws  ParserException Parsing failed.
     */
    private Term readPrefixOperator(final List operators) throws ParserException {
        Term term = null;
        markPosition();
        for (int number = 0; number < operators.size(); number++) {
            rewindPosition();
            markPosition();
            Operator operator = (Operator) operators.get(number);

            if (!operator.isPrefix()) {
                clearMark();
                throw new UnexpectedOperatorException(getPosition(), operator);
            }

            term = new Term(operator);

            if (operator.isFunction()) {
                // constants should have no argument list
                if (operator.getMax() == 0) {
                    break;
                }
                List list = readTupel();
                if (list == null) {     // here we don't distinguish between "a()" and "a"
                    list = new ArrayList();
                }
                // doesn't have enough arguments
                if (list.size() < operator.getMin()) {
                    if (number + 1 < operators.size()) {
                        continue;   // try again
                    }
                    clearMark();
                    throw new TooFewArgumentsException(getPosition(),
                        operator.getMin());
                }
                // has to much arguments
                if (operator.getMax() != -1 && list.size() > operator.getMax()) {
                    if (number + 1 < operators.size()) {
                        continue;   // try again
                    }
                    clearMark();
                    throw new TooMuchArgumentsException(getPosition(), operator,
                        operator.getMax());
                }
                for (int i = 0; i < list.size(); i++) {
                    term.addArgument((Term) list.get(i));
                }
                break;
            }

            int i = 0;
            while (i < operator.getMin()) {
                if (i > 0 && operator.getSeparatorSymbol() != null) {
                    final String separator = getToken();
                    if (!operator.getSeparatorSymbol().equals(separator)) {
                        if (number + 1 < operators.size()) {
                            continue;
                        }
                        clearMark();
                        throw new SeparatorNotFoundException(getPosition(),
                            operator.getSeparatorSymbol());
                    }
                    readToken();
                }
                final Term add = readMaximalTerm(operator.getPriority());
                if (add == null) {
                    if (number + 1 < operators.size()) {
                        continue;
                    }
                    clearMark();
                    throw new TooFewArgumentsException(getPosition(), operator.getMin());
                }
                term.addArgument(add);
                i++;
            }
            while (operator.getMax() == -1 || i < operator.getMax()) {
                if (i > 0 && operator.getSeparatorSymbol() != null) {
                    final String separator = getToken();
                    if (!operator.getSeparatorSymbol().equals(separator)) {
                        break;
                    }
                    readToken();
                }
                if (operator.getEndSymbol() != null) {
                    final String end = getToken();
                    if (operator.getEndSymbol().equals(end)) {
                        break;
                    }
                }
                Term add = null;
                markPosition();
                try {
                    add = readMaximalTerm(operator.getPriority());
                    clearMark();
                } catch (Exception e) {
                    rewindPosition();
                }
                if (add == null) {
                    break;
                }
                term.addArgument(add);
                i++;
            }
            if (operator.getEndSymbol() != null) {
                final String end = getToken();
                if (!operator.getEndSymbol().equals(end)) {
                    if (number + 1 < operators.size()) {
                        continue;
                    }
                    clearMark();
                    throw new EndSymbolNotFoundException(getPosition(),
                        operator.getEndSymbol());
                }
                readToken();
            }
            break;
        }
        clearMark();
        return term;
    }

    /**
     * Read n-tupel. This is a list of terms encapsulated by "(" and ")" and the terms are separated
     * by ",".
     *
     * @return  List of terms.  null if no bracket followed.
     * @throws  ParserException Parsing failed.
     */
    private final List readTupel() throws ParserException {
        final String method = "List readTupel()";
        Trace.begin(CLASS, this, method);
        try {
            final String firstToken;
            firstToken = getToken();
            if (!"(".equals(firstToken)) {
                Trace.trace(CLASS, this, method, "no start bracket found");
                return null;
            }
            readToken();    // read "("
            List list = new ArrayList();
            Term term = null;
            while (null != (term = readMaximalTerm(0))) {
                list.add(term);
                final String separatorToken = getToken();
                if (!",".equals(separatorToken)) {
                    break;
                }
                readToken();    // read ","
            }
            final String lastToken = readToken();
            if (!")".equals(lastToken)) {
                throw new ClosingBracketMissingException(getPosition(), ")", lastToken);
            }
            return list;
        } finally {
            Trace.end(CLASS, this, method);
        }
    }

    /**
     * Read next operator from input.
     *
     * @return  Found operator, maybe null.
     */
    private final Operator readOperator() {
        final String method = "Operator readOperator()";
        Trace.begin(CLASS, this, method);

        try {
            markPosition();
            final String token;
            token = readToken();
            if (token == null) {
                rewindPosition();
                Trace.trace(CLASS, this, method, "no operator found");
                return null;
            }
            final Operator operator = getOperator(token);
            if (operator == null) {
                rewindPosition();
                Trace.trace(CLASS, this, method, "no operator found");
                return null;
            }
            clearMark();
            Trace.param(CLASS, this, method, "operator", operator.getQedeq());
            return operator;
        } finally {
            Trace.end(CLASS, this, method);
        }
    }

    /**
     * Read next operators from input. Because the token that identifies an operator might be not
     * unique we could get multiple operators that start with that token. So this method reads
     * the operator token (if any) and returns a list of operators that start with that token.
     *
     * @return  Found operators, maybe null if no matching found..
     */
    private final List readOperators() {
        final String method = "List readOperators()";
        Trace.begin(CLASS, this, method);

        try {
            markPosition();
            final String token;
            token = readToken();
            if (token == null) {
                rewindPosition();
                Trace.trace(CLASS, this, method, "no operators found");
                return null;
            }
            final List ops = getOperators(token);
            if (ops == null || ops.size() == 0) {
                rewindPosition();
                Trace.trace(CLASS, this, method, "no operators found");
                return null;
            }
            clearMark();
            for (int i = 0; i < ops.size(); i++) {
                Trace.param(CLASS, this, method, "operator[" + i + "]", ops.get(i));
            }
            return ops;
        } finally {
            Trace.end(CLASS, this, method);
        }
    }

    /**
     * Get an operator for that token. If there are more than one possibilities the first matching
     * is returned.
     *
     * @param token  Get an operator for this token.
     * @return  Operator. Might be null.
     */
    protected abstract Operator getOperator(String token);

    /**
     * Get operators for that token. If there are more than one possibilities all matching are
     * returned.
     *
     * @param token  Get operators for this token.
     * @return  Operators. Might be null.
     */
    protected abstract List getOperators(String token);

    /**
     * Read next token from input and move reading position.
     * A token is a recognized character sequence. A token is no
     * elementary whitespace. Also a dosn't start or end with
     * elementary whitespace.
     *
     * @return  Token read, is null if end of data reached.
     */
    protected abstract String readToken();

    /**
     * Read next token from input but don't move reading position.
     * A token is a recognised character sequence. A token is no
     * elementary whitespace. Also a dosn't start or end with
     * elementary whitespace.
     *
     * @return  Token read, is null if end of data reached.
     */
    public final String getToken() {
        markPosition();
        final String result = readToken();
        rewindPosition();
        return result;
    }

    /**
     * Remember current position.
     */
    protected final void markPosition() {
        input.markPosition();
    }

    /**
     * Rewind to previous marked position. Also clears the mark.
     *
     * @return  Current position before pop.
     */
    protected final long rewindPosition() {
        return input.rewindPosition();
    }

    /**
     * Forget last remembered position.
     */
    protected final void clearMark() {
        input.clearMark();
    }

    /**
     * Get byte position.
     *
     * @return  Position.
     */
    private long getPosition() {
        return input.getPosition();
    }

    /**
     * Reads a single character and does not change the reading
     * position.
     *
     * @return  character read, if there are no more chars
     *          Character.MAX_VALUE is returned
     */
    protected final int getChar() {
        return input.getChar();
    }

    /**
     * Reads a single character and increments the reading position
     * by one.
     *
     * @return  character read, if there are no more chars
     *          Character.MAX_VALUE is returned
     */
    protected final int readChar() {
        return input.read();
    }

    /**
     * Is this an end of term token?
     *
     * @param   token   Check this token.
     * @return  Is this an end of term token.
     */
    protected abstract boolean eot(String token);

    /**
     * Are there still any characters to read?
     *
     * @return  Anything left for reading further?
     */
    public final boolean eof() {
        return input.isEmpty();
    }

    /**
     * Get rewind stack size.
     *
     * @return  Rewind stack size.
     */
    public final int getRewindStackSize() {
        return input.getRewindStackSize();
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy