/*
 * SPDX-FileCopyrightText: 2024 Fondazione Bruno Kessler
 *
 * SPDX-FileContributor: Tommaso Fonda - initial API and implementation
 */
grammar eu.fbk.sysmlv2.SysMLv2 with org.eclipse.xtext.common.Terminals

import "http://www.eclipse.org/emf/2002/Ecore" as Ecore
generate sysMLv2 "http://www.fbk.eu/sysmlv2/SysMLv2"

Model:
	(members+=Package | members+=Import)*;

Package:
	(standard?=StandardKeyword? library?=LibraryKeyword)? PackageKeyword Identification PackageBody;

fragment PackageBody returns Container:
	SEMICOLON | LBRACE PackageBodyMembers RBRACE;

fragment PackageBodyMembers returns Container:
	members+=PackageMember*;

PackageMember:
	Element | Import | Package;

/*
 * Generic elements.
 */
fragment Identification returns NamedElement:
	(Less shortName=Name Greater)? name=Name;

/*
 * Imports.
 */
Import:
	type=ImportType ImportKeyword importedNamespace=[PackageMember|QualifiedName<false>] wildcard?='::*'? SEMICOLON;

enum ImportType:
	PRIVATE='private' | PUBLIC='public';

/*
 * Definitions.
 */
fragment AbstractPrefix *:
	abstract?=AbstractKeyword?;

fragment DefinitionFragment returns Definition:
	DefinitionKeyword Identification specialization=Specialization?;

fragment DefinitionBody returns Definition:
	SEMICOLON | LBRACE DefinitionBodyMembers RBRACE;

Definition:
	PartDefinition
	| ItemDefinition
	| AttributeDefinition
	| StateDefinition
	| EnumDefinition
	| ActionDefinition
	| PortDefinition<false>
	| ConstraintDefinition
	| CalculationDefinition
	| MetadataDefinition;

Usage:
	PartUsage
	| ItemUsage
	| AttributeUsage
	| StateUsage
	| EnumUsage<true>
	| ActionUsage
	| PortUsage
	| ConstraintUsage
	| CalculationUsage
	| MetadataUsage
	| Feature<false>;

NamedElement:
	Usage | Definition;

Element:
	NamedElement | ConnectionElement | Comment | Documentation;

fragment DefinitionBodyMembers returns Container:
	(members+=Element | members+=AssertionAction)*;

/*
 * Usages.
 */
fragment UsageFragment<AllowConjugation, IdentificationOptional> returns Usage:
	(=>redefinition=Redefinition
	(TypingFragment<true, AllowConjugation>? & multiplicity=Multiplicity? & subsetting=Subsetting?)
	|
	(<IdentificationOptional> Identification? | <!IdentificationOptional> Identification)
	(TypingFragment<true, AllowConjugation>? & multiplicity=Multiplicity? & redefinition=Redefinition?
	& subsetting=Subsetting?))
	value=Value?;

fragment TypingFragment<ForceKeyword, AllowConjugation> returns TypedElement:
	(<ForceKeyword> DefinedByKeyword) typing=Typing<AllowConjugation>;

fragment UsageBody returns Usage:
	SEMICOLON | LBRACE DefinitionBodyMembers RBRACE;

Multiplicity:
	LBRACKET lowerBound=MultiplicityBound (DOT DOT upperBound=MultiplicityBound)? RBRACKET
	(ordered?=OrderedKeyword? & nonunique?=NonuniqueKeyword?);

MultiplicityBound returns Ecore::EIntegerObject:
	IntegerValue | Mul;

fragment ReferencedElement<AllowConjugation> returns FeatureSpecialization:
	references+=ReferenceExpression<AllowConjugation>;

Typing<AllowConjugation>:
	ReferencedElement<AllowConjugation> (COMMA ReferencedElement<AllowConjugation>)*;

