Newer
Older
AlgebraicDataflowArchitectureModel / AlgebraicDataflowArchitectureModel / src / parser / Parser.java
package parser;

import models.algebra.*;
import models.dataConstraintModel.*;
import models.dataFlowModel.DataTransferChannel;
import models.dataFlowModel.DataTransferModel;
import parser.exceptions.*;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class Parser {
    protected TokenStream stream;

    public static final String CHANNEL = "channel";
    public static final String INIT = "init";
    public static final String IN = "in";
    public static final String OUT = "out";
    public static final String REF = "ref";
    public static final String SUB_CHANNEL = "sub";
    public static final String NATIVE = "native";
    public static final String LEFT_CURLY_BRACKET = "{";
    public static final String RIGHT_CURLY_BRACKET = "}";
    public static final String LEFT_CURLY_BRACKET_REGX = "\\{";
    public static final String RIGHT_CURLY_BRACKET_REGX = "\\}";
    public static final String LEFT_BRACKET = "(";
    public static final String RIGHT_BRACKET = ")";
    public static final String LEFT_BRACKET_REGX = "\\(";
    public static final String RIGHT_BRACKET_REGX = "\\)";
    public static final String LEFT_SQUARE_BRACKET = "[";
    public static final String RIGHT_SQUARE_BRACKET = "]";
    public static final String LEFT_SQUARE_BRACKET_REGX = "\\[";
    public static final String RIGHT_SQUARE_BRACKET_REGX = "\\]";
    public static final String ADD = "+";
    public static final String MUL = "*";
    public static final String SUB = "-";
    public static final String DIV = "/";
    public static final String MOD = "%";
    public static final String MINUS = "-";
    public static final String EQ = "==";
    public static final String NEQ = "!=";
    public static final String GT = ">";
    public static final String LT = "<";
    public static final String GE = ">=";
    public static final String LE = "<=";
    public static final String AND = "&&";
    public static final String OR = "||";
    public static final String NEG = "!";
    public static final String ADD_REGX = "\\+";
    public static final String MUL_REGX = "\\*";
    public static final String SUB_REGX = "\\-";
    public static final String DIV_REGX = "/";
    public static final String OR_REGX = "\\|\\|";
    public static final String EQUALS = "=";
    public static final String ASSIGNMENT = "=";
    public static final String COMMA = ",";
    public static final String COLON = ":";
    public static final String DOT = ".";
    public static final String DOT_REGX = "\\.";

    public Parser(final TokenStream stream) {
        this.stream = stream;
    }

    public Parser(final BufferedReader reader) {
        this.stream = new TokenStream();
        try {
            String line;
            while ((line = reader.readLine()) != null) {
                stream.addLine(line);
            }
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public DataTransferModel doParse() throws ExpectedRightBracket, ExpectedChannel, ExpectedChannelName, ExpectedLeftCurlyBracket, ExpectedRightCurlyBracket, ExpectedInOrOutOrRefOrSubKeyword, ExpectedStateTransition, ExpectedEquals, ExpectedRHSExpression, WrongLHSExpression, WrongRHSExpression, ExpectedAssignment, WrongPathExpression, WrongJsonExpression, ExpectedColon {
        return parseDataFlowModel();
    }

    public DataTransferModel parseDataFlowModel() throws ExpectedRightBracket, ExpectedChannel, ExpectedChannelName, ExpectedLeftCurlyBracket, ExpectedRightCurlyBracket, ExpectedInOrOutOrRefOrSubKeyword, ExpectedStateTransition, ExpectedEquals, ExpectedRHSExpression, WrongLHSExpression, WrongRHSExpression, ExpectedAssignment, WrongPathExpression, WrongJsonExpression, ExpectedColon {
        DataTransferModel model = new DataTransferModel();
        DataTransferChannel channel;
        while ((channel = parseChannel(model)) != null) {
            if (channel.getInputChannelMembers().isEmpty()) {
                model.addInputChannel(channel);
            } else {
                model.addChannel(channel);
            }
        }
        return model;
    }

    public DataTransferChannel parseChannel(DataTransferModel model) throws ExpectedLeftCurlyBracket, ExpectedRightBracket, ExpectedRightCurlyBracket, ExpectedAssignment, ExpectedRHSExpression, WrongLHSExpression, WrongRHSExpression, ExpectedChannel, ExpectedChannelName, ExpectedInOrOutOrRefOrSubKeyword, ExpectedStateTransition, ExpectedEquals, WrongPathExpression, WrongJsonExpression, ExpectedColon {
        if (!stream.hasNext()) return null;
        if (stream.checkNext().equals(RIGHT_CURLY_BRACKET)) return null;

        boolean isNative = false;
        String channelOrInitOrNativeKeyword = stream.next();
        if (channelOrInitOrNativeKeyword.equals(NATIVE)) {
            // A native channel
            isNative = true;
            channelOrInitOrNativeKeyword = stream.next();
        }
        if (!channelOrInitOrNativeKeyword.equals(CHANNEL) && !channelOrInitOrNativeKeyword.equals(SUB_CHANNEL)) {
            if (!channelOrInitOrNativeKeyword.equals(INIT)) throw new ExpectedChannel(stream.getLine());
            parseInit(model);
            channelOrInitOrNativeKeyword = stream.next();
            if (channelOrInitOrNativeKeyword.equals(NATIVE)) {
                // A native channel
                isNative = true;
                channelOrInitOrNativeKeyword = stream.next();
            }
        }
        if (!stream.hasNext()) throw new ExpectedChannelName(stream.getLine());

        String channelName = stream.next();
        if (channelName.equals(LEFT_CURLY_BRACKET)) throw new ExpectedChannelName(stream.getLine());

        int fromLine = stream.getLine();
        DataTransferChannel channel = new DataTransferChannel(channelName);
        if (isNative) {
            channel.setNative(true);
        }
        String leftBracket = stream.next();
        if (leftBracket.equals(LEFT_BRACKET)) {
            // has a selector
            String selector = stream.next();
            Variable var = parseVariable(stream, model, selector);
            channel.addSelector(var);
            String rightBracket = stream.next();
            if (!rightBracket.equals(RIGHT_BRACKET)) throw new ExpectedRightBracket(stream.getLine());
            leftBracket = stream.next();
        }
        if (!leftBracket.equals(LEFT_CURLY_BRACKET)) throw new ExpectedLeftCurlyBracket(stream.getLine());

        String inOrOutOrRefOrSub = null;
        while (stream.hasNext() && !(inOrOutOrRefOrSub = stream.checkNext()).equals(RIGHT_CURLY_BRACKET)) {
            ChannelMember channelMember = null;
            if (inOrOutOrRefOrSub.equals(IN)) {
                stream.next();
                channelMember = parseChannelMember(model, inOrOutOrRefOrSub);
                if (channelMember != null) {
                    channel.addChannelMemberAsInput(channelMember);
                }
            } else if (inOrOutOrRefOrSub.equals(OUT)) {
                stream.next();
                channelMember = parseChannelMember(model, inOrOutOrRefOrSub);
                if (channelMember != null) {
                    channel.addChannelMemberAsOutput(channelMember);
                }
            } else if (inOrOutOrRefOrSub.equals(REF)) {
                stream.next();
                channelMember = parseChannelMember(model, inOrOutOrRefOrSub);
                if (channelMember != null) {
                    channel.addChannelMemberAsReference(channelMember);
                }
            } else if (inOrOutOrRefOrSub.equals(SUB_CHANNEL)) {
                DataTransferChannel subChannel = parseChannel(model);
                if (subChannel != null) {
                    channel.addChild(subChannel);
                }
            } else {
                throw new ExpectedInOrOutOrRefOrSubKeyword(stream.getLine());
            }
        }
        if (stream.hasNext()) stream.next();
        int toLine = stream.getLine();
        channel.setSourceText(stream.getSourceText(fromLine, toLine));
        return channel;
    }

    public void parseInit(DataTransferModel model) throws ExpectedLeftCurlyBracket, ExpectedAssignment, ExpectedRHSExpression, WrongRHSExpression, ExpectedRightBracket, ExpectedRightCurlyBracket, WrongPathExpression, WrongJsonExpression, ExpectedColon {
        String leftBracket = stream.next();
        if (!leftBracket.equals(LEFT_CURLY_BRACKET)) throw new ExpectedLeftCurlyBracket(stream.getLine());
        while (stream.hasNext() && !stream.checkNext().equals(RIGHT_CURLY_BRACKET)) {
            int fromLine = stream.getLine();
            ResourceHierarchy resourceHierarchy = parseResourceHierarchy(stream, model);

            if (!stream.hasNext()) throw new ExpectedAssignment(stream.getLine());
            String colon = stream.next();
            if (!colon.equals(COLON)) throw new ExpectedAssignment(stream.getLine());
            if (!stream.hasNext()) throw new ExpectedAssignment(stream.getLine());
            String equals = stream.next();
            if (!equals.equals(ASSIGNMENT)) throw new ExpectedAssignment(stream.getLine());

            int toLine = stream.getLine();
            Expression rightTerm = null;
            if (!stream.hasNext()) throw new ExpectedRHSExpression(stream.getLine());
            rightTerm = parseTerm(stream, model);
            if (rightTerm == null) throw new WrongRHSExpression(stream.getLine());

            resourceHierarchy.setInitialValue(rightTerm);
            resourceHierarchy.setInitText(stream.getSourceText(fromLine, toLine));
        }
        if (stream.hasNext()) stream.next();
    }

    public ChannelMember parseChannelMember(DataTransferModel model, final String inOrOutOrRef) throws ExpectedRightBracket, ExpectedRightCurlyBracket, ExpectedStateTransition, ExpectedEquals, ExpectedRHSExpression, WrongLHSExpression, WrongRHSExpression, WrongPathExpression, WrongJsonExpression, ExpectedColon {
        if (!stream.hasNext()) throw new ExpectedStateTransition(stream.getLine());
        StateTransitionTerm leftTerm = parseStateTransitionTerm(stream, model);
        if (leftTerm == null || !(leftTerm instanceof Term)) throw new WrongLHSExpression(stream.getLine());
        Expression rightTerm = null;

        if (!inOrOutOrRef.equals(REF)) {
            if (!stream.hasNext()) throw new ExpectedEquals(stream.getLine());
            String equals = stream.next();
            if (!equals.equals(EQUALS)) throw new ExpectedEquals(stream.getLine());

            if (!stream.hasNext()) throw new ExpectedRHSExpression(stream.getLine());
            rightTerm = parseTerm(stream, model);
            if (rightTerm == null) throw new WrongRHSExpression(stream.getLine());
        }

        ResourcePath resourcePath = (ResourcePath) leftTerm.getSymbol();
        ChannelMember channelMember = new ChannelMember(resourcePath);
        StateTransition stateTransition = new StateTransition();
        stateTransition.setCurStateExpression(((Term) leftTerm).getChild(0));
        stateTransition.setMessageExpression(((Term) leftTerm).getChild(1));
        if (!inOrOutOrRef.equals(REF)) stateTransition.setNextStateExpression(rightTerm);
        channelMember.setStateTransition(stateTransition);
        // for type definition
        if (resourcePath.getResourceStateType() == null && ((Term) leftTerm).getChild(0) instanceof Variable) {
            Variable stateVar = (Variable) ((Term) leftTerm).getChild(0);
            if (stateVar.getType() != null) {
                resourcePath.setResourceStateType(stateVar.getType());
            }
        }
        if (((Term) leftTerm).getChild(1) instanceof Term) {
            Term messageTerm = (Term) ((Term) leftTerm).getChild(1);
            if (messageTerm.getSymbol().getSignature() == null && !messageTerm.getChildren().isEmpty()) {
                Type[] signature = new Type[messageTerm.getChildren().size() + 1];
                int i = 1;
                for (Expression e : messageTerm.getChildren()) {
                    if (e instanceof Variable && ((Variable) e).getType() != null) {
                        signature[i] = ((Variable) e).getType();
                    }
                    i++;
                }
                messageTerm.getSymbol().setSignature(signature);
            }
        }
        return channelMember;
    }

    public Expression parseTerm(TokenStream stream, DataTransferModel model) throws ExpectedRightBracket, WrongJsonExpression, ExpectedColon {
        ArrayList<Expression> expressions = new ArrayList<>();
        ArrayList<Symbol> operators = new ArrayList<>();
        String operator = null;
        for (; ; ) {
            String leftBracketOrMinusOrNeg = stream.next();
            if (leftBracketOrMinusOrNeg.equals(LEFT_BRACKET)) {
                Expression exp = parseTerm(stream, model);
                String rightBracket = stream.next();
                if (!rightBracket.equals(RIGHT_BRACKET)) throw new ExpectedRightBracket(stream.getLine());
                expressions.add(exp);
            } else if (leftBracketOrMinusOrNeg.equals(LEFT_CURLY_BRACKET)) {
                Expression exp = parseJsonTerm(stream, model);
                String rightBracket = stream.next();
                if (!rightBracket.equals(RIGHT_CURLY_BRACKET)) throw new ExpectedRightBracket(stream.getLine());
                expressions.add(exp);
            } else if (leftBracketOrMinusOrNeg.equals(LEFT_SQUARE_BRACKET)) {
                Expression exp = parseListTerm(stream, model);
                String rightBracket = stream.next();
                if (!rightBracket.equals(RIGHT_SQUARE_BRACKET)) throw new ExpectedRightBracket(stream.getLine());
                expressions.add(exp);
            } else {
                Symbol minusOrNeg = null;
                String symbolName = null;
                if (leftBracketOrMinusOrNeg.equals(MINUS)) {
                    minusOrNeg = DataTransferModel.minus;        // not sub
                    symbolName = stream.next();
                } else if (leftBracketOrMinusOrNeg.equals(NEG)) {
                    minusOrNeg = DataTransferModel.neg;
                    symbolName = stream.next();
                } else {
                    symbolName = leftBracketOrMinusOrNeg;
                }
                Expression exp = null;
                if (stream.checkNext() != null && stream.checkNext().equals(LEFT_BRACKET)) {
                    // a function symbol
                    Symbol symbol = model.getSymbol(symbolName);
                    if (symbol == null) {
                        symbol = new Symbol(symbolName);
                        model.addSymbol(symbol);
                    }
                    Term term = new Term(symbol);
                    int arity = 0;
                    do {
                        stream.next();        // LEFT_BRACKET or COMMA
                        if (stream.checkNext().equals(RIGHT_BRACKET)) break;
                        arity++;
                        Expression subTerm = parseTerm(stream, model);
                        term.addChild(subTerm, true);
                        if (!stream.hasNext()) throw new ExpectedRightBracket(stream.getLine());
                    } while (stream.checkNext().equals(COMMA));
                    String rightBracket = stream.next();
                    if (!rightBracket.equals(RIGHT_BRACKET)) throw new ExpectedRightBracket(stream.getLine());
                    symbol.setArity(arity);
                    exp = term;
                } else {
                    // constant or variable or json access
                    Symbol symbol = model.getSymbol(symbolName);
                    if (symbol != null && symbol.getArity() == 0) {
                        // a constant
                        exp = new Constant(symbol);
                    } else {
                        if (Character.isDigit(symbolName.charAt(0))) {
                            // maybe a numerical value
                            if (stream.checkNext() != null && stream.checkNext().equals(DOT)) {
                                // Because tokens are separated by a DOT.
                                stream.next();
                                symbolName += DOT + stream.next();    // decimal fraction
                            }
                            Double d = Double.parseDouble(symbolName);
                            // a numerical value
                            if (symbolName.contains(DOT)) {
                                exp = new Constant(symbolName, DataTransferModel.typeDouble);
                            } else {
                                exp = new Constant(symbolName, DataTransferModel.typeInt);
                            }
                        } else if (symbolName.startsWith("\"") && symbolName.endsWith("\"")) {
                            // a string value
                            exp = new Constant(symbolName, DataTransferModel.typeString);
                        } else {
                            // a variable
                            exp = parseVariable(stream, model, symbolName);
                        }
                    }
                }
                if (minusOrNeg != null) {
                    Term minusOrNegTerm = new Term(minusOrNeg);
                    minusOrNegTerm.addChild(exp);
                    expressions.add(minusOrNegTerm);
                } else {
                    expressions.add(exp);
                }
            }
            operator = stream.checkNext();
            if (operator == null) {
                break;
            } else if (operator.equals(ADD)) {
                operators.add(DataTransferModel.add);
                stream.next();
            } else if (operator.equals(MUL)) {
                operators.add(DataTransferModel.mul);
                stream.next();
            } else if (operator.equals(SUB)) {
                operators.add(DataTransferModel.sub);    // not minus
                stream.next();
            } else if (operator.equals(DIV)) {
                operators.add(DataTransferModel.div);
                stream.next();
            } else if (operator.equals(MOD)) {
                operators.add(DataTransferModel.mod);
                stream.next();
            } else if (operator.equals(EQ)) {
                operators.add(DataTransferModel.eq);
                stream.next();
            } else if (operator.equals(NEQ)) {
                operators.add(DataTransferModel.neq);
                stream.next();
            } else if (operator.equals(GT)) {
                operators.add(DataTransferModel.gt);
                stream.next();
            } else if (operator.equals(LT)) {
                operators.add(DataTransferModel.lt);
                stream.next();
            } else if (operator.equals(GE)) {
                operators.add(DataTransferModel.ge);
                stream.next();
            } else if (operator.equals(LE)) {
                operators.add(DataTransferModel.le);
                stream.next();
            } else if (operator.equals(AND)) {
                operators.add(DataTransferModel.and);
                stream.next();
            } else if (operator.equals(OR)) {
                operators.add(DataTransferModel.or);
                stream.next();
            } else if (operator.equals(DOT)) {
                // json accessor
                Expression exp = expressions.remove(0);
                stream.next();        // DOT
                if (stream.checkNext() == null) throw new WrongJsonExpression(stream.getLine());
                String literalOrLeftCurlyBracket = stream.next();
                Expression paramTerm = null;
                if (literalOrLeftCurlyBracket.equals(LEFT_CURLY_BRACKET)) {
                    // parameter
                    paramTerm = parseTerm(stream, model);
                    String rightCurlyBracket = stream.next();
                    if (rightCurlyBracket == null || !rightCurlyBracket.equals(RIGHT_CURLY_BRACKET))
                        throw new WrongJsonExpression(stream.getLine());
                } else {
                    // literal
                    paramTerm = new Constant(literalOrLeftCurlyBracket, DataTransferModel.typeString);
                }
                Type paramType = null;
                if (paramTerm instanceof Variable) {
                    paramType = ((Variable) paramTerm).getType();
                } else if (paramTerm instanceof Term) {
                    paramType = ((Term) paramTerm).getType();
                }
                Term term = null;
                if (paramType != null && DataConstraintModel.typeInt.isAncestorOf(paramType)) {
                    term = new JsonAccessor(DataConstraintModel.dotParam);
                } else {
                    term = new JsonAccessor(DataConstraintModel.dot);
                }
                term.addChild(exp);
                term.addChild(paramTerm);
                expressions.add(term);
                operator = stream.checkNext();
                if (operator == null || !operator.equals(DOT)) break;
            } else {
                break;
            }
        }
        if (expressions.size() == 1) {
            // no arithmetic operators
            return expressions.get(0);
        }
        ArrayList<Expression> monomials = new ArrayList<>();
        ArrayList<Symbol> addSubs = new ArrayList<>();
        Expression first = expressions.get(0);
        int i = 1;
        Term rootTerm = null;
        for (Symbol op : operators) {
            Expression second = expressions.get(i);
            if (op.getName().equals(MUL) || op.getName().equals(DIV) || op.getName().equals(MOD)) {
                // higher priority than add and sub
                Term term = new Term(op);
                term.addChild(first);
                term.addChild(second);
                first = term;
            } else if (op.getName().equals(EQ) || op.getName().equals(NEQ) || op.getName().equals(GT) || op.getName().equals(LT) || op.getName().equals(GE) || op.getName().equals(LE) || op.getName().equals(AND) || op.getName().equals(OR)) {
                // lower priority than add and sub
                if (first != null) monomials.add(first);
                Expression firstMonomial = monomials.get(0);
                int j = 1;
                for (Symbol op2 : addSubs) {
                    Expression secondMonomial = monomials.get(j);
                    Term term = new Term(op2);
                    term.addChild(firstMonomial);
                    term.addChild(secondMonomial);
                    firstMonomial = term;
                    j++;
                }
                if (rootTerm == null) {
                    rootTerm = new Term(op);
                    rootTerm.addChild(firstMonomial);
                } else {
                    rootTerm.addChild(firstMonomial);
                    firstMonomial = rootTerm;
                    rootTerm = new Term(op);
                    rootTerm.addChild(firstMonomial);
                }
                monomials.clear();
                addSubs.clear();
                first = second;
            } else {
                // add or sub ==> new monomial
                monomials.add(first);
                addSubs.add(op);
                first = second;
            }
            i++;
        }
        if (first != null) monomials.add(first);
        Expression firstMonomial = monomials.get(0);
        i = 1;
        for (Symbol op : addSubs) {
            Expression secondMonomial = monomials.get(i);
            Term term = new Term(op);
            term.addChild(firstMonomial);
            term.addChild(secondMonomial);
            firstMonomial = term;
            i++;
        }
        if (rootTerm == null) {
            return firstMonomial;
        } else {
            rootTerm.addChild(firstMonomial);
            return rootTerm;
        }
    }

//	private Expression parseJsonTerm(TokenStream stream, DataTransferModel model) throws ExpectedRightBracket, WrongJsonExpression, ExpectedColon {
//		Term jsonTerm = new Constant(DataConstraintModel.nil);
//		jsonTerm.setType(DataConstraintModel.typeJson);
//		while (stream.checkNext() != null && !stream.checkNext().equals(RIGHT_CURLY_BRACKET)) {
//			String key = stream.next();
//			Constant keyExp = new Constant(key);
//			keyExp.setType(DataConstraintModel.typeString);
//			if (stream.checkNext() == null || !stream.checkNext().equals(COLON)) throw new ExpectedColon(stream.getLine());
//			String colon = stream.next();
//			Expression value = parseTerm(stream, model);
//			Term nextTerm = new Term(DataConstraintModel.addMember);
//			nextTerm.addChild(jsonTerm);
//			nextTerm.addChild(keyExp);
//			nextTerm.addChild(value);
//			jsonTerm = nextTerm;
//			if (stream.checkNext() == null || !stream.checkNext().equals(COMMA)) break;
//			String comma = stream.next();
//		}
//		return jsonTerm;
//	}

    private Expression parseJsonTerm(TokenStream stream, DataTransferModel model) throws ExpectedRightBracket, WrongJsonExpression, ExpectedColon {
        JsonTerm jsonTerm = new JsonTerm();
        while (stream.checkNext() != null && !stream.checkNext().equals(RIGHT_CURLY_BRACKET)) {
            String key = stream.next();
            if (stream.checkNext() == null || !stream.checkNext().equals(COLON))
                throw new ExpectedColon(stream.getLine());
            String colon = stream.next();
            Expression value = parseTerm(stream, model);
            jsonTerm.addMember(key, value);
            if (stream.checkNext() == null || !stream.checkNext().equals(COMMA)) break;
            String comma = stream.next();
        }
        return jsonTerm;
    }

    private Expression parseListTerm(TokenStream stream2, DataTransferModel model) throws ExpectedRightBracket, WrongJsonExpression, ExpectedColon {
        Term listTerm = new Constant(DataConstraintModel.nil);
        listTerm.setType(DataConstraintModel.typeList);
        while (stream.checkNext() != null && !stream.checkNext().equals(RIGHT_SQUARE_BRACKET)) {
            Expression element = parseTerm(stream, model);
            Term nextTerm = new Term(DataConstraintModel.cons);
            nextTerm.addChild(element);
            nextTerm.addChild(listTerm);
            listTerm = nextTerm;
            if (stream.checkNext() == null || !stream.checkNext().equals(COMMA)) break;
            String comma = stream.next();
        }
        return listTerm;
    }

    private Variable parseVariable(TokenStream stream, DataTransferModel model, String symbolName) {
        Variable var;
        if (stream.checkNext() != null && stream.checkNext().equals(COLON)) {
            // when a type is specified.
            stream.next();
            String typeName = stream.next();
            Type type = model.getType(typeName);
            if (type == null) {
                type = new Type(typeName, typeName);
            }
            var = new Variable(symbolName, type);
        } else {
            var = new Variable(symbolName);
        }
        return var;
    }

    public StateTransitionTerm parseStateTransitionTerm(TokenStream stream, DataTransferModel model) throws ExpectedRightBracket, ExpectedRightCurlyBracket, WrongPathExpression, WrongJsonExpression, ExpectedColon {
        ResourcePath resourcePath = parseResourcePath(stream, model);
        StateTransitionTerm term = new StateTransitionTerm(resourcePath);
        int arity = 0;
        do {
            stream.next();        // LEFT_BRACKET or COMMA
            arity++;
            Expression subTerm = parseTerm(stream, model);
            term.addChild(subTerm, true);
            if (!stream.hasNext()) throw new ExpectedRightBracket(stream.getLine());
        } while (stream.checkNext().equals(COMMA));
        String rightBracket = stream.next();
        if (!rightBracket.equals(RIGHT_BRACKET)) throw new ExpectedRightBracket(stream.getLine());
        resourcePath.setArity(arity);
        return term;
    }

    public ResourceHierarchy parseResourceHierarchy(TokenStream stream, DataTransferModel model) throws ExpectedRightBracket, ExpectedRightCurlyBracket, WrongPathExpression {
        ResourceHierarchy hierarchy = null;
        do {
            String literalOrLeftCurlyBracket = stream.next();
            if (literalOrLeftCurlyBracket.equals(LEFT_CURLY_BRACKET)) {
                // No path parameter
                String rightCurlyBracket = stream.next();
                if (rightCurlyBracket == null || !rightCurlyBracket.equals(RIGHT_CURLY_BRACKET))
                    throw new ExpectedRightCurlyBracket(stream.getLine());
                hierarchy = new ResourceHierarchy(hierarchy, 1);
            } else {
                // Path literal
                hierarchy = new ResourceHierarchy(hierarchy, literalOrLeftCurlyBracket);
            }
            if (!stream.hasNext()) throw new WrongPathExpression(stream.getLine());
            if (stream.checkNext().equals(LEFT_BRACKET)) break;
            if (stream.checkNext().equals(COLON)) break;
        } while (stream.next().equals(DOT));
        hierarchy = model.getOrPutResourceHierarchy(hierarchy);
        return hierarchy;
    }

    public ResourcePath parseResourcePath(TokenStream stream, DataTransferModel model) throws ExpectedRightBracket, ExpectedRightCurlyBracket, WrongPathExpression, WrongJsonExpression, ExpectedColon {
        ResourcePath path = null;
        do {
            String literalOrLeftCurlyBracket = stream.next();
            if (literalOrLeftCurlyBracket.equals(LEFT_CURLY_BRACKET)) {
                // Path parameter
                Expression paramTerm = parseTerm(stream, model);
                String rightCurlyBracket = stream.next();
                if (rightCurlyBracket == null || !rightCurlyBracket.equals(RIGHT_CURLY_BRACKET))
                    throw new ExpectedRightCurlyBracket(stream.getLine());
                path = new ResourcePath(path, paramTerm);
            } else {
                // Path literal
                if (path == null) {
                    path = new ResourcePath(literalOrLeftCurlyBracket);
                } else {
                    path = new ResourcePath(path, literalOrLeftCurlyBracket);
                }
            }
            if (!stream.hasNext()) throw new WrongPathExpression(stream.getLine());
            if (stream.checkNext().equals(LEFT_BRACKET)) break;
        } while (stream.next().equals(DOT));
        model.addResourcePath(path);
        return path;
    }

    protected Boolean doesMatchToKeyword(final String token, final String specificTokenName) {
        if (token == null) return false;
        if (specificTokenName == null) return false;
        return token.equals(specificTokenName);
    }

    public static class TokenStream {
        private ArrayList<ArrayList<Token>> tokens = new ArrayList<>();
        private ArrayList<String> lines = new ArrayList<>();
        private int line = 0;
        private int n = 0;

        public TokenStream() {
            line = 0;
            n = 0;
        }

        public void addLine(String line) {
            lines.add(line);
            line = line.trim();
            tokens.add(splitBy(splitBy(splitBy(splitBy(splitBy(splitBy(splitBy(splitBy(splitBy(splitBy(splitBy(splitBy(splitBy(splitBy(splitBy(splitBy(splitBy(splitBy(splitBy(splitBy(splitBy(splitBy(splitBy(splitBy(line.split("[ \t]"), ADD, ADD_REGX), MUL, MUL_REGX), SUB, SUB_REGX), DIV, DIV_REGX), MOD, MOD), EQ, EQ), NEQ, NEQ), GE, GE), LE, LE), GT, GT), LT, LT), AND, AND), OR, OR_REGX), NEG, NEG), DOT, DOT_REGX), COMMA, COMMA), COLON, COLON), LEFT_BRACKET, LEFT_BRACKET_REGX), RIGHT_BRACKET, RIGHT_BRACKET_REGX), EQUALS, EQUALS), LEFT_CURLY_BRACKET, LEFT_CURLY_BRACKET_REGX), RIGHT_CURLY_BRACKET, RIGHT_CURLY_BRACKET_REGX), LEFT_SQUARE_BRACKET, LEFT_SQUARE_BRACKET_REGX), RIGHT_SQUARE_BRACKET, RIGHT_SQUARE_BRACKET_REGX));
        }

        private ArrayList<Token> splitBy(String[] tokens, final String delimiter, final String delimiterRegx) {
            ArrayList<Token> newTokens = new ArrayList<>();
            for (String token : tokens) {
                newTokens.add(new Token(token));
            }
            return splitBy(newTokens, delimiter, delimiterRegx);
        }

        private ArrayList<Token> splitBy(final List<Token> tokens, final String delimiter, final String delimiterRegx) {
            ArrayList<Token> newTokens = new ArrayList<>();
            for (Token token : tokens) {
                if (token.isAtomic()) {
                    newTokens.add(token);
                } else {
                    String[] splitTokens = token.split(delimiterRegx);
                    boolean fFirstToken = true;
                    for (String t : splitTokens) {
                        if (!fFirstToken) {
                            newTokens.add(new Token(delimiter, true));
                        }
                        if (!t.isEmpty()) {
                            newTokens.add(new Token(t));
                        }
                        fFirstToken = false;
                    }
                    while (token.endsWith(delimiter)) {
                        newTokens.add(new Token(delimiter, true));
                        token = token.substring(0, token.length() - 1);
                    }
                }
            }
            return newTokens;
        }

        public String next() {
            if (line >= tokens.size()) return null;
            while (n >= tokens.get(line).size()) {
                line++;
                n = 0;
                if (line >= tokens.size()) return null;
            }
            String token = tokens.get(line).get(n).getTokenStr();
            n++;
            return token;
        }

        public String checkNext() {
            if (line >= tokens.size()) return null;
            while (n >= tokens.get(line).size()) {
                line++;
                n = 0;
                if (line >= tokens.size()) return null;
            }
            return tokens.get(line).get(n).getTokenStr();
        }

        public boolean hasNext() {
            if (line >= tokens.size()) return false;
            while (n >= tokens.get(line).size()) {
                line++;
                n = 0;
                if (line >= tokens.size()) return false;
            }
            return true;
        }

        public int getLine() {
            return line;
        }

        public String getSourceText(int from, int to) {
            StringBuilder text = new StringBuilder();
            for (int l = from; l <= to; l++) {
                text.append(lines.get(l)).append("\n");
            }
            return text.toString();
        }
    }

    public static class Token {
        String token;
        boolean isAtomic = false;

        public Token(String token) {
            this.token = token;
        }

        public Token(String token, boolean isAtomic) {
            this.token = token;
            this.isAtomic = isAtomic;
        }

        public String getTokenStr() {
            return token;
        }

        public boolean isAtomic() {
            return isAtomic;
        }

        public String[] split(String delimiterRegx) {
            return token.split(delimiterRegx);
        }

        public boolean endsWith(String delimiter) {
            return token.endsWith(delimiter);
        }

        public int length() {
            return token.length();
        }

        public Token substring(int beginIdx, int endIdx) {
            return new Token(token.substring(beginIdx, endIdx));
        }
    }
}