package uk.me.nxg.unity;

/**
 * A unit parser.
 */
abstract class Parser {

    // These instance variables are defined here, intended to be
    // visible in the classes which extend this class.  The Makefile
    // ensures that those generated classes don't redeclare these
    // variables, and thus hide them.
    Yylex lexer = null;
    UnitVal yylval;
    protected OneUnit.Maker unitMaker = null;
    protected FunctionDefinition.Maker functionMaker = null;

    /**
     * Set the source of the text to parse
     * @param r the input
     * @param syntax the syntax to use when parsing
     */
    void setReader(java.io.Reader r, Syntax syntax) {
        lexer = new Yylex(r, this);
        switch (syntax) {
          case VOUNITS:
            lexer.yybegin(Yylex.vouinitial);
            break;
          case CDS:
            lexer.yybegin(Yylex.cdsinitial);
            break;
          default:
            // redundant, but complete
            lexer.yybegin(Yylex.YYINITIAL);
            break;
        }
    }

    void setUnitMaker(OneUnit.Maker unitMaker) {
        this.unitMaker = unitMaker;
    }

    void setFunctionMaker(FunctionDefinition.Maker functionMaker) {
        this.functionMaker = functionMaker;
    }

    /**
     * Return the position within the parsed string
     * @return a zero-offset count of characters into the parser's input stream
     */
    int parsePosition() {
        return lexer.parsePosition();
    }

    /**
     * When parsing units, should we try to guess units?
     * By default, we don't guess.  But if we are asked to guess, then
     * unrecognised units will be re-examined with some unspecified
     * heuristics so that, for example, ‘degrees’ will be recognised
     * as the degree of angle.
     * @param guess_p true to turn on guessing
     */
    public void setGuessing(boolean guess_p) {
        unitMaker.setGuessing(guess_p);
    }

    /**
     * Parses the input.
     * The results of the parse can be obtained using {@link #getParseResult}.
     * @throws IllegalStateException if the parser hasn't been provided with a source
     * @throws UnitParserException if there was an unexpected
     * character in the input, or if the parse failed
     */
    void parse()
            throws UnitParserException {
        if (unitMaker == null) {
            throw new IllegalStateException("Parser: unitMaker not initialised (this shouldn't happen)");
        }
        if (lexer == null) {
            throw new IllegalStateException("Parser hasn't been given a source to parse");
        }
        int status = yyparse(); // throws UnitParserException on parse errors
        if (lexer.lex_unexpected_character() != '\0') {
            throw new UnitParserException("Unexpected character in input: " + lexer.lex_unexpected_character());
        }
        assert status == 0;
        return;
    }

    // The following methods are defined in the generated parsers
    abstract protected int yyparse()
            throws UnitParserException;
    /**
     * Retrieves the result of the parse
     * @return an expression
     */
    abstract UnitExpr getParseResult();
}