Specialization:
	SpecializesKeyword ReferencedElement<false> (COMMA ReferencedElement<false>)*;

Redefinition:
	{Redefinition} RedefinesKeyword (ReferencedElement<false> | self=SelfKeyword);

Subsetting:
	SubsetsKeyword ReferencedElement<false> (COMMA ReferencedElement<false>)*;

Value:
	(binding?=Bind | initial?=AssignmentOp | default?=DefaultKeyword (binding?=Bind | initial?=AssignmentOp)?)
	expression=(Expression | UnevaluatedExpression);

Unit:
	ID ((Mul | Div | Exp) ID)*;

fragment UnitPart returns Expression:
	(LBRACKET unit=Unit RBRACKET)?;

/*
 * Parts.
 */
PartDefinition:
	AbstractPrefix PartKeyword DefinitionFragment DefinitionBody;

PartUsage:
	AbstractPrefix DirectionPrefix? PartKeyword UsageFragment<false, false> UsageBody;

Part: PartDefinition | PartUsage;

/*
 * Items.
 */
ItemDefinition:
	AbstractPrefix ItemKeyword DefinitionFragment DefinitionBody;

ItemUsage:
	AbstractPrefix DirectionPrefix? ItemKeyword UsageFragment<false, false> UsageBody;

/*
 * Attributes.
 */
AttributeDefinition:
	AbstractPrefix AttributeKeyword DefinitionFragment DefinitionBody;

AttributeUsage:
	AbstractPrefix DirectionPrefix? AttributeKeyword UsageFragment<false, false> UsageBody;

/*
 * Ports.
 * Always call the PortDefinition rule with the 'Dummy' parameter set to false:
 * the conjugatedPortDefintion reference must not be parsed, this is just a trick
 * to create it in the generated Ecore metamodel.
 */
PortDefinition<Dummy>:
	PortKeyword DefinitionFragment DefinitionBody (<Dummy> conjugatedPortDefinition=[PortDefinition|Name]);

PortUsage:
	PortKeyword UsageFragment<true, false> UsageBody;

/*
 * State machines.
 */
StateDefinition:
	AbstractPrefix StateKeyword DefinitionFragment StateDefinitionBody;

fragment StateDefinitionBody *:
	SEMICOLON | LBRACE StateBodyMembers RBRACE;

fragment StateBodyMembers returns Container:
	(members+=EntryAction members+=EntryTransition // entry ... then ...
	| members+=Transition<false>
	| members+=Element
	| members+=AssertionAction)*;

EntryAction:
	{EntryAction} EntryKeyword (effect=Effect<true> | SEMICOLON);

fragment TransitionEnd returns Transition:
	ThenKeyword destination=[StateUsage|Name];

EntryTransition:
	guard=Guard? TransitionEnd ActionBody;

Transition<Dummy>:
	TransitionKeyword Identification? TypingFragment<true, false>? FirstKeyword source=[StateUsage|Name]
	triggers+=TriggerAction? guard=Guard? effect=Effect<false>?
	TransitionEnd ActionBody (<Dummy> stutter?='stutter');

TriggerAction:
	AcceptKeyword payload=Payload (ViaKeyword port=ReferenceExpression<false>)?;

Payload:
	TypingFragment<false, false> | Identification TypingFragment<true, false>;

Guard:
	IfKeyword expression=Expression;

Effect<IsEntryTransition>:
	(<!IsEntryTransition> DoKeyword)
	(action=(Assignment<false> | PerformActionReference<false> | SendUsage) (<IsEntryTransition> SEMICOLON) | action=ActionUsage);

StateUsage:
	AbstractPrefix StateKeyword UsageFragment<false, false> StateDefinitionBody;

/*
 * Metadata.
 */
MetadataDefinition:
	MetadataKeyword DefinitionFragment MetadataDefinitionBody;

MetadataUsage:
	MetadataUsageDeclaration
	Annotation
	MetadataDefinitionBody;

