jmbr
jmbr

Reputation: 13

Precedence in Antlr using parentheses

We are developing a DSL, and we're facing some problems:

Problem 1:
In our DSL, it's allowed to do this:
A + B + C

but not this:
A + B - C

If the user needs to use two or more different operators, he'll need to insert parentheses:
A + (B - C)
or
(A + B) - C.

Problem 2: In our DSL, the most precedent operator must be surrounded by parentheses.

For example, instead of using this way: A + B * C

The user needs to use this: A + (B * C)

To solve the Problem 1 I've got a snippet of ANTLR that worked, but I'm not sure if it's the best way to solve it:

sumExpr
@init {boolean isSum=false;boolean isSub=false;}
    : {isSum(input.LT(2).getText()) && !isSub}? multExpr('+'^{isSum=true;} sumExpr)+
    | {isSub(input.LT(2).getText()) && !isSum}? multExpr('-'^{isSub=true;} sumExpr)+
    | multExpr;

To solve the Problem 2, I have no idea where to start.

I appreciate your help to find out a better solution to the first problem and a direction to solve the seconde one. (Sorry for my bad english)

Below is the grammar that we have developed:

grammar TclGrammar;

options {
    output=AST;
    ASTLabelType=CommonTree;
}

@members {
    public boolean isSum(String type) {
    System.out.println("Tipo: " + type);
    return "+".equals(type);
}

public boolean isSub(String type) {
    System.out.println("Tipo: " + type);
    return "-".equals(type);
}
}

prog    
: exprMain ';' {System.out.println( 
    $exprMain.tree == null ? "null" : $exprMain.tree.toStringTree());}
;

exprMain
:   exprQuando? (exprDeveSatis | exprDeveFalharCaso)
;

exprDeveSatis
: 'DEVE SATISFAZER' '{'! expr '}'!
;

exprDeveFalharCaso
: 'DEVE FALHAR CASO' '{'! expr '}'! 
;

exprQuando
: 'QUANDO' '{'! expr '}'!
;

expr    
: logicExpr
;

logicExpr
:   boolExpr (('E'|'OU')^ boolExpr)*
;

boolExpr
: comparatorExpr
| emExpr
| 'VERDADE'
| 'FALSO'
;

emExpr
: FIELD 'EM' '[' (variable_lista | field_lista) comCruzamentoExpr? ']'
-> ^('EM' FIELD (variable_lista+)? (field_lista+)? comCruzamentoExpr?)
;

comCruzamentoExpr
: 'COM CRUZAMENTO' '('  FIELD ';' FIELD (';' FIELD)* ')' -> ^('COM CRUZAMENTO' FIELD+)
;

comparatorExpr
: sumExpr (('<'^|'<='^|'>'^|'>='^|'='^|'<>'^) sumExpr)?
| naoPreenchidoExpr
| preenchidoExpr
;

naoPreenchidoExpr
: FIELD 'NAO PREENCHIDO' -> ^('NAO PREENCHIDO' FIELD)
;

preenchidoExpr
: FIELD 'PREENCHIDO' -> ^('PREENCHIDO' FIELD)
;

sumExpr
@init {boolean isSum=false;boolean isSub=false;}
: {isSum(input.LT(2).getText()) && !isSub}? multExpr('+'^{isSum=true;} sumExpr)+
| {isSub(input.LT(2).getText()) && !isSum}? multExpr('-'^{isSub=true;} sumExpr)+
| multExpr
;

multExpr
: funcExpr(('*'^|'/'^) funcExpr)?
;

funcExpr
: 'QUANTIDADE'^ '('! FIELD ')'!
| 'EXTRAI_TEXTO'^ '('! FIELD ';' INTEGER ';' INTEGER ')'! 
| cruzaExpr
| 'COMBINACAO_UNICA' '(' FIELD ';' FIELD (';' FIELD)* ')' -> ^('COMBINACAO_UNICA' FIELD+)
| 'EXISTE'^ '('! FIELD ')'!
| 'UNICO'^ '('! FIELD ')'!
| atom
;

cruzaExpr
:   operadorCruzaExpr ('CRUZA COM'^|'CRUZA AMBOS'^) operadorCruzaExpr ondeExpr?
;

operadorCruzaExpr
:   FIELD('('!field_lista')'!)?
;

ondeExpr
:   ('ONDE'^ '('!expr')'!)
;

atom
: FIELD 
| VARIABLE
| '('! expr ')'!
;

field_lista
: FIELD(';' field_lista)?
;

variable_lista
: VARIABLE(';' variable_lista)?
;

FIELD  
:   NONCONTROL_CHAR+
    ;

NUMBER
:   INTEGER | FLOAT
;

VARIABLE
: '\'' NONCONTROL_CHAR+ '\''
;

fragment SIGN: '+' | '-';   
fragment NONCONTROL_CHAR: LETTER | DIGIT | SYMBOL;
fragment LETTER: LOWER | UPPER;
fragment LOWER: 'a'..'z';
fragment UPPER: 'A'..'Z';
fragment DIGIT: '0'..'9';
fragment SYMBOL: '_' | '.' | ',';
fragment FLOAT: INTEGER '.' '0'..'9'*;
fragment INTEGER: '0' | SIGN? '1'..'9' '0'..'9'*;

WS  :   ( ' '
    | '\t'
    | '\r'
    | '\n'
    ) {skip();}
;

Upvotes: 1

Views: 965

Answers (2)

Sam Harwell
Sam Harwell

Reputation: 100059

This is similar to not having operator precedence at all.

expr
  : funcExpr
    ( ('+' funcExpr)*
    | ('-' funcExpr)*
    | ('*' funcExpr)*
    | ('/' funcExpr)*
    )
  ;

Upvotes: 2

Troy Daniels
Troy Daniels

Reputation: 3608

I think the following should work. I'm assuming some lexer tokens with obvious names.

expr: sumExpr;

sumExpr: onlySum | subExpr;

onlySum: atom ( PLUS onlySum )?;

subExpr: onlySub | multExpr;

onlySub: atom ( MINUS onlySub )? ;

multExpr: atom ( STAR atomic )? ;

parenExpr: OPEN_PAREN expr CLOSE_PAREN;

atom: FIELD | VARIABLE | parenExpr

The only* rules match an expression if it only has one type of operator outside of parentheses. The *Expr rules match either a line with the appropriate type of operators or go to the next operator.

If you have multiple types of operators, then they are forced to be inside parentheses because the match will go through atom.

Upvotes: 0

Related Questions