meyfa/structogram2byob

View on GitHub
src/main/java/structogram2byob/parser/expression/ExpressionParser.java

Summary

Maintainability
A
0 mins
Test Coverage
B
86%
package structogram2byob.parser.expression;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import nsdlib.elements.NSDElement;
import structogram2byob.blocks.BlockDescription;
import structogram2byob.lexer.Token;
import structogram2byob.lexer.TokenType;
import structogram2byob.parser.AstNode;
import structogram2byob.parser.AstParser;
import structogram2byob.parser.AstParserException;
import structogram2byob.program.expressions.BlockExpression;
import structogram2byob.program.expressions.Expression;
import structogram2byob.program.expressions.NumberExpression;
import structogram2byob.program.expressions.StringExpression;


/**
 * Parser for constructing an {@link Expression} from a given input string by
 * first building the AST ({@link AstParser}) and then traversing it.
 */
public class ExpressionParser
{
    private final NSDElement element;
    private final AstParser astParser;

    /**
     * Constructs an expression parser on the given input.
     *
     * @param element The element this expression stems from.
     * @param input The input string.
     */
    public ExpressionParser(NSDElement element, String input)
    {
        this.element = element;
        this.astParser = new AstParser(input);
    }

    /**
     * Parses the input as an {@link Expression}.
     *
     * @return The parsed expression.
     *
     * @throws ExpressionParserException If the input is malformed and cannot be parsed as an expression.
     */
    public Expression parse() throws ExpressionParserException
    {
        AstNode ast;
        try {
            ast = astParser.parse();
        } catch (AstParserException e) {
            throw new ExpressionParserException(e);
        }
        return parse(ast);
    }

    /**
     * Parses the given AST as an expression. Nested expressions are de-nested
     * before they are parsed.
     *
     * @param ast The AST node to parse.
     * @return The parsed expression.
     *
     * @throws ExpressionParserException If the node is empty, an unexpected token is encountered,
     *          or a token is malformed.
     */
    private Expression parse(AstNode ast) throws ExpressionParserException
    {
        if (ast.hasValue()) {
            return parseSingle(ast);
        }

        if (ast.countBranches() == 0) {
            throw new ExpressionParserException("expression is empty");
        } else if (ast.countBranches() == 1) {
            return parse(ast.getBranch(0));
        }

        return parseComplex(IntStream.range(0, ast.countBranches())
                .mapToObj(ast::getBranch).collect(Collectors.toList()));
    }

    /**
     * Parses the given AST as an expression by taking its direct value and
     * converting it to the appropriate type.
     *
     * @param ast The AST node to parse.
     * @return The parsed expression.
     *
     * @throws ExpressionParserException If the node contains something other than a label, number or string.
     */
    private Expression parseSingle(AstNode ast) throws ExpressionParserException
    {
        Token t = ast.getValue();

        switch (t.getType()) {
            case LABEL:
                return parseComplex(Collections.singletonList(ast));
            case NUMBER:
                return parseNumber(t.getValue());
            case STRING:
                return parseString(t.getValue());
            default:
                break;
        }

        throw new ExpressionParserException("unexpected token: " + t);
    }

    /**
     * Parses the given list of AST nodes as a complex (multi-part or block)
     * expression.
     *
     * @param nodes The AST nodes to parse.
     * @return The parsed expression.
     *
     * @throws ExpressionParserException If the node is empty, an unexpected token is encountered,
     *          or a token is malformed.
     */
    private Expression parseComplex(List<AstNode> nodes) throws ExpressionParserException
    {
        BlockDescription.Builder builder = new BlockDescription.Builder();
        List<Expression> params = new ArrayList<>();

        for (AstNode ast : nodes) {
            if (ast.hasValue() && ast.getValue().getType() == TokenType.LABEL) {
                builder.label(ast.getValue().getValue());
            } else {
                Expression subExp = parse(ast);
                builder.param(subExp.getType());
                params.add(subExp);
            }
        }

        return new BlockExpression(element, builder.build(), params);
    }

    /**
     * Converts the given string to a number expression.
     *
     * @param s The string to parse.
     * @return The parsed expression.
     *
     * @throws ExpressionParserException If the string is malformed.
     */
    private NumberExpression parseNumber(String s) throws ExpressionParserException
    {
        double value;
        try {
            value = Double.parseDouble(s);
        } catch (NumberFormatException e) {
            throw new ExpressionParserException(e);
        }
        return new NumberExpression(element, value);
    }

    /**
     * Converts the given string to a string expression by removing the outer
     * quotes.
     *
     * @param s The string to parse.
     * @return The parsed expression.
     *
     * @throws ExpressionParserException If the string is malformed.
     */
    private StringExpression parseString(String s) throws ExpressionParserException
    {
        String value;
        try {
            value = s.substring(1, s.length() - 1);
        } catch (IndexOutOfBoundsException e) {
            throw new ExpressionParserException(e);
        }
        return new StringExpression(element, value);
    }
}