fragment MetadataDefinitionBody *:
	SEMICOLON | LBRACE MetadataDefinitionBodyMembers RBRACE;

fragment MetadataDefinitionBodyMembers returns Container:
	(members+=Element | members+=AnnotatedElement)*;

AnnotatedElement:
	SubsetsKeyword 'annotatedElement' COLON annotatedType=QualifiedName<false> SEMICOLON;

fragment MetadataUsageDeclaration returns Usage:
	(MetadataKeyword | '@') (Identification TypingFragment<true, false> |  TypingFragment<false, false>);

Comment hidden(WS, SL_COMMENT):
	(CommentKeyword Identification? Annotation)? AnnotationBody;

Documentation hidden(WS, SL_COMMENT):
	DocumentationKeyword Identification? AnnotationBody;

fragment AnnotationBody *:
	(LocaleKeyword locale=STRING_VALUE)? body=ML_COMMENT;

fragment Annotation *:
	(AboutKeyword annotatedElements+=[Element|QualifiedName<false>]
	     (COMMA annotatedElements+=[Element|QualifiedName<false>])*)?;

/*
 * Actions.
 */
ActionDefinition:
	AbstractPrefix ActionKeyword DefinitionFragment ActionBody;

ActionUsage:
	AbstractPrefix ActionKeyword UsageFragment<false, true> ActionBody;

fragment ActionBody returns Container:
	SEMICOLON | LBRACE ActionBodyMembers RBRACE;

fragment ActionBodyMembers returns Container:
	(members+=Element | members+=ActionNode | members+=SuccessionNode)*;

ActionNode:
	Assignment<true>
	| AssertionAction
	| SendAction
	| StartAction
	| DoneAction
	| WhileLoop
	| AcceptAction
	| PerformActionReference<true>
	| TriggerAction SEMICOLON;

StartAction:
	{StartAction} FirstKeyword StartKeyword;

DoneAction:
	{DoneAction} DoneKeyword;

SendAction:
	SendUsage ActionBody;

AcceptAction:
	AcceptKeyword AfterKeyword time=Expression ActionBody;

AssertionAction:
	AssertKeyword (constraint=[ConstraintUsage|QualifiedName<false>] ActionBody | inlineConstraint=ConstraintUsage);

SuccessionNode:
	ThenKeyword (node=ActionNode | node=ActionUsage);

WhileLoop:
	WhileKeyword condition=Expression ActionBody;

PerformActionReference<Keyword>:
	(<Keyword> PerformKeyword) reference=ActionReference SEMICOLON;

fragment ReferenceForPAR returns ReferenceExpression:
	referencedElement=[ActionUsage|QualifiedName<false>];

ActionReference:
	ReferenceForPAR;

SendUsage:
	SendKeyword payload=Expression
	(ViaKeyword transport=ReferenceExpression<false>)?
	(ToKeyword destination=ReferenceExpression<false>)?;

/*
 * Enums.
 */
EnumDefinition:
	EnumKeyword DefinitionFragment EnumDefinitionBody;

fragment EnumDefinitionBody returns Container:
	LBRACE EnumDefinitionBodyMembers RBRACE;

fragment EnumDefinitionBodyMembers returns Container:
	(members+=EnumUsage<false> | members+=MetadataUsage)*;

EnumUsage<ForceKeyword>:
	(<ForceKeyword> EnumKeyword | <!ForceKeyword> EnumKeyword?) UsageFragment<false, false> UsageBody;

/*
 * Calculations.
 */
fragment AbstractPrefixForCalculation *:
	AbstractPrefix;

CalculationDefinition:
	AbstractPrefixForCalculation CalculationKeyword DefinitionFragment CalculationBody;

fragment CalculationBody returns Calculation:
	SEMICOLON | LBRACE CalculationBodyMembers CalculationBodyResult? RBRACE;

fragment CalculationBodyMembers returns Container:
	members+=Element*;

