From 8bb0e0693496f4d4672af28845995361eb5de9f5 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Tue, 22 Feb 2022 23:42:33 -0800 Subject: [PATCH 01/21] Extend syntax --- org.lflang/src/org/lflang/LinguaFranca.xtext | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/org.lflang/src/org/lflang/LinguaFranca.xtext b/org.lflang/src/org/lflang/LinguaFranca.xtext index 9b69e83675..2a424020b7 100644 --- a/org.lflang/src/org/lflang/LinguaFranca.xtext +++ b/org.lflang/src/org/lflang/LinguaFranca.xtext @@ -192,6 +192,7 @@ Action: (':' type=Type)? ';'?; Reaction: + (attributes+=Attribute)* ('reaction') ('(' (triggers+=TriggerRef (',' triggers+=TriggerRef)*)? ')')? (sources+=VarRef (',' sources+=VarRef)*)? @@ -248,6 +249,15 @@ Serializer: 'serializer' type=STRING ; +/////////// Attributes +Attribute: + '@' attrName=AttrName ('(' attrParms+=AttrParm (',' attrParms+=AttrParm)* ')')? +; + +AttrParm: + STRING +; + /////////// For target parameters KeyValuePairs: @@ -488,6 +498,9 @@ enum ActionOrigin: enum Visibility: NONE | PRIVATE='private' | PUBLIC='public'; +enum AttrName: + LABEL='label'; + // 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. From 861ac63750897e4eb36051663341bbcd60e42027 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 23 Feb 2022 15:34:43 -0800 Subject: [PATCH 02/21] Extend syntax, update diagram synthesis, update validator --- .../synthesis/LinguaFrancaSynthesis.xtend | 31 +++++++- org.lflang/src/org/lflang/LinguaFranca.xtext | 10 ++- .../org/lflang/validation/LFValidator.java | 78 +++++++++++++++++++ 3 files changed, 114 insertions(+), 5 deletions(-) diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend index 014aa69ef6..7f4d1ccc2f 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend @@ -92,7 +92,11 @@ import org.lflang.generator.ReactorInstance import org.lflang.generator.TimerInstance import org.lflang.generator.TriggerInstance import org.lflang.generator.TriggerInstance.BuiltinTriggerVariable +import org.lflang.lf.Action import org.lflang.lf.Model +import org.lflang.lf.Reaction +import org.lflang.lf.Reactor +import org.lflang.lf.Timer import static extension org.eclipse.emf.ecore.util.EcoreUtil.* import static extension org.lflang.ASTUtils.* @@ -1078,8 +1082,31 @@ class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { private def Iterable createUserComments(EObject element, KNode targetNode) { if (SHOW_USER_LABELS.booleanValue) { - val commentText = ASTUtils.findAnnotationInComments(element, "@label") - + var String commentText + // The list of elements where labels could be placed. + switch element { + Reaction : { + var list = element.getAttributes.filter[ it.attrName.toString === 'label' ] + if (list.length != 0) + commentText = list.get(0).getAttrParms.get(0).getValue.replaceAll("^\"|\"$", "") + } + Reactor : { + var list = element.getAttributes.filter[ it.attrName.toString === 'label' ] + if (list.length != 0) + commentText = list.get(0).getAttrParms.get(0).getValue.replaceAll("^\"|\"$", "") + } + Action : { + var list = element.getAttributes.filter[ it.attrName.toString === 'label' ] + if (list.length != 0) + commentText = list.get(0).getAttrParms.get(0).getValue.replaceAll("^\"|\"$", "") + } + Timer : { + var list = element.getAttributes.filter[ it.attrName.toString === 'label' ] + if (list.length != 0) + commentText = list.get(0).getAttrParms.get(0).getValue.replaceAll("^\"|\"$", "") + } + } + if (!commentText.nullOrEmpty) { val comment = createNode() comment.setLayoutOption(CoreOptions.COMMENT_BOX, true) diff --git a/org.lflang/src/org/lflang/LinguaFranca.xtext b/org.lflang/src/org/lflang/LinguaFranca.xtext index 2a424020b7..13fb1050f3 100644 --- a/org.lflang/src/org/lflang/LinguaFranca.xtext +++ b/org.lflang/src/org/lflang/LinguaFranca.xtext @@ -85,7 +85,7 @@ QualifiedNameWithWildcard: * 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)? @@ -138,7 +138,7 @@ TargetDecl: * - 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: +StateVar: 'state' name=ID ( (':' (type=Type))? ((parens+='(' (init+=Value (',' init+=Value)*)? parens+=')') @@ -170,7 +170,7 @@ Output: // 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=Value (',' period=Value)? ')')? ';'?; + (attributes+=Attribute)* 'timer' name=ID ('(' offset=Value (',' period=Value)? ')')? ';'?; Boolean: TRUE | FALSE @@ -187,6 +187,7 @@ Boolean: // 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=Value (',' minSpacing=Value (',' policy=STRING)? )? ')')? (':' type=Type)? ';'?; @@ -255,6 +256,9 @@ Attribute: ; AttrParm: + (name=ID '=')? value=AttrParmValue; + +AttrParmValue: STRING ; diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 2d7bd1bb25..d180c7bcef 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -56,6 +56,8 @@ import org.lflang.lf.Action; import org.lflang.lf.ActionOrigin; import org.lflang.lf.Assignment; +import org.lflang.lf.Attribute; +import org.lflang.lf.AttrParm; import org.lflang.lf.Connection; import org.lflang.lf.Deadline; import org.lflang.lf.Host; @@ -1442,6 +1444,82 @@ public void checkVarRef(VarRef varRef) { } } + @Check(CheckType.FAST) + public void checkReactionAttributes(Reaction reaction) { + EList attrs = reaction.getAttributes(); + for (Attribute attr : attrs) { + switch (attr.getAttrName().toString()) { + case "label" : + EList parms = attr.getAttrParms(); + if (parms.size() > 1) { + error("The label attribute only takes 1 parameter.", Literals.REACTION__ATTRIBUTES); + } + String parmName = parms.get(0).getName(); + if (parmName != null && !parmName.equals("name")) { + error("Invalid attribute parameter name. The supported parameter name is \"name.\"", Literals.REACTION__ATTRIBUTES); + } + break; + } + } + } + + @Check(CheckType.FAST) + public void checkReactorAttributes(Reactor reactor) { + EList attrs = reactor.getAttributes(); + for (Attribute attr : attrs) { + switch (attr.getAttrName().toString()) { + case "label" : + EList parms = attr.getAttrParms(); + if (parms.size() > 1) { + error("The label attribute only takes 1 parameter.", Literals.REACTOR__ATTRIBUTES); + } + String parmName = parms.get(0).getName(); + if (parmName != null && !parmName.equals("name")) { + error("Invalid attribute parameter name. The supported parameter name is \"name.\"", Literals.REACTOR__ATTRIBUTES); + } + break; + } + } + } + + @Check(CheckType.FAST) + public void checkActionAttributes(Action action) { + EList attrs = action.getAttributes(); + for (Attribute attr : attrs) { + switch (attr.getAttrName().toString()) { + case "label" : + EList parms = attr.getAttrParms(); + if (parms.size() > 1) { + error("The label attribute only takes 1 parameter.", Literals.ACTION__ATTRIBUTES); + } + String parmName = parms.get(0).getName(); + if (parmName != null && !parmName.equals("name")) { + error("Invalid attribute parameter name. The supported parameter name is \"name.\"", Literals.ACTION__ATTRIBUTES); + } + break; + } + } + } + + @Check(CheckType.FAST) + public void checkTimerAttributes(Timer timer) { + EList attrs = timer.getAttributes(); + for (Attribute attr : attrs) { + switch (attr.getAttrName().toString()) { + case "label" : + EList parms = attr.getAttrParms(); + if (parms.size() > 1) { + error("The label attribute only takes 1 parameter.", Literals.TIMER__ATTRIBUTES); + } + String parmName = parms.get(0).getName(); + if (parmName != null && !parmName.equals("name")) { + error("Invalid attribute parameter name. The supported parameter name is \"name.\"", Literals.TIMER__ATTRIBUTES); + } + break; + } + } + } + static String UNDERSCORE_MESSAGE = "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": "; static String ACTIONS_MESSAGE = "\"actions\" is a reserved word for the TypeScript target for objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation): "; static String RESERVED_MESSAGE = "Reserved words in the target language are not allowed for objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation): "; From 605c3f95d1626b8bd982d679e29dc52e75e84e63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Wed, 8 Jun 2022 19:33:27 +0200 Subject: [PATCH 03/21] Add annotation validation --- .../org/lflang/validation/LFValidator.java | 96 ++++++++++++++++--- 1 file changed, 84 insertions(+), 12 deletions(-) diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 4d8d05a890..31987a3aeb 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -37,9 +37,11 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -66,8 +68,8 @@ import org.lflang.lf.Action; import org.lflang.lf.ActionOrigin; import org.lflang.lf.Assignment; -import org.lflang.lf.Attribute; import org.lflang.lf.AttrParm; +import org.lflang.lf.Attribute; import org.lflang.lf.Connection; import org.lflang.lf.Deadline; import org.lflang.lf.Expression; @@ -108,6 +110,8 @@ import org.lflang.lf.WidthSpec; import org.lflang.lf.WidthTerm; import org.lflang.util.FileUtil; +import org.lflang.validation.LFValidator.AttributeSpec.AttrParamSpec; +import org.lflang.validation.LFValidator.AttributeSpec.AttrParamType; import com.google.inject.Inject; @@ -1199,20 +1203,88 @@ public void checkVarRef(VarRef varRef) { } - @Check(CheckType.FAST) - public void checkAttributes(Attribute attr) { - switch (attr.getAttrName().toString()) { - case "label" : - EList parms = attr.getAttrParms(); - if (parms.size() > 1) { - error("The label attribute only takes 1 parameter.", Literals.REACTION__ATTRIBUTES); + /** + * Specification of the structure of an annotation. + */ + static class AttributeSpec { + + private final Map paramSpecByName; + + + public AttributeSpec(List params) { + + paramSpecByName = params.stream().collect(Collectors.toMap(it -> it.name, it -> it)); + + } + + /** + * Check that the attribute conforms to this spec. The + * attr has the correct name. + */ + public void check(LFValidator validator, Attribute attr) { + Set seen = new HashSet<>(); + for (AttrParm parm : attr.getAttrParms()) { + AttrParamSpec parmSpec = paramSpecByName.get(parm.getName()); + if (parmSpec == null) { + validator.error("Unknown attribute parameter.", Literals.ATTR_PARM__NAME); + continue; + } + seen.add(parm.getName()); + // todo check type when there are several ones + // currently only strings are alloed anyway } - String parmName = parms.get(0).getName(); - if (parmName != null && !parmName.equals("name")) { - error("Invalid attribute parameter name. The supported parameter name is \"name.\"", Literals.REACTION__ATTRIBUTES); + + Map missingParams = new HashMap<>(paramSpecByName); + missingParams.keySet().removeAll(seen); + missingParams.forEach((name, spec) -> { + if (!spec.isOptional()) { + validator.error("Missing required attribute '" + name + "'.", Literals.ATTRIBUTE__ATTR_PARMS); + } + }); + } + + + /** + * @param defaultValue If non-null, parameter is optional. + */ + record AttrParamSpec(String name, AttrParamType string, Object defaultValue) { + + private boolean isOptional() { + return defaultValue == null; } - break; } + + + enum AttrParamType { + STRING + } + } + + /** + * + */ + private static final Map ATTRIBUTE_SPECS_BY_NAME = new HashMap<>(); + + + static { + // put specs for known annotations here + + // @label("value") + ATTRIBUTE_SPECS_BY_NAME.put("label", new AttributeSpec( + List.of(new AttrParamSpec("value", AttrParamType.STRING, null)) + )); + + } + + @Check(CheckType.FAST) + public void checkAttributes(Attribute attr) { + String name = attr.getAttrName().toString(); + AttributeSpec spec = ATTRIBUTE_SPECS_BY_NAME.get(name); + if (spec == null) { + error("Unknown attribute.", Literals.ATTRIBUTE__ATTR_NAME); + return; + } + spec.check(this, attr); } @Check(CheckType.FAST) From 39f22de5d55e4f8b99380adfdd4e9a2400f7146f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Wed, 8 Jun 2022 20:57:22 +0200 Subject: [PATCH 04/21] Support shorthand syntax Add test --- .../compiler/LinguaFrancaValidationTest.java | 12 +++++ .../org/lflang/validation/LFValidator.java | 47 +++++++++++++++---- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java index 86f872f406..58b0ce081d 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java @@ -2221,6 +2221,18 @@ public void testUnrecognizedTarget() throws Exception { "Unrecognized target: Pjthon"); } + + @Test + public void testWrongStructureForLabelAttribute() throws Exception { + String testCase = """ + target C; + @label(name="somethign") + main reactor { } + """; + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getAttribute(), null, + "Unknown attribute parameter."); + } + } diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 31987a3aeb..be3a22673b 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -56,6 +56,7 @@ import org.eclipse.xtext.validation.Check; import org.eclipse.xtext.validation.CheckType; import org.eclipse.xtext.validation.ValidationMessageAcceptor; +import org.jetbrains.annotations.NotNull; import org.lflang.ASTUtils; import org.lflang.ModelInfo; @@ -1208,6 +1209,7 @@ public void checkVarRef(VarRef varRef) { */ static class AttributeSpec { + private static final String VALUE_ATTR = "value"; private final Map paramSpecByName; @@ -1222,16 +1224,21 @@ public AttributeSpec(List params) { * attr has the correct name. */ public void check(LFValidator validator, Attribute attr) { - Set seen = new HashSet<>(); - for (AttrParm parm : attr.getAttrParms()) { - AttrParamSpec parmSpec = paramSpecByName.get(parm.getName()); - if (parmSpec == null) { - validator.error("Unknown attribute parameter.", Literals.ATTR_PARM__NAME); - continue; + + Set seen; + if (attr.getAttrParms().size() == 1 && attr.getAttrParms().get(0).getName() == null) { + // then this is @attr("value") + // shorthand for @attr(value="value") + AttrParamSpec valueSpec = paramSpecByName.get(VALUE_ATTR); + if (valueSpec == null) { + validator.error("Attribute doesn't have a 'value' parameter.", Literals.ATTR_PARM__NAME); + return; } - seen.add(parm.getName()); - // todo check type when there are several ones - // currently only strings are alloed anyway + + valueSpec.check(attr.getAttrParms().get(0)); + seen = Set.of(VALUE_ATTR); + } else { + seen = processNamedAttrs(validator, attr); } Map missingParams = new HashMap<>(paramSpecByName); @@ -1243,6 +1250,21 @@ public void check(LFValidator validator, Attribute attr) { }); } + @NotNull + private Set processNamedAttrs(LFValidator validator, Attribute attr) { + Set seen = new HashSet<>(); + for (AttrParm parm : attr.getAttrParms()) { + AttrParamSpec parmSpec = paramSpecByName.get(parm.getName()); + if (parmSpec == null) { + validator.error("Unknown attribute parameter.", Literals.ATTRIBUTE__ATTR_NAME); + continue; + } + parmSpec.check(parm); + seen.add(parm.getName()); + } + return seen; + } + /** * @param defaultValue If non-null, parameter is optional. @@ -1252,6 +1274,11 @@ record AttrParamSpec(String name, AttrParamType string, Object defaultValue) { private boolean isOptional() { return defaultValue == null; } + + public void check(AttrParm parm) { + // todo check type when there are several ones + // currently only strings are allowed anyway + } } @@ -1271,7 +1298,7 @@ enum AttrParamType { // @label("value") ATTRIBUTE_SPECS_BY_NAME.put("label", new AttributeSpec( - List.of(new AttrParamSpec("value", AttrParamType.STRING, null)) + List.of(new AttrParamSpec(AttributeSpec.VALUE_ATTR, AttrParamType.STRING, null)) )); } From f047cc81878b19be24214a8ed6bf129bc80aa6f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Wed, 8 Jun 2022 21:08:44 +0200 Subject: [PATCH 05/21] add another test --- .../tests/compiler/LinguaFrancaValidationTest.java | 14 +++++++++++++- .../src/org/lflang/validation/LFValidator.java | 5 +++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java index 58b0ce081d..5983270017 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java @@ -2233,7 +2233,19 @@ public void testWrongStructureForLabelAttribute() throws Exception { "Unknown attribute parameter."); } - + + @Test + public void testMissingName() throws Exception { + String testCase = """ + target C; + @label("somethign", "else") + main reactor { } + """; + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getAttribute(), null, + "Missing name for attribute parameter."); + } + + } diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index be3a22673b..1f144ae98f 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -1254,6 +1254,11 @@ public void check(LFValidator validator, Attribute attr) { private Set processNamedAttrs(LFValidator validator, Attribute attr) { Set seen = new HashSet<>(); for (AttrParm parm : attr.getAttrParms()) { + if (parm.getName() == null) { + validator.error("Missing name for attribute parameter.", Literals.ATTRIBUTE__ATTR_NAME); + continue; + } + AttrParamSpec parmSpec = paramSpecByName.get(parm.getName()); if (parmSpec == null) { validator.error("Unknown attribute parameter.", Literals.ATTRIBUTE__ATTR_NAME); From 9d15a7198ccf9e1b6b39b071263911f1c5ab870b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Wed, 8 Jun 2022 21:21:34 +0200 Subject: [PATCH 06/21] Support trailing comma and empty param list --- .../compiler/LinguaFrancaParsingTest.java | 36 ++++++++++++++++++- org.lflang/src/org/lflang/LinguaFranca.xtext | 5 +-- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.java index f898b76c08..74c8f694ce 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.java @@ -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; @@ -65,4 +67,36 @@ public void checkForTarget() throws Exception { Assertions.assertNotNull(result); Assertions.assertFalse(result.eResource().getErrors().isEmpty(), "Failed to catch misspelled target keyword."); } -} \ No newline at end of file + + @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 { + + @ohio reaction() {==} + @ohio logical action f; + @ohio timer t; + } + """; + 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; + } + +} diff --git a/org.lflang/src/org/lflang/LinguaFranca.xtext b/org.lflang/src/org/lflang/LinguaFranca.xtext index 6d259d0ed8..9f8d8fe7da 100644 --- a/org.lflang/src/org/lflang/LinguaFranca.xtext +++ b/org.lflang/src/org/lflang/LinguaFranca.xtext @@ -245,7 +245,7 @@ Serializer: /////////// Attributes Attribute: - '@' attrName=AttrName ('(' attrParms+=AttrParm (',' attrParms+=AttrParm)* ')')? + '@' attrName=ID ('(' (attrParms+=AttrParm (',' attrParms+=AttrParm)* ','?)? ')')? ; AttrParm: @@ -465,9 +465,6 @@ enum ActionOrigin: enum Visibility: NONE | PRIVATE='private' | PUBLIC='public'; -enum AttrName: - LABEL='label'; - // 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. From 505efe672f0ecdaa2b91fa58252b05e04d8a3e53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Thu, 9 Jun 2022 22:37:26 +0200 Subject: [PATCH 07/21] Support attributes on more constructs --- .../compiler/LinguaFrancaParsingTest.java | 20 +++++-- org.lflang/src/org/lflang/ASTUtils.java | 52 ++++++++++++++++++- org.lflang/src/org/lflang/LinguaFranca.xtext | 6 ++- 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.java index 74c8f694ce..22c91ef021 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.java @@ -81,10 +81,24 @@ public void testAttributes() throws Exception { @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 reaction() {==} + @ohio logical action f; + @ohio timer t; + @ohio input q: int; + @ohio output q2: int; } """; parseWithoutError(testCase); diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index 404fbf1b0e..5fdc37b38c 100644 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ b/org.lflang/src/org/lflang/ASTUtils.java @@ -63,6 +63,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; @@ -1738,14 +1739,61 @@ public static String findAnnotationInComments(EObject object, String key) { return null; } + /** + * 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 attrs = getAttributes(node); + return attrs.stream() + .filter(it -> it.getAttrName().equals("label")) + .map(it -> it.getAttrParms().get(0).getValue()) + .findFirst() + .orElse(null); + + } + + /** + * 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 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); + } + /** * 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"); + String fromAttr = findLabelAttribute(n); + if (fromAttr == null) { + return findAnnotationInComments(n, "@label"); + } + return fromAttr; } /** diff --git a/org.lflang/src/org/lflang/LinguaFranca.xtext b/org.lflang/src/org/lflang/LinguaFranca.xtext index 9f8d8fe7da..b718e395be 100644 --- a/org.lflang/src/org/lflang/LinguaFranca.xtext +++ b/org.lflang/src/org/lflang/LinguaFranca.xtext @@ -129,6 +129,7 @@ TargetDecl: * must be given, or a literal or code that denotes zero. */ StateVar: + (attributes+=Attribute)* 'state' name=ID ( (':' (type=Type))? ((parens+='(' (init+=Expression (',' init+=Expression)*)? parens+=')') @@ -150,10 +151,10 @@ 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). @@ -301,6 +302,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+='}') From 12ec7d58d43b36bacd4b2583e2e00f2b9ba0a0d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Thu, 9 Jun 2022 22:40:08 +0200 Subject: [PATCH 08/21] Fix diagram synthesis --- .../diagram/synthesis/LinguaFrancaSynthesis.java | 2 +- org.lflang/src/org/lflang/ASTUtils.java | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java index 86e3fb4e0a..6f7024ce01 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java @@ -1329,7 +1329,7 @@ private KNode addErrorComment(KNode node, String message) { private Iterable createUserComments(EObject element, KNode targetNode) { if (getBooleanValue(SHOW_USER_LABELS)) { - String commentText = ASTUtils.findAnnotationInComments(element, "@label"); + String commentText = ASTUtils.label(element); if (!StringExtensions.isNullOrEmpty(commentText)) { KNode comment = _kNodeExtensions.createNode(); diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index 5fdc37b38c..74a1b630d0 100644 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ b/org.lflang/src/org/lflang/ASTUtils.java @@ -1789,13 +1789,23 @@ public static List getAttributes(EObject node) { * @return The annotated string if an `@label` annotation was found. `null` otherwise. */ public static String label(Reaction n) { + return label((EObject) n); + } + + /** + * 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 findAnnotationInComments(n, "@label"); } return fromAttr; } - + /** * Find the main reactor and set its name if none was defined. * @param resource The resource to find the main reactor in. From af7502e45a532e4e861eeb781f30b4b223e96bdb Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Fri, 17 Jun 2022 17:48:12 -0700 Subject: [PATCH 09/21] Fix remaining conflict --- org.lflang/src/org/lflang/validation/LFValidator.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 9f91599e96..c000bcfcff 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -1347,17 +1347,6 @@ public void checkWidthSpec(WidthSpec widthSpec) { } } } -<<<<<<< HEAD - - @Check(CheckType.FAST) - public void checkModeModifier(VarRef ref) { - if (ref.getVariable() instanceof Mode && ref.getModifier() != null && !ModeTransitionType.KEYWORDS.contains(ref.getModifier())) { - error(String.format("Illegal mode transition modifier! Only %s is allowed.", - String.join("/", ModeTransitionType.KEYWORDS)), Literals.VAR_REF__MODIFIER); - } - } -======= ->>>>>>> origin/master @Check(CheckType.FAST) public void checkInitialMode(Reactor reactor) { From bed69403d51db964f67ee12d56b26735c232101b Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 22 Jun 2022 09:19:33 -0700 Subject: [PATCH 10/21] Re-organize attributes in the LFValidator --- .../org/lflang/validation/LFValidator.java | 224 ++++++++++-------- 1 file changed, 121 insertions(+), 103 deletions(-) diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index c000bcfcff..d004d98036 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -1206,111 +1206,12 @@ public void checkVarRef(VarRef varRef) { } } - - /** - * Specification of the structure of an annotation. - */ - static class AttributeSpec { - - private static final String VALUE_ATTR = "value"; - private final Map paramSpecByName; - - - public AttributeSpec(List params) { - - paramSpecByName = params.stream().collect(Collectors.toMap(it -> it.name, it -> it)); - - } - - /** - * Check that the attribute conforms to this spec. The - * attr has the correct name. - */ - public void check(LFValidator validator, Attribute attr) { - - Set seen; - if (attr.getAttrParms().size() == 1 && attr.getAttrParms().get(0).getName() == null) { - // then this is @attr("value") - // shorthand for @attr(value="value") - AttrParamSpec valueSpec = paramSpecByName.get(VALUE_ATTR); - if (valueSpec == null) { - validator.error("Attribute doesn't have a 'value' parameter.", Literals.ATTR_PARM__NAME); - return; - } - - valueSpec.check(attr.getAttrParms().get(0)); - seen = Set.of(VALUE_ATTR); - } else { - seen = processNamedAttrs(validator, attr); - } - - Map missingParams = new HashMap<>(paramSpecByName); - missingParams.keySet().removeAll(seen); - missingParams.forEach((name, spec) -> { - if (!spec.isOptional()) { - validator.error("Missing required attribute '" + name + "'.", Literals.ATTRIBUTE__ATTR_PARMS); - } - }); - } - - @NotNull - private Set processNamedAttrs(LFValidator validator, Attribute attr) { - Set seen = new HashSet<>(); - for (AttrParm parm : attr.getAttrParms()) { - if (parm.getName() == null) { - validator.error("Missing name for attribute parameter.", Literals.ATTRIBUTE__ATTR_NAME); - continue; - } - - AttrParamSpec parmSpec = paramSpecByName.get(parm.getName()); - if (parmSpec == null) { - validator.error("Unknown attribute parameter.", Literals.ATTRIBUTE__ATTR_NAME); - continue; - } - parmSpec.check(parm); - seen.add(parm.getName()); - } - return seen; - } - - - /** - * @param defaultValue If non-null, parameter is optional. - */ - record AttrParamSpec(String name, AttrParamType string, Object defaultValue) { - - private boolean isOptional() { - return defaultValue == null; - } - - public void check(AttrParm parm) { - // todo check type when there are several ones - // currently only strings are allowed anyway - } - } - - - enum AttrParamType { - STRING - } - } - /** - * + * Check whether an attribute is supported + * and the validity of the attribute. + * + * @param attr The attribute being checked */ - private static final Map ATTRIBUTE_SPECS_BY_NAME = new HashMap<>(); - - - static { - // put specs for known annotations here - - // @label("value") - ATTRIBUTE_SPECS_BY_NAME.put("label", new AttributeSpec( - List.of(new AttrParamSpec(AttributeSpec.VALUE_ATTR, AttrParamType.STRING, null)) - )); - - } - @Check(CheckType.FAST) public void checkAttributes(Attribute attr) { String name = attr.getAttrName().toString(); @@ -1319,6 +1220,7 @@ public void checkAttributes(Attribute attr) { error("Unknown attribute.", Literals.ATTRIBUTE__ATTR_NAME); return; } + // Check the validity of the attribute. spec.check(this, attr); } @@ -1818,4 +1720,120 @@ private boolean sameType(Type type1, Type type2) { + "state, reactor definitions, and reactor instantiation) may not start with \"__\": "; private static String USERNAME_REGEX = "^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\\$)$"; + + /** A map from a string to a supported AttributeSpec */ + private static final Map ATTRIBUTE_SPECS_BY_NAME = new HashMap<>(); + + ////////////////////////////////////////////////////////////// + //// Inner classes. + /** + * Specification of the structure of an annotation. + */ + static class AttributeSpec { + + private static final String VALUE_ATTR = "value"; + private final Map paramSpecByName; + + public AttributeSpec(List params) { + paramSpecByName = params.stream().collect(Collectors.toMap(it -> it.name, it -> it)); + } + + /** + * Check that the attribute conforms to this spec and whether + * attr has the correct name. + */ + public void check(LFValidator validator, Attribute attr) { + Set seen; + if (attr.getAttrParms().size() == 1 && attr.getAttrParms().get(0).getName() == null) { + // then this is @attr("value") + // shorthand for @attr(value="value") + AttrParamSpec valueSpec = paramSpecByName.get(VALUE_ATTR); + if (valueSpec == null) { + validator.error("Attribute doesn't have a 'value' parameter.", Literals.ATTR_PARM__NAME); + return; + } + + valueSpec.check(attr.getAttrParms().get(0)); + seen = Set.of(VALUE_ATTR); + } else { + seen = processNamedAttrParms(validator, attr); + } + + Map missingParams = new HashMap<>(paramSpecByName); + missingParams.keySet().removeAll(seen); + missingParams.forEach((name, paramSpec) -> { + if (!paramSpec.isOptional()) { + validator.error("Missing required attribute parameter '" + name + "'.", Literals.ATTRIBUTE__ATTR_PARMS); + } + }); + } + + /** + * Check if the attribute parameters are named, whether + * these names are known, and whether the named parameters + * conform to the param spec (whether the param has the + * right type, etc.). + * + * @param validator The current validator in use + * @param attr The attribute being checked + * @return A set of named attribute parameters the user provides + */ + @NotNull + private Set processNamedAttrParms(LFValidator validator, Attribute attr) { + Set seen = new HashSet<>(); + for (AttrParm parm : attr.getAttrParms()) { + if (parm.getName() == null) { + validator.error("Missing name for attribute parameter.", Literals.ATTRIBUTE__ATTR_NAME); + continue; + } + + AttrParamSpec parmSpec = paramSpecByName.get(parm.getName()); + if (parmSpec == null) { + validator.error("Unknown attribute parameter.", Literals.ATTRIBUTE__ATTR_NAME); + continue; + } + // Check whether a parameter conforms to its spec. + parmSpec.check(parm); + seen.add(parm.getName()); + } + return seen; + } + + /** + * The specification of the attribute parameter + * + * @param name The name of the attribute parameter + * @param type The type of the parameter + * @param defaultValue If non-null, parameter is optional. + */ + record AttrParamSpec(String name, AttrParamType type, Object defaultValue) { + + private boolean isOptional() { + return defaultValue == null; + } + + // FIXME: Check if a parameter has the right type. + // Currently only strings are allowed so we are okay. + public void check(AttrParm parm) { + + } + } + + /** + * The type of attribute parameters currently supported + */ + enum AttrParamType { + STRING + } + } + + /** + * The specs of the known annotations are declared here. + */ + static { + // @label("value") + ATTRIBUTE_SPECS_BY_NAME.put("label", new AttributeSpec( + List.of(new AttrParamSpec(AttributeSpec.VALUE_ATTR, AttrParamType.STRING, null)) + )); + } } From 080e5f82db09d4f302d6e24266813678375b864c Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 22 Jun 2022 12:05:01 -0700 Subject: [PATCH 11/21] Add comments --- org.lflang/src/org/lflang/validation/LFValidator.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index d004d98036..4e7ee1c44a 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -1744,9 +1744,11 @@ public AttributeSpec(List params) { */ public void check(LFValidator validator, Attribute attr) { Set seen; + // Check to see if there is one or multiple parameters. if (attr.getAttrParms().size() == 1 && attr.getAttrParms().get(0).getName() == null) { - // then this is @attr("value") - // shorthand for @attr(value="value") + // If we are in this branch, + // then the user has provided @attr("value"), + // which is a shorthand for @attr(value="value"). AttrParamSpec valueSpec = paramSpecByName.get(VALUE_ATTR); if (valueSpec == null) { validator.error("Attribute doesn't have a 'value' parameter.", Literals.ATTR_PARM__NAME); @@ -1756,9 +1758,11 @@ public void check(LFValidator validator, Attribute attr) { valueSpec.check(attr.getAttrParms().get(0)); seen = Set.of(VALUE_ATTR); } else { + // Process multiple attributes, each of which has to be named. seen = processNamedAttrParms(validator, attr); } + // Check if there are any missing parameters required by this attribute. Map missingParams = new HashMap<>(paramSpecByName); missingParams.keySet().removeAll(seen); missingParams.forEach((name, paramSpec) -> { @@ -1789,7 +1793,8 @@ private Set processNamedAttrParms(LFValidator validator, Attribute attr) AttrParamSpec parmSpec = paramSpecByName.get(parm.getName()); if (parmSpec == null) { - validator.error("Unknown attribute parameter.", Literals.ATTRIBUTE__ATTR_NAME); + validator.error("\"" + parm.getName() + "\"" + " is an unknown attribute parameter.", + Literals.ATTRIBUTE__ATTR_NAME); continue; } // Check whether a parameter conforms to its spec. From c30a7c96840a519c195bd6c40dfdef6af22b9172 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 22 Jun 2022 22:17:30 -0700 Subject: [PATCH 12/21] Support basic param types such as String, Int, Boolean, and Float. --- org.lflang/src/org/lflang/ASTUtils.java | 2 +- org.lflang/src/org/lflang/LinguaFranca.xtext | 5 ++- .../org/lflang/validation/LFValidator.java | 42 ++++++++++++++++--- 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index c8146e5fa9..55ad4072c5 100644 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ b/org.lflang/src/org/lflang/ASTUtils.java @@ -1758,7 +1758,7 @@ public static String findLabelAttribute(EObject node) { List attrs = getAttributes(node); return attrs.stream() .filter(it -> it.getAttrName().equals("label")) - .map(it -> it.getAttrParms().get(0).getValue()) + .map(it -> it.getAttrParms().get(0).getValue().getStr()) .findFirst() .orElse(null); diff --git a/org.lflang/src/org/lflang/LinguaFranca.xtext b/org.lflang/src/org/lflang/LinguaFranca.xtext index 9e00697cc5..5f9dacda1e 100644 --- a/org.lflang/src/org/lflang/LinguaFranca.xtext +++ b/org.lflang/src/org/lflang/LinguaFranca.xtext @@ -256,7 +256,10 @@ AttrParm: (name=ID '=')? value=AttrParmValue; AttrParmValue: - STRING + str=STRING + | int=SignedInt + | bool=Boolean + | float=SignedFloat ; /////////// For target parameters diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 4e7ee1c44a..ed8b44cb75 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -1755,7 +1755,7 @@ public void check(LFValidator validator, Attribute attr) { return; } - valueSpec.check(attr.getAttrParms().get(0)); + valueSpec.check(validator, attr.getAttrParms().get(0)); seen = Set.of(VALUE_ATTR); } else { // Process multiple attributes, each of which has to be named. @@ -1798,7 +1798,7 @@ private Set processNamedAttrParms(LFValidator validator, Attribute attr) continue; } // Check whether a parameter conforms to its spec. - parmSpec.check(parm); + parmSpec.check(validator, parm); seen.add(parm.getName()); } return seen; @@ -1817,9 +1817,35 @@ private boolean isOptional() { return defaultValue == null; } - // FIXME: Check if a parameter has the right type. - // Currently only strings are allowed so we are okay. - public void check(AttrParm parm) { + // Check if a parameter has the right type. + // Currently only String, Int, Boolean, and Float are supported. + public void check(LFValidator validator, AttrParm parm) { + switch(type) { + case STRING: + if (parm.getValue().getStr() == null) { + validator.error("Incorrect type: \"" + parm.getName() + "\"" + " should have type String.", + Literals.ATTRIBUTE__ATTR_NAME); + } + break; + case INT: + if (parm.getValue().getInt() == null) { + validator.error("Incorrect type: \"" + parm.getName() + "\"" + " should have type Int.", + Literals.ATTRIBUTE__ATTR_NAME); + } + break; + case BOOLEAN: + if (parm.getValue().getBool() == null) { + validator.error("Incorrect type: \"" + parm.getName() + "\"" + " should have type Boolean.", + Literals.ATTRIBUTE__ATTR_NAME); + } + break; + case FLOAT: + if (parm.getValue().getFloat() == null) { + validator.error("Incorrect type: \"" + parm.getName() + "\"" + " should have type Float.", + Literals.ATTRIBUTE__ATTR_NAME); + } + break; + } } } @@ -1828,12 +1854,16 @@ public void check(AttrParm parm) { * The type of attribute parameters currently supported */ enum AttrParamType { - STRING + STRING, + INT, + BOOLEAN, + FLOAT } } /** * The specs of the known annotations are declared here. + * Note: If an attribute only has one parameter, the parameter name should be "value." */ static { // @label("value") From 14c7ec2d809673f23e6448f0b7b32efdc4c43792 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Wed, 22 Jun 2022 22:18:12 -0700 Subject: [PATCH 13/21] Add a unit test that checks whether the validator can detect wrong param types. --- .../tests/compiler/LinguaFrancaValidationTest.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java index 98ed8d16f9..1561788bf6 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java @@ -2234,7 +2234,6 @@ public void testWrongStructureForLabelAttribute() throws Exception { "Unknown attribute parameter."); } - @Test public void testMissingName() throws Exception { String testCase = """ @@ -2246,6 +2245,17 @@ public void testMissingName() throws Exception { "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 = """ From 8b944e552cc54b49d4859b00c2f1c868be327603 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 23 Jun 2022 09:52:27 -0700 Subject: [PATCH 14/21] Remove the dependency on NotNull --- org.lflang/src/org/lflang/validation/LFValidator.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index bd8b993f31..f378957a58 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -57,7 +57,6 @@ import org.eclipse.xtext.validation.Check; import org.eclipse.xtext.validation.CheckType; import org.eclipse.xtext.validation.ValidationMessageAcceptor; -import org.jetbrains.annotations.NotNull; import org.lflang.ASTUtils; import org.lflang.ModelInfo; @@ -1792,7 +1791,6 @@ public void check(LFValidator validator, Attribute attr) { * @param attr The attribute being checked * @return A set of named attribute parameters the user provides */ - @NotNull private Set processNamedAttrParms(LFValidator validator, Attribute attr) { Set seen = new HashSet<>(); for (AttrParm parm : attr.getAttrParms()) { From d5154753f2e29c60244cb0ff6ceacc71cf46e6ea Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 23 Jun 2022 10:26:29 -0700 Subject: [PATCH 15/21] Factor out AttributeSpec --- .../org/lflang/validation/AttributeSpec.java | 171 ++++++++++++++++++ .../org/lflang/validation/LFValidator.java | 151 ++-------------- 2 files changed, 185 insertions(+), 137 deletions(-) create mode 100644 org.lflang/src/org/lflang/validation/AttributeSpec.java diff --git a/org.lflang/src/org/lflang/validation/AttributeSpec.java b/org.lflang/src/org/lflang/validation/AttributeSpec.java new file mode 100644 index 0000000000..6aa53713ed --- /dev/null +++ b/org.lflang/src/org/lflang/validation/AttributeSpec.java @@ -0,0 +1,171 @@ +/************* + * Copyright (c) 2019-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. + ***************/ + +package org.lflang.validation; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.lflang.lf.AttrParm; +import org.lflang.lf.Attribute; +import org.lflang.lf.LfPackage.Literals; + +/** + * Specification of the structure of an annotation. + */ +class AttributeSpec { + + private final Map paramSpecByName; + public static final String VALUE_ATTR = "value"; + + public AttributeSpec(List params) { + paramSpecByName = params.stream().collect(Collectors.toMap(it -> it.name, it -> it)); + } + + /** + * Check that the attribute conforms to this spec and whether + * attr has the correct name. + */ + public void check(LFValidator validator, Attribute attr) { + Set seen; + // Check to see if there is one or multiple parameters. + if (attr.getAttrParms().size() == 1 && attr.getAttrParms().get(0).getName() == null) { + // If we are in this branch, + // then the user has provided @attr("value"), + // which is a shorthand for @attr(value="value"). + AttrParamSpec valueSpec = paramSpecByName.get(VALUE_ATTR); + if (valueSpec == null) { + validator.error("Attribute doesn't have a 'value' parameter.", Literals.ATTR_PARM__NAME); + return; + } + + valueSpec.check(validator, attr.getAttrParms().get(0)); + seen = Set.of(VALUE_ATTR); + } else { + // Process multiple attributes, each of which has to be named. + seen = processNamedAttrParms(validator, attr); + } + + // Check if there are any missing parameters required by this attribute. + Map missingParams = new HashMap<>(paramSpecByName); + missingParams.keySet().removeAll(seen); + missingParams.forEach((name, paramSpec) -> { + if (!paramSpec.isOptional()) { + validator.error("Missing required attribute parameter '" + name + "'.", Literals.ATTRIBUTE__ATTR_PARMS); + } + }); + } + + /** + * Check if the attribute parameters are named, whether + * these names are known, and whether the named parameters + * conform to the param spec (whether the param has the + * right type, etc.). + * + * @param validator The current validator in use + * @param attr The attribute being checked + * @return A set of named attribute parameters the user provides + */ + private Set processNamedAttrParms(LFValidator validator, Attribute attr) { + Set seen = new HashSet<>(); + for (AttrParm parm : attr.getAttrParms()) { + if (parm.getName() == null) { + validator.error("Missing name for attribute parameter.", Literals.ATTRIBUTE__ATTR_NAME); + continue; + } + + AttrParamSpec parmSpec = paramSpecByName.get(parm.getName()); + if (parmSpec == null) { + validator.error("\"" + parm.getName() + "\"" + " is an unknown attribute parameter.", + Literals.ATTRIBUTE__ATTR_NAME); + continue; + } + // Check whether a parameter conforms to its spec. + parmSpec.check(validator, parm); + seen.add(parm.getName()); + } + return seen; + } + + /** + * The specification of the attribute parameter + * + * @param name The name of the attribute parameter + * @param type The type of the parameter + * @param defaultValue If non-null, parameter is optional. + */ + record AttrParamSpec(String name, AttrParamType type, Object defaultValue) { + + private boolean isOptional() { + return defaultValue == null; + } + + // Check if a parameter has the right type. + // Currently only String, Int, Boolean, and Float are supported. + public void check(LFValidator validator, AttrParm parm) { + switch(type) { + case STRING: + if (parm.getValue().getStr() == null) { + validator.error("Incorrect type: \"" + parm.getName() + "\"" + " should have type String.", + Literals.ATTRIBUTE__ATTR_NAME); + } + break; + case INT: + if (parm.getValue().getInt() == null) { + validator.error("Incorrect type: \"" + parm.getName() + "\"" + " should have type Int.", + Literals.ATTRIBUTE__ATTR_NAME); + } + break; + case BOOLEAN: + if (parm.getValue().getBool() == null) { + validator.error("Incorrect type: \"" + parm.getName() + "\"" + " should have type Boolean.", + Literals.ATTRIBUTE__ATTR_NAME); + } + break; + case FLOAT: + if (parm.getValue().getFloat() == null) { + validator.error("Incorrect type: \"" + parm.getName() + "\"" + " should have type Float.", + Literals.ATTRIBUTE__ATTR_NAME); + } + break; + } + + } + } + + /** + * The type of attribute parameters currently supported + */ + enum AttrParamType { + STRING, + INT, + BOOLEAN, + FLOAT + } +} \ No newline at end of file diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index f378957a58..139704d3ad 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -113,8 +113,8 @@ import org.lflang.lf.WidthSpec; import org.lflang.lf.WidthTerm; import org.lflang.util.FileUtil; -import org.lflang.validation.LFValidator.AttributeSpec.AttrParamSpec; -import org.lflang.validation.LFValidator.AttributeSpec.AttrParamType; +import org.lflang.validation.AttributeSpec.AttrParamSpec; +import org.lflang.validation.AttributeSpec.AttrParamType; import com.google.inject.Inject; @@ -1440,6 +1440,17 @@ public List getTargetPropertyErrors() { return this.targetPropertyErrors; } + /** + * Generate an error message for an AST node. + * Overriding the parent method here to make the method public, + * so that it can be called from the AttributeSpec class. + */ + @Override + public void error(java.lang.String message, + org.eclipse.emf.ecore.EStructuralFeature feature) { + super.error(message, feature); + } + ////////////////////////////////////////////////////////////// //// Private methods. @@ -1734,141 +1745,7 @@ private boolean sameType(Type type1, Type type2) { private static final Map ATTRIBUTE_SPECS_BY_NAME = new HashMap<>(); ////////////////////////////////////////////////////////////// - //// Inner classes. - /** - * Specification of the structure of an annotation. - */ - static class AttributeSpec { - - private static final String VALUE_ATTR = "value"; - private final Map paramSpecByName; - - public AttributeSpec(List params) { - paramSpecByName = params.stream().collect(Collectors.toMap(it -> it.name, it -> it)); - } - - /** - * Check that the attribute conforms to this spec and whether - * attr has the correct name. - */ - public void check(LFValidator validator, Attribute attr) { - Set seen; - // Check to see if there is one or multiple parameters. - if (attr.getAttrParms().size() == 1 && attr.getAttrParms().get(0).getName() == null) { - // If we are in this branch, - // then the user has provided @attr("value"), - // which is a shorthand for @attr(value="value"). - AttrParamSpec valueSpec = paramSpecByName.get(VALUE_ATTR); - if (valueSpec == null) { - validator.error("Attribute doesn't have a 'value' parameter.", Literals.ATTR_PARM__NAME); - return; - } - - valueSpec.check(validator, attr.getAttrParms().get(0)); - seen = Set.of(VALUE_ATTR); - } else { - // Process multiple attributes, each of which has to be named. - seen = processNamedAttrParms(validator, attr); - } - - // Check if there are any missing parameters required by this attribute. - Map missingParams = new HashMap<>(paramSpecByName); - missingParams.keySet().removeAll(seen); - missingParams.forEach((name, paramSpec) -> { - if (!paramSpec.isOptional()) { - validator.error("Missing required attribute parameter '" + name + "'.", Literals.ATTRIBUTE__ATTR_PARMS); - } - }); - } - - /** - * Check if the attribute parameters are named, whether - * these names are known, and whether the named parameters - * conform to the param spec (whether the param has the - * right type, etc.). - * - * @param validator The current validator in use - * @param attr The attribute being checked - * @return A set of named attribute parameters the user provides - */ - private Set processNamedAttrParms(LFValidator validator, Attribute attr) { - Set seen = new HashSet<>(); - for (AttrParm parm : attr.getAttrParms()) { - if (parm.getName() == null) { - validator.error("Missing name for attribute parameter.", Literals.ATTRIBUTE__ATTR_NAME); - continue; - } - - AttrParamSpec parmSpec = paramSpecByName.get(parm.getName()); - if (parmSpec == null) { - validator.error("\"" + parm.getName() + "\"" + " is an unknown attribute parameter.", - Literals.ATTRIBUTE__ATTR_NAME); - continue; - } - // Check whether a parameter conforms to its spec. - parmSpec.check(validator, parm); - seen.add(parm.getName()); - } - return seen; - } - - /** - * The specification of the attribute parameter - * - * @param name The name of the attribute parameter - * @param type The type of the parameter - * @param defaultValue If non-null, parameter is optional. - */ - record AttrParamSpec(String name, AttrParamType type, Object defaultValue) { - - private boolean isOptional() { - return defaultValue == null; - } - - // Check if a parameter has the right type. - // Currently only String, Int, Boolean, and Float are supported. - public void check(LFValidator validator, AttrParm parm) { - switch(type) { - case STRING: - if (parm.getValue().getStr() == null) { - validator.error("Incorrect type: \"" + parm.getName() + "\"" + " should have type String.", - Literals.ATTRIBUTE__ATTR_NAME); - } - break; - case INT: - if (parm.getValue().getInt() == null) { - validator.error("Incorrect type: \"" + parm.getName() + "\"" + " should have type Int.", - Literals.ATTRIBUTE__ATTR_NAME); - } - break; - case BOOLEAN: - if (parm.getValue().getBool() == null) { - validator.error("Incorrect type: \"" + parm.getName() + "\"" + " should have type Boolean.", - Literals.ATTRIBUTE__ATTR_NAME); - } - break; - case FLOAT: - if (parm.getValue().getFloat() == null) { - validator.error("Incorrect type: \"" + parm.getName() + "\"" + " should have type Float.", - Literals.ATTRIBUTE__ATTR_NAME); - } - break; - } - - } - } - - /** - * The type of attribute parameters currently supported - */ - enum AttrParamType { - STRING, - INT, - BOOLEAN, - FLOAT - } - } - + //// Static blocks. /** * The specs of the known annotations are declared here. * Note: If an attribute only has one parameter, the parameter name should be "value." From 9b38e2533f01e91afd5196160123e46bc12b8ed3 Mon Sep 17 00:00:00 2001 From: "Shaokai (Jerry) Lin" Date: Thu, 23 Jun 2022 11:32:03 -0700 Subject: [PATCH 16/21] Apply suggestions from code review Co-authored-by: Marten Lohstroh --- org.lflang/src/org/lflang/validation/AttributeSpec.java | 6 +++--- org.lflang/src/org/lflang/validation/LFValidator.java | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/org.lflang/src/org/lflang/validation/AttributeSpec.java b/org.lflang/src/org/lflang/validation/AttributeSpec.java index 6aa53713ed..b3d1a86b03 100644 --- a/org.lflang/src/org/lflang/validation/AttributeSpec.java +++ b/org.lflang/src/org/lflang/validation/AttributeSpec.java @@ -114,7 +114,7 @@ private Set processNamedAttrParms(LFValidator validator, Attribute attr) } /** - * The specification of the attribute parameter + * The specification of the attribute parameter. * * @param name The name of the attribute parameter * @param type The type of the parameter @@ -160,7 +160,7 @@ public void check(LFValidator validator, AttrParm parm) { } /** - * The type of attribute parameters currently supported + * The type of attribute parameters currently supported. */ enum AttrParamType { STRING, @@ -168,4 +168,4 @@ enum AttrParamType { BOOLEAN, FLOAT } -} \ No newline at end of file +} diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 139704d3ad..a5d0a05d14 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -1442,8 +1442,6 @@ public List getTargetPropertyErrors() { /** * Generate an error message for an AST node. - * Overriding the parent method here to make the method public, - * so that it can be called from the AttributeSpec class. */ @Override public void error(java.lang.String message, From b33484b02a31cea961f09d642c32c5b710e5f17a Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 23 Jun 2022 11:41:55 -0700 Subject: [PATCH 17/21] Add author tags --- org.lflang/src/org/lflang/validation/AttributeSpec.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/org.lflang/src/org/lflang/validation/AttributeSpec.java b/org.lflang/src/org/lflang/validation/AttributeSpec.java index b3d1a86b03..c951ea830a 100644 --- a/org.lflang/src/org/lflang/validation/AttributeSpec.java +++ b/org.lflang/src/org/lflang/validation/AttributeSpec.java @@ -38,6 +38,9 @@ /** * Specification of the structure of an annotation. + * + * @author{Clément Fournier, TU Dresden, INSA Rennes} + * @author{Shaokai Lin } */ class AttributeSpec { From 5de2169b7403452cde99802497c8aa8f5f6f7a06 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 23 Jun 2022 13:31:52 -0700 Subject: [PATCH 18/21] Move the static block and static map back to AttributeSpec --- .../org/lflang/validation/AttributeSpec.java | 15 +++++++++++++++ .../org/lflang/validation/LFValidator.java | 19 ++----------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/org.lflang/src/org/lflang/validation/AttributeSpec.java b/org.lflang/src/org/lflang/validation/AttributeSpec.java index c951ea830a..07c70af72a 100644 --- a/org.lflang/src/org/lflang/validation/AttributeSpec.java +++ b/org.lflang/src/org/lflang/validation/AttributeSpec.java @@ -45,8 +45,12 @@ class AttributeSpec { private final Map paramSpecByName; + public static final String VALUE_ATTR = "value"; + /** A map from a string to a supported AttributeSpec */ + public static final Map ATTRIBUTE_SPECS_BY_NAME = new HashMap<>(); + public AttributeSpec(List params) { paramSpecByName = params.stream().collect(Collectors.toMap(it -> it.name, it -> it)); } @@ -171,4 +175,15 @@ enum AttrParamType { BOOLEAN, FLOAT } + + /** + * The specs of the known annotations are declared here. + * Note: If an attribute only has one parameter, the parameter name should be "value." + */ + static { + // @label("value") + ATTRIBUTE_SPECS_BY_NAME.put("label", new AttributeSpec( + List.of(new AttrParamSpec(AttributeSpec.VALUE_ATTR, AttrParamType.STRING, null)) + )); + } } diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index a5d0a05d14..135889b204 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -1224,7 +1224,7 @@ public void checkVarRef(VarRef varRef) { @Check(CheckType.FAST) public void checkAttributes(Attribute attr) { String name = attr.getAttrName().toString(); - AttributeSpec spec = ATTRIBUTE_SPECS_BY_NAME.get(name); + AttributeSpec spec = AttributeSpec.ATTRIBUTE_SPECS_BY_NAME.get(name); if (spec == null) { error("Unknown attribute.", Literals.ATTRIBUTE__ATTR_NAME); return; @@ -1738,20 +1738,5 @@ private boolean sameType(Type type1, Type type2) { + "state, reactor definitions, and reactor instantiation) may not start with \"__\": "; private static String USERNAME_REGEX = "^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\\$)$"; - - /** A map from a string to a supported AttributeSpec */ - private static final Map ATTRIBUTE_SPECS_BY_NAME = new HashMap<>(); - - ////////////////////////////////////////////////////////////// - //// Static blocks. - /** - * The specs of the known annotations are declared here. - * Note: If an attribute only has one parameter, the parameter name should be "value." - */ - static { - // @label("value") - ATTRIBUTE_SPECS_BY_NAME.put("label", new AttributeSpec( - List.of(new AttrParamSpec(AttributeSpec.VALUE_ATTR, AttrParamType.STRING, null)) - )); - } + } From b24e5b5a52f8a51b5fd2914f1228b9576f6d06a1 Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Thu, 23 Jun 2022 13:41:27 -0700 Subject: [PATCH 19/21] Change the visibility of the overridden error() back to protected. --- org.lflang/src/org/lflang/validation/LFValidator.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 135889b204..59e6f4e4d2 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -1440,11 +1440,14 @@ public List getTargetPropertyErrors() { return this.targetPropertyErrors; } + ////////////////////////////////////////////////////////////// + //// Protected methods. + /** * Generate an error message for an AST node. */ @Override - public void error(java.lang.String message, + protected void error(java.lang.String message, org.eclipse.emf.ecore.EStructuralFeature feature) { super.error(message, feature); } From 8b0d893b95eb38640c2d68291474e8048353c04e Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sat, 25 Jun 2022 09:38:03 -0700 Subject: [PATCH 20/21] Apply quick fix suggested by @lhstrh to remove ambiguity --- org.lflang/src/org/lflang/LinguaFranca.xtext | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/LinguaFranca.xtext b/org.lflang/src/org/lflang/LinguaFranca.xtext index 5f9dacda1e..7746aa822a 100644 --- a/org.lflang/src/org/lflang/LinguaFranca.xtext +++ b/org.lflang/src/org/lflang/LinguaFranca.xtext @@ -230,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)*) From c92516ec547effe748c93e258f7e26236498e6ae Mon Sep 17 00:00:00 2001 From: Shaokai Lin Date: Sat, 25 Jun 2022 09:53:23 -0700 Subject: [PATCH 21/21] Factor out attribute-related methods into their own AttributeUtils class --- .../synthesis/LinguaFrancaSynthesis.java | 3 +- org.lflang/src/org/lflang/ASTUtils.java | 67 ---------- org.lflang/src/org/lflang/AstExtensions.kt | 2 +- org.lflang/src/org/lflang/AttributeUtils.java | 115 ++++++++++++++++++ .../org/lflang/generator/rust/RustModel.kt | 2 +- 5 files changed, 119 insertions(+), 70 deletions(-) create mode 100644 org.lflang/src/org/lflang/AttributeUtils.java diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java index d53729469b..3f27cc0f52 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java @@ -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; @@ -1383,7 +1384,7 @@ private KNode addErrorComment(KNode node, String message) { private Iterable createUserComments(EObject element, KNode targetNode) { if (getBooleanValue(SHOW_USER_LABELS)) { - String commentText = ASTUtils.label(element); + String commentText = AttributeUtils.label(element); if (!StringExtensions.isNullOrEmpty(commentText)) { KNode comment = _kNodeExtensions.createNode(); diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index 55ad4072c5..d93c61c901 100644 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ b/org.lflang/src/org/lflang/ASTUtils.java @@ -1748,73 +1748,6 @@ public static String findAnnotationInComments(EObject object, String key) { return null; } - /** - * 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 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 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 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); - } - - /** - * 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); - } - - /** - * 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 findAnnotationInComments(n, "@label"); - } - return fromAttr; - } - /** * Find the main reactor and set its name if none was defined. * @param resource The resource to find the main reactor in. diff --git a/org.lflang/src/org/lflang/AstExtensions.kt b/org.lflang/src/org/lflang/AstExtensions.kt index eb53d40f3d..25d09161c9 100644 --- a/org.lflang/src/org/lflang/AstExtensions.kt +++ b/org.lflang/src/org/lflang/AstExtensions.kt @@ -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 diff --git a/org.lflang/src/org/lflang/AttributeUtils.java b/org.lflang/src/org/lflang/AttributeUtils.java new file mode 100644 index 0000000000..9c67b06754 --- /dev/null +++ b/org.lflang/src/org/lflang/AttributeUtils.java @@ -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 } + * @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 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 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); + } +} diff --git a/org.lflang/src/org/lflang/generator/rust/RustModel.kt b/org.lflang/src/org/lflang/generator/rust/RustModel.kt index 6d46e4f9cb..8fd0340ca7 100644 --- a/org.lflang/src/org/lflang/generator/rust/RustModel.kt +++ b/org.lflang/src/org/lflang/generator/rust/RustModel.kt @@ -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, "{= ... =}"))