Skip to content

Commit f411c49

Browse files
authored
Redo how maxwait is specified and used (#297)
* Make the (0) optional in maxwait(0) * Steps towards cleaning up AttributeUtils and supporting maxwait attribute * Silence ridicously pedantic linter * Spotless * Disable ridiculous linter error * Another pass on cleaning up AttributeUtils * Format * Revert silly lint changes * Leverate Time improvements * Support maxwait attribute on instantiations and connections * Spotless * Fix grammar error * Format
1 parent 5f6b4e6 commit f411c49

19 files changed

+433
-425
lines changed
Lines changed: 99 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,3 @@
1-
/*
2-
Copyright (c) 2022, The University of California at Berkeley.
3-
4-
Redistribution and use in source and binary forms, with or without modification,
5-
are permitted provided that the following conditions are met:
6-
7-
1. Redistributions of source code must retain the above copyright notice,
8-
this list of conditions and the following disclaimer.
9-
10-
2. Redistributions in binary form must reproduce the above copyright notice,
11-
this list of conditions and the following disclaimer in the documentation
12-
and/or other materials provided with the distribution.
13-
14-
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15-
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16-
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17-
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
18-
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19-
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20-
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
21-
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22-
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23-
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24-
*/
25-
261
package org.lflang;
272

283
import static org.lflang.ast.ASTUtils.factory;
@@ -32,9 +7,6 @@
327
import java.util.Map;
338
import java.util.Objects;
349
import org.eclipse.emf.ecore.EObject;
35-
import org.eclipse.xtext.nodemodel.ICompositeNode;
36-
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
37-
import org.eclipse.xtext.resource.XtextResource;
3810
import org.lflang.ast.ASTUtils;
3911
import org.lflang.lf.*;
4012
import org.lflang.target.property.type.PlatformType;
@@ -46,16 +18,18 @@
4618
* @author Shaokai Lin
4719
* @author Clément Fournier
4820
* @author Alexander Schulz-Rosengarten
21+
* @author Edward A. Lee
4922
*/
5023
public class AttributeUtils {
5124

5225
/**
53-
* Return the attributes declared on the given node. Throws if the node does not support declaring
54-
* attributes.
26+
* Return a list of attributes declared on the given node. An empty list is returned if the node
27+
* does not have any attributes.
5528
*
56-
* @throws IllegalArgumentException If the node cannot have attributes
29+
* @param node The node to get the attributes from.
30+
* @throws IllegalArgumentException If the node cannot have attributes.
5731
*/
58-
public static List<Attribute> getAttributes(EObject node) {
32+
public static List<Attribute> getAttributes(EObject node) throws IllegalArgumentException {
5933
if (node instanceof Reactor) {
6034
return ((Reactor) node).getAttributes();
6135
} else if (node instanceof Reaction) {
@@ -83,11 +57,15 @@ public static List<Attribute> getAttributes(EObject node) {
8357
}
8458

8559
/**
86-
* Return the attribute with the given name if present, otherwise return null.
60+
* Return the first attribute with the given name if present, otherwise return null. If there are
61+
* multiple attributes with the same name, only the first one is returned.
8762
*
63+
* @param node The node to get the attribute from.
64+
* @param name The name of the attribute to get.
8865
* @throws IllegalArgumentException If the node cannot have attributes
8966
*/
90-
public static Attribute findAttributeByName(EObject node, String name) {
67+
public static Attribute findAttributeByName(EObject node, String name)
68+
throws IllegalArgumentException {
9169
List<Attribute> attrs = getAttributes(node);
9270
return attrs.stream()
9371
.filter(
@@ -99,11 +77,15 @@ public static Attribute findAttributeByName(EObject node, String name) {
9977
}
10078

10179
/**
102-
* Return the attributes with the given name.
80+
* Return a list of attributes with the given name. An empty list is returned if the node does not
81+
* have any attributes with the given name.
10382
*
83+
* @param node The node to get the attributes from.
84+
* @param name The name of the attributes to get.
10485
* @throws IllegalArgumentException If the node cannot have attributes
10586
*/
106-
public static List<Attribute> findAttributesByName(EObject node, String name) {
87+
public static List<Attribute> findAttributesByName(EObject node, String name)
88+
throws IllegalArgumentException {
10789
List<Attribute> attrs = getAttributes(node);
10890
return attrs.stream()
10991
.filter(
@@ -113,119 +95,61 @@ public static List<Attribute> findAttributesByName(EObject node, String name) {
11395
.toList();
11496
}
11597

116-
public static List<Attribute> findAttributesByNameStartingWith(EObject node, String name) {
117-
List<Attribute> attrs = getAttributes(node);
118-
return attrs.stream()
119-
.filter(
120-
it -> it.getAttrName().contains(name)) // case-insensitive search (more user-friendly)
121-
.toList();
122-
}
123-
124-
/**
125-
* Return the first argument specified for the attribute.
126-
*
127-
* <p>This should be used if the attribute is expected to have a single argument. If there is no
128-
* argument, null is returned.
129-
*/
130-
public static String getFirstArgumentValue(Attribute attr) {
131-
if (attr == null || attr.getAttrParms().isEmpty()) {
132-
return null;
133-
}
134-
return StringUtil.removeQuotes(attr.getAttrParms().get(0).getValue());
135-
}
136-
13798
/**
138-
* Search for an attribute with the given name on the given AST node and return its first argument
139-
* as a String.
99+
* Return the first argument specified for the first attribute with the given name. If there is no
100+
* attribute with the given name, return null. If the attribute has no arguments, or if the
101+
* arguments are not of a suitable form, return null. This ignores any argument name, if one is
102+
* given, and only returns arguments of the form of strings ("foo"), numbers (123), boolean values
103+
* (true, false), or the time values `forever` or `never`. Time values with units are not returned
104+
* (the return value will be null).
140105
*
141-
* <p>This should only be used on attributes that are expected to have a single argument.
106+
* <p>This is a convenience method for common use cases where an attribute is specified with a
107+
* single argument of a suitable form. The validator should check that the arguments are of a
108+
* suitable form.
142109
*
143-
* <p>Returns null if the attribute is not found or if it does not have any arguments.
110+
* @param node The node to get the attribute value from.
111+
* @param attrName The name of the attribute to get the value from.
144112
*/
145113
public static String getAttributeValue(EObject node, String attrName) {
146114
final var attr = findAttributeByName(node, attrName);
147-
String value = getFirstArgumentValue(attr);
148-
// Attribute annotations in comments are deprecated, but we still check for then for backwards
149-
// compatibility
150-
if (value == null) {
151-
return findAnnotationInComments(node, "@" + attrName);
152-
}
153-
return value;
154-
}
155-
156-
/**
157-
* Search for an attribute with the given name on the given AST node and return its first argument
158-
* as a String.
159-
*
160-
* <p>This should only be used on attributes that are expected to have a single argument.
161-
*
162-
* <p>Returns null if the attribute is not found or if it does not have any arguments.
163-
*/
164-
public static Map<String, String> getAttributeValues(EObject node, String attrName) {
165-
final List<Attribute> attrs = findAttributesByName(node, attrName);
166-
HashMap<String, String> layoutOptions = new HashMap<>();
167-
for (Attribute attribute : attrs) {
168-
layoutOptions.put(
169-
StringUtil.removeQuotes(attribute.getAttrParms().get(0).getValue()),
170-
StringUtil.removeQuotes(attribute.getAttrParms().get(1).getValue()));
115+
if (attr == null || attr.getAttrParms().isEmpty()) {
116+
return null;
171117
}
172-
return layoutOptions;
173-
}
174-
175-
/**
176-
* Retrieve a specific annotation in a comment associated with the given model element in the AST.
177-
*
178-
* <p>This will look for a comment. If one is found, it searches for the given annotation {@code
179-
* key}. and extracts any string that follows the annotation marker.
180-
*
181-
* @param object the AST model element to search a comment for
182-
* @param key the specific annotation key to be extracted
183-
* @return {@code null} if no JavaDoc style comment was found or if it does not contain the given
184-
* key. The string immediately following the annotation marker otherwise.
185-
*/
186-
public static String findAnnotationInComments(EObject object, String key) {
187-
if (!(object.eResource() instanceof XtextResource)) return null;
188-
ICompositeNode node = NodeModelUtils.findActualNodeFor(object);
189-
return ASTUtils.getPrecedingComments(node, n -> true)
190-
.flatMap(String::lines)
191-
.filter(line -> line.contains(key))
192-
.map(String::trim)
193-
.map(it -> it.substring(it.indexOf(key) + key.length()))
194-
.map(it -> it.endsWith("*/") ? it.substring(0, it.length() - "*/".length()) : it)
195-
.findFirst()
196-
.orElse(null);
118+
return StringUtil.removeQuotes(attr.getAttrParms().get(0).getValue());
197119
}
198120

199121
/**
200-
* Return the parameter of the given attribute with the given name.
122+
* Return the parameter with the given name for the specified attribute or null if no such
123+
* parameter is found.
201124
*
202-
* <p>Returns null if no such parameter is found.
125+
* @param attribute The attribute to get the parameter from.
126+
* @param parameterName The name of the parameter to get.
203127
*/
204-
public static String getAttributeParameter(Attribute attribute, String parameterName) {
128+
public static AttrParm getAttributeParameter(Attribute attribute, String parameterName) {
205129
return (attribute == null)
206130
? null
207131
: attribute.getAttrParms().stream()
208132
.filter(param -> Objects.equals(param.getName(), parameterName))
209-
.map(AttrParm::getValue)
210-
.map(StringUtil::removeQuotes)
211133
.findFirst()
212134
.orElse(null);
213135
}
214136

215137
/**
216-
* Return the parameter of the given attribute with the given name and interpret it as a boolean.
138+
* Return true if there is a parameter of the given attribute with the given name whose value is
139+
* "true" (case-insensitive) and return false otherwise.
217140
*
218-
* <p>Returns null if no such parameter is found.
141+
* @param attribute The attribute to get the parameter from.
142+
* @param parameterName The name of the parameter to get.
219143
*/
220-
public static Boolean getBooleanAttributeParameter(Attribute attribute, String parameterName) {
144+
public static boolean getBooleanAttributeParameter(Attribute attribute, String parameterName) {
221145
if (attribute == null || parameterName == null) {
222-
return null;
146+
return false;
223147
}
224148
final var param = getAttributeParameter(attribute, parameterName);
225-
if (param == null) {
226-
return null;
149+
if (param == null || param.getValue() == null) {
150+
return false;
227151
}
228-
return param.equalsIgnoreCase("true");
152+
return param.getValue().equalsIgnoreCase("true");
229153
}
230154

231155
/**
@@ -271,24 +195,39 @@ public static String getPortSide(EObject node) {
271195
}
272196

273197
/**
274-
* Return the {@code layout} annotation for the given element or null if there is no such
275-
* annotation.
198+
* Return the `@layout` annotations for the given element or null if there is no such annotation.
199+
* Layout annotations have the form: ```
200+
*
201+
* @layout(option="string", value="any") ``` For example, ```
202+
* @layout(option="port.side", value="WEST") ``` This will return all such annotations for the
203+
* specified node in the form of a map from the option name to the value.
276204
*/
277205
public static Map<String, String> getLayoutOption(EObject node) {
278-
return getAttributeValues(node, "layout");
206+
final List<Attribute> attrs = findAttributesByName(node, "layout");
207+
HashMap<String, String> result = new HashMap<>();
208+
for (Attribute attribute : attrs) {
209+
result.put(
210+
// FIXME: This assumes the parameters are in the correct order.
211+
StringUtil.removeQuotes(attribute.getAttrParms().get(0).getValue()),
212+
StringUtil.removeQuotes(attribute.getAttrParms().get(1).getValue()));
213+
}
214+
return result;
279215
}
280216

281217
/**
282-
* Return the {@code @enclave} attribute annotated on the given node.
218+
* Return a list of attributes with names containing the text "interface". An empty list is
219+
* returned if the node does not have any attributes with names starting with "interface".
283220
*
284-
* <p>Returns null if there is no such attribute.
221+
* @param node The instantiation node to get the attributes from.
285222
*/
286-
public static Attribute getEnclaveAttribute(Instantiation node) {
287-
return findAttributeByName(node, "enclave");
288-
}
289-
290223
public static List<Attribute> getInterfaceAttributes(Instantiation node) {
291-
return findAttributesByNameStartingWith(node, "interface");
224+
List<Attribute> attrs = getAttributes(node);
225+
return attrs.stream()
226+
.filter(
227+
it ->
228+
it.getAttrName()
229+
.contains("interface")) // case-insensitive search (more user-friendly)
230+
.toList();
292231
}
293232

294233
public static Attribute getJoiningPolicy(Instantiation node) {
@@ -319,7 +258,7 @@ public static Attribute getLinkAttribute(Connection node) {
319258

320259
/** Return true if the specified instance has an {@code @enclave} attribute. */
321260
public static boolean isEnclave(Instantiation node) {
322-
return getEnclaveAttribute(node) != null;
261+
return findAttributeByName(node, "enclave") != null;
323262
}
324263

325264
/**
@@ -354,4 +293,31 @@ public static boolean isGrandmaster(Instantiation node) {
354293
public static Attribute getClockSyncAttr(Instantiation inst) {
355294
return findAttributeByName(inst, "clock_sync");
356295
}
296+
297+
/**
298+
* Return the value of the `@maxwait` attribute of the given node or TimeValue.ZERO if does not
299+
* have one.
300+
*
301+
* @param The AST node (Instantiation or Connection).
302+
*/
303+
public static TimeValue getMaxWait(EObject node) {
304+
final var attr = findAttributeByName(node, "maxwait");
305+
if (attr != null) {
306+
// The attribute is expected to have a single argument of type Time
307+
// or one of the literals "forever", "never", or "0".
308+
// The validator checks this.
309+
final var time = attr.getAttrParms().get(0).getTime();
310+
if (time == null) {
311+
if (attr.getAttrParms().get(0).getValue().equals("forever")) {
312+
return TimeValue.MAX_VALUE;
313+
} else if (attr.getAttrParms().get(0).getValue().equals("never")) {
314+
// Interpret "never" as 0.
315+
return TimeValue.ZERO;
316+
}
317+
} else {
318+
return ASTUtils.toTimeValue(time);
319+
}
320+
}
321+
return TimeValue.ZERO;
322+
}
357323
}

lfc/core/src/main/java/org/lflang/LinguaFranca.xtext

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -227,11 +227,11 @@ Serializer:
227227

228228
/////////// Attributes
229229
Attribute:
230-
'@' attrName=ID ('(' (attrParms+=AttrParm (',' attrParms+=AttrParm)* ','?)? ')')?
230+
'@' attrName=(ID | 'maxwait') ('(' (attrParms+=AttrParm (',' attrParms+=AttrParm)* ','?)? ')')?
231231
;
232232

233233
AttrParm:
234-
(name=ID '=')? value=Literal;
234+
(name=ID '=')? (value=Literal | time=Time);
235235

236236
/////////// For target parameters
237237

@@ -276,7 +276,7 @@ Assignment:
276276
*/
277277
Parameter:
278278
(attributes+=Attribute)*
279-
name=(ID | 'maxwait') (':' type=Type)?
279+
name=ID (':' type=Type)?
280280
init=Initializer?
281281
;
282282

@@ -503,7 +503,7 @@ Token:
503503
'startup' | 'shutdown' | 'after' | 'deadline' | 'mutation' | 'preamble' |
504504
'new' | 'federated' | 'at' | 'as' | 'from' | 'widthof' | 'const' | 'method' |
505505
'interleaved' | 'mode' | 'initial' | 'reset' | 'history' | 'watchdog' |
506-
'extends' | 'forever' | 'never' |
506+
'extends' | 'forever' | 'never' | 'maxwait' |
507507

508508
// Other terminals
509509
NEGINT | TRUE | FALSE |

lfc/core/src/main/java/org/lflang/ast/ASTUtils.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,14 +1150,19 @@ public static String generateVarRef(VarRef reference) {
11501150
return prefix + reference.getVariable().getName();
11511151
}
11521152

1153-
/** Assuming that the given expression denotes a valid time, return a time value. */
1153+
/**
1154+
* Assuming that the given expression denotes a valid time, return a time value.
1155+
*
1156+
* @param expr The expression to get the time value of.
1157+
* @return The time value of the expression, or TimeValue.ZERO if the expression is null.
1158+
*/
11541159
public static TimeValue getLiteralTimeValue(Expression expr) {
11551160
if (expr instanceof Time) {
11561161
return toTimeValue((Time) expr);
11571162
} else if (expr instanceof Literal && isZero(((Literal) expr).getLiteral())) {
11581163
return TimeValue.ZERO;
11591164
} else {
1160-
return null;
1165+
return TimeValue.ZERO;
11611166
}
11621167
}
11631168

0 commit comments

Comments
 (0)