fragment CalculationBodyResult returns Calculation:
	result=(ReturnStatement | Expression);

CalculationUsage:
	AbstractPrefixForCalculation CalculationKeyword UsageFragment<false, false> CalculationBody;

/*
 * This rule is never called, it is only required to force Calculation
 * to extend NamedElement in the generated Ecore metamodel.
 */
Calculation returns NamedElement: {Calculation};

ReturnStatement:
	ReturnKeyword feature=Feature<true>;

/*
 * Constraints.
 */
ConstraintDefinition:
	AbstractPrefix ConstraintKeyword DefinitionFragment ConstraintBody;

/*
 * Keeping the expression at the bottom of the body seems to be
 * common practice, and also avoids a cryptic antlr error. We'll
 * have to figure it out if we want/must allow expressions in other
 * parts of the body.
 */
fragment ConstraintBody:
	SEMICOLON | LBRACE ConstraintBodyMembers ConstraintBodyExpression RBRACE;

fragment ConstraintBodyMembers returns Container:
	(members+=Feature<false> | members+=AttributeUsage)*;

fragment ConstraintBodyExpression:
	expression=Expression;

ConstraintUsage:
	AbstractPrefix DirectionPrefix? ConstraintKeyword UsageFragment<false, true> ConstraintBody;

/*
 * Directions and features.
 */
Feature<IdentificationOptional>:
	DirectionPrefix? UsageFragment<false, IdentificationOptional> UsageBody;

fragment DirectionPrefix returns DirectedElement:
	direction=Direction;

enum Direction:
	NONE='none' | IN='in' | OUT='out' | INOUT='inout';

/*
 * This rule is never called, it is only required to force DirectedElement
 * to extend Usage in the generated Ecore metamodel.
 */
DirectedElement: UsageFragment<true, false>;

/*
 * Connections.
 */
ConnectionElement:
	ConnectionUsage | Binding;

fragment ConnectionSource returns ConnectionElement:
	source=Expression;

fragment ConnectionTarget returns ConnectionElement:
	target=ReferenceExpression<false>;

ConnectionUsage:
	ConnectKeyword ConnectionSource ToKeyword ConnectionTarget SEMICOLON;

/*
 * Assignments.
 */
Assignment<Body>:
	AssignKeyword element=ReferenceExpression<false> value=Value (<Body> ActionBody);

/*
 * Bindings.
 */
Binding:
	BindKeyword ConnectionSource Bind ConnectionTarget SEMICOLON;

/*
 * Qualified names.
 */
Name:
	ValidID | UNRESTRICTED_NAME;

ValidID:
	ID | PayloadKeyword | EXPONENT_VALUE;

QualifiedName<AllowConjugation>:
	(Name '::')* (<AllowConjugation> '~'?) Name;

/*
 * Keywords.
 */
AboutKeyword:			'about';
AbstractKeyword:		'abstract';
AcceptKeyword:			'accept';
ActionKeyword:			'action';
AfterKeyword:			'after';
AssertKeyword:			'assert';
AssignKeyword:			'assign';
AttributeKeyword:		'attribute';
BindKeyword:			'bind';
CalculationKeyword:		'calc';
CommentKeyword:			'comment';
ConnectKeyword:			'connect';
ConstraintKeyword:		'constraint';
DefaultKeyword:			'default';
DefinedByKeyword:		COLON | 'defined' 'by';
DefinitionKeyword:		'def';
DoKeyword:				'do';
DocumentationKeyword:	'doc';
DoneKeyword:			'done';
EntryKeyword:			'entry';
EnumKeyword:			'enum';
FirstKeyword:			'first';
IfKeyword:				'if';
ImportKeyword:			'import';
ItemKeyword:			'item';
LibraryKeyword:			'library';
LocaleKeyword:			'locale';
MetadataKeyword:		'metadata';
NonuniqueKeyword:		'nonunique';
OrderedKeyword:			'ordered';
PackageKeyword:			'package';
PartKeyword:			'part';
PayloadKeyword:			'payload';
PerformKeyword:			'perform';
PortKeyword:			'port';
RedefinesKeyword:		':>>' | 'redefines';
ReturnKeyword:			'return';
SelfKeyword:			'self';
SendKeyword:			'send';
SpecializesKeyword:		':>' | 'specializes';
StandardKeyword:		'standard';
StartKeyword:			'start';
StateKeyword:			'state';
SubsetsKeyword:			':>' | 'subsets';
ThatKeyword:			'that';
ThenKeyword:			'then';
ToKeyword:				'to';
TransitionKeyword:		'transition';
ViaKeyword:				'via';
WhileKeyword:			'while';

