anchor.diff:  MUL: '*' ;
   DIV: '/' ;
   MOD: '%' ;
  +AND: '&' ;
  +XOR: '^' ;
  +OR: '|' ;
  +IS_IDENT: '===';
  +IS_NIDENT: '!==';
   IS_EQ: '==' ;
   IS_NEQ: '!=' ;
  +IS_DIFF: '<>';
   IS_LE: '<=' ;
   IS_LT: '<' ;
   IS_GE: '>=' ;
   IS_GT: '>' ;
   LOG_AND: '&&' ;
   LOG_OR: '||' ;
  +INCR: '++';
  +DECR: '--';
  +SHL: '<<' ;
  +SHR: '>>' ;
   EQ: '=' ;
   NOT: '!' ;
  +NEG: '~' ;
  +PLUS_EQ: '+=' ;
  +MINUS_EQ: '-=' ;
  +MUL_EQ: '*=' ;
  +DIV_EQ: '/=' ;
  +DOT_EQ: '.=' ;
  +MOD_EQ: '%=' ;
  +AND_EQ: '&=' ;
  +OR_EQ: '|=' ;
  +XOR_EQ: '^=' ;
  +SHL_EQ: '<<=' ;
  +SHR_EQ: '>>=' ;
   fragment ESC_SEQ
     : '\\' .
     ;
  
positive.code:   SimpleCall;
    Name;
    CallerObject;
  }
  
  program
    : statement+
    ;
  
  variable
    : DOLLAR^ ID
    | DOLLAR^ variable
    | DOLLAR^ LBRACE! expr RBRACE!
    ;
  
  statement
    : expr SEMICOLON!
    | LBRACE! statement+ RBRACE!
    | if_statement
    | while_statement
    | function_def
    ;
  
  function_def
    : KW_FUNCTION! ID LPAREN! (DOLLAR! ID (COMMA! DOLLAR! ID)*)? RPAREN!
      LBRACE! statement* RBRACE!
    ;
  
  if_statement
    : KW_IF LPAREN! expr RPAREN! statement ((KW_ELSE) => KW_ELSE statement)?
    ;
  
  while_statement
    : KW_WHILE LPAREN! expr RPAREN! statement
    ;
  
  expr
    : (variable EQ) => assignment
    | logical
    ;
  
  assignment
    : variable EQ^ expr
    ;
  
  logical
    : comp ((LOG_AND | LOG_OR) =>
      (LOG_AND^ | LOG_OR^) comp)*
    ;
  
  comp
    : concat ((IS_LE | IS_GE | IS_EQ | IS_LT | IS_GT) =>
      (IS_LE^ | IS_GE^ | IS_EQ^ | IS_LT^ | IS_GT^) concat)*
    ;
  
  concat
    : sum ((DOT) => DOT^ sum)*
    ;
  
  sum
    : term ((PLUS | MINUS) => (PLUS | MINUS)^ term)*
    ;
  
  term
    : fact ((MUL | DIV) => (MUL^ | DIV^) fact)*
    ;
  
  fact
    : (((atom OBJ_ACCESS) => call_chain) | atom)
    ;
  
  call_chain
  
negative.code:   ;
  
  atom
    : LPAREN expr RPAREN -> expr
    | L_INT
    | L_STRING
    | KW_TRUE
    | KW_FALSE
    | (ID LPAREN) => call_tail -> ^(SimpleCall call_tail)
    ;
  
  call_tail
    : ID LPAREN (expr (COMMA expr)*)? RPAREN ->
      ^(Name ID) ^(LPAREN (expr+)?) ;
  
  variable
    : DOLLAR^ ID
    | DOLLAR^ variable
    | DOLLAR^ LBRACE! expr RBRACE!
    ;
  
  /**
  * Lexer
  */
  
  KW_FUNCTION : 'function' ;
  KW_CLASS: 'class' ;
  KW_INTERFACE: 'interface';
  KW_EXTENDS: 'extends';
  KW_IMPLEMENTS: 'implements';
  KW_PUBLIC: 'public';
  KW_PROTECTED: 'protected';
  KW_PRIVATE: 'private';
  KW_STATIC: 'static';
  KW_ABSTRACT: 'abstract';
  KW_FINAL: 'final';
  // TODO: de-uglify with a token filter
  KW_TRUE: ('t' | 'T') ('r' | 'R') ('u' | 'U') ('e' | 'E') ;
  KW_FALSE: ('f' | 'F') ('a' | 'A') ('l' | 'L') ('s' | 'S') ('e' | 'E') ;
  KW_IF: 'if' ;
  KW_ELSE: 'else' ;
  KW_WHILE: 'while' ;
  KW_CONST: 'const';
  KW_INSTANCEOF: 'instanceof';
  
  ID:
    ('a'..'z'|'A'..'Z'|'_'|'\u007f'..'\u00ff') ('a'..'z'|'A'..'Z'|'0'..'9'|'_'|'\u007f'..'\u00ff')*
  