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 7bb5502ffa..1092d76a87 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java @@ -38,9 +38,6 @@ import javax.inject.Inject; -import org.eclipse.elk.alg.layered.options.EdgeStraighteningStrategy; -import org.eclipse.elk.alg.layered.options.FixedAlignment; -import org.eclipse.elk.alg.layered.options.GreedySwitchType; import org.eclipse.elk.alg.layered.options.LayerConstraint; import org.eclipse.elk.alg.layered.options.LayeredOptions; import org.eclipse.elk.alg.layered.options.NodePlacementStrategy; @@ -1390,7 +1387,7 @@ private KNode addErrorComment(KNode node, String message) { private Iterable createUserComments(EObject element, KNode targetNode) { if (getBooleanValue(SHOW_USER_LABELS)) { - String commentText = AttributeUtils.label(element); + String commentText = AttributeUtils.getLabel(element); if (!StringExtensions.isNullOrEmpty(commentText)) { KNode comment = _kNodeExtensions.createNode(); diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/ReactorIcons.java b/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/ReactorIcons.java index fea03f819d..b01f7dae96 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/ReactorIcons.java +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/ReactorIcons.java @@ -24,13 +24,10 @@ ***************/ package org.lflang.diagram.synthesis.util; -import java.io.InputStream; -import java.util.HashMap; - //import org.eclipse.swt.graphics.ImageData; //import org.eclipse.swt.graphics.ImageLoader; import org.eclipse.xtext.xbase.lib.Extension; -import org.lflang.ASTUtils; + import org.lflang.AttributeUtils; import org.lflang.diagram.synthesis.AbstractSynthesisExtensions; import org.lflang.lf.ReactorDecl; @@ -38,10 +35,7 @@ import com.google.inject.Inject; -import de.cau.cs.kieler.klighd.krendering.Colors; import de.cau.cs.kieler.klighd.krendering.KContainerRendering; -import de.cau.cs.kieler.klighd.krendering.KGridPlacementData; -import de.cau.cs.kieler.klighd.krendering.KRectangle; import de.cau.cs.kieler.klighd.krendering.ViewSynthesisShared; import de.cau.cs.kieler.klighd.krendering.extensions.KContainerRenderingExtensions; import de.cau.cs.kieler.klighd.krendering.extensions.KRenderingExtensions; @@ -74,10 +68,7 @@ public void handleIcon(KContainerRendering rendering, ReactorDecl reactor, boole error = null; // Get annotation - String iconPath = AttributeUtils.findAttributeByName(reactor, "icon"); - if (iconPath == null) { // Fallback to old syntax (in comment) - iconPath = ASTUtils.findAnnotationInComments(reactor, "@icon"); - } + var iconPath = AttributeUtils.getIconPath(reactor); if (iconPath != null && !iconPath.isEmpty()) { var iconLocation = FileUtil.locateFile(iconPath, reactor.eResource()); if (iconLocation == null) { diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index d49f1e5815..dc15877879 100644 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ b/org.lflang/src/org/lflang/ASTUtils.java @@ -50,7 +50,6 @@ import org.eclipse.xtext.nodemodel.INode; import org.eclipse.xtext.nodemodel.impl.HiddenLeafNode; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; -import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.util.Pair; import org.eclipse.xtext.util.Tuples; import org.eclipse.xtext.xbase.lib.IterableExtensions; @@ -1026,6 +1025,30 @@ public static boolean isInteger(String literal) { return true; } + /** + * Report whether the given string literal is a boolean value or not. + * @param literal AST node to inspect. + * @return True if the given value is a boolean, false otherwise. + */ + public static boolean isBoolean(String literal) { + return literal.equalsIgnoreCase("true") || literal.equalsIgnoreCase("false"); + } + + /** + * Report whether the given string literal is a float value or not. + * @param literal AST node to inspect. + * @return True if the given value is a float, false otherwise. + */ + public static boolean isFloat(String literal) { + try { + //noinspection ResultOfMethodCallIgnored + Float.parseFloat(literal); + } catch (NumberFormatException e) { + return false; + } + return true; + } + /** * Report whether the given code is an integer number or not. * @param code AST node to inspect. @@ -1772,28 +1795,6 @@ public static Predicate sameLine(ICompositeNode compNode) { return false; }; } - - /** - * Retrieve a specific annotation in a comment associated with the given model element in the AST. - * - * This will look for a comment. If one is found, it searches for the given annotation `key`. - * and extracts any string that follows the annotation marker. - * - * @param object the AST model element to search a comment for - * @param key the specific annotation key to be extracted - * @return `null` if no JavaDoc style comment was found or if it does not contain the given key. - * The string immediately following the annotation marker otherwise. - */ - public static String findAnnotationInComments(EObject object, String key) { - if (!(object.eResource() instanceof XtextResource)) return null; - ICompositeNode node = NodeModelUtils.findActualNodeFor(object); - return getPrecedingComments(node, n -> true).flatMap(String::lines) - .filter(line -> line.contains(key)) - .map(String::trim) - .map(it -> it.substring(it.indexOf(key) + key.length())) - .map(it -> it.endsWith("*/") ? it.substring(0, it.length() - "*/".length()) : it) - .findFirst().orElse(null); - } /** * Find the main reactor and set its name if none was defined. diff --git a/org.lflang/src/org/lflang/AstExtensions.kt b/org.lflang/src/org/lflang/AstExtensions.kt index 25d09161c9..3c9bd3c52a 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 = AttributeUtils.label(this) ?: "reaction_$priority" +val Reaction.label get(): String = AttributeUtils.getLabel(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 index fcafc3cdab..ee8cef1bbf 100644 --- a/org.lflang/src/org/lflang/AttributeUtils.java +++ b/org.lflang/src/org/lflang/AttributeUtils.java @@ -28,16 +28,21 @@ import java.util.List; import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.nodemodel.ICompositeNode; +import org.eclipse.xtext.nodemodel.util.NodeModelUtils; +import org.eclipse.xtext.resource.XtextResource; import org.lflang.lf.Action; import org.lflang.lf.Attribute; import org.lflang.lf.Input; +import org.lflang.lf.Instantiation; 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; +import org.lflang.util.StringUtil; /** * A helper class for processing attributes in the AST. @@ -71,33 +76,78 @@ public static List getAttributes(EObject node) { return ((Input) node).getAttributes(); } else if (node instanceof Output) { return ((Output) node).getAttributes(); + } else if (node instanceof Instantiation) { + return ((Instantiation) node).getAttributes(); } throw new IllegalArgumentException("Not annotatable: " + node); } /** - * Return the value of the attribute with the given name + * Return the attribute with the given name * if present, otherwise return null. * * @throws IllegalArgumentException If the node cannot have attributes */ - public static String findAttributeByName(EObject node, String name) { + public static Attribute findAttributeByName(EObject node, String name) { List attrs = getAttributes(node); return attrs.stream() .filter(it -> it.getAttrName().equalsIgnoreCase(name)) // case-insensitive search (more user-friendly) - .map(it -> it.getAttrParms().get(0).getValue().getStr()) .findFirst() .orElse(null); } - + /** - * Return the value of the {@code @label} attribute if - * present, otherwise return null. + * Return the first argument specified for the attribute. * - * @throws IllegalArgumentException If the node cannot have attributes + * This should be used if the attribute is expected to have a single argument. + * If there is no argument, null is returned. + */ + public static String getFirstArgumentValue(Attribute attr) { + if (attr == null || attr.getAttrParms().isEmpty()) { + return null; + } + return StringUtil.removeQuotes(attr.getAttrParms().get(0).getValue()); + } + + /** + * Search for an attribute with the given name on the given AST node and return its first + * argument as a String. + * + * This should only be used on attributes that are expected to have a single argument. + * + * Returns null if the attribute is not found or if it does not have any arguments. + */ + public static String getAttributeValue(EObject node, String attrName) { + final var attr = findAttributeByName(node, attrName); + String value = getFirstArgumentValue(attr); + // Attribute annotations in comments are deprecated, but we still check for then for backwards + // compatibility + if (value == null) { + return findAnnotationInComments(node, "@" + attrName); + } + return value; + } + + /** + * Retrieve a specific annotation in a comment associated with the given model element in the AST. + * + * This will look for a comment. If one is found, it searches for the given annotation `key`. + * and extracts any string that follows the annotation marker. + * + * @param object the AST model element to search a comment for + * @param key the specific annotation key to be extracted + * @return `null` if no JavaDoc style comment was found or if it does not contain the given key. + * The string immediately following the annotation marker otherwise. */ - public static String findLabelAttribute(EObject node) { - return findAttributeByName(node, "label"); + public static String findAnnotationInComments(EObject object, String key) { + if (!(object.eResource() instanceof XtextResource)) return null; + ICompositeNode node = NodeModelUtils.findActualNodeFor(object); + return ASTUtils.getPrecedingComments(node, n -> true).flatMap(String::lines) + .filter(line -> line.contains(key)) + .map(String::trim) + .map(it -> it.substring(it.indexOf(key) + key.length())) + .map(it -> it.endsWith("*/") ? it.substring(0, it.length() - "*/".length()) : it) + .findFirst().orElse(null); } /** @@ -106,35 +156,21 @@ public static String findLabelAttribute(EObject node) { * @param node An AST node. */ public static boolean isSparse(EObject node) { - if (node instanceof Input) { - for (var attribute : getAttributes(node)) { - if (attribute.getAttrName().equalsIgnoreCase("sparse")) return true; - } - } - return false; + return findAttributeByName(node, "sparse") != 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 + * Return the declared label of the node, as given by the @label annotation. */ - public static String label(EObject n) { - String fromAttr = findLabelAttribute(n); - if (fromAttr == null) { - return ASTUtils.findAnnotationInComments(n, "@label"); - } - return fromAttr; + public static String getLabel(EObject node) { + return getAttributeValue(node, "label"); } /** - * 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. + * Return the declared icon of the node, as given by the @icon annotation. */ - public static String label(Reaction n) { - return label((EObject) n); + public static String getIconPath(EObject node) { + return getAttributeValue(node, "icon"); } + } diff --git a/org.lflang/src/org/lflang/LinguaFranca.xtext b/org.lflang/src/org/lflang/LinguaFranca.xtext index cd175fab09..38dc520c0b 100644 --- a/org.lflang/src/org/lflang/LinguaFranca.xtext +++ b/org.lflang/src/org/lflang/LinguaFranca.xtext @@ -219,6 +219,7 @@ Preamble: (visibility=Visibility)? 'preamble' code=Code; Instantiation: + (attributes+=Attribute)* name=ID '=' 'new' (widthSpec=WidthSpec)? reactorClass=[ReactorDecl] ('<' typeParms+=TypeParm (',' typeParms+=TypeParm)* '>')? '(' (parameters+=Assignment (',' parameters+=Assignment)*)? @@ -245,14 +246,7 @@ Attribute: ; AttrParm: - (name=ID '=')? value=AttrParmValue; - -AttrParmValue: - str=STRING - | int=SignedInt - | bool=Boolean - | float=SignedFloat -; + (name=ID '=')? value=Literal; /////////// For target parameters diff --git a/org.lflang/src/org/lflang/ast/IsEqual.java b/org.lflang/src/org/lflang/ast/IsEqual.java index d35fe45781..91b92e93fb 100644 --- a/org.lflang/src/org/lflang/ast/IsEqual.java +++ b/org.lflang/src/org/lflang/ast/IsEqual.java @@ -15,7 +15,6 @@ import org.lflang.lf.ArraySpec; import org.lflang.lf.Assignment; import org.lflang.lf.AttrParm; -import org.lflang.lf.AttrParmValue; import org.lflang.lf.Attribute; import org.lflang.lf.BuiltinTriggerRef; import org.lflang.lf.Code; @@ -260,17 +259,7 @@ public Boolean caseAttribute(Attribute object) { public Boolean caseAttrParm(AttrParm object) { return new ComparisonMachine<>(object, AttrParm.class) .equalAsObjects(AttrParm::getName) - .equivalent(AttrParm::getValue) - .conclusion; - } - - @Override - public Boolean caseAttrParmValue(AttrParmValue object) { - return new ComparisonMachine<>(object, AttrParmValue.class) - .equalAsObjects(AttrParmValue::getBool) - .equalAsObjects(AttrParmValue::getFloat) - .equalAsObjects(AttrParmValue::getInt) - .equalAsObjects(AttrParmValue::getStr) + .equalAsObjects(AttrParm::getValue) .conclusion; } diff --git a/org.lflang/src/org/lflang/ast/ToLf.java b/org.lflang/src/org/lflang/ast/ToLf.java index 1b34db594b..129ec3c352 100644 --- a/org.lflang/src/org/lflang/ast/ToLf.java +++ b/org.lflang/src/org/lflang/ast/ToLf.java @@ -30,7 +30,6 @@ import org.lflang.lf.ArraySpec; import org.lflang.lf.Assignment; import org.lflang.lf.AttrParm; -import org.lflang.lf.AttrParmValue; import org.lflang.lf.Attribute; import org.lflang.lf.BuiltinTriggerRef; import org.lflang.lf.Code; @@ -288,24 +287,7 @@ public MalleableString caseAttrParm(AttrParm object) { // (name=ID '=')? value=AttrParmValue; var builder = new Builder(); if (object.getName() != null) builder.append(object.getName()).append(" = "); - return builder.append(doSwitch(object.getValue())).get(); - } - - @Override - public MalleableString caseAttrParmValue(AttrParmValue object) { - // str=STRING - // | int=SignedInt - // | bool=Boolean - // | float=SignedFloat - if (object.getStr() != null) { - return MalleableString.anyOf(StringUtil.addDoubleQuotes(object.getStr())); - } - if (object.getInt() != null) return MalleableString.anyOf(object.getInt()); - if (object.getBool() != null) return MalleableString.anyOf(object.getBool()); - if (object.getFloat() != null) return MalleableString.anyOf(object.getFloat()); - throw new IllegalArgumentException( - "The attributes of an AttrParmValue should not all be null." - ); + return builder.append(object.getValue()).get(); } @Override diff --git a/org.lflang/src/org/lflang/generator/rust/RustModel.kt b/org.lflang/src/org/lflang/generator/rust/RustModel.kt index d05ebe03c5..d4611451dc 100644 --- a/org.lflang/src/org/lflang/generator/rust/RustModel.kt +++ b/org.lflang/src/org/lflang/generator/rust/RustModel.kt @@ -547,7 +547,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 = AttributeUtils.label(n), + debugLabel = AttributeUtils.getLabel(n), loc = n.locationInfo().let { // remove code block it.copy(lfText = it.lfText.replace(TARGET_BLOCK_R, "{= ... =}")) diff --git a/org.lflang/src/org/lflang/util/StringUtil.java b/org.lflang/src/org/lflang/util/StringUtil.java index 3352796e12..9be21d46e8 100644 --- a/org.lflang/src/org/lflang/util/StringUtil.java +++ b/org.lflang/src/org/lflang/util/StringUtil.java @@ -73,13 +73,23 @@ public static String removeQuotes(String str) { if (str.length() < 2) { return str; } - if (str.startsWith("\"") && str.endsWith("\"") - || str.startsWith("'") && str.endsWith("'")) { + if (hasQuotes(str)) { return str.substring(1, str.length() - 1); } return str; } + /** + * Return true if the given string is surrounded by single or double + * quotes, + */ + public static boolean hasQuotes(String str) { + if (str == null) { + return false; + } + return str.startsWith("\"") && str.endsWith("\"") || str.startsWith("'") && str.endsWith("'"); + } + /** * Intelligently trim the white space in a code block. * diff --git a/org.lflang/src/org/lflang/validation/AttributeSpec.java b/org.lflang/src/org/lflang/validation/AttributeSpec.java index 75fecc48d3..766621e595 100644 --- a/org.lflang/src/org/lflang/validation/AttributeSpec.java +++ b/org.lflang/src/org/lflang/validation/AttributeSpec.java @@ -32,9 +32,11 @@ import java.util.Set; import java.util.stream.Collectors; +import org.lflang.ASTUtils; import org.lflang.lf.AttrParm; import org.lflang.lf.Attribute; import org.lflang.lf.LfPackage.Literals; +import org.lflang.util.StringUtil; /** * Specification of the structure of an attribute annotation. @@ -154,31 +156,35 @@ private boolean isOptional() { // 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; + switch (type) { + case STRING: + if (!StringUtil.hasQuotes(parm.getValue())) { + validator.error("Incorrect type: \"" + parm.getName() + "\"" + + " should have type String.", + Literals.ATTRIBUTE__ATTR_NAME); + } + break; + case INT: + if (!ASTUtils.isInteger(parm.getValue())) { + validator.error( + "Incorrect type: \"" + parm.getName() + "\"" + " should have type Int.", + Literals.ATTRIBUTE__ATTR_NAME); + } + break; + case BOOLEAN: + if (!ASTUtils.isBoolean(parm.getValue())) { + validator.error( + "Incorrect type: \"" + parm.getName() + "\"" + " should have type Boolean.", + Literals.ATTRIBUTE__ATTR_NAME); + } + break; + case FLOAT: + if (!ASTUtils.isFloat(parm.getValue())) { + validator.error( + "Incorrect type: \"" + parm.getName() + "\"" + " should have type Float.", + Literals.ATTRIBUTE__ATTR_NAME); + } + break; } } } diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index e5dd5cdcf4..5a6107839f 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -1274,7 +1274,7 @@ public void checkReactorIconAttribute(Reactor reactor) { .findFirst() .orElse(null); if (iconAttr != null) { - var path = iconAttr.getAttrParms().get(0).getValue().getStr(); + var path = iconAttr.getAttrParms().get(0).getValue(); // Check file extension var validExtensions = Set.of("bmp", "png", "gif", "ico", "jpeg");