/*
 * Arithmetic and logical operators, operands and expressions.
 */
OperatorExpression:
	TernaryOperatorExpression;

TernaryOperatorExpression returns OperatorExpression:
	ImplicationExpression | Guard {TernaryOperatorExpression.guard=current} operator=ConditionTestOperator left=ImplicationExpression 'else' right=TernaryOperatorExpression;

ImplicationExpression returns OperatorExpression:
	OrExpression ({ImplicationExpression.left=current} operator=ImplicationOperator right=OrExpression)*;

OrExpression returns OperatorExpression:
	AndExpression ({OrExpression.left=current} operator=OrOperator right=AndExpression)*;

AndExpression returns OperatorExpression:
	EqualityExpression ({AndExpression.left=current} operator=AndOperator right=EqualityExpression)*;

EqualityExpression returns OperatorExpression:
	UnaryLogicalExpression ({EqualityExpression.left=current} operator=EqualityOperator right=UnaryLogicalExpression)*;

UnaryLogicalExpression returns OperatorExpression:
	RelationalExpression | => left=EmptyExpression operator=NotOperator right=RelationalExpression;

RelationalExpression returns OperatorExpression:
	AddSubExpression ({RelationalExpression.left=current} operator=RelationalOperator right=AddSubExpression)*;

AddSubExpression returns OperatorExpression:
	MulDivExpression ({AddSubExpression.left=current} operator=AddSubOperator right=MulDivExpression)*;

MulDivExpression returns OperatorExpression:
	ExponentExpression ({MulDivExpression.left=current} operator=MulDivOperator right=ExponentExpression)*;

ExponentExpression returns OperatorExpression:
	UnaryAlgebraicExpression ({ExponentExpression.left=current} operator=ExponentOperator right=UnaryAlgebraicExpression)*;

UnaryAlgebraicExpression returns OperatorExpression:
	PrimaryExpression | => left=EmptyExpression operator=UnaryAlgebraicOperator right=PrimaryExpression;

EmptyExpression returns OperatorExpression: {EmptyExpression};

PrimaryExpression: // still instance of OperatorExpression, (seemingly) unavoidable...
	Literal
	| ReferenceExpression<true>
	| (=> BoundedExpression | ListExpression);

fragment WrappedExpression *:
	expression=Expression;

UnevaluatedExpression:
	LBRACE WrappedExpression RBRACE;

ReferenceExpression<AllowConjugation>:
	QualifiedNameExpression<AllowConjugation> | UsageChainExpression;

Expression:
	OperatorExpression UnitPart;

fragment ReferenceForQNE<AllowConjugation> returns ReferenceExpression:
	referencedElement=[NamedElement|QualifiedName<AllowConjugation>];

QualifiedNameExpression<AllowConjugation>:
	ReferenceForQNE<AllowConjugation> Arguments?;

/*
 * No need to have an ArgumentExpression type, this is just an umbrella rule to
 * group the two Expression subtypes that are allowed in fragment Arguments.
 * Hence, this rule returns Expression.
 */
ArgumentExpression returns Expression:
	OperatorExpression | UnevaluatedExpression;

