/* The Lingua Franca grammar. */

/*************
Copyright (c) 2020, The University of California at Berkeley.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
***************/

/**
 * Grammar for Lingua Franca.
 * A note of caution: extending this grammar with productions that introduce
 * new terminals (i.e., anything written between quotes), will require those
 * terminals to also be added to the Token production at the bottom of this
 * file. Failing to do so will cause a parse error whenever a terminal not
 * listed in the Token production is featured in a segment of target-language
 * code (e.g., a reaction body).
 * @author{Marten Lohstroh <marten@berkeley.edu>}
 * @author{Edward A. Lee <eal@berkeley.edu>}
 */

grammar org.lflang.LF
    hidden(WS, ML_COMMENT, SL_COMMENT)

import "http://www.eclipse.org/emf/2002/Ecore" as ecore

// Use the package name "lf" for generated files defining the classes
// in the metamodel (i.e., the classes representing nodes in the
// abstract syntax tree (AST), i.e., the eCore model).
generate lf "https://lf-lang.org"

/////////// Overall file

/**
 * Top-level AST node.
 */
Model:
    target=TargetDecl
    (imports+=Import)*
    (preambles+=Preamble)*
    (reactors+=Reactor)+
    ;

/////////// Top level elements

/**
 * Import declaration.
 */
Import: 'import' reactorClasses+=ImportedReactor (',' reactorClasses+=ImportedReactor)* 'from' (importURI=STRING | '<' importPackage=Path '>') ';'?;

ReactorDecl: Reactor | ImportedReactor;

ImportedReactor: reactorClass=[Reactor] ('as' name=ID)?;

/**
 * Declaration of a reactor class.
 */
Reactor:
    {Reactor} (attributes+=Attribute)* ((federated?='federated' | main?='main')? & realtime?='realtime'?) 'reactor' (name=ID)?
    ('<' typeParms+=TypeParm (',' typeParms+=TypeParm)* '>')?
    ('(' parameters+=Parameter (',' parameters+=Parameter)* ')')?
    ('at' host=Host)?
    ('extends' (superClasses+=[ReactorDecl] (',' superClasses+=[ReactorDecl])*))?
    '{'
    (     (preambles+=Preamble)
        | (stateVars+=StateVar)
        | (methods+=Method)
        | (inputs+=Input)
        | (outputs+=Output)
        | (timers+=Timer)
        | (actions+=Action)
        | (watchdogs+=Watchdog)
        | (instantiations+=Instantiation)
        | (connections+=Connection)
        | (reactions+=Reaction)
        | (modes+=Mode)
    )* '}';


TypeParm:
    literal=TypeExpr | code=Code
;

// Allows simple statements like "A extends B". We probably want to further expand this.
TypeExpr:
    ID+
;

/**
 * Specification of the target language. Target properties can be specified in
 * YAML format to pass on configuration details to the runtime environment.
 */
TargetDecl:
    'target' name=ID (config=KeyValuePairs)? ';'?;


/////////// Statements

/**
 * Declaration of a state variable. Types are optional, but may be required
 * during validation (depending on the target language). Initialization is also
 * optional. A state variable can be initialized by assigning a `Expression` or list
 * of these. Note that a `Expression` may also be a reference to a parameter.
 * The following checks must be carried out during validation:
 *  - if the list of initialization expressions has more than one element in it, a
 *  type must be specified;
 *  - if the `time` type is specified, there can only be a single initialization
 *  element, which has to denote a time or a reference to a parameter that
 *  denotes a time; and
 *  - if the `time` type is specified, either a proper time interval and unit
 *  must be given, or a literal or code that denotes zero.
 */
 StateVar:
    (attributes+=Attribute)*
    (reset?='reset')? 'state' name=ID
    (':' type=Type)?
    init=Initializer?
    ';'?
;

// An initializer expression. Used for state vars, parameters and parameter assignments.
Initializer:
    assign?='=' expr=Expression
    // The above case is the default. The lower alternative rules are only
    // relevant for the C++ target.
    | expr=BracedListExpression
    | expr=ParenthesisListExpression
;

Method:
    const?='const'? 'method' name=ID
    '(' (arguments+=MethodArgument (',' arguments+=MethodArgument)*)? ')'
    (':' return=Type)?
    code=Code
    ';'?
;

MethodArgument:
    name=ID (':' type=Type)?
;

