Skip to content

Commit

Permalink
Merge pull request #1 from sybila/new_parser
Browse files Browse the repository at this point in the history
New parser
  • Loading branch information
daemontus committed Sep 24, 2015
2 parents 5fc1ad1 + 6c57f5f commit a413129
Show file tree
Hide file tree
Showing 30 changed files with 1,587 additions and 1,035 deletions.
62 changes: 48 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,57 @@
Simple Java CTL Parser.
Simple parser, normalizer and optimizer for CTL temporal logic formulas written in Kotlin.

###API
###Features

- File includes
- References
- Full range of boolean and temporal operators
- Simple formula optimizer
- Normalizer into until normal form (EU, AU, EX, &&, ||, !)

###Syntax

Each CTL file contains zero or more include statements and zero or more assignment statements. Each statement is separated by a new line. Multi-line formulas are not supported, use references instead.

Input statement has the following form: ```#include "string"``` where ```string``` is a path to a file (can be absolute or relative) that should be included. For now, escaping quotation marks is not allowed, so make sure your path does not contain any. Each file is included at most once, so you don't have to worry about multiple includes.

Assignment statement has the form of ```identifier = formula```. ```identifier``` consists of numbers and upper/lowercase characters (has to start with a character). Formula can be
- identifier (reference to formula defined elsewhere)
- boolean constant (True/False/tt/ff)
- float proposition: ```id operator number``` where ```id``` is a name of the model property and operator is one of <, >, <=, >=, !=, ==. Numbers can't use scientific notation (1e10).
- direction proposition: ```id:direction facet```(space is not mandatory) where ```id``` is a name of the model property, direction is either in or our and facet can be positive(+) or negative(-). Example: ```val:in+```
- another formula in parentheses
- Formula with unary operator applied. Unary operators: !, EX, AX, EF, AF, EG, AG
- Two formulas with binary operator applied (infix notation: ```f1 op f2```). Binary operators: &&, ||, =>, <=>, EU, AU

Forward declaration, even accross files is supported, so that you can define your formulas in whatever order you choose. For obvious reasons, cyclic references are not allowed.

Operator priority: In general, unary operators have higher priority than binary operators. Binary operators have following priority: && >> || >> => >> <=> >> EU >> AU

- FormulaParser - Parses a file or string into object representing a formula.
- FormulaNormalizer - Normalizes given formula to the form that can be used for model checking.
Also note that =>, EU and AU are right associative, so that ```a EU b EU c``` is parsed as ```a EU (b EU c)```, as expected.

Example of usage:

###Input Syntax
```
#define propositionName variableName <= floatValue
#property A ((True => AF proposotion) U EX False)
f = (AG (p1 EU a)) || (EG (p2 AU b))
p1 = val > 3.14
p2 = val2 < -44
p2 = var:in+ && var:in-
a = EF (p1 && p2)
b = AF foo
#include "other/foo.ctl"
```

- operators (temporal and boolean) should be separated by whitespace (wrong: AFEX, UEX, !AX)
- binary operators have to be enclosed in brackets: (f && g), (f => g), E(f U g), A(f U g)
- all standard boolean and CTL operators are supported except reversed implication (<=):
!, &&, ||, =>, <=>, AX, EX, AF, EF, AG, EG, AU, EU
- all standard float comparison operators are supported: <,>,<=,>=,==,!=
- boolean literals are accepted as proposition names: True, False
- variable and proposition names cannot contain digits
###Normalization and Optimization

Currently you can transform formulas into normal form that supports only operators EX, EU, AU, &&, ||, !. Alternatively, you can provide our own normalization function map that will implement your own normal form (or modify current one).

You can also automatically perform some simple formula optimizations (proposition negation, double negation removal, boolean propagation and some simplifications). Note that these optimizations expect the formula to be in until normal form. (Other formulas are also supported, but possible optimizations are limited)

###API

