Skip to content

Input Conditional Expression Parser

Bob Evans edited this page Oct 9, 2013 · 5 revisions

Overview of basic parser for evaluating conditional inputs.

Introduction

In order to allow conditional display (also known in surveys as skip logic) of questions (Inputs), there is a small Antlr-based parser for evaluating conditional expressions attached to Inputs to determine whether they should be collected or not.

It is currently only used on the Android client in the ExperimentExecutor when the user is answering inputs. It should also be employed on the server to do validation of experiment creation and editing.

Essentially it allows evaluation of simple or compound clauses about the numeric values to inputs.

For example,


- questionOne == 1
- questionOne < 3 || questionOne > 5

questionOne would reference the name of another input in the experiment. It supports basic equality and difference comparators on number values. It allows joining of expressions with and (&&) and or (||).

Antlr Grammar


grammar QuestionCondition;
@header {
package com.google.android.apps.paco.questioncondparser;
}
@lexer::header {
package com.google.android.apps.paco.questioncondparser;
}
@members {
   Environment environment;
   public QuestionConditionParser(TokenStream input, Environment environment) {
       this(input);
       this.environment = environment;
   }
}


comparison  returns [boolean value]
   : question_part LT i=INTEGER { $value = environment.getValue($question_part.text) != null && ((Integer)environment.getValue($question_part.text)) < Integer.parseInt($i.text); }
   | question_part GT i=INTEGER { $value = environment.getValue($question_part.text) != null && ((Integer)environment.getValue($question_part.text)) > Integer.parseInt($i.text); }
   | question_part EQ i=INTEGER { 
        if (environment.getValue($question_part.text) == null) {
          $value = false;
        } else {
          Object obj = environment.getValue($question_part.text);
          if (obj instanceof Integer) {
            $value =  ((Integer)obj) == Integer.parseInt($i.text); 
          } else if (obj instanceof List) {
            $value = ((List)obj).contains(Integer.parseInt($i.text));
          }
        }
      }
   | question_part NE i=INTEGER { 
        if (environment.getValue($question_part.text) == null) {
          $value = false;
        } else {
          Object obj = environment.getValue($question_part.text);
          if (obj instanceof Integer) {
            $value = ((Integer)obj) != Integer.parseInt($i.text); 
          } else if (obj instanceof List) {
            $value = !((List)obj).contains(Integer.parseInt($i.text));
          } else {
            $value = false; //default case
          }
        }
     }
   | question_part 'contains' i=INTEGER { 
        if (environment.getValue($question_part.text) == null) {
          $value = false;
        } else {
          Object obj = environment.getValue($question_part.text);
          if (obj instanceof List) {        
            $value = ((List)obj).contains(Integer.parseInt($i.text)); 
          } else {
            $value = false; // default case
          }
        }
      }
   ;

expression returns [boolean value]
   :   c=comparison {$value = $c.value;}
   ( OR c1=comparison {$value = $value || $c1.value; }
   | AND c1=comparison {$value = $value && $c1.value; }
   )*
   ;

question_part
:QUESTION_NAME { if (!environment.exists($QUESTION_NAME.text)) {
       throw new IllegalArgumentException("unknown reference: " + $QUESTION_NAME.text);
   }
  // if (!environment.correctType($QUESTION_NAME.text)) {
  //   throw new IllegalArgumentException("Does not have the proper response type: " + $QUESTION_NAME.text);
  // }
   }
       ;

OR : '||' ;
AND : '&&' ;
QUESTION_NAME
 :     ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*
   ;

INTEGER :       '0'..'9'+
   ;

COMMENT
   :   '//' ~('\n'|'\r')* '\r'? '\n' {$channel=HIDDEN;}
   |   '/*' ( options {greedy=false;} : . )* '*/' {$channel=HIDDEN;}
   ;

WS  :   ( ' '
       | '\t'
       | '\r'
       | '\n'
       ) {$channel=HIDDEN;}
   ;

STRING
   :  '"' ( ESC_SEQ | ~('\\'|'"') )* '"'
   ;

CHAR:  '\'' ( ESC_SEQ | ~('\''|'\\') ) '\''
   ;

fragment
HEX_DIGIT : ('0'..'9'|'a'..'f'|'A'..'F') ;

fragment
ESC_SEQ
   :   '\\' ('b'|'t'|'n'|'f'|'r'|'\"'|'\''|'\\')
   |   UNICODE_ESC
   |   OCTAL_ESC
   ;

fragment
OCTAL_ESC
   :   '\\' ('0'..'3') ('0'..'7') ('0'..'7')
   |   '\\' ('0'..'7') ('0'..'7')
   |   '\\' ('0'..'7')
   ;

fragment
UNICODE_ESC
   :   '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT
   ;



EQ      :        '=' | '==';
LT      :        '<';
GT      :        '>';
NE      :       '!=' ;


Clone this wiki locally