Input:
    (attributes+=Attribute)* mutable?='mutable'? 'input' (widthSpec=WidthSpec)? name=ID (':' type=Type)? ';'?;

Output:
    (attributes+=Attribute)* 'output' (widthSpec=WidthSpec)? name=ID (':' type=Type)? ';'?;

// Timing specification for a timer: (offset, period)
// Can be empty, which means (0,0) = (NOW, ONCE).
// E.g. (0) or (NOW) or (NOW, ONCE) or (100, 1000)
// The latter means fire with period 1000, offset 100.
Timer:
   (attributes+=Attribute)* 'timer' name=ID ('(' offset=Expression (',' period=Expression)? ')')? ';'?;

Mode:
    {Mode} (initial?='initial')? 'mode' (name=ID)?
    '{' (
        (stateVars+=StateVar) |
        (timers+=Timer) |
        (actions+=Action) |
        (watchdogs+=Watchdog) |
        (instantiations+=Instantiation) |
        (connections+=Connection) |
        (reactions+=Reaction)
    )* '}';

// Action that has either a physical or logical origin.
//
// If the origin is logical, the minDelay is a minimum logical delay
// after the logical time at which schedule() is called that the
// action will occur. If the origin is physical, then the
// minDelay is a minimum logical delay after the physical time
// at which schedule() is called that the action will occur.
//
// For all actions, minSpacing is the minimum difference between
// the tags of two subsequently scheduled events.
Action:
    (attributes+=Attribute)*
    (origin=ActionOrigin)? 'action' name=ID
    ('(' minDelay=Expression (',' minSpacing=Expression (',' policy=STRING)? )? ')')?
    (':' type=Type)? ';'?;

Reaction:
    (attributes+=Attribute)*
    (('reaction') | mutation ?= 'mutation')
    (name=ID)?
    ('(' (triggers+=TriggerRef (',' triggers+=TriggerRef)*)? ')')
    ( => sources+=VarRef (',' sources+=VarRef)*)?
    ('->' effects+=VarRefOrModeTransition (',' effects+=VarRefOrModeTransition)*)?
    (code=Code)? (stp=STP)? (deadline=Deadline)? (delimited?=';')?
    ;

TriggerRef:
    BuiltinTriggerRef | VarRef;

BuiltinTriggerRef:
    type = BuiltinTrigger;

Deadline:
    'deadline' '(' delay=Expression ')' code=Code;

Watchdog:
    (attributes+=Attribute)*
    'watchdog' name=ID '(' timeout=Expression ')'
    ('->' effects+=VarRefOrModeTransition (',' effects+=VarRefOrModeTransition)*)?
    code=Code;

STP:
    ('STP' | 'STAA') '(' value=Expression ')' code=Code;

Preamble:
    (visibility=Visibility)? 'preamble' code=Code;

Instantiation:
    (attributes+=Attribute)*
    name=ID '=' 'new' (widthSpec=WidthSpec)?
    reactorClass=[ReactorDecl] ('<' typeArgs+=Type (',' typeArgs+=Type)* '>')? '('
    (parameters+=Assignment (',' parameters+=Assignment)*)?
    ')' (('at' host=Host ';') | ';'?);

Connection:
    ((leftPorts += VarRef (',' leftPorts += VarRef)*)
    | ( '(' leftPorts += VarRef (',' leftPorts += VarRef)* ')' iterated ?= '+'?))
    ('->' | physical?='~>')
    rightPorts += VarRef (',' rightPorts += VarRef)*
    ('after' delay=Expression)?
    (serializer=Serializer)?
    ';'?
;

// Chooses the serializer to use for the connection
Serializer:
    'serializer' type=STRING
;

/////////// Attributes
Attribute:
    '@' attrName=ID ('(' (attrParms+=AttrParm (',' attrParms+=AttrParm)* ','?)? ')')?
;

AttrParm:
    (name=ID '=')? value=Literal;

/////////// For target parameters

KeyValuePairs:
    {KeyValuePairs} '{' (pairs+=KeyValuePair (',' (pairs+=KeyValuePair))* ','?)? '}';

KeyValuePair:
    name=(Kebab|STRING) ':' value=Element;

Array: // todo allow empty array in grammar, replace with validator error
    '[' elements+=Element (',' (elements+=Element))* ','? ']';

Element:
    keyvalue=KeyValuePairs
    | array=Array
    | literal=Literal
    | (time=INT unit=TimeUnit)
    | id=Path;

///////// Pieces
TypedVariable:
    Port | Action