There are currently three main classes you can use ```Parser```, ```Normalizer```, ```Optimizer```. ```Parser``` provides methods that will parse a file, string or a single formula string (you don't have to perform an assignment). These methods automatically resolve all includes and references.

For more complex usage example, see [IntegrationTest]{https://github.com/sybila/CTL-Parser/blob/new_parser/src/test/kotlin/cz/muni/fi/ctl/IntegrationTest.kt}

###Building and using

Expand Down
14 changes: 11 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,37 +1,40 @@
//include antlr plugin
buildscript {
ext.kotlin_version = '0.13.1513'
repositories {
maven {
name 'JFrog OSS snapshot repo'
url 'https://oss.jfrog.org/oss-snapshot-local/'
}
jcenter()
mavenCentral()
}

dependencies {
classpath 'me.champeau.gradle:antlr4-gradle-plugin:0.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'me.champeau.gradle.antlr4'

sourceCompatibility = 1.7
version = '1.0'

//antlr config
antlr4 {
extraArgs=['-package', 'cz.muni.fi.ctl.antlr']
output=new File('src/main/gen/cz/muni/fi/ctl/antlr')
visitor=false
listener=false
listener=true
}

// make the Java compile task depend on the antlr4 task
compileJava.dependsOn antlr4

// add the generated source files to the list of java sources
sourceSets.main.java.srcDirs += antlr4.output
sourceSets.main.java.srcDirs += new File('src/main/gen')

// add antlr4 to classpath
configurations {
Expand All @@ -45,6 +48,7 @@ repositories {
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.11'
compile 'com.intellij:annotations:5.1'
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}

//create a single Jar with all dependencies
Expand All @@ -57,3 +61,7 @@ task fullJar(type: Jar) {
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
with jar
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
test.java.srcDirs += 'src/test/kotlin'
}
201 changes: 77 additions & 124 deletions src/main/antlr4/CTL.g4
Original file line number Diff line number Diff line change
@@ -1,129 +1,82 @@
/* Grammar for CTL parser */

grammar CTL;

@header {
import cz.muni.fi.ctl.formula.Formula;
import cz.muni.fi.ctl.formula.FormulaImpl;
import cz.muni.fi.ctl.formula.operator.Operator;
import cz.muni.fi.ctl.formula.operator.BinaryOperator;
import cz.muni.fi.ctl.formula.operator.UnaryOperator;
import cz.muni.fi.ctl.formula.proposition.Contradiction;
import cz.muni.fi.ctl.formula.proposition.FloatProposition;
import cz.muni.fi.ctl.formula.proposition.Proposition;
import cz.muni.fi.ctl.formula.proposition.Tautology;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
}

root locals [
Map<String, Proposition> propositions = new HashMap<>(),
Formula result
] : ctl;

ctl :
define ctl
| property
;

define :
DEF
VAR_NAME { String name = $VAR_NAME.text; }
VAR_NAME { String prop = $VAR_NAME.text; }
op_float
FLOAT_VAL
{
if ($root::propositions.containsKey(name)) {
throw new IllegalArgumentException("Redefinition of proposition "+name);
}
$root::propositions.put( name,
new FloatProposition(
Double.parseDouble($FLOAT_VAL.text),
prop,
$op_float.op
)
);
}
;

property : PROP formula { $root::result = $property::formula().processed; };

formula returns [ Formula processed ] :
TRUE
{ $processed = Tautology.INSTANCE; }
| FALSE
{ $processed = Contradiction.INSTANCE; }
| VAR_NAME
{ $processed = $root::propositions.get($VAR_NAME.text);
if ($processed == null) throw new IllegalArgumentException("Unknown proposition: "+$VAR_NAME.text);
}
| NEG formula
{ $processed = new FormulaImpl(UnaryOperator.NEGATION, $formula.ctx.formula.processed ); }
| B_OPEN formula op_bool formula B_CLOSE
{ $processed = new FormulaImpl($op_bool.op, $formula.ctx.formula(0).processed, $formula.ctx.formula(1).processed ); }
| EXISTS B_OPEN formula UNTIL formula B_CLOSE
{ $processed = new FormulaImpl(BinaryOperator.EXISTS_UNTIL, $formula.ctx.formula(0).processed, $formula.ctx.formula(1).processed ); }
| ALL B_OPEN formula UNTIL formula B_CLOSE
{ $processed = new FormulaImpl(BinaryOperator.ALL_UNTIL, $formula.ctx.formula(0).processed, $formula.ctx.formula(1).processed ); }
| temporal formula
{ $processed = new FormulaImpl($temporal.op, $formula.ctx.formula(0).processed ); }
/* Main format structure */

root : fullStop? (statement fullStop)*;

statement : '#include' STRING # include
| VAR_NAME '=' formula # assign
;

fullStop : NEWLINE+ | EOF;

/* Formula and propositions */

formula : VAR_NAME #id
| (TRUE | FALSE) #bool
| VAR_NAME ':' (IN | OUT) (PLUS | MINUS) #direction
| VAR_NAME floatOp FLOAT_VAL #proposition
| '(' formula ')' #parenthesis
| unaryOp formula #unary
//we list operators explicitly, becuase writing them as a subrule broke operator priority
| formula CON formula #and
| formula DIS formula #or
|<assoc=right> formula IMPL formula #implies
| formula EQIV formula #equal
|<assoc=right> formula EU formula #EU
|<assoc=right> formula AU formula #AU
;

temporal returns [ Operator op ] :
'EX' { $op = UnaryOperator.EXISTS_NEXT; }
| 'AX' { $op = UnaryOperator.ALL_NEXT; }
| 'EF' { $op = UnaryOperator.EXISTS_FUTURE; }
| 'AF' { $op = UnaryOperator.ALL_FUTURE; }
| 'EG' { $op = UnaryOperator.EXISTS_GLOBAL; }
| 'AG' { $op = UnaryOperator.ALL_GLOBAL; }
;

op_bool returns [ Operator op ] :
AND { $op = BinaryOperator.AND; }
| OR { $op = BinaryOperator.OR; }
| IMPL { $op = BinaryOperator.IMPLICATION; }
| EQIV { $op = BinaryOperator.EQUIVALENCE; }
;

op_float returns [ FloatProposition.Operator op ] :
EQ { $op = FloatProposition.Operator.EQ; }
| N_EQ { $op = FloatProposition.Operator.NEQ; }
| GT { $op = FloatProposition.Operator.GT; }
| LT { $op = FloatProposition.Operator.LT; }
| GT_EQ { $op = FloatProposition.Operator.GT_EQ; }
| LT_EQ { $op = FloatProposition.Operator.LT_EQ; }
;

DEF : '#define';
PROP : '#property';

/* Terminals: */
bool : TRUE | FALSE ;
TRUE : 'True';
FALSE : 'False';
/* Boolean Operators */
NEG : '!';
AND : '&&';
OR : '||';
IMPL : '=>';
EQIV : '<=>';
/* Temporal Operators */
ALL : 'A';
EXISTS : 'E';
UNTIL : 'U';
/* Comparison operators */
EQ : '==';
N_EQ : '!=';
GT : '>';
LT : '<';
GT_EQ : '>=';
LT_EQ : '<=';
/* Brackets */
B_OPEN : '(';
B_CLOSE : ')';
/* Values */
VAR_NAME : [a-zA-Z]+[a-zA-Z0-9]*;
FLOAT_VAL : [-]?[0-9]*[.]?[0-9]+;
WS : [ \t\n]+ -> skip ; // toss out whitespace
/** Helper/Grouping parser rules **/

unaryOp : NEG | EX | EF | EG | AX | AF | AG;
floatOp : EQ | NEQ | | GT | LT | GTEQ | LTEQ;

/** Terminals **/

TRUE : ([tT]'rue' | 'tt');
FALSE : ([fF]'alse' | 'ff');

IN : 'in';
OUT : 'out';

PLUS : '+';
MINUS : '-';

/** Operators **/

EX : 'EX';
EF : 'EF';
EG : 'EG';
AX : 'AX';
AF : 'AF';
AG : 'AG';
EU : 'EU';
AU : 'AU';

EQ : '==';
NEQ : '!=';
GT : '>';
LT : '<';
GTEQ : '>=';
LTEQ : '<=';

NEG : '!';
CON : '&&';
DIS : '||';
IMPL : '=>';
EQIV : '<=>';

/* Literals */

STRING : ["].+?["]; //non-greedy match till next quotation mark
VAR_NAME : [a-zA-Z]+[a-zA-Z0-9]*;
FLOAT_VAL : [-]?[0-9]*[.]?[0-9]+;

NEWLINE : '\r'?'\n';

WS : [ \t\u]+ -> channel(HIDDEN) ;

Block_comment : '/*' (Block_comment|.)*? '*/' -> channel(HIDDEN) ; // nesting allow
Line_comment : '//' .*? '\n' -> channel(HIDDEN) ;
Loading

0 comments on commit a413129

Please sign in to comment.