Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Annotations #977

Merged
merged 24 commits into from
Jun 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8bb0e06
Extend syntax
lsk567 Feb 23, 2022
861ac63
Extend syntax, update diagram synthesis, update validator
lsk567 Feb 23, 2022
82fdef1
Merge branch 'master' into attribute
oowekyala Jun 8, 2022
605c3f9
Add annotation validation
oowekyala Jun 8, 2022
39f22de
Support shorthand syntax
oowekyala Jun 8, 2022
f047cc8
add another test
oowekyala Jun 8, 2022
9d15a71
Support trailing comma
oowekyala Jun 8, 2022
505efe6
Support attributes on more constructs
oowekyala Jun 9, 2022
12ec7d5
Fix diagram synthesis
oowekyala Jun 9, 2022
bb74cf3
Resolve conflicts with master
lhstrh Jun 18, 2022
af7502e
Fix remaining conflict
lhstrh Jun 18, 2022
bed6940
Re-organize attributes in the LFValidator
lsk567 Jun 22, 2022
080e5f8
Add comments
lsk567 Jun 22, 2022
c30a7c9
Support basic param types such as String, Int, Boolean, and Float.
lsk567 Jun 23, 2022
14c7ec2
Add a unit test that checks whether the validator can detect wrong pa…
lsk567 Jun 23, 2022
d261dc4
Merge remote-tracking branch 'origin/master' into attribute
lsk567 Jun 23, 2022
8b944e5
Remove the dependency on NotNull
lsk567 Jun 23, 2022
d515475
Factor out AttributeSpec
lsk567 Jun 23, 2022
9b38e25
Apply suggestions from code review
lsk567 Jun 23, 2022
b33484b
Add author tags
lsk567 Jun 23, 2022
5de2169
Move the static block and static map back to AttributeSpec
lsk567 Jun 23, 2022
b24e5b5
Change the visibility of the overridden error() back to protected.
lsk567 Jun 23, 2022
8b0d893
Apply quick fix suggested by @lhstrh to remove ambiguity
lsk567 Jun 25, 2022
c92516e
Factor out attribute-related methods into their own AttributeUtils class
lsk567 Jun 25, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
import org.eclipse.xtext.xbase.lib.Pair;
import org.eclipse.xtext.xbase.lib.StringExtensions;
import org.lflang.ASTUtils;
import org.lflang.AttributeUtils;
import org.lflang.InferredType;
import org.lflang.diagram.synthesis.action.CollapseAllReactorsAction;
import org.lflang.diagram.synthesis.action.ExpandAllReactorsAction;
Expand Down Expand Up @@ -1383,7 +1384,7 @@ private KNode addErrorComment(KNode node, String message) {

private Iterable<KNode> createUserComments(EObject element, KNode targetNode) {
if (getBooleanValue(SHOW_USER_LABELS)) {
String commentText = ASTUtils.findAnnotationInComments(element, "@label");
String commentText = AttributeUtils.label(element);

if (!StringExtensions.isNullOrEmpty(commentText)) {
KNode comment = _kNodeExtensions.createNode();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import org.eclipse.xtext.testing.InjectWith;
import org.eclipse.xtext.testing.extensions.InjectionExtension;
import org.eclipse.xtext.testing.util.ParseHelper;

import org.lflang.lf.LfPackage;
import org.lflang.lf.Model;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Assertions;
Expand Down Expand Up @@ -65,4 +67,50 @@ public void checkForTarget() throws Exception {
Assertions.assertNotNull(result);
Assertions.assertFalse(result.eResource().getErrors().isEmpty(), "Failed to catch misspelled target keyword.");
}
}

@Test
public void testAttributes() throws Exception {
String testCase = """
target C;
@label("somethign", "else")
@ohio()
@a
@bdebd(a="b")
@bd("abc")
@bd("abc",)
@a(a="a", b="b")
@a(a="a", b="b",)
main reactor {

}
""";
parseWithoutError(testCase);
}

@Test
public void testAttributeContexts() throws Exception {
String testCase = """
target C;
@a
main reactor(@b parm: int) {

@ohio reaction() {==}
@ohio logical action f;
@ohio timer t;
@ohio input q: int;
@ohio output q2: int;
}
""";
parseWithoutError(testCase);
}

private Model parseWithoutError(String s) throws Exception {
Model model = parser.parse(s);
Assertions.assertNotNull(model);
Assertions.assertTrue(model.eResource().getErrors().isEmpty(),
"Encountered unexpected error while parsing: " +
model.eResource().getErrors());
return model;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2222,6 +2222,40 @@ public void testUnrecognizedTarget() throws Exception {
"Unrecognized target: Pjthon");
}


@Test
public void testWrongStructureForLabelAttribute() throws Exception {
String testCase = """
target C;
@label(name="something")
main reactor { }
""";
validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getAttribute(), null,
"Unknown attribute parameter.");
}

@Test
public void testMissingName() throws Exception {
String testCase = """
target C;
@label("something", "else")
main reactor { }
""";
validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getAttribute(), null,
"Missing name for attribute parameter.");
}

@Test
public void testWrongParamType() throws Exception {
String testCase = """
target C;
@label(value=1)
main reactor { }
""";
validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getAttribute(), null,
"Incorrect type: \"value\" should have type String.");
}