;

Variable:
    TypedVariable | Timer | Mode | Watchdog;

VarRef:
    (variable=[Variable] | container=[Instantiation] '.' variable=[Variable]
    | interleaved?='interleaved' '(' (variable=[Variable] | container=[Instantiation] '.' variable=[Variable]) ')') ('as' (alias=ID))?
;
VarRefOrModeTransition returns VarRef:
    VarRef | transition=ModeTransition '(' variable=[Mode] ')';

Assignment:
    lhs=[Parameter]
    rhs=Initializer
;

/**
 * Parameter declaration with optional type and mandatory initialization.
 */
Parameter:
    (attributes+=Attribute)*
    name=ID (':' type=Type)?
    init=Initializer?
;

Expression:
      {Literal} literal=Literal
    | Time
    | ParameterReference
    | {CodeExpr} code=Code
    | BracedListExpression
    | BracketListExpression
    | ParenthesisListExpression
;

// A list of expressions within braces.
// In C/C++, this is an array initializer, struct initializer, or std::initializer_list.
BracedListExpression:
     '{' {BracedListExpression} (items+=Expression (',' items+=Expression)*)? ','? '}'
;

// A list of expressions within square brackets.
// In Python and TS, this is a list literal. In Rust this could be an array but Rust
// array expressions are relatively rare so probably not worth supporting.
BracketListExpression:
     '[' {BracketListExpression} (items+=Expression (',' items+=Expression)*)? ','? ']'
;

ParenthesisListExpression:
     '(' {ParenthesisListExpression} (items+=Expression (',' items+=Expression)*)? ','? ')'
;


ParameterReference:
    parameter=[Parameter]
;

Time:
    (interval=INT unit=TimeUnit)
;

Port:
    Input | Output;

// A type is in the target language, hence either an ID or target code.
Type:
   time?='time'
   | id=DottedName ('<' typeArgs+=Type (',' typeArgs+=Type)* '>')? (stars+='*')* (CStyleArraySpec=CStyleArraySpec)?
   | code=Code
;

CStyleArraySpec:
    '[' ( ofVariableLength?=']' | length=INT ']' );

WidthSpec:
    '[' ( ofVariableLength?=']' | (terms+=WidthTerm) ('+' terms+=WidthTerm)* ']' );

WidthTerm:
    width=INT
    | parameter=[Parameter]
    | 'widthof(' port=VarRef ')'
    | code=Code;

IPV4Host:
    (user=Kebab '@')? addr=IPV4Addr (':' port=INT)?
;

IPV6Host:
    ('[' (user=Kebab '@')? addr=IPV6Addr ']' (':' port=INT)?)
;

NamedHost:
    (user=Kebab '@')? addr=HostName (':' port=INT)?
;

Host:
    IPV4Host | IPV6Host | NamedHost
;

HostName:
    (Kebab (('.') Kebab)*)
;

DottedName:
    (ID (('.'|'::') ID)*)
;

SignedInt:
    INT | NEGINT
;

Forever:
    'forever'
;

Never:
    'never'
;

Literal:
    STRING | CHAR_LIT | SignedFloat | SignedInt | Boolean | Forever | Never
;

Boolean:
    TRUE | FALSE
;

/////////// Elementary components

/////////// Lexical grammar
//// Terminals must be mutually exclusive. They are used by the lexer before parsing.

//// Note that the rest of the grammar implicitly defines
//// keywords for every string literal, eg 'reaction'.

terminal WS: (' '|'\t'|'\r'|'\n')+;

terminal TRUE:  'true' | 'True';
terminal FALSE: 'false' | 'False';

terminal ID: ('a'..'z'|'A'..'Z'|'_')('a'..'z'|'A'..'Z'|'_'|'0'..'9')*;

terminal INT returns ecore::EInt:     ('0'..'9')+;
terminal NEGINT:                  '-' ('0'..'9')+;

terminal FLOAT_EXP_SUFFIX: INT ('e' | 'E') ('+' | '-')? INT;

terminal SL_COMMENT: ('//' | '#') !('\n'|'\r')* ('\r'? '\n')?;
terminal ML_COMMENT: ('/*' -> '*/') | ("'''" -> "'''");

terminal LT_ANNOT: "'" ID?;

terminal CPP_RAW_STR: 'R"' -> '"';

terminal STRING:   '"' ( '\\' . | !('\\' | '"' | '\t' | '\r' | '\n') )* '"'
                 | '"""' -> '"""'
                 ;