fragment Arguments returns ReferenceExpression:
	invocation?=LPAREN (arguments+=ArgumentExpression (COMMA arguments+=ArgumentExpression)*)? RPAREN ('#' LPAREN INT RPAREN)?;

BoundedExpression:
	LPAREN WrappedExpression RPAREN;

ListExpression:
	LPAREN expressions+=Expression (COMMA expressions+=Expression)+ RPAREN;

fragment TailReferenceForUE returns ReferenceExpression:
	referencedElement=[Usage|Name];

UsageChainExpression returns UsageExpression:
	UsageReferenceExpression ({UsageChainExpression.body=current} operator=ChainingOperator TailReferenceForUE)+ Arguments?;

UsageReferenceExpression returns UsageExpression:
	{UsageReferenceExpression} TailReferenceForUE;

Literal:
	IntegerLiteral | RealLiteral | BooleanLiteral | StringLiteral;

IntegerLiteral:
	value=IntegerValue;

RealLiteral:
	value=(DecimalValue | ExponentialValue);

BooleanLiteral:
	value=(True | False);

StringLiteral:
	value=(STRING_VALUE | ThatKeyword | Mul);

IntegerValue hidden():
	PLUSMINUS? INT;

DecimalValue hidden():
	PLUSMINUS? INT DOT INT;

ExponentialValue hidden():
	PLUSMINUS? INT (DOT INT)? EXPONENT_VALUE;

EqualityOperator returns Operator:
	name=(Equal | NotEqual | TripleEqual | TripleNotEqual);

RelationalOperator returns Operator:
	name=(GreaterEqual | Greater | LessEqual | Less);

ImplicationOperator returns Operator:
	name=Implies;

OrOperator returns Operator:
	name=(Or | Xor | Xnor);

AndOperator returns Operator:
	name=And;

NotOperator returns Operator:
	name=Not;

ConditionTestOperator returns Operator:
	name=ConditionTest;

UnaryAlgebraicOperator returns Operator:
	name=PLUSMINUS;

AddSubOperator returns Operator:
	name=PLUSMINUS;

MulDivOperator returns Operator:
	name=(Mul | Div | Mod);

ExponentOperator returns Operator:
	name=Exp;

ChainingOperator returns Operator:
	name=DOT;

AssignmentOp:  ':=';
Bind:           '=';
Equal:			'==';
NotEqual:       '!=';
TripleEqual:	'===';
TripleNotEqual:	'!==';
Greater:        '>';
GreaterEqual:   '>=';
Less:           '<';
LessEqual:      '<=';
ConditionTest:	'?';
And:            'and';
Or:             'or';
Xor:            'xor';
Xnor:           'xnor';
Implies:		'implies';
Not:            'not';
Mul:            '*';
Div:	        '/';
Exp:			'^';
Mod:	        '%';
True:           'true';
False:          'false';

/*
 * Terminal rules.
 */

terminal EXPONENT_VALUE:
	('E' | 'e') PLUSMINUS? INT;

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

terminal UNRESTRICTED_NAME returns Ecore::EString:
	'\'' ('\\' ('b' | 't' | 'n' | 'f' | 'r' | '"' | "'" | '\\') | !('\\' | '\''))* '\'';

terminal STRING_VALUE:
	'"' ('\\' ('b' | 't' | 'n' | 'f' | 'r' | '"' | "'" | '\\') | !('\\' | '"'))* '"';

terminal PLUS:		'+';
terminal MINUS:		'-';
terminal PLUSMINUS:	PLUS | MINUS;
terminal COLON:		':';
terminal COMMA:		',';
terminal DOT:		'.';
terminal LBRACE:	'{';
terminal RBRACE:	'}';
terminal LBRACKET:	'[';
terminal RBRACKET:	']';
terminal LPAREN:	'(';
terminal RPAREN:	')';
terminal SEMICOLON:	';';
