private FormulaNode number() { Token token = lexer.next(); double value; try { value = Double.parseDouble(token.getString()); } catch (NumberFormatException e) { throw new FormulaSyntaxException(new SourceRange(token), "Invalid number '" + token.getString() + "': " + e.getMessage()); } return new ConstantNode(value, new SourceRange(token)); }
private FormulaNode unary() { // <unary> ::= + <unary> | - <unary> | <unary2> if(!lexer.hasNext()) { throw new FormulaSyntaxException("Unexpected end of formula"); } Token token = lexer.peek(); if(token.getType() == TokenType.OPERATOR) { if(token.getString().equals("-") || token.getString().equals("+")) { Token opToken = lexer.next(); FormulaFunction op = function(opToken); FormulaNode operand = unary(); SourceRange sourceRange = new SourceRange(opToken.getStart(), operand.getSourceRange().getEnd()); return new FunctionCallNode(op, singletonList(operand), sourceRange); } } return unary2(); }
private FormulaNode unary2() { if(!lexer.hasNext()) { throw new FormulaSyntaxException("Unexpected end of formula"); } Token token = lexer.peek(); if(token.getType() == TokenType.OPERATOR) { if(token.getString().equals("!")) { Token opToken = lexer.next(); FormulaFunction op = function(opToken); FormulaNode operand = primary(); SourceRange sourceRange = new SourceRange(opToken.getStart(), operand.getSourceRange().getEnd()); return new FunctionCallNode(op, singletonList(operand), sourceRange); } } return primary(); }
private FormulaNode enumLiteral() { Token token = lexer.next(); assert token.getType() == TokenType.SYMBOL; return new ConstantNode(token, new SourceRange(token)); }
private void applyMarks(ParsedFormula formula) { if(!formula.isValid()) { for (FormulaError error : formula.getErrors()) { if(error.hasSourceRange()) { SourceRange range = error.getSourceRange(); MarkOptions options = MarkOptions.create(); options.setClassName("CodeMirror-lint-mark-error"); options.setTitle(error.getMessage()); TextMarker marker = editor.getDoc().markText(pos(range.getStart()), pos(range.getEnd()), options); markers.add(marker); } } } else { for (FieldReference fieldReference : formula.getReferences()) { SourceRange range = fieldReference.getSourceRange(); MarkOptions options = MarkOptions.create(); options.setClassName(FormulaResources.INSTANCE.styles().fieldAnnotation()); options.setTitle(fieldReference.getDescription()); TextMarker marker = editor.getDoc().markText(pos(range.getStart()), pos(range.getEnd()), options); markers.add(marker); } } }
public SymbolNode(Token token) { assert token.getType() == TokenType.SYMBOL; this.name = token.getString(); this.sourceRange = new SourceRange(token); }
public FormulaNode parse() { FormulaNode expr = expression(); if(lexer.hasNext()) { Token extraToken = lexer.next(); throw new FormulaSyntaxException(new SourceRange(extraToken), "Missing an operator like + - / *, etc."); } return expr; }
private FormulaNode stringLiteral() { Token token = lexer.next(); assert token.getType() == TokenType.STRING_LITERAL; return new ConstantNode(token.getString(), new SourceRange(token)); }
private FormulaFunction function(Token token) { try { return FormulaFunctions.get(token.getString()); } catch (UnsupportedOperationException e) { throw new FormulaSyntaxException(new SourceRange(token), "'" + token.getString() + "' is not a function."); } }
private FunctionCallNode binaryInfixCall(FormulaFunction op, FormulaNode left, FormulaNode right) { SourceRange source = new SourceRange(left.getSourceRange(), right.getSourceRange()); return new FunctionCallNode(op, Arrays.asList(left, right), source); }
private FormulaNode booleanLiteral() { Token token = lexer.next(); assert token.getType() == TokenType.BOOLEAN_LITERAL; boolean value = token.getString().toLowerCase().equals("true"); SourceRange source = new SourceRange(token); return new ConstantNode(value, source); }
private FormulaNode compound(SymbolNode symbol) { FormulaNode result = symbol; while(lexer.hasNext() && lexer.peek().isDot()) { lexer.next(); SymbolNode field = symbol(); result = new CompoundExpr(result, field, new SourceRange(result.getSourceRange(), field.getSourceRange())); } return result; }
private FormulaNode primary() { switch (lexer.peek().getType()) { case SYMBOL: if(lexer.peek().getString().matches("t\\d{10}")) { return enumLiteral(); } else { return symbolOrCall(); } case NUMBER: return number(); case BOOLEAN_LITERAL: return booleanLiteral(); case STRING_LITERAL: return stringLiteral(); case PAREN_START: Token openToken = lexer.next(); FormulaNode e = expression(); Token closeToken = expect(TokenType.PAREN_END); return new GroupNode(e, new SourceRange(openToken, closeToken)); default: throw new FormulaSyntaxException(new SourceRange(lexer.peek()), "Expected a symbol, a number, a string, or '('"); } }
private FormulaNode call(Token functionToken) { FormulaFunction function = function(functionToken); expect(TokenType.PAREN_START); List<FormulaNode> arguments = new ArrayList<>(); while(true) { if (!lexer.hasNext()) { throw new FormulaSyntaxException("Unexpected end of formula"); } TokenType nextToken = lexer.peek().getType(); if (nextToken == TokenType.COMMA) { // Consume comma and parse next argument lexer.next(); continue; } if (nextToken == TokenType.PAREN_END) { // consume paren and complete argument list Token closingParen = lexer.next(); return new FunctionCallNode(function, arguments, new SourceRange(functionToken, closingParen)); } // Otherwise parse the next argument arguments.add(expression()); } }
@Test public void badNumberOfArguments() { FormulaValidator validator = validate("IF(1)"); assertThat(validator.getErrors(), hasSize(1)); assertThat(validator.getErrors().get(0).getSourceRange(), equalTo(new SourceRange(new SourcePos(0, 0), 5))); }
@Test public void symbolSourceRefs() { FunctionCallNode call = (FunctionCallNode) FormulaParser.parse("[A] + [B]"); assertThat(call.getSourceRange(), equalTo(new SourceRange(new SourcePos(0, 0), new SourcePos(0, 9)))); assertThat(call.getArgument(1).getSourceRange(), equalTo(new SourceRange(new SourcePos(0, 6), new SourcePos(0, 9)))); }
@Test public void symbolSourceConstants() { FunctionCallNode call = (FunctionCallNode) FormulaParser.parse("1+3.456"); assertThat(call.getSourceRange(), equalTo(new SourceRange(new SourcePos(0, 0), new SourcePos(0, 7)))); }
@Test public void invalidNumber() { FormulaException exception = null; try { FormulaParser.parse("A*B+\nC + 13.342342.2343"); } catch (FormulaException e) { exception = e; } assertNotNull(exception); assertThat(exception.getSourceRange(), equalTo(new SourceRange(new SourcePos(1, 4), new SourcePos(1, 18)))); }