@Test
public void testInitialMode() throws Exception {
String testCase = """
Expand Down
11 changes: 1 addition & 10 deletions org.lflang/src/org/lflang/ASTUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
import org.lflang.lf.Action;
import org.lflang.lf.ActionOrigin;
import org.lflang.lf.Assignment;
import org.lflang.lf.Attribute;
import org.lflang.lf.Code;
import org.lflang.lf.Connection;
import org.lflang.lf.Element;
Expand Down Expand Up @@ -1747,16 +1748,6 @@ public static String findAnnotationInComments(EObject object, String key) {
return null;
}

/**
* Search for an `@label` annotation for a given reaction.
*
* @param n the reaction for which the label should be searched
* @return The annotated string if an `@label` annotation was found. `null` otherwise.
*/
public static String label(Reaction n) {
return findAnnotationInComments(n, "@label");
}

/**
* Find the main reactor and set its name if none was defined.
* @param resource The resource to find the main reactor in.
Expand Down
2 changes: 1 addition & 1 deletion org.lflang/src/org/lflang/AstExtensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ val Resource.model: Model get() = this.allContents.asSequence().filterIsInstance
* If the reaction is annotated with a label, then the label is returned. Otherwise, a reaction name
* is generated based on its priority.
*/
val Reaction.label get(): String = ASTUtils.label(this) ?: "reaction_$priority"
val Reaction.label get(): String = AttributeUtils.label(this) ?: "reaction_$priority"

/** Get the priority of a receiving reaction */
val Reaction.priority
Expand Down
115 changes: 115 additions & 0 deletions org.lflang/src/org/lflang/AttributeUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
Copyright (c) 2022, 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.
*/

package org.lflang;

import java.util.List;

import org.eclipse.emf.ecore.EObject;

import org.lflang.lf.Action;
import org.lflang.lf.Attribute;
import org.lflang.lf.Input;
import org.lflang.lf.Output;
import org.lflang.lf.Parameter;
import org.lflang.lf.Reaction;
import org.lflang.lf.Reactor;
import org.lflang.lf.StateVar;
import org.lflang.lf.Timer;

/**
* A helper class for processing attributes in the AST.
* @author{Shaokai Lin <[email protected]>}
* @author{Clément Fournier, TU Dresden, INSA Rennes}
*/
public class AttributeUtils {

/**
* Return the attributes declared on the given node. Throws
* if the node does not support declaring attributes.
*
* @throws IllegalArgumentException If the node cannot have attributes
*/
public static List<Attribute> getAttributes(EObject node) {
if (node instanceof Reactor) {
return ((Reactor) node).getAttributes();
} else if (node instanceof Reaction) {
return ((Reaction) node).getAttributes();
} else if (node instanceof Action) {
return ((Action) node).getAttributes();
} else if (node instanceof Timer) {
return ((Timer) node).getAttributes();
} else if (node instanceof StateVar) {
return ((StateVar) node).getAttributes();
} else if (node instanceof Parameter) {
return ((Parameter) node).getAttributes();
} else if (node instanceof Input) {
return ((Input) node).getAttributes();
} else if (node instanceof Output) {
return ((Output) node).getAttributes();
}
throw new IllegalArgumentException("Not annotatable: " + node);
}

/**
* Return the value of the {@code @label} attribute if
* present, otherwise return null.
*
* @throws IllegalArgumentException If the node cannot have attributes
*/
public static String findLabelAttribute(EObject node) {
List<Attribute> attrs = getAttributes(node);
return attrs.stream()
.filter(it -> it.getAttrName().equals("label"))
.map(it -> it.getAttrParms().get(0).getValue().getStr())
.findFirst()
.orElse(null);

}

/**
* Return the declared label of the node, as given by the @label
* annotation (or an @label comment).
*
* @throws IllegalArgumentException If the node cannot have attributes
*/
public static String label(EObject n) {
String fromAttr = findLabelAttribute(n);
if (fromAttr == null) {
return ASTUtils.findAnnotationInComments(n, "@label");
}
return fromAttr;
}

/**
* Search for an `@label` annotation for a given reaction.
*
* @param n the reaction for which the label should be searched
* @return The annotated string if an `@label` annotation was found. `null` otherwise.
*/
public static String label(Reaction n) {
return label((EObject) n);
}
}
35 changes: 27 additions & 8 deletions org.lflang/src/org/lflang/LinguaFranca.xtext
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ ImportedReactor: reactorClass=[Reactor] ('as' name=ID)?;
* Declaration of a reactor class.
*/
Reactor:
{Reactor} ((federated?='federated' | main?='main')? & realtime?='realtime'?) 'reactor' (name=ID)?
{Reactor} (attributes+=Attribute)* ((federated?='federated' | main?='main')? & realtime?='realtime'?) 'reactor' (name=ID)?
('<' typeParms+=TypeParm (',' typeParms+=TypeParm)* '>')?
('(' parameters+=Parameter (',' parameters+=Parameter)* ')')?
('at' host=Host)?
Expand Down Expand Up @@ -129,6 +129,7 @@ TargetDecl:
* must be given, or a literal or code that denotes zero.
*/
StateVar:
(attributes+=Attribute)*
(reset?='reset')? 'state' name=ID (
(':' (type=Type))?
((parens+='(' (init+=Expression (',' init+=Expression)*)? parens+=')')
Expand All @@ -150,24 +151,24 @@ MethodArgument:
;

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

Output:
'output' (widthSpec=WidthSpec)? name=ID (':' type=Type)? ';'?;
(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:
'timer' name=ID ('(' offset=Expression (',' period=Expression)? ')')? ';'?;
(attributes+=Attribute)* 'timer' name=ID ('(' offset=Expression (',' period=Expression)? ')')? ';'?;

Boolean:
TRUE | FALSE
;

Mode:
{Mode} (initial?='initial')? 'mode' (name=ID)?
{Mode} (initial?='initial')? 'mode' (name=ID)?
'{' (
(stateVars+=StateVar) |
(timers+=Timer) |
Expand All @@ -188,11 +189,13 @@ Mode:
// For all actions, minSpacing is the minimum difference between
// the tags of two subsequently scheduled events.
Action:
(origin=ActionOrigin)? 'action' name=ID
(attributes+=Attribute)*
(origin=ActionOrigin)? 'action' name=ID
('(' minDelay=Expression (',' minSpacing=Expression (',' policy=STRING)? )? ')')?
(':' type=Type)? ';'?;

Reaction:
(attributes+=Attribute)*
('reaction')
('(' (triggers+=TriggerRef (',' triggers+=TriggerRef)*)? ')')?
(sources+=VarRef (',' sources+=VarRef)*)?
Expand Down Expand Up @@ -227,7 +230,7 @@ Instantiation:
name=ID '=' 'new' (widthSpec=WidthSpec)?
reactorClass=[ReactorDecl] ('<' typeParms+=TypeParm (',' typeParms+=TypeParm)* '>')? '('
(parameters+=Assignment (',' parameters+=Assignment)*)?
')' ('at' host=Host)? ';'?;
')' (('at' host=Host ';') | ';'?);

Connection:
((leftPorts += VarRef (',' leftPorts += VarRef)*)
Expand All @@ -244,6 +247,21 @@ Serializer:
'serializer' type=STRING
;

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

AttrParm:
(name=ID '=')? value=AttrParmValue;
oowekyala marked this conversation as resolved.
Show resolved Hide resolved

AttrParmValue:
str=STRING
| int=SignedInt
| bool=Boolean
| float=SignedFloat
;

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

KeyValuePairs:
Expand Down Expand Up @@ -289,6 +307,7 @@ Assignment:
* Parameter declaration with optional type and mandatory initialization.
*/
Parameter:
(attributes+=Attribute)*
name=ID (':' (type=Type))?
((parens+='(' (init+=Expression (',' init+=Expression)*)? parens+=')')
| (braces+='{' (init+=Expression (',' init+=Expression)*)? braces+='}')
Expand Down Expand Up @@ -510,4 +529,4 @@ Token:
'@' |
// Single quotes
"'"
;
;
2 changes: 1 addition & 1 deletion org.lflang/src/org/lflang/generator/rust/RustModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ object RustModelBuilder {
body = n.code.toText(),
isStartup = n.triggers.any { it is BuiltinTriggerRef && it.type == BuiltinTrigger.STARTUP },
isShutdown = n.triggers.any { it is BuiltinTriggerRef && it.type == BuiltinTrigger.SHUTDOWN },
debugLabel = ASTUtils.label(n),
debugLabel = AttributeUtils.label(n),
loc = n.locationInfo().let {
// remove code block
it.copy(lfText = it.lfText.replace(TARGET_BLOCK_R, "{= ... =}"))
Expand Down
Loading