From 12ccfd792fe91c83ef9713022027d9b9e16e60cd Mon Sep 17 00:00:00 2001 From: Louis Chouane <116257827+ChouaneLouis@users.noreply.github.com> Date: Mon, 3 Jun 2024 10:56:05 +0200 Subject: [PATCH] Re-implement functional tests in yaml + multiple fixes (#35) Solved multiple issues: - parsing of shift expressions was wrong - integer variables were not correctly transferred to solver - improved some error messages at parsing time Co-authored-by: vargastat --- grammar/Expr.g4 | 66 +- .../expression/parsing/antlr/Expr.interp | 27 +- .../expression/parsing/antlr/Expr.tokens | 38 +- .../expression/parsing/antlr/ExprLexer.interp | 28 +- .../expression/parsing/antlr/ExprLexer.py | 369 ++-- .../expression/parsing/antlr/ExprLexer.tokens | 38 +- .../expression/parsing/antlr/ExprParser.py | 1780 ++++++++++++----- .../expression/parsing/antlr/ExprVisitor.py | 68 +- .../expression/parsing/antlr/__init__.py | 0 .../expression/parsing/parse_expression.py | 85 +- src/andromede/model/parsing.py | 48 +- src/andromede/model/resolve_library.py | 4 +- src/andromede/simulation/optimization.py | 21 +- tests/functional/conftest.py | 33 + tests/functional/libs/lib.yml | 188 ++ tests/functional/test_andromede.py | 302 +-- tests/functional/test_andromede_yml.py | 483 +++++ tests/unittests/data/lib.yml | 16 +- .../parsing/test_expression_parsing.py | 31 + 19 files changed, 2439 insertions(+), 1186 deletions(-) delete mode 100644 src/andromede/expression/parsing/antlr/__init__.py create mode 100644 tests/functional/conftest.py create mode 100644 tests/functional/libs/lib.yml create mode 100644 tests/functional/test_andromede_yml.py diff --git a/grammar/Expr.g4 b/grammar/Expr.g4 index d0d16424..072bf52e 100644 --- a/grammar/Expr.g4 +++ b/grammar/Expr.g4 @@ -17,23 +17,55 @@ grammar Expr; /* To match the whole input */ fullexpr: expr EOF; -shift: TIME (op=('+' | '-') expr)?; - -expr: '-' expr # negation - | expr op=('/' | '*') expr # muldiv - | expr op=('+' | '-') expr # addsub - | expr COMPARISON expr # comparison - | IDENTIFIER # identifier - | IDENTIFIER '.' IDENTIFIER # portField - | NUMBER # number - | '(' expr ')' # expression - | IDENTIFIER '(' expr ')' # function - | IDENTIFIER '[' shift (',' shift)* ']' # timeShift - | IDENTIFIER '[' expr (',' expr )* ']' # timeIndex - | IDENTIFIER '[' shift1=shift '..' shift2=shift ']' # timeShiftRange - | IDENTIFIER '[' expr '..' expr ']' # timeRange +expr + : atom # unsignedAtom + | IDENTIFIER '.' IDENTIFIER # portField + | '-' expr # negation + | '(' expr ')' # expression + | expr op=('/' | '*') expr # muldiv + | expr op=('+' | '-') expr # addsub + | expr COMPARISON expr # comparison + | IDENTIFIER '(' expr ')' # function + | IDENTIFIER '[' shift (',' shift)* ']' # timeShift + | IDENTIFIER '[' expr (',' expr )* ']' # timeIndex + | IDENTIFIER '[' shift1=shift '..' shift2=shift ']' # timeShiftRange + | IDENTIFIER '[' expr '..' expr ']' # timeRange ; +atom + : NUMBER # number + | IDENTIFIER # identifier + ; + +// a shift is required to be either "t" or "t + ..." or "t - ..." +// Note: simply defining it as "shift: TIME ('+' | '-') expr" won't work +// because the minus sign will not have the expected precedence: +// "t - d + 1" would be equivalent to "t - (d + 1)" +shift: TIME shift_expr?; + +// Because the shift MUST start with + or -, we need +// to differentiate it from generic "expr". +// A shift expression can only be extended to the right by a +// "right_expr" which cannot start with a + or -, +// unlike shift_expr itself. +// TODO: the grammar is still a little weird, because we +// allow more things in the "expr" parts of those +// shift expressions than on their left-most part +// (port fields, nested time shifts and so on). +shift_expr + : shift_expr op=('*' | '/') right_expr # shiftMuldiv + | shift_expr op=('+' | '-') right_expr # shiftAddsub + | op=('+' | '-') atom # signedAtom + | op=('+' | '-') '(' expr ')' # signedExpression + ; + +right_expr + : right_expr op=('/' | '*') right_expr # rightMuldiv + | '(' expr ')' # rightExpression + | atom # rightAtom + ; + + fragment DIGIT : [0-9] ; fragment CHAR : [a-zA-Z_]; fragment CHAR_OR_DIGIT : (CHAR | DIGIT); @@ -42,9 +74,5 @@ NUMBER : DIGIT+ ('.' DIGIT+)?; TIME : 't'; IDENTIFIER : CHAR CHAR_OR_DIGIT*; COMPARISON : ( '=' | '>=' | '<=' ); -ADDSUB : ( '+' | '-' ); -MULDIV : ( '*' | '/' ); -LBRACKET: '['; -RBRACKET: ']'; WS: (' ' | '\t' | '\r'| '\n') -> skip; diff --git a/src/andromede/expression/parsing/antlr/Expr.interp b/src/andromede/expression/parsing/antlr/Expr.interp index bed35193..bf05ae28 100644 --- a/src/andromede/expression/parsing/antlr/Expr.interp +++ b/src/andromede/expression/parsing/antlr/Expr.interp @@ -1,23 +1,21 @@ token literal names: null -'+' -'-' -'/' -'*' '.' +'-' '(' ')' +'/' +'*' +'+' +'[' ',' +']' '..' null 't' null null null -null -'[' -']' -null token symbolic names: null @@ -30,21 +28,22 @@ null null null null +null +null NUMBER TIME IDENTIFIER COMPARISON -ADDSUB -MULDIV -LBRACKET -RBRACKET WS rule names: fullexpr -shift expr +atom +shift +shift_expr +right_expr atn: -[4, 1, 18, 86, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 3, 1, 13, 8, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 5, 2, 37, 8, 2, 10, 2, 12, 2, 40, 9, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 5, 2, 49, 8, 2, 10, 2, 12, 2, 52, 9, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 70, 8, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 5, 2, 81, 8, 2, 10, 2, 12, 2, 84, 9, 2, 1, 2, 0, 1, 4, 3, 0, 2, 4, 0, 2, 1, 0, 1, 2, 1, 0, 3, 4, 97, 0, 6, 1, 0, 0, 0, 2, 9, 1, 0, 0, 0, 4, 69, 1, 0, 0, 0, 6, 7, 3, 4, 2, 0, 7, 8, 5, 0, 0, 1, 8, 1, 1, 0, 0, 0, 9, 12, 5, 11, 0, 0, 10, 11, 7, 0, 0, 0, 11, 13, 3, 4, 2, 0, 12, 10, 1, 0, 0, 0, 12, 13, 1, 0, 0, 0, 13, 3, 1, 0, 0, 0, 14, 15, 6, 2, -1, 0, 15, 16, 5, 2, 0, 0, 16, 70, 3, 4, 2, 13, 17, 70, 5, 12, 0, 0, 18, 19, 5, 12, 0, 0, 19, 20, 5, 5, 0, 0, 20, 70, 5, 12, 0, 0, 21, 70, 5, 10, 0, 0, 22, 23, 5, 6, 0, 0, 23, 24, 3, 4, 2, 0, 24, 25, 5, 7, 0, 0, 25, 70, 1, 0, 0, 0, 26, 27, 5, 12, 0, 0, 27, 28, 5, 6, 0, 0, 28, 29, 3, 4, 2, 0, 29, 30, 5, 7, 0, 0, 30, 70, 1, 0, 0, 0, 31, 32, 5, 12, 0, 0, 32, 33, 5, 16, 0, 0, 33, 38, 3, 2, 1, 0, 34, 35, 5, 8, 0, 0, 35, 37, 3, 2, 1, 0, 36, 34, 1, 0, 0, 0, 37, 40, 1, 0, 0, 0, 38, 36, 1, 0, 0, 0, 38, 39, 1, 0, 0, 0, 39, 41, 1, 0, 0, 0, 40, 38, 1, 0, 0, 0, 41, 42, 5, 17, 0, 0, 42, 70, 1, 0, 0, 0, 43, 44, 5, 12, 0, 0, 44, 45, 5, 16, 0, 0, 45, 50, 3, 4, 2, 0, 46, 47, 5, 8, 0, 0, 47, 49, 3, 4, 2, 0, 48, 46, 1, 0, 0, 0, 49, 52, 1, 0, 0, 0, 50, 48, 1, 0, 0, 0, 50, 51, 1, 0, 0, 0, 51, 53, 1, 0, 0, 0, 52, 50, 1, 0, 0, 0, 53, 54, 5, 17, 0, 0, 54, 70, 1, 0, 0, 0, 55, 56, 5, 12, 0, 0, 56, 57, 5, 16, 0, 0, 57, 58, 3, 2, 1, 0, 58, 59, 5, 9, 0, 0, 59, 60, 3, 2, 1, 0, 60, 61, 5, 17, 0, 0, 61, 70, 1, 0, 0, 0, 62, 63, 5, 12, 0, 0, 63, 64, 5, 16, 0, 0, 64, 65, 3, 4, 2, 0, 65, 66, 5, 9, 0, 0, 66, 67, 3, 4, 2, 0, 67, 68, 5, 17, 0, 0, 68, 70, 1, 0, 0, 0, 69, 14, 1, 0, 0, 0, 69, 17, 1, 0, 0, 0, 69, 18, 1, 0, 0, 0, 69, 21, 1, 0, 0, 0, 69, 22, 1, 0, 0, 0, 69, 26, 1, 0, 0, 0, 69, 31, 1, 0, 0, 0, 69, 43, 1, 0, 0, 0, 69, 55, 1, 0, 0, 0, 69, 62, 1, 0, 0, 0, 70, 82, 1, 0, 0, 0, 71, 72, 10, 12, 0, 0, 72, 73, 7, 1, 0, 0, 73, 81, 3, 4, 2, 13, 74, 75, 10, 11, 0, 0, 75, 76, 7, 0, 0, 0, 76, 81, 3, 4, 2, 12, 77, 78, 10, 10, 0, 0, 78, 79, 5, 13, 0, 0, 79, 81, 3, 4, 2, 11, 80, 71, 1, 0, 0, 0, 80, 74, 1, 0, 0, 0, 80, 77, 1, 0, 0, 0, 81, 84, 1, 0, 0, 0, 82, 80, 1, 0, 0, 0, 82, 83, 1, 0, 0, 0, 83, 5, 1, 0, 0, 0, 84, 82, 1, 0, 0, 0, 6, 12, 38, 50, 69, 80, 82] \ No newline at end of file +[4, 1, 16, 131, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 37, 8, 1, 10, 1, 12, 1, 40, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 49, 8, 1, 10, 1, 12, 1, 52, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 70, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 81, 8, 1, 10, 1, 12, 1, 84, 9, 1, 1, 2, 1, 2, 3, 2, 88, 8, 2, 1, 3, 1, 3, 3, 3, 92, 8, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 3, 4, 102, 8, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 5, 4, 110, 8, 4, 10, 4, 12, 4, 113, 9, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 121, 8, 5, 1, 5, 1, 5, 1, 5, 5, 5, 126, 8, 5, 10, 5, 12, 5, 129, 9, 5, 1, 5, 0, 3, 2, 8, 10, 6, 0, 2, 4, 6, 8, 10, 0, 2, 1, 0, 5, 6, 2, 0, 2, 2, 7, 7, 144, 0, 12, 1, 0, 0, 0, 2, 69, 1, 0, 0, 0, 4, 87, 1, 0, 0, 0, 6, 89, 1, 0, 0, 0, 8, 101, 1, 0, 0, 0, 10, 120, 1, 0, 0, 0, 12, 13, 3, 2, 1, 0, 13, 14, 5, 0, 0, 1, 14, 1, 1, 0, 0, 0, 15, 16, 6, 1, -1, 0, 16, 70, 3, 4, 2, 0, 17, 18, 5, 14, 0, 0, 18, 19, 5, 1, 0, 0, 19, 70, 5, 14, 0, 0, 20, 21, 5, 2, 0, 0, 21, 70, 3, 2, 1, 10, 22, 23, 5, 3, 0, 0, 23, 24, 3, 2, 1, 0, 24, 25, 5, 4, 0, 0, 25, 70, 1, 0, 0, 0, 26, 27, 5, 14, 0, 0, 27, 28, 5, 3, 0, 0, 28, 29, 3, 2, 1, 0, 29, 30, 5, 4, 0, 0, 30, 70, 1, 0, 0, 0, 31, 32, 5, 14, 0, 0, 32, 33, 5, 8, 0, 0, 33, 38, 3, 6, 3, 0, 34, 35, 5, 9, 0, 0, 35, 37, 3, 6, 3, 0, 36, 34, 1, 0, 0, 0, 37, 40, 1, 0, 0, 0, 38, 36, 1, 0, 0, 0, 38, 39, 1, 0, 0, 0, 39, 41, 1, 0, 0, 0, 40, 38, 1, 0, 0, 0, 41, 42, 5, 10, 0, 0, 42, 70, 1, 0, 0, 0, 43, 44, 5, 14, 0, 0, 44, 45, 5, 8, 0, 0, 45, 50, 3, 2, 1, 0, 46, 47, 5, 9, 0, 0, 47, 49, 3, 2, 1, 0, 48, 46, 1, 0, 0, 0, 49, 52, 1, 0, 0, 0, 50, 48, 1, 0, 0, 0, 50, 51, 1, 0, 0, 0, 51, 53, 1, 0, 0, 0, 52, 50, 1, 0, 0, 0, 53, 54, 5, 10, 0, 0, 54, 70, 1, 0, 0, 0, 55, 56, 5, 14, 0, 0, 56, 57, 5, 8, 0, 0, 57, 58, 3, 6, 3, 0, 58, 59, 5, 11, 0, 0, 59, 60, 3, 6, 3, 0, 60, 61, 5, 10, 0, 0, 61, 70, 1, 0, 0, 0, 62, 63, 5, 14, 0, 0, 63, 64, 5, 8, 0, 0, 64, 65, 3, 2, 1, 0, 65, 66, 5, 11, 0, 0, 66, 67, 3, 2, 1, 0, 67, 68, 5, 10, 0, 0, 68, 70, 1, 0, 0, 0, 69, 15, 1, 0, 0, 0, 69, 17, 1, 0, 0, 0, 69, 20, 1, 0, 0, 0, 69, 22, 1, 0, 0, 0, 69, 26, 1, 0, 0, 0, 69, 31, 1, 0, 0, 0, 69, 43, 1, 0, 0, 0, 69, 55, 1, 0, 0, 0, 69, 62, 1, 0, 0, 0, 70, 82, 1, 0, 0, 0, 71, 72, 10, 8, 0, 0, 72, 73, 7, 0, 0, 0, 73, 81, 3, 2, 1, 9, 74, 75, 10, 7, 0, 0, 75, 76, 7, 1, 0, 0, 76, 81, 3, 2, 1, 8, 77, 78, 10, 6, 0, 0, 78, 79, 5, 15, 0, 0, 79, 81, 3, 2, 1, 7, 80, 71, 1, 0, 0, 0, 80, 74, 1, 0, 0, 0, 80, 77, 1, 0, 0, 0, 81, 84, 1, 0, 0, 0, 82, 80, 1, 0, 0, 0, 82, 83, 1, 0, 0, 0, 83, 3, 1, 0, 0, 0, 84, 82, 1, 0, 0, 0, 85, 88, 5, 12, 0, 0, 86, 88, 5, 14, 0, 0, 87, 85, 1, 0, 0, 0, 87, 86, 1, 0, 0, 0, 88, 5, 1, 0, 0, 0, 89, 91, 5, 13, 0, 0, 90, 92, 3, 8, 4, 0, 91, 90, 1, 0, 0, 0, 91, 92, 1, 0, 0, 0, 92, 7, 1, 0, 0, 0, 93, 94, 6, 4, -1, 0, 94, 95, 7, 1, 0, 0, 95, 102, 3, 4, 2, 0, 96, 97, 7, 1, 0, 0, 97, 98, 5, 3, 0, 0, 98, 99, 3, 2, 1, 0, 99, 100, 5, 4, 0, 0, 100, 102, 1, 0, 0, 0, 101, 93, 1, 0, 0, 0, 101, 96, 1, 0, 0, 0, 102, 111, 1, 0, 0, 0, 103, 104, 10, 4, 0, 0, 104, 105, 7, 0, 0, 0, 105, 110, 3, 10, 5, 0, 106, 107, 10, 3, 0, 0, 107, 108, 7, 1, 0, 0, 108, 110, 3, 10, 5, 0, 109, 103, 1, 0, 0, 0, 109, 106, 1, 0, 0, 0, 110, 113, 1, 0, 0, 0, 111, 109, 1, 0, 0, 0, 111, 112, 1, 0, 0, 0, 112, 9, 1, 0, 0, 0, 113, 111, 1, 0, 0, 0, 114, 115, 6, 5, -1, 0, 115, 116, 5, 3, 0, 0, 116, 117, 3, 2, 1, 0, 117, 118, 5, 4, 0, 0, 118, 121, 1, 0, 0, 0, 119, 121, 3, 4, 2, 0, 120, 114, 1, 0, 0, 0, 120, 119, 1, 0, 0, 0, 121, 127, 1, 0, 0, 0, 122, 123, 10, 3, 0, 0, 123, 124, 7, 0, 0, 0, 124, 126, 3, 10, 5, 4, 125, 122, 1, 0, 0, 0, 126, 129, 1, 0, 0, 0, 127, 125, 1, 0, 0, 0, 127, 128, 1, 0, 0, 0, 128, 11, 1, 0, 0, 0, 129, 127, 1, 0, 0, 0, 12, 38, 50, 69, 80, 82, 87, 91, 101, 109, 111, 120, 127] \ No newline at end of file diff --git a/src/andromede/expression/parsing/antlr/Expr.tokens b/src/andromede/expression/parsing/antlr/Expr.tokens index 29d96df5..9401c83a 100644 --- a/src/andromede/expression/parsing/antlr/Expr.tokens +++ b/src/andromede/expression/parsing/antlr/Expr.tokens @@ -7,24 +7,22 @@ T__5=6 T__6=7 T__7=8 T__8=9 -NUMBER=10 -TIME=11 -IDENTIFIER=12 -COMPARISON=13 -ADDSUB=14 -MULDIV=15 -LBRACKET=16 -RBRACKET=17 -WS=18 -'+'=1 +T__9=10 +T__10=11 +NUMBER=12 +TIME=13 +IDENTIFIER=14 +COMPARISON=15 +WS=16 +'.'=1 '-'=2 -'/'=3 -'*'=4 -'.'=5 -'('=6 -')'=7 -','=8 -'..'=9 -'t'=11 -'['=16 -']'=17 +'('=3 +')'=4 +'/'=5 +'*'=6 +'+'=7 +'['=8 +','=9 +']'=10 +'..'=11 +'t'=13 diff --git a/src/andromede/expression/parsing/antlr/ExprLexer.interp b/src/andromede/expression/parsing/antlr/ExprLexer.interp index e98cab1e..2e85e1b7 100644 --- a/src/andromede/expression/parsing/antlr/ExprLexer.interp +++ b/src/andromede/expression/parsing/antlr/ExprLexer.interp @@ -1,23 +1,21 @@ token literal names: null -'+' -'-' -'/' -'*' '.' +'-' '(' ')' +'/' +'*' +'+' +'[' ',' +']' '..' null 't' null null null -null -'[' -']' -null token symbolic names: null @@ -30,14 +28,12 @@ null null null null +null +null NUMBER TIME IDENTIFIER COMPARISON -ADDSUB -MULDIV -LBRACKET -RBRACKET WS rule names: @@ -50,6 +46,8 @@ T__5 T__6 T__7 T__8 +T__9 +T__10 DIGIT CHAR CHAR_OR_DIGIT @@ -57,10 +55,6 @@ NUMBER TIME IDENTIFIER COMPARISON -ADDSUB -MULDIV -LBRACKET -RBRACKET WS channel names: @@ -71,4 +65,4 @@ mode names: DEFAULT_MODE atn: -[4, 0, 18, 111, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 10, 1, 10, 1, 11, 1, 11, 3, 11, 69, 8, 11, 1, 12, 4, 12, 72, 8, 12, 11, 12, 12, 12, 73, 1, 12, 1, 12, 4, 12, 78, 8, 12, 11, 12, 12, 12, 79, 3, 12, 82, 8, 12, 1, 13, 1, 13, 1, 14, 1, 14, 5, 14, 88, 8, 14, 10, 14, 12, 14, 91, 9, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 3, 15, 98, 8, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 0, 0, 21, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 0, 21, 0, 23, 0, 25, 10, 27, 11, 29, 12, 31, 13, 33, 14, 35, 15, 37, 16, 39, 17, 41, 18, 1, 0, 5, 1, 0, 48, 57, 3, 0, 65, 90, 95, 95, 97, 122, 2, 0, 43, 43, 45, 45, 2, 0, 42, 42, 47, 47, 3, 0, 9, 10, 13, 13, 32, 32, 114, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 1, 43, 1, 0, 0, 0, 3, 45, 1, 0, 0, 0, 5, 47, 1, 0, 0, 0, 7, 49, 1, 0, 0, 0, 9, 51, 1, 0, 0, 0, 11, 53, 1, 0, 0, 0, 13, 55, 1, 0, 0, 0, 15, 57, 1, 0, 0, 0, 17, 59, 1, 0, 0, 0, 19, 62, 1, 0, 0, 0, 21, 64, 1, 0, 0, 0, 23, 68, 1, 0, 0, 0, 25, 71, 1, 0, 0, 0, 27, 83, 1, 0, 0, 0, 29, 85, 1, 0, 0, 0, 31, 97, 1, 0, 0, 0, 33, 99, 1, 0, 0, 0, 35, 101, 1, 0, 0, 0, 37, 103, 1, 0, 0, 0, 39, 105, 1, 0, 0, 0, 41, 107, 1, 0, 0, 0, 43, 44, 5, 43, 0, 0, 44, 2, 1, 0, 0, 0, 45, 46, 5, 45, 0, 0, 46, 4, 1, 0, 0, 0, 47, 48, 5, 47, 0, 0, 48, 6, 1, 0, 0, 0, 49, 50, 5, 42, 0, 0, 50, 8, 1, 0, 0, 0, 51, 52, 5, 46, 0, 0, 52, 10, 1, 0, 0, 0, 53, 54, 5, 40, 0, 0, 54, 12, 1, 0, 0, 0, 55, 56, 5, 41, 0, 0, 56, 14, 1, 0, 0, 0, 57, 58, 5, 44, 0, 0, 58, 16, 1, 0, 0, 0, 59, 60, 5, 46, 0, 0, 60, 61, 5, 46, 0, 0, 61, 18, 1, 0, 0, 0, 62, 63, 7, 0, 0, 0, 63, 20, 1, 0, 0, 0, 64, 65, 7, 1, 0, 0, 65, 22, 1, 0, 0, 0, 66, 69, 3, 21, 10, 0, 67, 69, 3, 19, 9, 0, 68, 66, 1, 0, 0, 0, 68, 67, 1, 0, 0, 0, 69, 24, 1, 0, 0, 0, 70, 72, 3, 19, 9, 0, 71, 70, 1, 0, 0, 0, 72, 73, 1, 0, 0, 0, 73, 71, 1, 0, 0, 0, 73, 74, 1, 0, 0, 0, 74, 81, 1, 0, 0, 0, 75, 77, 5, 46, 0, 0, 76, 78, 3, 19, 9, 0, 77, 76, 1, 0, 0, 0, 78, 79, 1, 0, 0, 0, 79, 77, 1, 0, 0, 0, 79, 80, 1, 0, 0, 0, 80, 82, 1, 0, 0, 0, 81, 75, 1, 0, 0, 0, 81, 82, 1, 0, 0, 0, 82, 26, 1, 0, 0, 0, 83, 84, 5, 116, 0, 0, 84, 28, 1, 0, 0, 0, 85, 89, 3, 21, 10, 0, 86, 88, 3, 23, 11, 0, 87, 86, 1, 0, 0, 0, 88, 91, 1, 0, 0, 0, 89, 87, 1, 0, 0, 0, 89, 90, 1, 0, 0, 0, 90, 30, 1, 0, 0, 0, 91, 89, 1, 0, 0, 0, 92, 98, 5, 61, 0, 0, 93, 94, 5, 62, 0, 0, 94, 98, 5, 61, 0, 0, 95, 96, 5, 60, 0, 0, 96, 98, 5, 61, 0, 0, 97, 92, 1, 0, 0, 0, 97, 93, 1, 0, 0, 0, 97, 95, 1, 0, 0, 0, 98, 32, 1, 0, 0, 0, 99, 100, 7, 2, 0, 0, 100, 34, 1, 0, 0, 0, 101, 102, 7, 3, 0, 0, 102, 36, 1, 0, 0, 0, 103, 104, 5, 91, 0, 0, 104, 38, 1, 0, 0, 0, 105, 106, 5, 93, 0, 0, 106, 40, 1, 0, 0, 0, 107, 108, 7, 4, 0, 0, 108, 109, 1, 0, 0, 0, 109, 110, 6, 20, 0, 0, 110, 42, 1, 0, 0, 0, 7, 0, 68, 73, 79, 81, 89, 97, 1, 6, 0, 0] \ No newline at end of file +[4, 0, 16, 103, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 1, 7, 1, 8, 1, 8, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 3, 13, 69, 8, 13, 1, 14, 4, 14, 72, 8, 14, 11, 14, 12, 14, 73, 1, 14, 1, 14, 4, 14, 78, 8, 14, 11, 14, 12, 14, 79, 3, 14, 82, 8, 14, 1, 15, 1, 15, 1, 16, 1, 16, 5, 16, 88, 8, 16, 10, 16, 12, 16, 91, 9, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 3, 17, 98, 8, 17, 1, 18, 1, 18, 1, 18, 1, 18, 0, 0, 19, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 0, 25, 0, 27, 0, 29, 12, 31, 13, 33, 14, 35, 15, 37, 16, 1, 0, 3, 1, 0, 48, 57, 3, 0, 65, 90, 95, 95, 97, 122, 3, 0, 9, 10, 13, 13, 32, 32, 106, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 1, 39, 1, 0, 0, 0, 3, 41, 1, 0, 0, 0, 5, 43, 1, 0, 0, 0, 7, 45, 1, 0, 0, 0, 9, 47, 1, 0, 0, 0, 11, 49, 1, 0, 0, 0, 13, 51, 1, 0, 0, 0, 15, 53, 1, 0, 0, 0, 17, 55, 1, 0, 0, 0, 19, 57, 1, 0, 0, 0, 21, 59, 1, 0, 0, 0, 23, 62, 1, 0, 0, 0, 25, 64, 1, 0, 0, 0, 27, 68, 1, 0, 0, 0, 29, 71, 1, 0, 0, 0, 31, 83, 1, 0, 0, 0, 33, 85, 1, 0, 0, 0, 35, 97, 1, 0, 0, 0, 37, 99, 1, 0, 0, 0, 39, 40, 5, 46, 0, 0, 40, 2, 1, 0, 0, 0, 41, 42, 5, 45, 0, 0, 42, 4, 1, 0, 0, 0, 43, 44, 5, 40, 0, 0, 44, 6, 1, 0, 0, 0, 45, 46, 5, 41, 0, 0, 46, 8, 1, 0, 0, 0, 47, 48, 5, 47, 0, 0, 48, 10, 1, 0, 0, 0, 49, 50, 5, 42, 0, 0, 50, 12, 1, 0, 0, 0, 51, 52, 5, 43, 0, 0, 52, 14, 1, 0, 0, 0, 53, 54, 5, 91, 0, 0, 54, 16, 1, 0, 0, 0, 55, 56, 5, 44, 0, 0, 56, 18, 1, 0, 0, 0, 57, 58, 5, 93, 0, 0, 58, 20, 1, 0, 0, 0, 59, 60, 5, 46, 0, 0, 60, 61, 5, 46, 0, 0, 61, 22, 1, 0, 0, 0, 62, 63, 7, 0, 0, 0, 63, 24, 1, 0, 0, 0, 64, 65, 7, 1, 0, 0, 65, 26, 1, 0, 0, 0, 66, 69, 3, 25, 12, 0, 67, 69, 3, 23, 11, 0, 68, 66, 1, 0, 0, 0, 68, 67, 1, 0, 0, 0, 69, 28, 1, 0, 0, 0, 70, 72, 3, 23, 11, 0, 71, 70, 1, 0, 0, 0, 72, 73, 1, 0, 0, 0, 73, 71, 1, 0, 0, 0, 73, 74, 1, 0, 0, 0, 74, 81, 1, 0, 0, 0, 75, 77, 5, 46, 0, 0, 76, 78, 3, 23, 11, 0, 77, 76, 1, 0, 0, 0, 78, 79, 1, 0, 0, 0, 79, 77, 1, 0, 0, 0, 79, 80, 1, 0, 0, 0, 80, 82, 1, 0, 0, 0, 81, 75, 1, 0, 0, 0, 81, 82, 1, 0, 0, 0, 82, 30, 1, 0, 0, 0, 83, 84, 5, 116, 0, 0, 84, 32, 1, 0, 0, 0, 85, 89, 3, 25, 12, 0, 86, 88, 3, 27, 13, 0, 87, 86, 1, 0, 0, 0, 88, 91, 1, 0, 0, 0, 89, 87, 1, 0, 0, 0, 89, 90, 1, 0, 0, 0, 90, 34, 1, 0, 0, 0, 91, 89, 1, 0, 0, 0, 92, 98, 5, 61, 0, 0, 93, 94, 5, 62, 0, 0, 94, 98, 5, 61, 0, 0, 95, 96, 5, 60, 0, 0, 96, 98, 5, 61, 0, 0, 97, 92, 1, 0, 0, 0, 97, 93, 1, 0, 0, 0, 97, 95, 1, 0, 0, 0, 98, 36, 1, 0, 0, 0, 99, 100, 7, 2, 0, 0, 100, 101, 1, 0, 0, 0, 101, 102, 6, 18, 0, 0, 102, 38, 1, 0, 0, 0, 7, 0, 68, 73, 79, 81, 89, 97, 1, 6, 0, 0] \ No newline at end of file diff --git a/src/andromede/expression/parsing/antlr/ExprLexer.py b/src/andromede/expression/parsing/antlr/ExprLexer.py index 7d750dc2..1ad7f368 100644 --- a/src/andromede/expression/parsing/antlr/ExprLexer.py +++ b/src/andromede/expression/parsing/antlr/ExprLexer.py @@ -14,8 +14,8 @@ def serializedATN(): return [ 4, 0, - 18, - 111, + 16, + 103, 6, -1, 2, @@ -94,14 +94,6 @@ def serializedATN(): 18, 7, 18, - 2, - 19, - 7, - 19, - 2, - 20, - 7, - 20, 1, 0, 1, @@ -139,8 +131,6 @@ def serializedATN(): 1, 8, 1, - 8, - 1, 9, 1, 9, @@ -149,107 +139,101 @@ def serializedATN(): 1, 10, 1, + 10, + 1, 11, 1, 11, + 1, + 12, + 1, + 12, + 1, + 13, + 1, + 13, 3, - 11, + 13, 69, 8, - 11, + 13, 1, - 12, + 14, 4, - 12, + 14, 72, 8, - 12, + 14, 11, + 14, 12, - 12, - 12, + 14, 73, 1, - 12, + 14, 1, - 12, + 14, 4, - 12, + 14, 78, 8, - 12, + 14, 11, + 14, 12, - 12, - 12, + 14, 79, 3, - 12, + 14, 82, 8, - 12, + 14, 1, - 13, + 15, 1, - 13, + 15, 1, - 14, + 16, 1, - 14, + 16, 5, - 14, + 16, 88, 8, - 14, + 16, 10, - 14, + 16, 12, - 14, + 16, 91, 9, - 14, + 16, 1, - 15, + 17, 1, - 15, + 17, 1, - 15, + 17, 1, - 15, + 17, 1, - 15, + 17, 3, - 15, + 17, 98, 8, - 15, - 1, - 16, - 1, - 16, - 1, - 17, - 1, 17, 1, 18, 1, 18, 1, - 19, - 1, - 19, - 1, - 20, - 1, - 20, - 1, - 20, + 18, 1, - 20, + 18, 0, 0, - 21, + 19, 1, 1, 3, @@ -269,15 +253,15 @@ def serializedATN(): 17, 9, 19, - 0, + 10, 21, - 0, + 11, 23, 0, 25, - 10, + 0, 27, - 11, + 0, 29, 12, 31, @@ -288,13 +272,9 @@ def serializedATN(): 15, 37, 16, - 39, - 17, - 41, - 18, 1, 0, - 5, + 3, 1, 0, 48, @@ -307,18 +287,6 @@ def serializedATN(): 95, 97, 122, - 2, - 0, - 43, - 43, - 45, - 45, - 2, - 0, - 42, - 42, - 47, - 47, 3, 0, 9, @@ -327,7 +295,7 @@ def serializedATN(): 13, 32, 32, - 114, + 106, 0, 1, 1, @@ -383,13 +351,13 @@ def serializedATN(): 0, 0, 0, - 25, + 19, 1, 0, 0, 0, 0, - 27, + 21, 1, 0, 0, @@ -424,140 +392,140 @@ def serializedATN(): 0, 0, 0, - 0, + 1, 39, 1, 0, 0, 0, - 0, + 3, 41, 1, 0, 0, 0, - 1, + 5, 43, 1, 0, 0, 0, - 3, + 7, 45, 1, 0, 0, 0, - 5, + 9, 47, 1, 0, 0, 0, - 7, + 11, 49, 1, 0, 0, 0, - 9, + 13, 51, 1, 0, 0, 0, - 11, + 15, 53, 1, 0, 0, 0, - 13, + 17, 55, 1, 0, 0, 0, - 15, + 19, 57, 1, 0, 0, 0, - 17, + 21, 59, 1, 0, 0, 0, - 19, + 23, 62, 1, 0, 0, 0, - 21, + 25, 64, 1, 0, 0, 0, - 23, + 27, 68, 1, 0, 0, 0, - 25, + 29, 71, 1, 0, 0, 0, - 27, + 31, 83, 1, 0, 0, 0, - 29, + 33, 85, 1, 0, 0, 0, - 31, + 35, 97, 1, 0, 0, 0, - 33, + 37, 99, 1, 0, 0, 0, - 35, - 101, - 1, - 0, + 39, + 40, + 5, + 46, 0, 0, - 37, - 103, + 40, + 2, 1, 0, 0, 0, - 39, - 105, - 1, - 0, + 41, + 42, + 5, + 45, 0, 0, - 41, - 107, + 42, + 4, 1, 0, 0, @@ -565,11 +533,11 @@ def serializedATN(): 43, 44, 5, - 43, + 40, 0, 0, 44, - 2, + 6, 1, 0, 0, @@ -577,11 +545,11 @@ def serializedATN(): 45, 46, 5, - 45, + 41, 0, 0, 46, - 4, + 8, 1, 0, 0, @@ -593,7 +561,7 @@ def serializedATN(): 0, 0, 48, - 6, + 10, 1, 0, 0, @@ -605,7 +573,7 @@ def serializedATN(): 0, 0, 50, - 8, + 12, 1, 0, 0, @@ -613,11 +581,11 @@ def serializedATN(): 51, 52, 5, - 46, + 43, 0, 0, 52, - 10, + 14, 1, 0, 0, @@ -625,11 +593,11 @@ def serializedATN(): 53, 54, 5, - 40, + 91, 0, 0, 54, - 12, + 16, 1, 0, 0, @@ -637,11 +605,11 @@ def serializedATN(): 55, 56, 5, - 41, + 44, 0, 0, 56, - 14, + 18, 1, 0, 0, @@ -649,11 +617,11 @@ def serializedATN(): 57, 58, 5, - 44, + 93, 0, 0, 58, - 16, + 20, 1, 0, 0, @@ -671,7 +639,7 @@ def serializedATN(): 0, 0, 61, - 18, + 22, 1, 0, 0, @@ -683,7 +651,7 @@ def serializedATN(): 0, 0, 63, - 20, + 24, 1, 0, 0, @@ -695,7 +663,7 @@ def serializedATN(): 0, 0, 65, - 22, + 26, 1, 0, 0, @@ -703,14 +671,14 @@ def serializedATN(): 66, 69, 3, - 21, - 10, + 25, + 12, 0, 67, 69, 3, - 19, - 9, + 23, + 11, 0, 68, 66, @@ -725,7 +693,7 @@ def serializedATN(): 0, 0, 69, - 24, + 28, 1, 0, 0, @@ -733,8 +701,8 @@ def serializedATN(): 70, 72, 3, - 19, - 9, + 23, + 11, 0, 71, 70, @@ -775,8 +743,8 @@ def serializedATN(): 76, 78, 3, - 19, - 9, + 23, + 11, 0, 77, 76, @@ -821,7 +789,7 @@ def serializedATN(): 0, 0, 82, - 26, + 30, 1, 0, 0, @@ -833,7 +801,7 @@ def serializedATN(): 0, 0, 84, - 28, + 32, 1, 0, 0, @@ -841,14 +809,14 @@ def serializedATN(): 85, 89, 3, - 21, - 10, + 25, + 12, 0, 86, 88, 3, - 23, - 11, + 27, + 13, 0, 87, 86, @@ -875,7 +843,7 @@ def serializedATN(): 0, 0, 90, - 30, + 34, 1, 0, 0, @@ -935,7 +903,7 @@ def serializedATN(): 0, 0, 98, - 32, + 36, 1, 0, 0, @@ -947,71 +915,23 @@ def serializedATN(): 0, 0, 100, - 34, + 101, 1, 0, 0, 0, 101, 102, - 7, - 3, + 6, + 18, 0, 0, 102, - 36, - 1, - 0, - 0, - 0, - 103, - 104, - 5, - 91, - 0, - 0, - 104, 38, 1, 0, 0, 0, - 105, - 106, - 5, - 93, - 0, - 0, - 106, - 40, - 1, - 0, - 0, - 0, - 107, - 108, - 7, - 4, - 0, - 0, - 108, - 109, - 1, - 0, - 0, - 0, - 109, - 110, - 6, - 20, - 0, - 0, - 110, - 42, - 1, - 0, - 0, - 0, 7, 0, 68, @@ -1041,15 +961,13 @@ class ExprLexer(Lexer): T__6 = 7 T__7 = 8 T__8 = 9 - NUMBER = 10 - TIME = 11 - IDENTIFIER = 12 - COMPARISON = 13 - ADDSUB = 14 - MULDIV = 15 - LBRACKET = 16 - RBRACKET = 17 - WS = 18 + T__9 = 10 + T__10 = 11 + NUMBER = 12 + TIME = 13 + IDENTIFIER = 14 + COMPARISON = 15 + WS = 16 channelNames = ["DEFAULT_TOKEN_CHANNEL", "HIDDEN"] @@ -1057,32 +975,21 @@ class ExprLexer(Lexer): literalNames = [ "", - "'+'", - "'-'", - "'/'", - "'*'", "'.'", + "'-'", "'('", "')'", + "'/'", + "'*'", + "'+'", + "'['", "','", + "']'", "'..'", "'t'", - "'['", - "']'", ] - symbolicNames = [ - "", - "NUMBER", - "TIME", - "IDENTIFIER", - "COMPARISON", - "ADDSUB", - "MULDIV", - "LBRACKET", - "RBRACKET", - "WS", - ] + symbolicNames = ["", "NUMBER", "TIME", "IDENTIFIER", "COMPARISON", "WS"] ruleNames = [ "T__0", @@ -1094,6 +1001,8 @@ class ExprLexer(Lexer): "T__6", "T__7", "T__8", + "T__9", + "T__10", "DIGIT", "CHAR", "CHAR_OR_DIGIT", @@ -1101,10 +1010,6 @@ class ExprLexer(Lexer): "TIME", "IDENTIFIER", "COMPARISON", - "ADDSUB", - "MULDIV", - "LBRACKET", - "RBRACKET", "WS", ] diff --git a/src/andromede/expression/parsing/antlr/ExprLexer.tokens b/src/andromede/expression/parsing/antlr/ExprLexer.tokens index 29d96df5..9401c83a 100644 --- a/src/andromede/expression/parsing/antlr/ExprLexer.tokens +++ b/src/andromede/expression/parsing/antlr/ExprLexer.tokens @@ -7,24 +7,22 @@ T__5=6 T__6=7 T__7=8 T__8=9 -NUMBER=10 -TIME=11 -IDENTIFIER=12 -COMPARISON=13 -ADDSUB=14 -MULDIV=15 -LBRACKET=16 -RBRACKET=17 -WS=18 -'+'=1 +T__9=10 +T__10=11 +NUMBER=12 +TIME=13 +IDENTIFIER=14 +COMPARISON=15 +WS=16 +'.'=1 '-'=2 -'/'=3 -'*'=4 -'.'=5 -'('=6 -')'=7 -','=8 -'..'=9 -'t'=11 -'['=16 -']'=17 +'('=3 +')'=4 +'/'=5 +'*'=6 +'+'=7 +'['=8 +','=9 +']'=10 +'..'=11 +'t'=13 diff --git a/src/andromede/expression/parsing/antlr/ExprParser.py b/src/andromede/expression/parsing/antlr/ExprParser.py index a8c87cab..8f312fe9 100644 --- a/src/andromede/expression/parsing/antlr/ExprParser.py +++ b/src/andromede/expression/parsing/antlr/ExprParser.py @@ -15,8 +15,8 @@ def serializedATN(): return [ 4, 1, - 18, - 86, + 16, + 131, 2, 0, 7, @@ -29,6 +29,18 @@ def serializedATN(): 2, 7, 2, + 2, + 3, + 7, + 3, + 2, + 4, + 7, + 4, + 2, + 5, + 7, + 5, 1, 0, 1, @@ -41,316 +53,384 @@ def serializedATN(): 1, 1, 1, - 3, 1, - 13, - 8, 1, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, 5, - 2, + 1, 37, 8, - 2, + 1, 10, - 2, + 1, 12, - 2, + 1, 40, 9, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, 5, - 2, + 1, 49, 8, - 2, + 1, 10, - 2, + 1, 12, - 2, + 1, 52, 9, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, 3, - 2, + 1, 70, 8, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, 1, - 2, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, 5, - 2, + 1, 81, 8, - 2, + 1, 10, - 2, + 1, 12, - 2, + 1, 84, 9, - 2, + 1, 1, 2, - 0, 1, - 4, + 2, 3, - 0, 2, - 4, - 0, + 88, + 8, 2, 1, - 0, - 1, - 2, + 3, 1, - 0, 3, + 3, + 3, + 92, + 8, + 3, + 1, 4, - 97, - 0, - 6, 1, - 0, - 0, - 0, - 2, - 9, + 4, 1, - 0, - 0, - 0, 4, - 69, 1, - 0, - 0, - 0, - 6, - 7, + 4, + 1, + 4, + 1, + 4, + 1, + 4, + 1, + 4, 3, 4, - 2, - 0, - 7, + 102, 8, - 5, - 0, - 0, + 4, 1, - 8, + 4, 1, + 4, 1, - 0, - 0, - 0, - 9, - 12, + 4, + 1, + 4, + 1, + 4, + 1, + 4, 5, - 11, - 0, - 0, + 4, + 110, + 8, + 4, 10, - 11, - 7, - 0, - 0, - 0, - 11, - 13, - 3, 4, - 2, - 0, 12, - 10, + 4, + 113, + 9, + 4, 1, - 0, - 0, - 0, - 12, - 13, + 5, 1, - 0, - 0, - 0, - 13, - 3, + 5, + 1, + 5, + 1, + 5, + 1, + 5, + 1, + 5, + 3, + 5, + 121, + 8, + 5, + 1, + 5, + 1, + 5, + 1, + 5, + 5, + 5, + 126, + 8, + 5, + 10, + 5, + 12, + 5, + 129, + 9, + 5, + 1, + 5, + 0, + 3, + 2, + 8, + 10, + 6, + 0, + 2, + 4, + 6, + 8, + 10, + 0, + 2, 1, 0, - 0, - 0, - 14, - 15, - 6, - 2, - -1, - 0, - 15, - 16, 5, + 6, + 2, + 0, + 2, + 2, + 7, + 7, + 144, + 0, + 12, + 1, + 0, + 0, + 0, + 2, + 69, + 1, + 0, + 0, + 0, + 4, + 87, + 1, + 0, + 0, + 0, + 6, + 89, + 1, + 0, + 0, + 0, + 8, + 101, + 1, + 0, + 0, + 0, + 10, + 120, + 1, + 0, + 0, + 0, + 12, + 13, + 3, 2, + 1, + 0, + 13, + 14, + 5, + 0, + 0, + 1, + 14, + 1, + 1, + 0, 0, 0, + 15, + 16, + 6, + 1, + -1, + 0, 16, 70, 3, 4, 2, - 13, + 0, 17, - 70, + 18, 5, - 12, + 14, 0, 0, 18, 19, 5, - 12, + 1, 0, 0, 19, - 20, - 5, + 70, 5, + 14, 0, 0, 20, - 70, + 21, 5, - 12, + 2, 0, 0, 21, 70, - 5, + 3, + 2, + 1, 10, - 0, - 0, 22, 23, 5, - 6, + 3, 0, 0, 23, 24, 3, - 4, 2, + 1, 0, 24, 25, 5, - 7, + 4, 0, 0, 25, @@ -362,25 +442,25 @@ def serializedATN(): 26, 27, 5, - 12, + 14, 0, 0, 27, 28, 5, - 6, + 3, 0, 0, 28, 29, 3, - 4, 2, + 1, 0, 29, 30, 5, - 7, + 4, 0, 0, 30, @@ -392,32 +472,32 @@ def serializedATN(): 31, 32, 5, - 12, + 14, 0, 0, 32, 33, 5, - 16, + 8, 0, 0, 33, 38, 3, - 2, - 1, + 6, + 3, 0, 34, 35, 5, - 8, + 9, 0, 0, 35, 37, 3, - 2, - 1, + 6, + 3, 0, 36, 34, @@ -458,7 +538,7 @@ def serializedATN(): 41, 42, 5, - 17, + 10, 0, 0, 42, @@ -470,32 +550,32 @@ def serializedATN(): 43, 44, 5, - 12, + 14, 0, 0, 44, 45, 5, - 16, + 8, 0, 0, 45, 50, 3, - 4, 2, + 1, 0, 46, 47, 5, - 8, + 9, 0, 0, 47, 49, 3, - 4, 2, + 1, 0, 48, 46, @@ -536,7 +616,7 @@ def serializedATN(): 53, 54, 5, - 17, + 10, 0, 0, 54, @@ -548,37 +628,37 @@ def serializedATN(): 55, 56, 5, - 12, + 14, 0, 0, 56, 57, 5, - 16, + 8, 0, 0, 57, 58, 3, - 2, - 1, + 6, + 3, 0, 58, 59, 5, - 9, + 11, 0, 0, 59, 60, 3, - 2, - 1, + 6, + 3, 0, 60, 61, 5, - 17, + 10, 0, 0, 61, @@ -590,37 +670,37 @@ def serializedATN(): 62, 63, 5, - 12, + 14, 0, 0, 63, 64, 5, - 16, + 8, 0, 0, 64, 65, 3, - 4, 2, + 1, 0, 65, 66, 5, - 9, + 11, 0, 0, 66, 67, 3, - 4, 2, + 1, 0, 67, 68, 5, - 17, + 10, 0, 0, 68, @@ -630,7 +710,7 @@ def serializedATN(): 0, 0, 69, - 14, + 15, 1, 0, 0, @@ -642,13 +722,7 @@ def serializedATN(): 0, 0, 69, - 18, - 1, - 0, - 0, - 0, - 69, - 21, + 20, 1, 0, 0, @@ -698,57 +772,57 @@ def serializedATN(): 71, 72, 10, - 12, + 8, 0, 0, 72, 73, 7, - 1, + 0, 0, 0, 73, 81, 3, - 4, 2, - 13, + 1, + 9, 74, 75, 10, - 11, + 7, 0, 0, 75, 76, 7, - 0, + 1, 0, 0, 76, 81, 3, - 4, 2, - 12, + 1, + 8, 77, 78, 10, - 10, + 6, 0, 0, 78, 79, 5, - 13, + 15, 0, 0, 79, 81, 3, - 4, 2, - 11, + 1, + 7, 80, 71, 1, @@ -786,7 +860,7 @@ def serializedATN(): 0, 0, 83, - 5, + 3, 1, 0, 0, @@ -797,44 +871,358 @@ def serializedATN(): 0, 0, 0, - 6, + 85, + 88, + 5, 12, - 38, - 50, - 69, - 80, - 82, - ] - - -class ExprParser(Parser): - grammarFileName = "Expr.g4" - - atn = ATNDeserializer().deserialize(serializedATN()) - - decisionsToDFA = [DFA(ds, i) for i, ds in enumerate(atn.decisionToState)] - - sharedContextCache = PredictionContextCache() - - literalNames = [ - "", - "'+'", - "'-'", - "'/'", - "'*'", + 0, + 0, + 86, + 88, + 5, + 14, + 0, + 0, + 87, + 85, + 1, + 0, + 0, + 0, + 87, + 86, + 1, + 0, + 0, + 0, + 88, + 5, + 1, + 0, + 0, + 0, + 89, + 91, + 5, + 13, + 0, + 0, + 90, + 92, + 3, + 8, + 4, + 0, + 91, + 90, + 1, + 0, + 0, + 0, + 91, + 92, + 1, + 0, + 0, + 0, + 92, + 7, + 1, + 0, + 0, + 0, + 93, + 94, + 6, + 4, + -1, + 0, + 94, + 95, + 7, + 1, + 0, + 0, + 95, + 102, + 3, + 4, + 2, + 0, + 96, + 97, + 7, + 1, + 0, + 0, + 97, + 98, + 5, + 3, + 0, + 0, + 98, + 99, + 3, + 2, + 1, + 0, + 99, + 100, + 5, + 4, + 0, + 0, + 100, + 102, + 1, + 0, + 0, + 0, + 101, + 93, + 1, + 0, + 0, + 0, + 101, + 96, + 1, + 0, + 0, + 0, + 102, + 111, + 1, + 0, + 0, + 0, + 103, + 104, + 10, + 4, + 0, + 0, + 104, + 105, + 7, + 0, + 0, + 0, + 105, + 110, + 3, + 10, + 5, + 0, + 106, + 107, + 10, + 3, + 0, + 0, + 107, + 108, + 7, + 1, + 0, + 0, + 108, + 110, + 3, + 10, + 5, + 0, + 109, + 103, + 1, + 0, + 0, + 0, + 109, + 106, + 1, + 0, + 0, + 0, + 110, + 113, + 1, + 0, + 0, + 0, + 111, + 109, + 1, + 0, + 0, + 0, + 111, + 112, + 1, + 0, + 0, + 0, + 112, + 9, + 1, + 0, + 0, + 0, + 113, + 111, + 1, + 0, + 0, + 0, + 114, + 115, + 6, + 5, + -1, + 0, + 115, + 116, + 5, + 3, + 0, + 0, + 116, + 117, + 3, + 2, + 1, + 0, + 117, + 118, + 5, + 4, + 0, + 0, + 118, + 121, + 1, + 0, + 0, + 0, + 119, + 121, + 3, + 4, + 2, + 0, + 120, + 114, + 1, + 0, + 0, + 0, + 120, + 119, + 1, + 0, + 0, + 0, + 121, + 127, + 1, + 0, + 0, + 0, + 122, + 123, + 10, + 3, + 0, + 0, + 123, + 124, + 7, + 0, + 0, + 0, + 124, + 126, + 3, + 10, + 5, + 4, + 125, + 122, + 1, + 0, + 0, + 0, + 126, + 129, + 1, + 0, + 0, + 0, + 127, + 125, + 1, + 0, + 0, + 0, + 127, + 128, + 1, + 0, + 0, + 0, + 128, + 11, + 1, + 0, + 0, + 0, + 129, + 127, + 1, + 0, + 0, + 0, + 12, + 38, + 50, + 69, + 80, + 82, + 87, + 91, + 101, + 109, + 111, + 120, + 127, + ] + + +class ExprParser(Parser): + grammarFileName = "Expr.g4" + + atn = ATNDeserializer().deserialize(serializedATN()) + + decisionsToDFA = [DFA(ds, i) for i, ds in enumerate(atn.decisionToState)] + + sharedContextCache = PredictionContextCache() + + literalNames = [ + "", "'.'", + "'-'", "'('", "')'", + "'/'", + "'*'", + "'+'", + "'['", "','", + "']'", "'..'", "", "'t'", - "", - "", - "", - "", - "'['", - "']'", ] symbolicNames = [ @@ -848,22 +1236,23 @@ class ExprParser(Parser): "", "", "", + "", + "", "NUMBER", "TIME", "IDENTIFIER", "COMPARISON", - "ADDSUB", - "MULDIV", - "LBRACKET", - "RBRACKET", "WS", ] RULE_fullexpr = 0 - RULE_shift = 1 - RULE_expr = 2 + RULE_expr = 1 + RULE_atom = 2 + RULE_shift = 3 + RULE_shift_expr = 4 + RULE_right_expr = 5 - ruleNames = ["fullexpr", "shift", "expr"] + ruleNames = ["fullexpr", "expr", "atom", "shift", "shift_expr", "right_expr"] EOF = Token.EOF T__0 = 1 @@ -875,15 +1264,13 @@ class ExprParser(Parser): T__6 = 7 T__7 = 8 T__8 = 9 - NUMBER = 10 - TIME = 11 - IDENTIFIER = 12 - COMPARISON = 13 - ADDSUB = 14 - MULDIV = 15 - LBRACKET = 16 - RBRACKET = 17 - WS = 18 + T__9 = 10 + T__10 = 11 + NUMBER = 12 + TIME = 13 + IDENTIFIER = 14 + COMPARISON = 15 + WS = 16 def __init__(self, input: TokenStream, output: TextIO = sys.stdout): super().__init__(input, output) @@ -922,9 +1309,9 @@ def fullexpr(self): self.enterRule(localctx, 0, self.RULE_fullexpr) try: self.enterOuterAlt(localctx, 1) - self.state = 6 + self.state = 12 self.expr(0) - self.state = 7 + self.state = 13 self.match(ExprParser.EOF) except RecognitionException as re: localctx.exception = re @@ -934,63 +1321,7 @@ def fullexpr(self): self.exitRule() return localctx - class ShiftContext(ParserRuleContext): - __slots__ = "parser" - - def __init__( - self, parser, parent: ParserRuleContext = None, invokingState: int = -1 - ): - super().__init__(parent, invokingState) - self.parser = parser - self.op = None # Token - - def TIME(self): - return self.getToken(ExprParser.TIME, 0) - - def expr(self): - return self.getTypedRuleContext(ExprParser.ExprContext, 0) - - def getRuleIndex(self): - return ExprParser.RULE_shift - - def accept(self, visitor: ParseTreeVisitor): - if hasattr(visitor, "visitShift"): - return visitor.visitShift(self) - else: - return visitor.visitChildren(self) - - def shift(self): - localctx = ExprParser.ShiftContext(self, self._ctx, self.state) - self.enterRule(localctx, 2, self.RULE_shift) - self._la = 0 # Token type - try: - self.enterOuterAlt(localctx, 1) - self.state = 9 - self.match(ExprParser.TIME) - self.state = 12 - self._errHandler.sync(self) - _la = self._input.LA(1) - if _la == 1 or _la == 2: - self.state = 10 - localctx.op = self._input.LT(1) - _la = self._input.LA(1) - if not (_la == 1 or _la == 2): - localctx.op = self._errHandler.recoverInline(self) - else: - self._errHandler.reportMatch(self) - self.consume() - self.state = 11 - self.expr(0) - - except RecognitionException as re: - localctx.exception = re - self._errHandler.reportError(self, re) - self._errHandler.recover(self, re) - finally: - self.exitRule() - return localctx - - class ExprContext(ParserRuleContext): + class ExprContext(ParserRuleContext): __slots__ = "parser" def __init__( @@ -1005,35 +1336,35 @@ def getRuleIndex(self): def copyFrom(self, ctx: ParserRuleContext): super().copyFrom(ctx) - class IdentifierContext(ExprContext): + class NegationContext(ExprContext): def __init__( self, parser, ctx: ParserRuleContext ): # actually a ExprParser.ExprContext super().__init__(parser) self.copyFrom(ctx) - def IDENTIFIER(self): - return self.getToken(ExprParser.IDENTIFIER, 0) + def expr(self): + return self.getTypedRuleContext(ExprParser.ExprContext, 0) def accept(self, visitor: ParseTreeVisitor): - if hasattr(visitor, "visitIdentifier"): - return visitor.visitIdentifier(self) + if hasattr(visitor, "visitNegation"): + return visitor.visitNegation(self) else: return visitor.visitChildren(self) - class NegationContext(ExprContext): + class UnsignedAtomContext(ExprContext): def __init__( self, parser, ctx: ParserRuleContext ): # actually a ExprParser.ExprContext super().__init__(parser) self.copyFrom(ctx) - def expr(self): - return self.getTypedRuleContext(ExprParser.ExprContext, 0) + def atom(self): + return self.getTypedRuleContext(ExprParser.AtomContext, 0) def accept(self, visitor: ParseTreeVisitor): - if hasattr(visitor, "visitNegation"): - return visitor.visitNegation(self) + if hasattr(visitor, "visitUnsignedAtom"): + return visitor.visitUnsignedAtom(self) else: return visitor.visitChildren(self) @@ -1053,34 +1384,33 @@ def accept(self, visitor: ParseTreeVisitor): else: return visitor.visitChildren(self) - class ComparisonContext(ExprContext): + class TimeIndexContext(ExprContext): def __init__( self, parser, ctx: ParserRuleContext ): # actually a ExprParser.ExprContext super().__init__(parser) self.copyFrom(ctx) + def IDENTIFIER(self): + return self.getToken(ExprParser.IDENTIFIER, 0) + def expr(self, i: int = None): if i is None: return self.getTypedRuleContexts(ExprParser.ExprContext) else: return self.getTypedRuleContext(ExprParser.ExprContext, i) - def COMPARISON(self): - return self.getToken(ExprParser.COMPARISON, 0) - def accept(self, visitor: ParseTreeVisitor): - if hasattr(visitor, "visitComparison"): - return visitor.visitComparison(self) + if hasattr(visitor, "visitTimeIndex"): + return visitor.visitTimeIndex(self) else: return visitor.visitChildren(self) - class AddsubContext(ExprContext): + class ComparisonContext(ExprContext): def __init__( self, parser, ctx: ParserRuleContext ): # actually a ExprParser.ExprContext super().__init__(parser) - self.op = None # Token self.copyFrom(ctx) def expr(self, i: int = None): @@ -1089,169 +1419,136 @@ def expr(self, i: int = None): else: return self.getTypedRuleContext(ExprParser.ExprContext, i) - def accept(self, visitor: ParseTreeVisitor): - if hasattr(visitor, "visitAddsub"): - return visitor.visitAddsub(self) - else: - return visitor.visitChildren(self) - - class PortFieldContext(ExprContext): - def __init__( - self, parser, ctx: ParserRuleContext - ): # actually a ExprParser.ExprContext - super().__init__(parser) - self.copyFrom(ctx) - - def IDENTIFIER(self, i: int = None): - if i is None: - return self.getTokens(ExprParser.IDENTIFIER) - else: - return self.getToken(ExprParser.IDENTIFIER, i) + def COMPARISON(self): + return self.getToken(ExprParser.COMPARISON, 0) def accept(self, visitor: ParseTreeVisitor): - if hasattr(visitor, "visitPortField"): - return visitor.visitPortField(self) + if hasattr(visitor, "visitComparison"): + return visitor.visitComparison(self) else: return visitor.visitChildren(self) - class MuldivContext(ExprContext): + class TimeShiftContext(ExprContext): def __init__( self, parser, ctx: ParserRuleContext ): # actually a ExprParser.ExprContext super().__init__(parser) - self.op = None # Token self.copyFrom(ctx) - def expr(self, i: int = None): + def IDENTIFIER(self): + return self.getToken(ExprParser.IDENTIFIER, 0) + + def shift(self, i: int = None): if i is None: - return self.getTypedRuleContexts(ExprParser.ExprContext) + return self.getTypedRuleContexts(ExprParser.ShiftContext) else: - return self.getTypedRuleContext(ExprParser.ExprContext, i) + return self.getTypedRuleContext(ExprParser.ShiftContext, i) def accept(self, visitor: ParseTreeVisitor): - if hasattr(visitor, "visitMuldiv"): - return visitor.visitMuldiv(self) + if hasattr(visitor, "visitTimeShift"): + return visitor.visitTimeShift(self) else: return visitor.visitChildren(self) - class NumberContext(ExprContext): + class FunctionContext(ExprContext): def __init__( self, parser, ctx: ParserRuleContext ): # actually a ExprParser.ExprContext super().__init__(parser) self.copyFrom(ctx) - def NUMBER(self): - return self.getToken(ExprParser.NUMBER, 0) + def IDENTIFIER(self): + return self.getToken(ExprParser.IDENTIFIER, 0) + + def expr(self): + return self.getTypedRuleContext(ExprParser.ExprContext, 0) def accept(self, visitor: ParseTreeVisitor): - if hasattr(visitor, "visitNumber"): - return visitor.visitNumber(self) + if hasattr(visitor, "visitFunction"): + return visitor.visitFunction(self) else: return visitor.visitChildren(self) - class TimeIndexContext(ExprContext): + class AddsubContext(ExprContext): def __init__( self, parser, ctx: ParserRuleContext ): # actually a ExprParser.ExprContext super().__init__(parser) + self.op = None # Token self.copyFrom(ctx) - def IDENTIFIER(self): - return self.getToken(ExprParser.IDENTIFIER, 0) - - def LBRACKET(self): - return self.getToken(ExprParser.LBRACKET, 0) - def expr(self, i: int = None): if i is None: return self.getTypedRuleContexts(ExprParser.ExprContext) else: return self.getTypedRuleContext(ExprParser.ExprContext, i) - def RBRACKET(self): - return self.getToken(ExprParser.RBRACKET, 0) - def accept(self, visitor: ParseTreeVisitor): - if hasattr(visitor, "visitTimeIndex"): - return visitor.visitTimeIndex(self) + if hasattr(visitor, "visitAddsub"): + return visitor.visitAddsub(self) else: return visitor.visitChildren(self) - class TimeShiftContext(ExprContext): + class TimeShiftRangeContext(ExprContext): def __init__( self, parser, ctx: ParserRuleContext ): # actually a ExprParser.ExprContext super().__init__(parser) + self.shift1 = None # ShiftContext + self.shift2 = None # ShiftContext self.copyFrom(ctx) def IDENTIFIER(self): return self.getToken(ExprParser.IDENTIFIER, 0) - def LBRACKET(self): - return self.getToken(ExprParser.LBRACKET, 0) - def shift(self, i: int = None): if i is None: return self.getTypedRuleContexts(ExprParser.ShiftContext) else: return self.getTypedRuleContext(ExprParser.ShiftContext, i) - def RBRACKET(self): - return self.getToken(ExprParser.RBRACKET, 0) - def accept(self, visitor: ParseTreeVisitor): - if hasattr(visitor, "visitTimeShift"): - return visitor.visitTimeShift(self) + if hasattr(visitor, "visitTimeShiftRange"): + return visitor.visitTimeShiftRange(self) else: return visitor.visitChildren(self) - class FunctionContext(ExprContext): + class PortFieldContext(ExprContext): def __init__( self, parser, ctx: ParserRuleContext ): # actually a ExprParser.ExprContext super().__init__(parser) self.copyFrom(ctx) - def IDENTIFIER(self): - return self.getToken(ExprParser.IDENTIFIER, 0) - - def expr(self): - return self.getTypedRuleContext(ExprParser.ExprContext, 0) + def IDENTIFIER(self, i: int = None): + if i is None: + return self.getTokens(ExprParser.IDENTIFIER) + else: + return self.getToken(ExprParser.IDENTIFIER, i) def accept(self, visitor: ParseTreeVisitor): - if hasattr(visitor, "visitFunction"): - return visitor.visitFunction(self) + if hasattr(visitor, "visitPortField"): + return visitor.visitPortField(self) else: return visitor.visitChildren(self) - class TimeShiftRangeContext(ExprContext): + class MuldivContext(ExprContext): def __init__( self, parser, ctx: ParserRuleContext ): # actually a ExprParser.ExprContext super().__init__(parser) - self.shift1 = None # ShiftContext - self.shift2 = None # ShiftContext + self.op = None # Token self.copyFrom(ctx) - def IDENTIFIER(self): - return self.getToken(ExprParser.IDENTIFIER, 0) - - def LBRACKET(self): - return self.getToken(ExprParser.LBRACKET, 0) - - def RBRACKET(self): - return self.getToken(ExprParser.RBRACKET, 0) - - def shift(self, i: int = None): + def expr(self, i: int = None): if i is None: - return self.getTypedRuleContexts(ExprParser.ShiftContext) + return self.getTypedRuleContexts(ExprParser.ExprContext) else: - return self.getTypedRuleContext(ExprParser.ShiftContext, i) + return self.getTypedRuleContext(ExprParser.ExprContext, i) def accept(self, visitor: ParseTreeVisitor): - if hasattr(visitor, "visitTimeShiftRange"): - return visitor.visitTimeShiftRange(self) + if hasattr(visitor, "visitMuldiv"): + return visitor.visitMuldiv(self) else: return visitor.visitChildren(self) @@ -1265,18 +1562,12 @@ def __init__( def IDENTIFIER(self): return self.getToken(ExprParser.IDENTIFIER, 0) - def LBRACKET(self): - return self.getToken(ExprParser.LBRACKET, 0) - def expr(self, i: int = None): if i is None: return self.getTypedRuleContexts(ExprParser.ExprContext) else: return self.getTypedRuleContext(ExprParser.ExprContext, i) - def RBRACKET(self): - return self.getToken(ExprParser.RBRACKET, 0) - def accept(self, visitor: ParseTreeVisitor): if hasattr(visitor, "visitTimeRange"): return visitor.visitTimeRange(self) @@ -1288,95 +1579,87 @@ def expr(self, _p: int = 0): _parentState = self.state localctx = ExprParser.ExprContext(self, self._ctx, _parentState) _prevctx = localctx - _startState = 4 - self.enterRecursionRule(localctx, 4, self.RULE_expr, _p) + _startState = 2 + self.enterRecursionRule(localctx, 2, self.RULE_expr, _p) self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) self.state = 69 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input, 3, self._ctx) + la_ = self._interp.adaptivePredict(self._input, 2, self._ctx) if la_ == 1: - localctx = ExprParser.NegationContext(self, localctx) + localctx = ExprParser.UnsignedAtomContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 15 - self.match(ExprParser.T__1) self.state = 16 - self.expr(13) + self.atom() pass elif la_ == 2: - localctx = ExprParser.IdentifierContext(self, localctx) + localctx = ExprParser.PortFieldContext(self, localctx) self._ctx = localctx _prevctx = localctx self.state = 17 self.match(ExprParser.IDENTIFIER) - pass - - elif la_ == 3: - localctx = ExprParser.PortFieldContext(self, localctx) - self._ctx = localctx - _prevctx = localctx self.state = 18 - self.match(ExprParser.IDENTIFIER) + self.match(ExprParser.T__0) self.state = 19 - self.match(ExprParser.T__4) - self.state = 20 self.match(ExprParser.IDENTIFIER) pass - elif la_ == 4: - localctx = ExprParser.NumberContext(self, localctx) + elif la_ == 3: + localctx = ExprParser.NegationContext(self, localctx) self._ctx = localctx _prevctx = localctx + self.state = 20 + self.match(ExprParser.T__1) self.state = 21 - self.match(ExprParser.NUMBER) + self.expr(10) pass - elif la_ == 5: + elif la_ == 4: localctx = ExprParser.ExpressionContext(self, localctx) self._ctx = localctx _prevctx = localctx self.state = 22 - self.match(ExprParser.T__5) + self.match(ExprParser.T__2) self.state = 23 self.expr(0) self.state = 24 - self.match(ExprParser.T__6) + self.match(ExprParser.T__3) pass - elif la_ == 6: + elif la_ == 5: localctx = ExprParser.FunctionContext(self, localctx) self._ctx = localctx _prevctx = localctx self.state = 26 self.match(ExprParser.IDENTIFIER) self.state = 27 - self.match(ExprParser.T__5) + self.match(ExprParser.T__2) self.state = 28 self.expr(0) self.state = 29 - self.match(ExprParser.T__6) + self.match(ExprParser.T__3) pass - elif la_ == 7: + elif la_ == 6: localctx = ExprParser.TimeShiftContext(self, localctx) self._ctx = localctx _prevctx = localctx self.state = 31 self.match(ExprParser.IDENTIFIER) self.state = 32 - self.match(ExprParser.LBRACKET) + self.match(ExprParser.T__7) self.state = 33 self.shift() self.state = 38 self._errHandler.sync(self) _la = self._input.LA(1) - while _la == 8: + while _la == 9: self.state = 34 - self.match(ExprParser.T__7) + self.match(ExprParser.T__8) self.state = 35 self.shift() self.state = 40 @@ -1384,25 +1667,25 @@ def expr(self, _p: int = 0): _la = self._input.LA(1) self.state = 41 - self.match(ExprParser.RBRACKET) + self.match(ExprParser.T__9) pass - elif la_ == 8: + elif la_ == 7: localctx = ExprParser.TimeIndexContext(self, localctx) self._ctx = localctx _prevctx = localctx self.state = 43 self.match(ExprParser.IDENTIFIER) self.state = 44 - self.match(ExprParser.LBRACKET) + self.match(ExprParser.T__7) self.state = 45 self.expr(0) self.state = 50 self._errHandler.sync(self) _la = self._input.LA(1) - while _la == 8: + while _la == 9: self.state = 46 - self.match(ExprParser.T__7) + self.match(ExprParser.T__8) self.state = 47 self.expr(0) self.state = 52 @@ -1410,49 +1693,49 @@ def expr(self, _p: int = 0): _la = self._input.LA(1) self.state = 53 - self.match(ExprParser.RBRACKET) + self.match(ExprParser.T__9) pass - elif la_ == 9: + elif la_ == 8: localctx = ExprParser.TimeShiftRangeContext(self, localctx) self._ctx = localctx _prevctx = localctx self.state = 55 self.match(ExprParser.IDENTIFIER) self.state = 56 - self.match(ExprParser.LBRACKET) + self.match(ExprParser.T__7) self.state = 57 localctx.shift1 = self.shift() self.state = 58 - self.match(ExprParser.T__8) + self.match(ExprParser.T__10) self.state = 59 localctx.shift2 = self.shift() self.state = 60 - self.match(ExprParser.RBRACKET) + self.match(ExprParser.T__9) pass - elif la_ == 10: + elif la_ == 9: localctx = ExprParser.TimeRangeContext(self, localctx) self._ctx = localctx _prevctx = localctx self.state = 62 self.match(ExprParser.IDENTIFIER) self.state = 63 - self.match(ExprParser.LBRACKET) + self.match(ExprParser.T__7) self.state = 64 self.expr(0) self.state = 65 - self.match(ExprParser.T__8) + self.match(ExprParser.T__10) self.state = 66 self.expr(0) self.state = 67 - self.match(ExprParser.RBRACKET) + self.match(ExprParser.T__9) pass self._ctx.stop = self._input.LT(-1) self.state = 82 self._errHandler.sync(self) - _alt = self._interp.adaptivePredict(self._input, 5, self._ctx) + _alt = self._interp.adaptivePredict(self._input, 4, self._ctx) while _alt != 2 and _alt != ATN.INVALID_ALT_NUMBER: if _alt == 1: if self._parseListeners is not None: @@ -1460,7 +1743,7 @@ def expr(self, _p: int = 0): _prevctx = localctx self.state = 80 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input, 4, self._ctx) + la_ = self._interp.adaptivePredict(self._input, 3, self._ctx) if la_ == 1: localctx = ExprParser.MuldivContext( self, ExprParser.ExprContext(self, _parentctx, _parentState) @@ -1469,22 +1752,22 @@ def expr(self, _p: int = 0): localctx, _startState, self.RULE_expr ) self.state = 71 - if not self.precpred(self._ctx, 12): + if not self.precpred(self._ctx, 8): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException( - self, "self.precpred(self._ctx, 12)" + self, "self.precpred(self._ctx, 8)" ) self.state = 72 localctx.op = self._input.LT(1) _la = self._input.LA(1) - if not (_la == 3 or _la == 4): + if not (_la == 5 or _la == 6): localctx.op = self._errHandler.recoverInline(self) else: self._errHandler.reportMatch(self) self.consume() self.state = 73 - self.expr(13) + self.expr(9) pass elif la_ == 2: @@ -1495,22 +1778,22 @@ def expr(self, _p: int = 0): localctx, _startState, self.RULE_expr ) self.state = 74 - if not self.precpred(self._ctx, 11): + if not self.precpred(self._ctx, 7): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException( - self, "self.precpred(self._ctx, 11)" + self, "self.precpred(self._ctx, 7)" ) self.state = 75 localctx.op = self._input.LT(1) _la = self._input.LA(1) - if not (_la == 1 or _la == 2): + if not (_la == 2 or _la == 7): localctx.op = self._errHandler.recoverInline(self) else: self._errHandler.reportMatch(self) self.consume() self.state = 76 - self.expr(12) + self.expr(8) pass elif la_ == 3: @@ -1521,21 +1804,21 @@ def expr(self, _p: int = 0): localctx, _startState, self.RULE_expr ) self.state = 77 - if not self.precpred(self._ctx, 10): + if not self.precpred(self._ctx, 6): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException( - self, "self.precpred(self._ctx, 10)" + self, "self.precpred(self._ctx, 6)" ) self.state = 78 self.match(ExprParser.COMPARISON) self.state = 79 - self.expr(11) + self.expr(7) pass self.state = 84 self._errHandler.sync(self) - _alt = self._interp.adaptivePredict(self._input, 5, self._ctx) + _alt = self._interp.adaptivePredict(self._input, 4, self._ctx) except RecognitionException as re: localctx.exception = re @@ -1545,22 +1828,527 @@ def expr(self, _p: int = 0): self.unrollRecursionContexts(_parentctx) return localctx - def sempred(self, localctx: RuleContext, ruleIndex: int, predIndex: int): - if self._predicates == None: - self._predicates = dict() - self._predicates[2] = self.expr_sempred - pred = self._predicates.get(ruleIndex, None) - if pred is None: - raise Exception("No predicate with index:" + str(ruleIndex)) - else: - return pred(localctx, predIndex) + class AtomContext(ParserRuleContext): + __slots__ = "parser" - def expr_sempred(self, localctx: ExprContext, predIndex: int): - if predIndex == 0: - return self.precpred(self._ctx, 12) + def __init__( + self, parser, parent: ParserRuleContext = None, invokingState: int = -1 + ): + super().__init__(parent, invokingState) + self.parser = parser - if predIndex == 1: - return self.precpred(self._ctx, 11) + def getRuleIndex(self): + return ExprParser.RULE_atom - if predIndex == 2: - return self.precpred(self._ctx, 10) + def copyFrom(self, ctx: ParserRuleContext): + super().copyFrom(ctx) + + class NumberContext(AtomContext): + def __init__( + self, parser, ctx: ParserRuleContext + ): # actually a ExprParser.AtomContext + super().__init__(parser) + self.copyFrom(ctx) + + def NUMBER(self): + return self.getToken(ExprParser.NUMBER, 0) + + def accept(self, visitor: ParseTreeVisitor): + if hasattr(visitor, "visitNumber"): + return visitor.visitNumber(self) + else: + return visitor.visitChildren(self) + + class IdentifierContext(AtomContext): + def __init__( + self, parser, ctx: ParserRuleContext + ): # actually a ExprParser.AtomContext + super().__init__(parser) + self.copyFrom(ctx) + + def IDENTIFIER(self): + return self.getToken(ExprParser.IDENTIFIER, 0) + + def accept(self, visitor: ParseTreeVisitor): + if hasattr(visitor, "visitIdentifier"): + return visitor.visitIdentifier(self) + else: + return visitor.visitChildren(self) + + def atom(self): + localctx = ExprParser.AtomContext(self, self._ctx, self.state) + self.enterRule(localctx, 4, self.RULE_atom) + try: + self.state = 87 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [12]: + localctx = ExprParser.NumberContext(self, localctx) + self.enterOuterAlt(localctx, 1) + self.state = 85 + self.match(ExprParser.NUMBER) + pass + elif token in [14]: + localctx = ExprParser.IdentifierContext(self, localctx) + self.enterOuterAlt(localctx, 2) + self.state = 86 + self.match(ExprParser.IDENTIFIER) + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + class ShiftContext(ParserRuleContext): + __slots__ = "parser" + + def __init__( + self, parser, parent: ParserRuleContext = None, invokingState: int = -1 + ): + super().__init__(parent, invokingState) + self.parser = parser + + def TIME(self): + return self.getToken(ExprParser.TIME, 0) + + def shift_expr(self): + return self.getTypedRuleContext(ExprParser.Shift_exprContext, 0) + + def getRuleIndex(self): + return ExprParser.RULE_shift + + def accept(self, visitor: ParseTreeVisitor): + if hasattr(visitor, "visitShift"): + return visitor.visitShift(self) + else: + return visitor.visitChildren(self) + + def shift(self): + localctx = ExprParser.ShiftContext(self, self._ctx, self.state) + self.enterRule(localctx, 6, self.RULE_shift) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 89 + self.match(ExprParser.TIME) + self.state = 91 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la == 2 or _la == 7: + self.state = 90 + self.shift_expr(0) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + class Shift_exprContext(ParserRuleContext): + __slots__ = "parser" + + def __init__( + self, parser, parent: ParserRuleContext = None, invokingState: int = -1 + ): + super().__init__(parent, invokingState) + self.parser = parser + + def getRuleIndex(self): + return ExprParser.RULE_shift_expr + + def copyFrom(self, ctx: ParserRuleContext): + super().copyFrom(ctx) + + class SignedAtomContext(Shift_exprContext): + def __init__( + self, parser, ctx: ParserRuleContext + ): # actually a ExprParser.Shift_exprContext + super().__init__(parser) + self.op = None # Token + self.copyFrom(ctx) + + def atom(self): + return self.getTypedRuleContext(ExprParser.AtomContext, 0) + + def accept(self, visitor: ParseTreeVisitor): + if hasattr(visitor, "visitSignedAtom"): + return visitor.visitSignedAtom(self) + else: + return visitor.visitChildren(self) + + class SignedExpressionContext(Shift_exprContext): + def __init__( + self, parser, ctx: ParserRuleContext + ): # actually a ExprParser.Shift_exprContext + super().__init__(parser) + self.op = None # Token + self.copyFrom(ctx) + + def expr(self): + return self.getTypedRuleContext(ExprParser.ExprContext, 0) + + def accept(self, visitor: ParseTreeVisitor): + if hasattr(visitor, "visitSignedExpression"): + return visitor.visitSignedExpression(self) + else: + return visitor.visitChildren(self) + + class ShiftMuldivContext(Shift_exprContext): + def __init__( + self, parser, ctx: ParserRuleContext + ): # actually a ExprParser.Shift_exprContext + super().__init__(parser) + self.op = None # Token + self.copyFrom(ctx) + + def shift_expr(self): + return self.getTypedRuleContext(ExprParser.Shift_exprContext, 0) + + def right_expr(self): + return self.getTypedRuleContext(ExprParser.Right_exprContext, 0) + + def accept(self, visitor: ParseTreeVisitor): + if hasattr(visitor, "visitShiftMuldiv"): + return visitor.visitShiftMuldiv(self) + else: + return visitor.visitChildren(self) + + class ShiftAddsubContext(Shift_exprContext): + def __init__( + self, parser, ctx: ParserRuleContext + ): # actually a ExprParser.Shift_exprContext + super().__init__(parser) + self.op = None # Token + self.copyFrom(ctx) + + def shift_expr(self): + return self.getTypedRuleContext(ExprParser.Shift_exprContext, 0) + + def right_expr(self): + return self.getTypedRuleContext(ExprParser.Right_exprContext, 0) + + def accept(self, visitor: ParseTreeVisitor): + if hasattr(visitor, "visitShiftAddsub"): + return visitor.visitShiftAddsub(self) + else: + return visitor.visitChildren(self) + + def shift_expr(self, _p: int = 0): + _parentctx = self._ctx + _parentState = self.state + localctx = ExprParser.Shift_exprContext(self, self._ctx, _parentState) + _prevctx = localctx + _startState = 8 + self.enterRecursionRule(localctx, 8, self.RULE_shift_expr, _p) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 101 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input, 7, self._ctx) + if la_ == 1: + localctx = ExprParser.SignedAtomContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + + self.state = 94 + localctx.op = self._input.LT(1) + _la = self._input.LA(1) + if not (_la == 2 or _la == 7): + localctx.op = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 95 + self.atom() + pass + + elif la_ == 2: + localctx = ExprParser.SignedExpressionContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 96 + localctx.op = self._input.LT(1) + _la = self._input.LA(1) + if not (_la == 2 or _la == 7): + localctx.op = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 97 + self.match(ExprParser.T__2) + self.state = 98 + self.expr(0) + self.state = 99 + self.match(ExprParser.T__3) + pass + + self._ctx.stop = self._input.LT(-1) + self.state = 111 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input, 9, self._ctx) + while _alt != 2 and _alt != ATN.INVALID_ALT_NUMBER: + if _alt == 1: + if self._parseListeners is not None: + self.triggerExitRuleEvent() + _prevctx = localctx + self.state = 109 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input, 8, self._ctx) + if la_ == 1: + localctx = ExprParser.ShiftMuldivContext( + self, + ExprParser.Shift_exprContext( + self, _parentctx, _parentState + ), + ) + self.pushNewRecursionContext( + localctx, _startState, self.RULE_shift_expr + ) + self.state = 103 + if not self.precpred(self._ctx, 4): + from antlr4.error.Errors import FailedPredicateException + + raise FailedPredicateException( + self, "self.precpred(self._ctx, 4)" + ) + self.state = 104 + localctx.op = self._input.LT(1) + _la = self._input.LA(1) + if not (_la == 5 or _la == 6): + localctx.op = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 105 + self.right_expr(0) + pass + + elif la_ == 2: + localctx = ExprParser.ShiftAddsubContext( + self, + ExprParser.Shift_exprContext( + self, _parentctx, _parentState + ), + ) + self.pushNewRecursionContext( + localctx, _startState, self.RULE_shift_expr + ) + self.state = 106 + if not self.precpred(self._ctx, 3): + from antlr4.error.Errors import FailedPredicateException + + raise FailedPredicateException( + self, "self.precpred(self._ctx, 3)" + ) + self.state = 107 + localctx.op = self._input.LT(1) + _la = self._input.LA(1) + if not (_la == 2 or _la == 7): + localctx.op = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 108 + self.right_expr(0) + pass + + self.state = 113 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input, 9, self._ctx) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.unrollRecursionContexts(_parentctx) + return localctx + + class Right_exprContext(ParserRuleContext): + __slots__ = "parser" + + def __init__( + self, parser, parent: ParserRuleContext = None, invokingState: int = -1 + ): + super().__init__(parent, invokingState) + self.parser = parser + + def getRuleIndex(self): + return ExprParser.RULE_right_expr + + def copyFrom(self, ctx: ParserRuleContext): + super().copyFrom(ctx) + + class RightExpressionContext(Right_exprContext): + def __init__( + self, parser, ctx: ParserRuleContext + ): # actually a ExprParser.Right_exprContext + super().__init__(parser) + self.copyFrom(ctx) + + def expr(self): + return self.getTypedRuleContext(ExprParser.ExprContext, 0) + + def accept(self, visitor: ParseTreeVisitor): + if hasattr(visitor, "visitRightExpression"): + return visitor.visitRightExpression(self) + else: + return visitor.visitChildren(self) + + class RightMuldivContext(Right_exprContext): + def __init__( + self, parser, ctx: ParserRuleContext + ): # actually a ExprParser.Right_exprContext + super().__init__(parser) + self.op = None # Token + self.copyFrom(ctx) + + def right_expr(self, i: int = None): + if i is None: + return self.getTypedRuleContexts(ExprParser.Right_exprContext) + else: + return self.getTypedRuleContext(ExprParser.Right_exprContext, i) + + def accept(self, visitor: ParseTreeVisitor): + if hasattr(visitor, "visitRightMuldiv"): + return visitor.visitRightMuldiv(self) + else: + return visitor.visitChildren(self) + + class RightAtomContext(Right_exprContext): + def __init__( + self, parser, ctx: ParserRuleContext + ): # actually a ExprParser.Right_exprContext + super().__init__(parser) + self.copyFrom(ctx) + + def atom(self): + return self.getTypedRuleContext(ExprParser.AtomContext, 0) + + def accept(self, visitor: ParseTreeVisitor): + if hasattr(visitor, "visitRightAtom"): + return visitor.visitRightAtom(self) + else: + return visitor.visitChildren(self) + + def right_expr(self, _p: int = 0): + _parentctx = self._ctx + _parentState = self.state + localctx = ExprParser.Right_exprContext(self, self._ctx, _parentState) + _prevctx = localctx + _startState = 10 + self.enterRecursionRule(localctx, 10, self.RULE_right_expr, _p) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 120 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [3]: + localctx = ExprParser.RightExpressionContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + + self.state = 115 + self.match(ExprParser.T__2) + self.state = 116 + self.expr(0) + self.state = 117 + self.match(ExprParser.T__3) + pass + elif token in [12, 14]: + localctx = ExprParser.RightAtomContext(self, localctx) + self._ctx = localctx + _prevctx = localctx + self.state = 119 + self.atom() + pass + else: + raise NoViableAltException(self) + + self._ctx.stop = self._input.LT(-1) + self.state = 127 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input, 11, self._ctx) + while _alt != 2 and _alt != ATN.INVALID_ALT_NUMBER: + if _alt == 1: + if self._parseListeners is not None: + self.triggerExitRuleEvent() + _prevctx = localctx + localctx = ExprParser.RightMuldivContext( + self, + ExprParser.Right_exprContext(self, _parentctx, _parentState), + ) + self.pushNewRecursionContext( + localctx, _startState, self.RULE_right_expr + ) + self.state = 122 + if not self.precpred(self._ctx, 3): + from antlr4.error.Errors import FailedPredicateException + + raise FailedPredicateException( + self, "self.precpred(self._ctx, 3)" + ) + self.state = 123 + localctx.op = self._input.LT(1) + _la = self._input.LA(1) + if not (_la == 5 or _la == 6): + localctx.op = self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + self.state = 124 + self.right_expr(4) + self.state = 129 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input, 11, self._ctx) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.unrollRecursionContexts(_parentctx) + return localctx + + def sempred(self, localctx: RuleContext, ruleIndex: int, predIndex: int): + if self._predicates == None: + self._predicates = dict() + self._predicates[1] = self.expr_sempred + self._predicates[4] = self.shift_expr_sempred + self._predicates[5] = self.right_expr_sempred + pred = self._predicates.get(ruleIndex, None) + if pred is None: + raise Exception("No predicate with index:" + str(ruleIndex)) + else: + return pred(localctx, predIndex) + + def expr_sempred(self, localctx: ExprContext, predIndex: int): + if predIndex == 0: + return self.precpred(self._ctx, 8) + + if predIndex == 1: + return self.precpred(self._ctx, 7) + + if predIndex == 2: + return self.precpred(self._ctx, 6) + + def shift_expr_sempred(self, localctx: Shift_exprContext, predIndex: int): + if predIndex == 3: + return self.precpred(self._ctx, 4) + + if predIndex == 4: + return self.precpred(self._ctx, 3) + + def right_expr_sempred(self, localctx: Right_exprContext, predIndex: int): + if predIndex == 5: + return self.precpred(self._ctx, 3) diff --git a/src/andromede/expression/parsing/antlr/ExprVisitor.py b/src/andromede/expression/parsing/antlr/ExprVisitor.py index e7871aa9..0e924349 100644 --- a/src/andromede/expression/parsing/antlr/ExprVisitor.py +++ b/src/andromede/expression/parsing/antlr/ExprVisitor.py @@ -14,30 +14,42 @@ class ExprVisitor(ParseTreeVisitor): def visitFullexpr(self, ctx: ExprParser.FullexprContext): return self.visitChildren(ctx) - # Visit a parse tree produced by ExprParser#shift. - def visitShift(self, ctx: ExprParser.ShiftContext): - return self.visitChildren(ctx) - - # Visit a parse tree produced by ExprParser#identifier. - def visitIdentifier(self, ctx: ExprParser.IdentifierContext): - return self.visitChildren(ctx) - # Visit a parse tree produced by ExprParser#negation. def visitNegation(self, ctx: ExprParser.NegationContext): return self.visitChildren(ctx) + # Visit a parse tree produced by ExprParser#unsignedAtom. + def visitUnsignedAtom(self, ctx: ExprParser.UnsignedAtomContext): + return self.visitChildren(ctx) + # Visit a parse tree produced by ExprParser#expression. def visitExpression(self, ctx: ExprParser.ExpressionContext): return self.visitChildren(ctx) + # Visit a parse tree produced by ExprParser#timeIndex. + def visitTimeIndex(self, ctx: ExprParser.TimeIndexContext): + return self.visitChildren(ctx) + # Visit a parse tree produced by ExprParser#comparison. def visitComparison(self, ctx: ExprParser.ComparisonContext): return self.visitChildren(ctx) + # Visit a parse tree produced by ExprParser#timeShift. + def visitTimeShift(self, ctx: ExprParser.TimeShiftContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by ExprParser#function. + def visitFunction(self, ctx: ExprParser.FunctionContext): + return self.visitChildren(ctx) + # Visit a parse tree produced by ExprParser#addsub. def visitAddsub(self, ctx: ExprParser.AddsubContext): return self.visitChildren(ctx) + # Visit a parse tree produced by ExprParser#timeShiftRange. + def visitTimeShiftRange(self, ctx: ExprParser.TimeShiftRangeContext): + return self.visitChildren(ctx) + # Visit a parse tree produced by ExprParser#portField. def visitPortField(self, ctx: ExprParser.PortFieldContext): return self.visitChildren(ctx) @@ -46,28 +58,48 @@ def visitPortField(self, ctx: ExprParser.PortFieldContext): def visitMuldiv(self, ctx: ExprParser.MuldivContext): return self.visitChildren(ctx) + # Visit a parse tree produced by ExprParser#timeRange. + def visitTimeRange(self, ctx: ExprParser.TimeRangeContext): + return self.visitChildren(ctx) + # Visit a parse tree produced by ExprParser#number. def visitNumber(self, ctx: ExprParser.NumberContext): return self.visitChildren(ctx) - # Visit a parse tree produced by ExprParser#timeIndex. - def visitTimeIndex(self, ctx: ExprParser.TimeIndexContext): + # Visit a parse tree produced by ExprParser#identifier. + def visitIdentifier(self, ctx: ExprParser.IdentifierContext): return self.visitChildren(ctx) - # Visit a parse tree produced by ExprParser#timeShift. - def visitTimeShift(self, ctx: ExprParser.TimeShiftContext): + # Visit a parse tree produced by ExprParser#shift. + def visitShift(self, ctx: ExprParser.ShiftContext): return self.visitChildren(ctx) - # Visit a parse tree produced by ExprParser#function. - def visitFunction(self, ctx: ExprParser.FunctionContext): + # Visit a parse tree produced by ExprParser#signedAtom. + def visitSignedAtom(self, ctx: ExprParser.SignedAtomContext): return self.visitChildren(ctx) - # Visit a parse tree produced by ExprParser#timeShiftRange. - def visitTimeShiftRange(self, ctx: ExprParser.TimeShiftRangeContext): + # Visit a parse tree produced by ExprParser#signedExpression. + def visitSignedExpression(self, ctx: ExprParser.SignedExpressionContext): return self.visitChildren(ctx) - # Visit a parse tree produced by ExprParser#timeRange. - def visitTimeRange(self, ctx: ExprParser.TimeRangeContext): + # Visit a parse tree produced by ExprParser#shiftMuldiv. + def visitShiftMuldiv(self, ctx: ExprParser.ShiftMuldivContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by ExprParser#shiftAddsub. + def visitShiftAddsub(self, ctx: ExprParser.ShiftAddsubContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by ExprParser#rightExpression. + def visitRightExpression(self, ctx: ExprParser.RightExpressionContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by ExprParser#rightMuldiv. + def visitRightMuldiv(self, ctx: ExprParser.RightMuldivContext): + return self.visitChildren(ctx) + + # Visit a parse tree produced by ExprParser#rightAtom. + def visitRightAtom(self, ctx: ExprParser.RightAtomContext): return self.visitChildren(ctx) diff --git a/src/andromede/expression/parsing/antlr/__init__.py b/src/andromede/expression/parsing/antlr/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/andromede/expression/parsing/parse_expression.py b/src/andromede/expression/parsing/parse_expression.py index 9e958bfc..e96a70f1 100644 --- a/src/andromede/expression/parsing/parse_expression.py +++ b/src/andromede/expression/parsing/parse_expression.py @@ -55,6 +55,14 @@ class ExpressionNodeBuilderVisitor(ExprVisitor): def visitFullexpr(self, ctx: ExprParser.FullexprContext) -> ExpressionNode: return ctx.expr().accept(self) # type: ignore + # Visit a parse tree produced by ExprParser#number. + def visitNumber(self, ctx: ExprParser.NumberContext) -> ExpressionNode: + return literal(float(ctx.NUMBER().getText())) # type: ignore + + # Visit a parse tree produced by ExprParser#identifier. + def visitIdentifier(self, ctx: ExprParser.IdentifierContext) -> ExpressionNode: + return self._convert_identifier(ctx.IDENTIFIER().getText()) # type: ignore + # Visit a parse tree produced by ExprParser#division. def visitMuldiv(self, ctx: ExprParser.MuldivContext) -> ExpressionNode: left = ctx.expr(0).accept(self) # type: ignore @@ -77,10 +85,6 @@ def visitAddsub(self, ctx: ExprParser.AddsubContext) -> ExpressionNode: return left - right raise ValueError(f"Invalid operator {op}") - # Visit a parse tree produced by ExprParser#number. - def visitNumber(self, ctx: ExprParser.NumberContext) -> ExpressionNode: - return literal(float(ctx.getText())) - # Visit a parse tree produced by ExprParser#negation. def visitNegation(self, ctx: ExprParser.NegationContext) -> ExpressionNode: return -ctx.expr().accept(self) # type: ignore @@ -89,9 +93,9 @@ def visitNegation(self, ctx: ExprParser.NegationContext) -> ExpressionNode: def visitExpression(self, ctx: ExprParser.ExpressionContext) -> ExpressionNode: return ctx.expr().accept(self) # type: ignore - # Visit a parse tree produced by ExprParser#identifier. - def visitIdentifier(self, ctx: ExprParser.IdentifierContext) -> ExpressionNode: - return self._convert_identifier(ctx.IDENTIFIER().getText()) # type: ignore + # Visit a parse tree produced by ExprParser#unsignedAtom. + def visitUnsignedAtom(self, ctx: ExprParser.UnsignedAtomContext) -> ExpressionNode: + return ctx.atom().accept(self) # type: ignore def _convert_identifier(self, identifier: str) -> ExpressionNode: if self.identifiers.is_variable(identifier): @@ -158,13 +162,70 @@ def visitFunction(self, ctx: ExprParser.FunctionContext) -> ExpressionNode: # Visit a parse tree produced by ExprParser#shift. def visitShift(self, ctx: ExprParser.ShiftContext) -> ExpressionNode: - if ctx.expr() is None: # type: ignore + if ctx.shift_expr() is None: # type: ignore return literal(0) - shift = ctx.expr().accept(self) # type: ignore - if ctx.op.text == "-": # type: ignore - return -shift + shift = ctx.shift_expr().accept(self) # type: ignore return shift + # Visit a parse tree produced by ExprParser#shiftAddsub. + def visitShiftAddsub(self, ctx: ExprParser.ShiftAddsubContext) -> ExpressionNode: + left = ctx.shift_expr().accept(self) # type: ignore + right = ctx.right_expr().accept(self) # type: ignore + op = ctx.op.text # type: ignore + if op == "+": + return left + right + elif op == "-": + return left - right + raise ValueError(f"Invalid operator {op}") + + # Visit a parse tree produced by ExprParser#shiftMuldiv. + def visitShiftMuldiv(self, ctx: ExprParser.ShiftMuldivContext) -> ExpressionNode: + left = ctx.shift_expr().accept(self) # type: ignore + right = ctx.right_expr().accept(self) # type: ignore + op = ctx.op.text # type: ignore + if op == "*": + return left * right + elif op == "/": + return left / right + raise ValueError(f"Invalid operator {op}") + + # Visit a parse tree produced by ExprParser#signedExpression. + def visitSignedExpression( + self, ctx: ExprParser.SignedExpressionContext + ) -> ExpressionNode: + if ctx.op.text == "-": # type: ignore + return -ctx.expr().accept(self) # type: ignore + else: + return ctx.expr().accept(self) # type: ignore + + # Visit a parse tree produced by ExprParser#signedAtom. + def visitSignedAtom(self, ctx: ExprParser.SignedAtomContext) -> ExpressionNode: + if ctx.op.text == "-": # type: ignore + return -ctx.atom().accept(self) # type: ignore + else: + return ctx.atom().accept(self) # type: ignore + + # Visit a parse tree produced by ExprParser#rightExpression. + def visitRightExpression( + self, ctx: ExprParser.RightExpressionContext + ) -> ExpressionNode: + return ctx.expr().accept(self) # type: ignore + + # Visit a parse tree produced by ExprParser#rightMuldiv. + def visitRightMuldiv(self, ctx: ExprParser.RightMuldivContext) -> ExpressionNode: + left = ctx.right_expr(0).accept(self) # type: ignore + right = ctx.right_expr(1).accept(self) # type: ignore + op = ctx.op.text # type: ignore + if op == "*": + return left * right + elif op == "/": + return left / right + raise ValueError(f"Invalid operator {op}") + + # Visit a parse tree produced by ExprParser#rightAtom. + def visitRightAtom(self, ctx: ExprParser.RightAtomContext) -> ExpressionNode: + return ctx.atom().accept(self) # type: ignore + _FUNCTIONS = { "sum": ExpressionNode.sum, @@ -190,6 +251,8 @@ def parse_expression(expression: str, identifiers: ModelIdentifiers) -> Expressi return ExpressionNodeBuilderVisitor(identifiers).visit(parser.fullexpr()) # type: ignore + except ValueError as e: + raise AntaresParseException(f"An error occurred during parsing: {e}") from e except Exception as e: raise AntaresParseException( f"An error occurred during parsing: {type(e).__name__}" diff --git a/src/andromede/model/parsing.py b/src/andromede/model/parsing.py index 13bb713f..ef039ef5 100644 --- a/src/andromede/model/parsing.py +++ b/src/andromede/model/parsing.py @@ -12,13 +12,16 @@ import typing from typing import List, Optional -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, ValidationError from yaml import safe_load def parse_yaml_library(input: typing.TextIO) -> "InputLibrary": tree = safe_load(input) - return InputLibrary.model_validate(tree["library"]) + try: + return InputLibrary.model_validate(tree["library"]) + except ValidationError as e: + raise ValueError(f"An error occurred during parsing: {e}") # Design note: actual parsing and validation is delegated to pydantic models @@ -26,58 +29,61 @@ def _to_kebab(snake: str) -> str: return snake.replace("_", "-") -class InputParameter(BaseModel): +class ModifiedBaseModel(BaseModel): + class Config: + alias_generator = _to_kebab + extra = "forbid" + + +class InputParameter(ModifiedBaseModel): name: str time_dependent: bool = False scenario_dependent: bool = False - class Config: - alias_generator = _to_kebab - -class InputVariable(BaseModel): +class InputVariable(ModifiedBaseModel): name: str time_dependent: bool = True scenario_dependent: bool = True lower_bound: Optional[str] = None upper_bound: Optional[str] = None + variable_type: str = "float" class Config: alias_generator = _to_kebab coerce_numbers_to_str = True + extra = "forbid" -class InputConstraint(BaseModel): +class InputConstraint(ModifiedBaseModel): name: str expression: str lower_bound: Optional[str] = None upper_bound: Optional[str] = None - class Config: - alias_generator = _to_kebab - -class InputField(BaseModel): +class InputField(ModifiedBaseModel): name: str -class InputPortType(BaseModel): +class InputPortType(ModifiedBaseModel): id: str fields: List[InputField] = Field(default_factory=list) + description: Optional[str] = None -class InputModelPort(BaseModel): +class InputModelPort(ModifiedBaseModel): name: str type: str -class InputPortFieldDefinition(BaseModel): +class InputPortFieldDefinition(ModifiedBaseModel): port: str field: str definition: str -class InputModel(BaseModel): +class InputModel(ModifiedBaseModel): id: str parameters: List[InputParameter] = Field(default_factory=list) variables: List[InputVariable] = Field(default_factory=list) @@ -86,15 +92,11 @@ class InputModel(BaseModel): binding_constraints: List[InputConstraint] = Field(default_factory=list) constraints: List[InputConstraint] = Field(default_factory=list) objective: Optional[str] = None - - class Config: - alias_generator = _to_kebab + description: Optional[str] = None -class InputLibrary(BaseModel): +class InputLibrary(ModifiedBaseModel): id: str port_types: List[InputPortType] = Field(default_factory=list) models: List[InputModel] = Field(default_factory=list) - - class Config: - alias_generator = _to_kebab + description: Optional[str] = None diff --git a/src/andromede/model/resolve_library.py b/src/andromede/model/resolve_library.py index 0b80d191..546acdbd 100644 --- a/src/andromede/model/resolve_library.py +++ b/src/andromede/model/resolve_library.py @@ -132,7 +132,9 @@ def _to_expression_if_present( def _to_variable(var: InputVariable, identifiers: ModelIdentifiers) -> Variable: return Variable( name=var.name, - data_type=ValueType.FLOAT, + data_type={"float": ValueType.FLOAT, "integer": ValueType.INTEGER}[ + var.variable_type + ], structure=IndexingStructure(var.time_dependent, var.scenario_dependent), lower_bound=_to_expression_if_present(var.lower_bound, identifiers), upper_bound=_to_expression_if_present(var.upper_bound, identifiers), diff --git a/src/andromede/simulation/optimization.py b/src/andromede/simulation/optimization.py index 35c50e4a..4ec9cb63 100644 --- a/src/andromede/simulation/optimization.py +++ b/src/andromede/simulation/optimization.py @@ -36,6 +36,7 @@ from andromede.expression.port_resolver import PortFieldKey, resolve_port from andromede.expression.scenario_operator import Expectation from andromede.expression.time_operator import TimeEvaluation, TimeShift, TimeSum +from andromede.model.common import ValueType from andromede.model.constraint import Constraint from andromede.model.model import PortFieldId from andromede.simulation.linear_expression import LinearExpression, Term @@ -750,11 +751,19 @@ def _create_variables(self) -> None: # Externally, for the Solver, this variable will have a full name # Internally, it will be indexed by a structure that into account # the component id, variable name, timestep and scenario separately - solver_var = self.solver.NumVar( - lower_bound, - upper_bound, - f"{component.id}_{model_var.name}_t{block_timestep}_s{scenario}", - ) + solver_var = None + if model_var.data_type == ValueType.FLOAT: + solver_var = self.solver.NumVar( + lower_bound, + upper_bound, + f"{component.id}_{model_var.name}_t{block_timestep}_s{scenario}", + ) + elif model_var.data_type == ValueType.INTEGER: + solver_var = self.solver.IntVar( + lower_bound, + upper_bound, + f"{component.id}_{model_var.name}_t{block_timestep}_s{scenario}", + ) component_context.add_variable( block_timestep, scenario, model_var.name, solver_var ) @@ -814,7 +823,7 @@ def build_problem( *, problem_name: str = "optimization_problem", border_management: BlockBorderManagement = BlockBorderManagement.CYCLE, - solver_id: str = "GLOP", + solver_id: str = "SCIP", problem_strategy: ModelSelectionStrategy = MergedProblemStrategy(), ) -> OptimizationProblem: """ diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py new file mode 100644 index 00000000..9d443ee1 --- /dev/null +++ b/tests/functional/conftest.py @@ -0,0 +1,33 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. +from pathlib import Path + +import pytest + +from andromede.model.parsing import parse_yaml_library +from andromede.model.resolve_library import resolve_library + + +@pytest.fixture(scope="session") +def libs_dir() -> Path: + return Path(__file__).parent / "libs" + + +@pytest.fixture(scope="session") +def lib(libs_dir: Path): + lib_file = libs_dir / "lib.yml" + + with lib_file.open() as f: + input_lib = parse_yaml_library(f) + + lib = resolve_library(input_lib) + return lib diff --git a/tests/functional/libs/lib.yml b/tests/functional/libs/lib.yml new file mode 100644 index 00000000..27a8b47e --- /dev/null +++ b/tests/functional/libs/lib.yml @@ -0,0 +1,188 @@ +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. +library: + id: basic + description: Basic library + + port-types: + - id: flow + description: A port which transfers power flow + fields: + - name: flow + + models: + - id: node + ports: + - name: balance_port + type: flow + binding-constraints: + - name: balance + expression: sum_connections(balance_port.flow) = 0 + + - id: demand + parameters: + - name: demand + time-dependent: true + scenario-dependent: true + ports: + - name: balance_port + type: flow + port-field-definitions: + - port: balance_port + field: flow + definition: -demand + + - id: production + parameters: + - name: cost + time-dependent: false + scenario-dependent: false + - name: p_max + time-dependent: false + scenario-dependent: false + variables: + - name: generation + lower-bound: 0 + upper-bound: p_max + ports: + - name: balance_port + type: flow + port-field-definitions: + - port: balance_port + field: flow + definition: generation + objective: expec(sum(cost * generation)) + + - id: production_with_min + parameters: + - name: cost + time-dependent: false + scenario-dependent: false + - name: p_max + time-dependent: false + scenario-dependent: false + - name: p_min + time-dependent: false + scenario-dependent: false + variables: + - name: generation + lower-bound: p_min + upper-bound: p_max + ports: + - name: balance_port + type: flow + port-field-definitions: + - port: balance_port + field: flow + definition: generation + objective: expec(sum(cost * generation)) + + - id: link + parameters: + - name: f_max + time-dependent: false + scenario-dependent: false + variables: + - name: input + lower-bound: -f_max + upper-bound: f_max + ports: + - name: out_port + type: flow + - name: in_port + type: flow + port-field-definitions: + - port: out_port + field: flow + definition: input + - port: in_port + field: flow + definition: -input + + - id: spillage + parameters: + - name: cost + time-dependent: false + scenario-dependent: false + variables: + - name: input + lower-bound: 0 + ports: + - name: balance_port + type: flow + port-field-definitions: + - port: balance_port + field: flow + definition: -input + objective: expec(sum(cost * input)) + + - id: unsuplied + parameters: + - name: cost + time-dependent: false + scenario-dependent: false + variables: + - name: output + lower-bound: 0 + ports: + - name: balance_port + type: flow + port-field-definitions: + - port: balance_port + field: flow + definition: output + objective: expec(sum(cost * output)) + + - id: thermal_cluster + parameters: + - name: p_max + - name: p_min + - name: cost + - name: d_min_up + - name: d_min_down + - name: nb_units_max + - name: nb_failures + variables: + - name: nb_units_on + lower-bound: 0 + upper-bound: nb_units_max + variable-type: integer + - name: nb_starting + lower-bound: 0 + upper-bound: nb_units_max + variable-type: integer + - name: nb_stoping + lower-bound: 0 + upper-bound: nb_units_max + variable-type: integer + - name: production + lower-bound: 0 + upper-bound: nb_units_max * p_max + ports: + - name: balance_port + type: flow + port-field-definitions: + - port: balance_port + field: flow + definition: production + constraints: + - name: max production + expression: production <= nb_units_on * p_max + - name: min production + expression: production >= nb_units_on * p_min + - name: on units variation + expression: nb_units_on = nb_units_on[t-1] + nb_starting - nb_stoping + - name: starting time + expression: sum(nb_starting[t-d_min_up + 1 .. t]) <= nb_units_on + - name: stoping time + expression: sum(nb_stoping[t-d_min_down + 1 .. t]) <= nb_units_max - nb_units_on + objective: expec(sum(cost * production)) \ No newline at end of file diff --git a/tests/functional/test_andromede.py b/tests/functional/test_andromede.py index e977605f..6e87b01a 100644 --- a/tests/functional/test_andromede.py +++ b/tests/functional/test_andromede.py @@ -47,166 +47,6 @@ ) -def test_network() -> None: - network = Network("test") - assert network.id == "test" - assert list(network.nodes) == [] - assert list(network.components) == [] - assert list(network.all_components) == [] - assert list(network.connections) == [] - - with pytest.raises(KeyError): - network.get_node("N") - - N1 = Node(model=NODE_BALANCE_MODEL, id="N1") - N2 = Node(model=NODE_BALANCE_MODEL, id="N2") - network.add_node(N1) - network.add_node(N2) - assert list(network.nodes) == [N1, N2] - assert network.get_node(N1.id) == N1 - assert network.get_component("N1") == Node(model=NODE_BALANCE_MODEL, id="N1") - with pytest.raises(KeyError): - network.get_component("unknown") - - -def test_basic_balance() -> None: - """ - Balance on one node with one fixed demand and one generation, on 1 timestep. - """ - - database = DataBase() - database.add_data("D", "demand", ConstantData(100)) - - database.add_data("G", "p_max", ConstantData(100)) - database.add_data("G", "cost", ConstantData(30)) - - node = Node(model=NODE_BALANCE_MODEL, id="N") - demand = create_component( - model=DEMAND_MODEL, - id="D", - ) - - gen = create_component( - model=GENERATOR_MODEL, - id="G", - ) - - network = Network("test") - network.add_node(node) - network.add_component(demand) - network.add_component(gen) - network.connect(PortRef(demand, "balance_port"), PortRef(node, "balance_port")) - network.connect(PortRef(gen, "balance_port"), PortRef(node, "balance_port")) - - scenarios = 1 - problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) - status = problem.solver.Solve() - - assert status == problem.solver.OPTIMAL - assert problem.solver.Objective().Value() == 3000 - - -def test_link() -> None: - """ - Balance on one node with one fixed demand and one generation, on 1 timestep. - """ - - database = DataBase() - database.add_data("D", "demand", ConstantData(100)) - - database.add_data("G", "p_max", ConstantData(100)) - database.add_data("G", "cost", ConstantData(35)) - - database.add_data("L", "f_max", ConstantData(150)) - - node1 = Node(model=NODE_BALANCE_MODEL, id="1") - node2 = Node(model=NODE_BALANCE_MODEL, id="2") - demand = create_component( - model=DEMAND_MODEL, - id="D", - ) - gen = create_component( - model=GENERATOR_MODEL, - id="G", - ) - link = create_component( - model=LINK_MODEL, - id="L", - ) - - network = Network("test") - network.add_node(node1) - network.add_node(node2) - network.add_component(demand) - network.add_component(gen) - network.add_component(link) - network.connect(PortRef(demand, "balance_port"), PortRef(node1, "balance_port")) - network.connect(PortRef(gen, "balance_port"), PortRef(node2, "balance_port")) - network.connect(PortRef(link, "balance_port_from"), PortRef(node1, "balance_port")) - network.connect(PortRef(link, "balance_port_to"), PortRef(node2, "balance_port")) - - scenarios = 1 - problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) - status = problem.solver.Solve() - - assert status == problem.solver.OPTIMAL - assert problem.solver.Objective().Value() == 3500 - - for variable in problem.solver.variables(): - if "balance_port_from" in variable.name(): - assert variable.solution_value() == 100 - if "balance_port_to" in variable.name(): - assert variable.solution_value() == -100 - - -def test_stacking_generation() -> None: - """ - Balance on one node with one fixed demand and 2 generations with different costs, on 1 timestep. - """ - - database = DataBase() - database.add_data("D", "demand", ConstantData(150)) - - database.add_data("G1", "p_max", ConstantData(100)) - database.add_data("G1", "cost", ConstantData(30)) - - database.add_data("G2", "p_max", ConstantData(100)) - database.add_data("G2", "cost", ConstantData(50)) - - node1 = Node(model=NODE_BALANCE_MODEL, id="1") - - demand = create_component( - model=DEMAND_MODEL, - id="D", - ) - - gen1 = create_component( - model=GENERATOR_MODEL, - id="G1", - ) - - gen2 = create_component( - model=GENERATOR_MODEL, - id="G2", - ) - - network = Network("test") - network.add_node(node1) - network.add_component(demand) - network.add_component(gen1) - network.add_component(gen2) - network.connect(PortRef(demand, "balance_port"), PortRef(node1, "balance_port")) - network.connect(PortRef(gen1, "balance_port"), PortRef(node1, "balance_port")) - network.connect(PortRef(gen2, "balance_port"), PortRef(node1, "balance_port")) - - scenarios = 1 - problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) - status = problem.solver.Solve() - - assert status == problem.solver.OPTIMAL - assert problem.solver.Objective().Value() == 30 * 100 + 50 * 50 - - def test_timeseries() -> None: """ Basic case with 2 timesteps, where the demand is 100 on first timestep and 50 on second timestep. @@ -334,146 +174,6 @@ def test_variable_bound() -> None: assert status == problem.solver.INFEASIBLE # Infeasible -def test_spillage() -> None: - """ - Balance on one node with one fixed demand and 1 generation higher than demand and 1 timestep . - """ - - database = DataBase() - database.add_data("D", "demand", ConstantData(150)) - database.add_data("S", "cost", ConstantData(10)) - - database.add_data("G1", "p_max", ConstantData(300)) - database.add_data("G1", "p_min", ConstantData(200)) - database.add_data("G1", "cost", ConstantData(30)) - - node = Node(model=NODE_BALANCE_MODEL, id="1") - spillage = create_component(model=SPILLAGE_MODEL, id="S") - demand = create_component(model=DEMAND_MODEL, id="D") - - gen1 = create_component( - model=GENERATOR_MODEL_WITH_PMIN, - id="G1", - ) - - network = Network("test") - network.add_node(node) - network.add_component(demand) - network.add_component(gen1) - network.add_component(spillage) - network.connect(PortRef(demand, "balance_port"), PortRef(node, "balance_port")) - network.connect(PortRef(gen1, "balance_port"), PortRef(node, "balance_port")) - network.connect(PortRef(spillage, "balance_port"), PortRef(node, "balance_port")) - - problem = build_problem(network, database, TimeBlock(0, [1]), 1) - status = problem.solver.Solve() - - assert status == problem.solver.OPTIMAL - assert problem.solver.Objective().Value() == 30 * 200 + 50 * 10 - - -def test_min_up_down_times() -> None: - """ - Model on 3 time steps with one thermal generation and one demand on a single node. - - Demand is the following time series : [500 MW, 0, 0] - - Thermal generation is characterized with: - - P_min = 100 MW - - P_max = 500 MW - - Min up/down time = 3 - - Generation cost = 100€ / MWh - - Unsupplied energy = 3000 €/MWh - - Spillage = 10 €/MWh - - The optimal solution consists is turning on the thermal plant, which must then stay on for the 3 timesteps and producing [500, 100, 100] to satisfy P_min constraints. - - The optimal cost is then : - 500 x 100 (prod step 1) - + 100 x 100 (prod step 2) - + 100 x 100 (prod step 3) - + 100 x 10 (spillage step 2) - + 100 x 10 (spillage step 3) - = 72 000 - """ - - database = DataBase() - - database.add_data("G", "p_max", ConstantData(500)) - database.add_data("G", "p_min", ConstantData(100)) - database.add_data("G", "cost", ConstantData(100)) - database.add_data("G", "d_min_up", ConstantData(3)) - database.add_data("G", "d_min_down", ConstantData(3)) - database.add_data("G", "nb_units_max", ConstantData(1)) - database.add_data("G", "nb_failures", ConstantData(0)) - - database.add_data("U", "cost", ConstantData(3000)) - database.add_data("S", "cost", ConstantData(10)) - - demand_data = pd.DataFrame( - [ - [500], - [0], - [0], - ], - index=[0, 1, 2], - columns=[0], - ) - - demand_time_scenario_series = TimeScenarioSeriesData(demand_data) - database.add_data("D", "demand", demand_time_scenario_series) - - time_block = TimeBlock(1, [0, 1, 2]) - scenarios = 1 - - node = Node(model=NODE_BALANCE_MODEL, id="1") - demand = create_component(model=DEMAND_MODEL, id="D") - - gen = create_component(model=THERMAL_CLUSTER_MODEL_HD, id="G") - - spillage = create_component(model=SPILLAGE_MODEL, id="S") - - unsupplied_energy = create_component(model=UNSUPPLIED_ENERGY_MODEL, id="U") - - network = Network("test") - network.add_node(node) - network.add_component(demand) - network.add_component(gen) - network.add_component(spillage) - network.add_component(unsupplied_energy) - network.connect(PortRef(demand, "balance_port"), PortRef(node, "balance_port")) - network.connect(PortRef(gen, "balance_port"), PortRef(node, "balance_port")) - network.connect(PortRef(spillage, "balance_port"), PortRef(node, "balance_port")) - network.connect( - PortRef(unsupplied_energy, "balance_port"), PortRef(node, "balance_port") - ) - - problem = build_problem( - network, - database, - time_block, - scenarios, - border_management=BlockBorderManagement.CYCLE, - ) - status = problem.solver.Solve() - - assert status == problem.solver.OPTIMAL - assert problem.solver.Objective().Value() == 72000 - - output = OutputValues(problem) - expected_output = OutputValues() - expected_output.component("G").var("generation").value = [[500.0, 100.0, 100.0]] - expected_output.component("G").var("nb_on").value = [[1.0, 1.0, 1.0]] - expected_output.component("G").var("nb_start").value = [[-0.0, 0.0, 0.0]] - expected_output.component("G").var("nb_stop").value = [[0.0, 0.0, 0.0]] - - expected_output.component("S").var("spillage").value = [[0.0, 100.0, 100.0]] - expected_output.component("U").var("unsupplied_energy").value = [[0.0, 0.0, 0.0]] - - # TODO this test should pass with the next port implementation - # assert output == expected_output, f"Output differs from expected: {output}" - - print(f"Variables values: {output}") - - def generate_data( efficiency: float, horizon: int, scenarios: int ) -> TimeScenarioSeriesData: @@ -544,7 +244,7 @@ def short_term_storage_base(efficiency: float, horizon: int) -> None: # The short-term storage should satisfy the load # No spillage / unsupplied energy is expected - assert problem.solver.Objective().Value() == 0 + assert problem.solver.Objective().Value() == pytest.approx(0, abs=0.01) count_variables = 0 for variable in problem.solver.variables(): diff --git a/tests/functional/test_andromede_yml.py b/tests/functional/test_andromede_yml.py new file mode 100644 index 00000000..c959f0f4 --- /dev/null +++ b/tests/functional/test_andromede_yml.py @@ -0,0 +1,483 @@ +import pandas as pd +import pytest + +from andromede.expression import literal, param, var +from andromede.expression.indexing_structure import IndexingStructure +from andromede.model import Model, ModelPort, float_parameter, float_variable, model +from andromede.model.model import PortFieldDefinition, PortFieldId +from andromede.simulation import ( + BlockBorderManagement, + OutputValues, + TimeBlock, + build_problem, +) +from andromede.study import ( + ConstantData, + DataBase, + Network, + Node, + PortRef, + TimeScenarioIndex, + TimeScenarioSeriesData, + create_component, +) + + +def test_network(lib) -> None: + network = Network("test") + assert network.id == "test" + assert list(network.nodes) == [] + assert list(network.components) == [] + assert list(network.all_components) == [] + assert list(network.connections) == [] + + with pytest.raises(KeyError): + network.get_node("N") + + node_model = lib.models["node"] + + N1 = Node(model=node_model, id="N1") + N2 = Node(model=node_model, id="N2") + network.add_node(N1) + network.add_node(N2) + assert list(network.nodes) == [N1, N2] + assert network.get_node(N1.id) == N1 + assert network.get_component("N1") == Node(model=node_model, id="N1") + with pytest.raises(KeyError): + network.get_component("unknown") + + +def test_basic_balance(lib) -> None: + """ + Balance on one node with one fixed demand and one generation, on 1 timestep. + """ + + database = DataBase() + database.add_data("D", "demand", ConstantData(100)) + + database.add_data("G", "p_max", ConstantData(100)) + database.add_data("G", "cost", ConstantData(30)) + + node_model = lib.models["node"] + demand_model = lib.models["demand"] + production_model = lib.models["production"] + + node = Node(model=node_model, id="N") + demand = create_component( + model=demand_model, + id="D", + ) + + gen = create_component( + model=production_model, + id="G", + ) + + network = Network("test") + network.add_node(node) + network.add_component(demand) + network.add_component(gen) + network.connect(PortRef(demand, "balance_port"), PortRef(node, "balance_port")) + network.connect(PortRef(gen, "balance_port"), PortRef(node, "balance_port")) + + scenarios = 1 + problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) + status = problem.solver.Solve() + + assert status == problem.solver.OPTIMAL + assert problem.solver.Objective().Value() == 3000 + + +def test_link(lib) -> None: + """ + Balance on one node with one fixed demand and one generation, on 1 timestep. + """ + + database = DataBase() + database.add_data("D", "demand", ConstantData(100)) + + database.add_data("G", "p_max", ConstantData(100)) + database.add_data("G", "cost", ConstantData(35)) + + database.add_data("L", "f_max", ConstantData(150)) + + node_model = lib.models["node"] + demand_model = lib.models["demand"] + production_model = lib.models["production"] + link_model = lib.models["link"] + + node1 = Node(model=node_model, id="1") + node2 = Node(model=node_model, id="2") + demand = create_component( + model=demand_model, + id="D", + ) + gen = create_component( + model=production_model, + id="G", + ) + link = create_component( + model=link_model, + id="L", + ) + + network = Network("test") + network.add_node(node1) + network.add_node(node2) + network.add_component(demand) + network.add_component(gen) + network.add_component(link) + network.connect(PortRef(demand, "balance_port"), PortRef(node1, "balance_port")) + network.connect(PortRef(gen, "balance_port"), PortRef(node2, "balance_port")) + network.connect(PortRef(link, "in_port"), PortRef(node1, "balance_port")) + network.connect(PortRef(link, "out_port"), PortRef(node2, "balance_port")) + + scenarios = 1 + problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) + status = problem.solver.Solve() + + assert status == problem.solver.OPTIMAL + assert problem.solver.Objective().Value() == 3500 + + for variable in problem.solver.variables(): + if "balance_port_from" in variable.name(): + assert variable.solution_value() == 100 + if "balance_port_to" in variable.name(): + assert variable.solution_value() == -100 + + +def test_stacking_generation(lib) -> None: + """ + Balance on one node with one fixed demand and 2 generations with different costs, on 1 timestep. + """ + + database = DataBase() + database.add_data("D", "demand", ConstantData(150)) + + database.add_data("G1", "p_max", ConstantData(100)) + database.add_data("G1", "cost", ConstantData(30)) + + database.add_data("G2", "p_max", ConstantData(100)) + database.add_data("G2", "cost", ConstantData(50)) + + node_model = lib.models["node"] + demand_model = lib.models["demand"] + production_model = lib.models["production"] + + node1 = Node(model=node_model, id="1") + + demand = create_component( + model=demand_model, + id="D", + ) + + gen1 = create_component( + model=production_model, + id="G1", + ) + + gen2 = create_component( + model=production_model, + id="G2", + ) + + network = Network("test") + network.add_node(node1) + network.add_component(demand) + network.add_component(gen1) + network.add_component(gen2) + network.connect(PortRef(demand, "balance_port"), PortRef(node1, "balance_port")) + network.connect(PortRef(gen1, "balance_port"), PortRef(node1, "balance_port")) + network.connect(PortRef(gen2, "balance_port"), PortRef(node1, "balance_port")) + + scenarios = 1 + problem = build_problem(network, database, TimeBlock(1, [0]), scenarios) + status = problem.solver.Solve() + + assert status == problem.solver.OPTIMAL + assert problem.solver.Objective().Value() == 30 * 100 + 50 * 50 + + +def test_spillage(lib) -> None: + """ + Balance on one node with one fixed demand and 1 generation higher than demand and 1 timestep . + """ + + database = DataBase() + database.add_data("D", "demand", ConstantData(150)) + database.add_data("S", "cost", ConstantData(10)) + + database.add_data("G1", "p_max", ConstantData(300)) + database.add_data("G1", "p_min", ConstantData(200)) + database.add_data("G1", "cost", ConstantData(30)) + + node_model = lib.models["node"] + demand_model = lib.models["demand"] + production_with_min_model = lib.models["production_with_min"] + spillage_model = lib.models["spillage"] + + node = Node(model=node_model, id="1") + spillage = create_component(model=spillage_model, id="S") + demand = create_component(model=demand_model, id="D") + + gen1 = create_component(model=production_with_min_model, id="G1") + + network = Network("test") + network.add_node(node) + network.add_component(demand) + network.add_component(gen1) + network.add_component(spillage) + network.connect(PortRef(demand, "balance_port"), PortRef(node, "balance_port")) + network.connect(PortRef(gen1, "balance_port"), PortRef(node, "balance_port")) + network.connect(PortRef(spillage, "balance_port"), PortRef(node, "balance_port")) + + problem = build_problem(network, database, TimeBlock(0, [1]), 1) + status = problem.solver.Solve() + + assert status == problem.solver.OPTIMAL + assert problem.solver.Objective().Value() == 30 * 200 + 50 * 10 + + +def test_min_up_down_times(lib) -> None: + """ + Model on 3 time steps with one thermal generation and one demand on a single node. + - Demand is the following time series : [500 MW, 0, 0] + - Thermal generation is characterized with: + - P_min = 100 MW + - P_max = 500 MW + - Min up/down time = 3 + - Generation cost = 100€ / MWh + - Unsupplied energy = 3000 €/MWh + - Spillage = 10 €/MWh + + The optimal solution consists is turning on the thermal plant, which must then stay on for the 3 timesteps and producing [500, 100, 100] to satisfy P_min constraints. + + The optimal cost is then : + 500 x 100 (prod step 1) + + 100 x 100 (prod step 2) + + 100 x 100 (prod step 3) + + 100 x 10 (spillage step 2) + + 100 x 10 (spillage step 3) + = 72 000 + """ + + database = DataBase() + + database.add_data("G", "p_max", ConstantData(500)) + database.add_data("G", "p_min", ConstantData(100)) + database.add_data("G", "cost", ConstantData(100)) + database.add_data("G", "d_min_up", ConstantData(3)) + database.add_data("G", "d_min_down", ConstantData(3)) + database.add_data("G", "nb_units_max", ConstantData(1)) + database.add_data("G", "nb_failures", ConstantData(0)) + + database.add_data("U", "cost", ConstantData(3000)) + database.add_data("S", "cost", ConstantData(10)) + + demand_data = pd.DataFrame( + [ + [500], + [0], + [0], + ], + index=[0, 1, 2], + columns=[0], + ) + demand_time_scenario_series = TimeScenarioSeriesData(demand_data) + database.add_data("D", "demand", demand_time_scenario_series) + + time_block = TimeBlock(1, [0, 1, 2]) + scenarios = 1 + + node_model = lib.models["node"] + demand_model = lib.models["demand"] + spillage_model = lib.models["spillage"] + unsuplied_model = lib.models["unsuplied"] + thermal_cluster = lib.models["thermal_cluster"] + + node = Node(model=node_model, id="1") + demand = create_component(model=demand_model, id="D") + + gen = create_component(model=thermal_cluster, id="G") + + spillage = create_component(model=spillage_model, id="S") + + unsupplied_energy = create_component(model=unsuplied_model, id="U") + + network = Network("test") + network.add_node(node) + network.add_component(demand) + network.add_component(gen) + network.add_component(spillage) + network.add_component(unsupplied_energy) + network.connect(PortRef(demand, "balance_port"), PortRef(node, "balance_port")) + network.connect(PortRef(gen, "balance_port"), PortRef(node, "balance_port")) + network.connect(PortRef(spillage, "balance_port"), PortRef(node, "balance_port")) + network.connect( + PortRef(unsupplied_energy, "balance_port"), PortRef(node, "balance_port") + ) + + problem = build_problem( + network, + database, + time_block, + scenarios, + border_management=BlockBorderManagement.CYCLE, + ) + status = problem.solver.Solve() + + print(OutputValues(problem).component("G").var("nb_units_on").value) + + assert status == problem.solver.OPTIMAL + assert problem.solver.Objective().Value() == pytest.approx(72000, abs=0.01) + + +def test_changing_demand(lib) -> None: + """ + Model on 3 time steps simple production, demand + - P_max = 500 MW + - Generation cost = 100€ / MWh + """ + + database = DataBase() + + database.add_data("G", "p_max", ConstantData(500)) + database.add_data("G", "cost", ConstantData(100)) + + demand_data = pd.DataFrame( + [ + [300], + [100], + [0], + ], + index=[0, 1, 2], + columns=[0], + ) + demand_time_scenario_series = TimeScenarioSeriesData(demand_data) + database.add_data("D", "demand", demand_time_scenario_series) + + time_block = TimeBlock(1, [0, 1, 2]) + scenarios = 1 + + node_model = lib.models["node"] + demand_model = lib.models["demand"] + production_model = lib.models["production"] + + node = Node(model=node_model, id="1") + demand = create_component(model=demand_model, id="D") + + prod = create_component(model=production_model, id="G") + + network = Network("test") + network.add_node(node) + network.add_component(demand) + network.add_component(prod) + network.connect(PortRef(demand, "balance_port"), PortRef(node, "balance_port")) + network.connect(PortRef(prod, "balance_port"), PortRef(node, "balance_port")) + + problem = build_problem( + network, + database, + time_block, + scenarios, + border_management=BlockBorderManagement.CYCLE, + ) + status = problem.solver.Solve() + + assert status == problem.solver.OPTIMAL + assert problem.solver.Objective().Value() == 40000 + + +def test_min_up_down_times_2(lib) -> None: + """ + Model on 3 time steps with one thermal generation and one demand on a single node. + - Demand is the following time series : [500 MW, 0, 0] + - Thermal generation is characterized with: + - P_min = 100 MW + - P_max = 500 MW + - Min up/down time = 2 + - Generation cost = 100€ / MWh + - Unsupplied energy = 3000 €/MWh + - Spillage = 10 €/MWh + + The optimal solution consists is turning on the thermal plant, which must then stay on for the 3 timesteps and producing [500, 100, 100] to satisfy P_min constraints. + + The optimal cost is then : + 500 x 100 (prod step 1) + + 100 x 100 (prod step 2) + + 0 x 100 (prod step 3) + + 100 x 10 (spillage step 2) + + 0 x 10 (spillage step 3) + = 61 000 + """ + + database = DataBase() + + database.add_data("G", "p_max", ConstantData(500)) + database.add_data("G", "p_min", ConstantData(100)) + database.add_data("G", "cost", ConstantData(100)) + database.add_data("G", "d_min_up", ConstantData(2)) + database.add_data("G", "d_min_down", ConstantData(1)) + database.add_data("G", "nb_units_max", ConstantData(1)) + database.add_data("G", "nb_failures", ConstantData(0)) + + database.add_data("U", "cost", ConstantData(3000)) + database.add_data("S", "cost", ConstantData(10)) + + demand_data = pd.DataFrame( + [ + [500], + [0], + [0], + ], + index=[0, 1, 2], + columns=[0], + ) + demand_time_scenario_series = TimeScenarioSeriesData(demand_data) + database.add_data("D", "demand", demand_time_scenario_series) + + time_block = TimeBlock(1, [0, 1, 2]) + scenarios = 1 + + node_model = lib.models["node"] + demand_model = lib.models["demand"] + spillage_model = lib.models["spillage"] + unsuplied_model = lib.models["unsuplied"] + thermal_cluster = lib.models["thermal_cluster"] + + node = Node(model=node_model, id="1") + demand = create_component(model=demand_model, id="D") + + gen = create_component(model=thermal_cluster, id="G") + + spillage = create_component(model=spillage_model, id="S") + + unsupplied_energy = create_component(model=unsuplied_model, id="U") + + network = Network("test") + network.add_node(node) + network.add_component(demand) + network.add_component(gen) + network.add_component(spillage) + network.add_component(unsupplied_energy) + network.connect(PortRef(demand, "balance_port"), PortRef(node, "balance_port")) + network.connect(PortRef(gen, "balance_port"), PortRef(node, "balance_port")) + network.connect(PortRef(spillage, "balance_port"), PortRef(node, "balance_port")) + network.connect( + PortRef(unsupplied_energy, "balance_port"), PortRef(node, "balance_port") + ) + + problem = build_problem( + network, + database, + time_block, + scenarios, + border_management=BlockBorderManagement.CYCLE, + ) + status = problem.solver.Solve() + + print(problem.solver.ExportModelAsMpsFormat(fixed_format=False, obfuscated=False)) + print(OutputValues(problem).component("G").var("nb_units_on").value) + + assert status == problem.solver.OPTIMAL + assert problem.solver.Objective().Value() == pytest.approx(61000) diff --git a/tests/unittests/data/lib.yml b/tests/unittests/data/lib.yml index 2941c9b9..f42df312 100644 --- a/tests/unittests/data/lib.yml +++ b/tests/unittests/data/lib.yml @@ -143,23 +143,23 @@ library: scenario-dependent: true variables: - name: generation - lower_bound: 0 - upper_bound: nb_units_max * p_max + lower-bound: 0 + upper-bound: nb_units_max * p_max time-dependent: true scenario-dependent: true - name: nb_on - lower_bound: 0 - upper_bound: nb_units_max + lower-bound: 0 + upper-bound: nb_units_max time-dependent: true scenario-dependent: false - name: nb_stop - lower_bound: 0 - upper_bound: nb_units_max + lower-bound: 0 + upper-bound: nb_units_max time-dependent: true scenario-dependent: false - name: nb_start - lower_bound: 0 - upper_bound: nb_units_max + lower-bound: 0 + upper-bound: nb_units_max time-dependent: true scenario-dependent: false ports: diff --git a/tests/unittests/expressions/parsing/test_expression_parsing.py b/tests/unittests/expressions/parsing/test_expression_parsing.py index 5aab206d..13fb04a3 100644 --- a/tests/unittests/expressions/parsing/test_expression_parsing.py +++ b/tests/unittests/expressions/parsing/test_expression_parsing.py @@ -57,6 +57,36 @@ "x[t-1, t+4]", var("x").shift([-literal(1), literal(4)]), ), + ( + {"x"}, + {}, + "x[t-1+1]", + var("x").shift(-literal(1) + literal(1)), + ), + ( + {"x"}, + {"d"}, + "x[t-d+1]", + var("x").shift(-param("d") + literal(1)), + ), + ( + {"x"}, + {"d"}, + "x[t-2*d+1]", + var("x").shift(-literal(2) * param("d") + literal(1)), + ), + ( + {"x"}, + {"d"}, + "x[t-1+d*2]", + var("x").shift(-literal(1) + param("d") * literal(2)), + ), + ( + {"x"}, + {"d"}, + "x[t-2-d+1]", + var("x").shift(-literal(2) - param("d") + literal(1)), + ), ( {"x"}, {}, @@ -137,6 +167,7 @@ def test_parsing_visitor( "1 6", "x[t+1-t]", "x[2*t]", + "x[t 4]", ], ) def test_parse_cancellation_should_throw(expression_str: str):