terminal CHAR_LIT: "'" ( '\\' . | !('\\' | "'" | '\t' | '\r' | '\n') )  "'";

terminal ANY_OTHER: .;


Kebab: (ID|"physical")('-'ID)*;

IPV4Addr:
    INT '.' INT '.' INT '.' INT
;

IPV6Seg:
    // NOTE: This rule is too permissive by design.
    // Further checking is done during validation.
    (INT | (INT? ID))
;


IPV6Addr:
    // NOTE: This rule is too permissive by design.
    // Further checking is done during validation.
    // IPV6 with truncation.
    '::' | ('::' (IPV6Seg (':'))* IPV6Seg) | ((IPV6Seg (':'|'::'))+ IPV6Seg?) |

    // (Link-local IPv6 addresses with zone index) "fe80::7:8%1"
    (ID '::' IPV6Seg (':' IPV6Seg)* '%' (INT | ID)+) |

    // IPv4-mapped IPv6 addresses and IPv4-translated addresses
    ('::' IPV4Addr) | ('::' ID ':' (INT ':')? IPV4Addr) |

    // IPv4-Embedded IPv6 Address
    (((IPV6Seg (':' IPV6Seg)* '::') | (IPV6Seg (':' IPV6Seg)*) ':') IPV4Addr)
;


SignedFloat:
    (SignedInt | '-')? '.' (INT | FLOAT_EXP_SUFFIX);

// FIXME: What if the code needs to contain '=}'?
// Just escaping with \ is not a good idea because then every \ has to be escaped \\.
// Perhaps the string EQUALS_BRACE could become '=}'?
Code:
    // {Code} '{=' (tokens+=Token)* '=}'
    {Code} '{=' body=Body '=}'
;

FSName:
    (ID | '.' | '_' | '-')+
;
// Absolute or relative directory path in Windows, Linux, or MacOS.
Path:
    (FSName ':\\')? ('\\' | '/')? FSName (('\\' | '/') FSName)*
;

/////////// Enums
enum ActionOrigin:
    NONE | LOGICAL='logical' | PHYSICAL='physical';

enum Visibility:
    NONE | PRIVATE='private' | PUBLIC='public';

enum BuiltinTrigger:
    STARTUP='startup' | SHUTDOWN='shutdown' | RESET='reset';

enum ModeTransition:
    RESET='reset' | HISTORY='history';

// Note: time units are not keywords, otherwise it would reserve
// a lot of useful identifiers (like 's' or 'd').
// The validator ensures the unit is valid.
TimeUnit: ID;

// An arbitrary sequence of terminals.
Body:
    Token*
;
// Production for the tokenization of target code. All terminals used in any
// of the productions in the grammar (except for `{=` and `=}`) have to be
// listed here. Whenever a terminal is encountered amid a sequence of target-
// language tokens that is not featured in this production, this will demarcate
// the end of a target-code segment.
Token:
    // Non-constant terminals
    ID | INT | FLOAT_EXP_SUFFIX | LT_ANNOT | CPP_RAW_STR | STRING | CHAR_LIT | ML_COMMENT | SL_COMMENT | WS | ANY_OTHER |
    // Keywords
    'target' | 'import' | 'main' | 'realtime' | 'reactor' | 'state' | 'time' |
    'mutable' | 'input' | 'output' | 'timer' | 'action' | 'reaction' |
    'startup' | 'shutdown' | 'after' | 'deadline' | 'mutation' | 'preamble' |
    'new' | 'federated' | 'at' | 'as' | 'from' | 'widthof' | 'const' | 'method' |
    'interleaved' | 'mode' | 'initial' | 'reset' | 'history' | 'watchdog' |
    'extends' | 'forever' | 'never' |

    // Other terminals
    NEGINT | TRUE | FALSE |
    // Action origins
    'logical' | 'physical' |
    // Visibility modifiers
    'private' | 'public' |
    // Braces
    '(' | ')' | '{' | '}' |
    // Brackets
    '[' | ']' | '<' | '>' |
    // Punctuation
    ':' | ';' | ',' | '.' | '::' |
    // Slashes
    ':\\' | '\\' |
    // Arithmetic
    '+' | '-' | '*' | '/' |
    // Underscore
    '_' |
    // Arrow
    '->' |
    // Assignment
    '=' |
    // Percentage
    '%' |
    // Annotation
    '@' |
    // Single quotes
    "'"
;
