From 17f0eac5ecf43818eb675139ee641555018d146c Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Thu, 18 Sep 2025 14:29:21 -0700 Subject: [PATCH 1/2] Align with changes in lingua-franca master --- .../main/java/org/lflang/LinguaFranca.xtext | 42 ++---------- .../src/main/java/org/lflang/TimeUnit.java | 32 +++++++-- .../src/main/java/org/lflang/TimeValue.java | 64 ++++++++++-------- .../main/java/org/lflang/ast/ASTUtils.java | 14 ++-- .../src/main/java/org/lflang/ast/IsEqual.java | 34 ++++++---- .../src/main/java/org/lflang/ast/ToLf.java | 67 ++++++++++++------- .../src/main/java/org/lflang/ast/ToSExpr.java | 38 ++++++----- .../target/property/type/PrimitiveType.java | 2 +- .../generator/uc/UcReactionGenerator.kt | 2 +- 9 files changed, 163 insertions(+), 132 deletions(-) diff --git a/lfc/core/src/main/java/org/lflang/LinguaFranca.xtext b/lfc/core/src/main/java/org/lflang/LinguaFranca.xtext index ad42aa52..34bde400 100644 --- a/lfc/core/src/main/java/org/lflang/LinguaFranca.xtext +++ b/lfc/core/src/main/java/org/lflang/LinguaFranca.xtext @@ -1,30 +1,3 @@ -/* The Lingua Franca grammar. */ - -/************* -Copyright (c) 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. -***************/ - /** * Grammar for Lingua Franca. * A note of caution: extending this grammar with productions that introduce @@ -205,7 +178,7 @@ Reaction: ('(' (triggers+=TriggerRef (',' triggers+=TriggerRef)*)? ')') ( => sources+=VarRef (',' sources+=VarRef)*)? ('->' effects+=VarRefOrModeTransition (',' effects+=VarRefOrModeTransition)*)? - (code=Code)? (stp=STP)? (deadline=Deadline)? (maxWait=MaxWait)? (delimited?=';')? + (code=Code)? (maxWait=MaxWait)? (deadline=Deadline)? (delimited?=';')? ; TriggerRef: @@ -223,11 +196,8 @@ Watchdog: ('->' effects+=VarRefOrModeTransition (',' effects+=VarRefOrModeTransition)*)? code=Code; -STP: - ('STP' | 'STAA' ) '(' value=Expression ')' code=Code; - MaxWait: - ('maxwait') '(' value=Expression ')' (code=Code)?; + ('STP' | 'STAA' | 'maxwait') ('(' value=Expression ')')? (code=Code)?; Preamble: (visibility=Visibility)? 'preamble' code=Code; @@ -278,7 +248,7 @@ Element: keyvalue=KeyValuePairs | array=Array | literal=Literal - | (time=INT unit=TimeUnit) + | time=Time | id=Path; ///////// Pieces @@ -306,7 +276,7 @@ Assignment: */ Parameter: (attributes+=Attribute)* - name=ID (':' type=Type)? + name=(ID | 'maxwait') (':' type=Type)? init=Initializer? ; @@ -343,7 +313,7 @@ ParameterReference: ; Time: - (interval=INT unit=TimeUnit) + (interval=INT unit=TimeUnit) | forever=Forever | never=Never ; Port: @@ -405,7 +375,7 @@ Never: ; Literal: - STRING | CHAR_LIT | SignedFloat | SignedInt | Boolean | Forever | Never + STRING | CHAR_LIT | SignedFloat | SignedInt | Boolean ; Boolean: diff --git a/lfc/core/src/main/java/org/lflang/TimeUnit.java b/lfc/core/src/main/java/org/lflang/TimeUnit.java index a39bd505..ff37d9c0 100644 --- a/lfc/core/src/main/java/org/lflang/TimeUnit.java +++ b/lfc/core/src/main/java/org/lflang/TimeUnit.java @@ -24,9 +24,8 @@ package org.lflang; -import static org.lflang.util.CollectionUtil.immutableSetOf; - import java.util.Arrays; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -53,15 +52,28 @@ public enum TimeUnit { HOUR("hour", "h", "hours"), /** Day. */ DAY("day", "d", "days"), - WEEK("week", "weeks"), + WEEK("week", "wk", "weeks"), ; private final Set allNames; private final String canonicalName; + private final String siName; - TimeUnit(String canonicalName, String... aliases) { + /** + * Construct a time unit. + * + * @param canonicalName The name used in the generated code for the unit. + * @param siName The SI unit name, if there is one, and otherwise a short name. + * @param aliases Any number of alternative names for the unit. + */ + TimeUnit(String canonicalName, String siName, String... aliases) { this.canonicalName = canonicalName; - this.allNames = immutableSetOf(canonicalName, aliases); + this.siName = siName; + var all = new LinkedHashSet(); + all.add(canonicalName); + all.add(siName); + all.addAll(Arrays.asList(aliases)); + this.allNames = all; } /** Returns the name that is preferred when displaying this unit. */ @@ -69,6 +81,14 @@ public String getCanonicalName() { return canonicalName; } + /** Returns the name that is preferred when displaying this unit. */ + public static String staticGetCanonicalName(TimeUnit unit) { + if (unit == null) { + return null; + } + return unit.getCanonicalName(); + } + /** Returns true if the given name is one of the aliases of this unit. */ public boolean hasAlias(String name) { return allNames.contains(name); @@ -105,6 +125,6 @@ public static List list() { @Override public String toString() { - return this.canonicalName; + return this.siName; } } diff --git a/lfc/core/src/main/java/org/lflang/TimeValue.java b/lfc/core/src/main/java/org/lflang/TimeValue.java index f393544c..37d99555 100644 --- a/lfc/core/src/main/java/org/lflang/TimeValue.java +++ b/lfc/core/src/main/java/org/lflang/TimeValue.java @@ -1,44 +1,31 @@ -/************* - * Copyright (c) 2019, 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 org.lflang.lf.Time; + /** * Represents an amount of time (a duration). * * @author Marten Lohstroh * @author Clément Fournier - TU Dresden, INSA Rennes + * @ingroup Utilities */ public final class TimeValue implements Comparable { /** The maximum value of this type. This is approximately equal to 292 years. */ public static final TimeValue MAX_VALUE = new TimeValue(Long.MAX_VALUE, TimeUnit.NANO); + /** The minimum value of this type. */ + public static final TimeValue MIN_VALUE = new TimeValue(Long.MIN_VALUE, TimeUnit.NANO); + /** A time value equal to zero. */ public static final TimeValue ZERO = new TimeValue(0, null); + /** A time value representing NEVER, which is less than any other time value. */ + public static final TimeValue NEVER = new TimeValue(Long.MIN_VALUE, TimeUnit.NANO); + + /** A time value representing FOREVER which is greater than any other time value. */ + public static final TimeValue FOREVER = new TimeValue(Long.MAX_VALUE, TimeUnit.NANO); + /** * Primitive numerical representation of this time value, to be interpreted in terms the * associated time unit. @@ -57,7 +44,9 @@ public final class TimeValue implements Comparable { /** * Create a new time value. * - * @throws IllegalArgumentException If time is non-zero and the unit is null + * @param time The time value. + * @param unit The unit of the time value. + * @throws IllegalArgumentException If time is non-zero and the unit is null. */ public TimeValue(long time, TimeUnit unit) { if (unit == null && time != 0) { @@ -67,6 +56,25 @@ public TimeValue(long time, TimeUnit unit) { this.unit = unit; } + /** + * Create a new time value. + * + * @param time The time AST node.. + * @throws IllegalArgumentException If time is non-zero and the unit is null. + */ + public TimeValue(Time time) { + if (time == null || time.getNever() != null) { + this.time = Long.MIN_VALUE; + this.unit = TimeUnit.NANO; + } else if (time.getForever() != null) { + this.time = Long.MAX_VALUE; + this.unit = TimeUnit.NANO; + } else { + this.time = time.getInterval(); + this.unit = TimeUnit.fromName(time.getUnit()); + } + } + @Override public boolean equals(Object t1) { if (t1 instanceof TimeValue) { @@ -173,7 +181,9 @@ public static TimeValue fromNanoSeconds(long ns) { /** Return a string representation of this time value. */ public String toString() { - return unit != null ? time + " " + unit.getCanonicalName() : Long.toString(time); + if (this.equals(MAX_VALUE)) return "forever"; + if (this.equals(MIN_VALUE)) return "never"; + return unit != null ? time + " " + unit.toString() : Long.toString(time); } /** Return the latest of both values. */ diff --git a/lfc/core/src/main/java/org/lflang/ast/ASTUtils.java b/lfc/core/src/main/java/org/lflang/ast/ASTUtils.java index e3bbdd00..060a087b 100644 --- a/lfc/core/src/main/java/org/lflang/ast/ASTUtils.java +++ b/lfc/core/src/main/java/org/lflang/ast/ASTUtils.java @@ -739,7 +739,7 @@ public static Integer toInteger(Element e) { * @param e The element to be rendered as a time value. */ public static TimeValue toTimeValue(Element e) { - return new TimeValue(e.getTime(), TimeUnit.fromName(e.getUnit())); + return new TimeValue(e.getTime()); } /** Returns the time value represented by the given AST node. */ @@ -748,7 +748,7 @@ public static TimeValue toTimeValue(Time e) { // invalid unit, will have been reported by validator throw new IllegalArgumentException(); } - return new TimeValue(e.getInterval(), TimeUnit.fromName(e.getUnit())); + return new TimeValue(e); } /** @@ -882,10 +882,12 @@ else if (list.size() == 1) { */ public static Element toElement(TimeValue tv) { Element e = LfFactory.eINSTANCE.createElement(); - e.setTime((int) tv.time); + Time time = LfFactory.eINSTANCE.createTime(); + time.setInterval((int) tv.time); if (tv.unit != null) { - e.setUnit(tv.unit.toString()); + time.setUnit(tv.unit.toString()); } + e.setTime(time); return e; } @@ -1148,14 +1150,12 @@ public static String generateVarRef(VarRef reference) { return prefix + reference.getVariable().getName(); } - /** Assuming that the given expression denotes a valid time literal, return a time value. */ + /** Assuming that the given expression denotes a valid time, return a time value. */ public static TimeValue getLiteralTimeValue(Expression expr) { if (expr instanceof Time) { return toTimeValue((Time) expr); } else if (expr instanceof Literal && isZero(((Literal) expr).getLiteral())) { return TimeValue.ZERO; - } else if (expr instanceof Literal && isForever(((Literal) expr).getLiteral())) { - return TimeValue.MAX_VALUE; } else { return null; } diff --git a/lfc/core/src/main/java/org/lflang/ast/IsEqual.java b/lfc/core/src/main/java/org/lflang/ast/IsEqual.java index 8eec27a4..8f1164b6 100644 --- a/lfc/core/src/main/java/org/lflang/ast/IsEqual.java +++ b/lfc/core/src/main/java/org/lflang/ast/IsEqual.java @@ -34,6 +34,7 @@ import org.lflang.lf.KeyValuePair; import org.lflang.lf.KeyValuePairs; import org.lflang.lf.Literal; +import org.lflang.lf.MaxWait; import org.lflang.lf.Method; import org.lflang.lf.MethodArgument; import org.lflang.lf.Mode; @@ -48,7 +49,6 @@ import org.lflang.lf.Reaction; import org.lflang.lf.Reactor; import org.lflang.lf.ReactorDecl; -import org.lflang.lf.STP; import org.lflang.lf.Serializer; import org.lflang.lf.StateVar; import org.lflang.lf.TargetDecl; @@ -67,8 +67,10 @@ /** * Switch class that checks if subtrees of the AST are semantically equivalent to each other. Return - * {@code false} if they are not equivalent; return {@code true} or {@code false} (but preferably - * {@code true}) if they are equivalent. + * `false` if they are not equivalent; return `true` or `false` (but preferably + * `true`) if they are equivalent. + * + * @ingroup Utilities */ public class IsEqual extends LfSwitch { @@ -99,6 +101,7 @@ public Boolean caseModel(Model object) { public Boolean caseImport(Import object) { return new ComparisonMachine<>(object, Import.class) .equalAsObjects(Import::getImportURI) + .equalAsObjects(Import::getImportPackage) .listsEquivalent(Import::getReactorClasses) .conclusion; } @@ -283,7 +286,7 @@ public Boolean caseReaction(Reaction object) { .equalAsObjects(Reaction::isMutation) .equalAsObjects(Reaction::getName) .equivalent(Reaction::getCode) - .equivalent(Reaction::getStp) + .equivalent(Reaction::getMaxWait) .equivalent(Reaction::getDeadline) .conclusion; } @@ -309,10 +312,10 @@ public Boolean caseDeadline(Deadline object) { } @Override - public Boolean caseSTP(STP object) { - return new ComparisonMachine<>(object, STP.class) - .equivalent(STP::getValue) - .equivalent(STP::getCode) + public Boolean caseMaxWait(MaxWait object) { + return new ComparisonMachine<>(object, MaxWait.class) + .equivalent(MaxWait::getValue) + .equivalent(MaxWait::getCode) .conclusion; } @@ -384,7 +387,7 @@ public Boolean caseElement(Element object) { .equivalent(Element::getArray) .equalAsObjects(Element::getLiteral) .equalAsObjects(Element::getId) - .equalAsObjects(Element::getUnit) + .equivalent(Element::getTime) .conclusion; } @@ -472,11 +475,16 @@ public Boolean caseParameterReference(ParameterReference object) { @Override public Boolean caseTime(Time object) { + if (object == null) return false; + // (interval=INT unit=TimeUnit) | forever=Forever | never=Never return new ComparisonMachine<>(object, Time.class) + .equalAsObjects(Time::getForever) + .equalAsObjects(Time::getNever) .equalAsObjects(Time::getInterval) .equalAsObjectsModulo( Time::getUnit, - ((Function) TimeUnit::getCanonicalName).compose(TimeUnit::fromName)) + ((Function) TimeUnit::staticGetCanonicalName) + .compose(TimeUnit::fromName)) .conclusion; } @@ -648,8 +656,8 @@ ComparisonMachine equalAsObjects(Function propertyGetter) { } /** - * Conclude false if the two properties are not equal as objects, given that {@code - * projectionToClassRepresentatives} maps each object to some semantically equivalent object. + * Conclude false if the two properties are not equal as objects, given that + * `projectionToClassRepresentatives` maps each object to some semantically equivalent object. If either or both of the objects are null, also conclude false. */ ComparisonMachine equalAsObjectsModulo( Function propertyGetter, Function projectionToClassRepresentatives) { @@ -670,7 +678,7 @@ ComparisonMachine equivalent(Function propertyGette /** * Conclude false if the two properties are not semantically equivalent parse nodes, given that - * {@code projectionToClassRepresentatives} maps each parse node to some semantically equivalent + * `projectionToClassRepresentatives` maps each parse node to some semantically equivalent * node. */ ComparisonMachine equivalentModulo( diff --git a/lfc/core/src/main/java/org/lflang/ast/ToLf.java b/lfc/core/src/main/java/org/lflang/ast/ToLf.java index 3b1b1e48..13c9bc4e 100644 --- a/lfc/core/src/main/java/org/lflang/ast/ToLf.java +++ b/lfc/core/src/main/java/org/lflang/ast/ToLf.java @@ -48,6 +48,7 @@ import org.lflang.lf.KeyValuePair; import org.lflang.lf.KeyValuePairs; import org.lflang.lf.Literal; +import org.lflang.lf.MaxWait; import org.lflang.lf.Method; import org.lflang.lf.MethodArgument; import org.lflang.lf.Mode; @@ -62,7 +63,6 @@ import org.lflang.lf.Reaction; import org.lflang.lf.Reactor; import org.lflang.lf.ReactorDecl; -import org.lflang.lf.STP; import org.lflang.lf.Serializer; import org.lflang.lf.StateVar; import org.lflang.lf.TargetDecl; @@ -84,6 +84,8 @@ /** * Switch class for converting AST nodes to their textual representation as it would appear in LF * code. + * + * @ingroup Utilities */ public class ToLf extends LfSwitch { @@ -149,7 +151,7 @@ private MalleableString doSwitchHelper(EObject eObject) { return super.doSwitch(eObject).addComments(allComments.stream()).setSourceEObject(eObject); } - /** Return all comments contained by ancestors of {@code node} that belong to said ancestors. */ + /** Return all comments contained by ancestors of `node` that belong to said ancestors. */ static Set getAncestorComments(INode node) { Set ancestorComments = new HashSet<>(); for (ICompositeNode ancestor = node.getParent(); @@ -163,8 +165,8 @@ static Set getAncestorComments(INode node) { } /** - * Return the next composite sibling of {@code node}, as given by sequential application of {@code - * getNextSibling}. + * Return the next composite sibling of `node`, as given by sequential application of + * `getNextSibling`. */ static ICompositeNode getNextCompositeSibling(INode node, Function getNextSibling) { INode sibling = node; @@ -176,7 +178,7 @@ static ICompositeNode getNextCompositeSibling(INode node, Function } /** - * Return the siblings following {@code node} up to (and not including) the next non-leaf sibling. + * Return the siblings following `node` up to (and not including) the next non-leaf sibling. */ private static Stream getFollowingNonCompositeSiblings(ICompositeNode node) { INode sibling = node; @@ -188,8 +190,8 @@ private static Stream getFollowingNonCompositeSiblings(ICompositeNode nod } /** - * Return comments that follow {@code node} in the source code and that either satisfy {@code - * filter} or that cannot belong to any following sibling of {@code node}. + * Return comments that follow `node` in the source code and that either satisfy `filter` + * or that cannot belong to any following sibling of `node`. */ private static Stream getFollowingComments( ICompositeNode node, Predicate precedingFilter, Predicate followingFilter) { @@ -205,7 +207,7 @@ private static Stream getFollowingComments( } /** - * Return comments contained by {@code node} that logically belong to this node (and not to any of + * Return comments contained by `node` that logically belong to this node (and not to any of * its children). */ private static List getContainedCodeComments(INode node) { @@ -229,7 +231,7 @@ private static List getContainedCodeComments(INode node) { } /** - * Return all comments that are part of {@code node}, regardless of where they appear relative to + * Return all comments that are part of `node`, regardless of where they appear relative to * the main content of the node. */ private static List getContainedComments(INode node) { @@ -320,12 +322,17 @@ public MalleableString caseAttrParm(AttrParm object) { @Override public MalleableString caseTime(Time t) { - // (interval=INT unit=TimeUnit) + // (interval=INT unit=TimeUnit) | forever=Forever | never=Never + if (t.getForever() != null || t.getInterval() == Long.MAX_VALUE) { + return MalleableString.anyOf("forever"); + } + if (t.getNever() != null || t.getInterval() == Long.MIN_VALUE) { + return MalleableString.anyOf("never"); + } final var interval = Integer.toString(t.getInterval()); - if (t.getUnit() == null) { + if (t.getUnit() == null || t.getUnit().equals("")) { return MalleableString.anyOf(interval); } - return MalleableString.anyOf(interval + " " + t.getUnit()); } @@ -397,9 +404,11 @@ public MalleableString caseImport(Import object) { .append("import ") // TODO: This is a place where we can use conditional parentheses. .append(list(", ", "", "", false, true, true, object.getReactorClasses())) - .append(" from \"") - .append(object.getImportURI()) - .append("\"") + .append(" from ") + .append( + object.getImportURI() != null + ? "\"" + object.getImportURI() + "\"" + : "<" + object.getImportPackage() + ">") .get(); } @@ -645,7 +654,7 @@ public MalleableString caseReaction(Reaction object) { // ('(' (triggers+=TriggerRef (',' triggers+=TriggerRef)*)? ')') // (sources+=VarRef (',' sources+=VarRef)*)? // ('->' effects+=VarRefOrModeTransition (',' effects+=VarRefOrModeTransition)*)? - // ((('named' name=ID)? code=Code) | 'named' name=ID)(stp=STP)?(deadline=Deadline)? + // ((('named' name=ID)? code=Code) | 'named' name=ID)(maxwait=MaxWait)?(deadline=Deadline)? Builder msb = new Builder(); addAttributes(msb, object::getAttributes); if (object.isMutation()) { @@ -676,7 +685,7 @@ public MalleableString caseReaction(Reaction object) { .collect(new Joiner(", "))); } if (object.getCode() != null) msb.append(" ").append(doSwitch(object.getCode())); - if (object.getStp() != null) msb.append(" ").append(doSwitch(object.getStp())); + if (object.getMaxWait() != null) msb.append(" ").append(doSwitch(object.getMaxWait())); if (object.getDeadline() != null) msb.append(" ").append(doSwitch(object.getDeadline())); return msb.get(); } @@ -736,9 +745,9 @@ public MalleableString caseWatchdog(Watchdog object) { } @Override - public MalleableString caseSTP(STP object) { - // 'STP' '(' value=Expression ')' code=Code - return handler(object, "STP", STP::getValue, STP::getCode); + public MalleableString caseMaxWait(MaxWait object) { + // 'maxwait' '(' value=Expression ')' code=Code + return handler(object, "STAA", MaxWait::getValue, MaxWait::getCode); } private MalleableString handler( @@ -904,15 +913,23 @@ public MalleableString caseElement(Element object) { // keyvalue=KeyValuePairs // | array=Array // | literal=Literal - // | (time=INT unit=TimeUnit) + // | Time // | id=Path if (object.getKeyvalue() != null) return doSwitch(object.getKeyvalue()); if (object.getArray() != null) return doSwitch(object.getArray()); if (object.getLiteral() != null) return MalleableString.anyOf(object.getLiteral()); if (object.getId() != null) return MalleableString.anyOf(object.getId()); - if (object.getUnit() != null) - return MalleableString.anyOf(String.format("%d %s", object.getTime(), object.getUnit())); - return MalleableString.anyOf(String.valueOf(object.getTime())); + if (object.getTime() != null) { + var time = object.getTime(); + if (time.getForever() != null || time.getInterval() == Long.MAX_VALUE) { + return MalleableString.anyOf("forever"); + } + if (time.getNever() != null || time.getInterval() == Long.MIN_VALUE) { + return MalleableString.anyOf("never"); + } + return MalleableString.anyOf(String.format("%d %s", time.getInterval(), time.getUnit())); + } + return MalleableString.anyOf("ERROR"); } @Override @@ -1145,7 +1162,7 @@ private void addAttributes(Builder builder, Supplier> g * @param statementListList A list of groups of statements. * @param forceWhitespace Whether to force a line of vertical whitespace regardless of textual * input - * @return A string representation of {@code statementListList}. + * @return A string representation of `statementListList`. */ private MalleableString indentedStatements( List> statementListList, boolean forceWhitespace) { diff --git a/lfc/core/src/main/java/org/lflang/ast/ToSExpr.java b/lfc/core/src/main/java/org/lflang/ast/ToSExpr.java index a514d982..9d2cb8d3 100644 --- a/lfc/core/src/main/java/org/lflang/ast/ToSExpr.java +++ b/lfc/core/src/main/java/org/lflang/ast/ToSExpr.java @@ -45,6 +45,7 @@ import org.lflang.lf.KeyValuePair; import org.lflang.lf.KeyValuePairs; import org.lflang.lf.Literal; +import org.lflang.lf.MaxWait; import org.lflang.lf.Method; import org.lflang.lf.MethodArgument; import org.lflang.lf.Mode; @@ -58,7 +59,6 @@ import org.lflang.lf.Reaction; import org.lflang.lf.Reactor; import org.lflang.lf.ReactorDecl; -import org.lflang.lf.STP; import org.lflang.lf.Serializer; import org.lflang.lf.StateVar; import org.lflang.lf.TargetDecl; @@ -75,6 +75,11 @@ import org.lflang.lf.WidthTerm; import org.lflang.lf.util.LfSwitch; +/** + * Converts an LF model to an S-expression. + * + * @ingroup Utilities + */ public class ToSExpr extends LfSwitch { /** @@ -217,7 +222,8 @@ public SExpr caseImport(Import object) { // reactorClasses+=ImportedReactor)* 'from' importURI=STRING ';'?; return sList( "import", - new SAtom<>(object.getImportURI()), + new SAtom<>( + object.getImportURI() != null ? object.getImportURI() : object.getImportPackage()), sList("reactors", object.getReactorClasses())); } @@ -468,7 +474,7 @@ public SExpr caseReaction(Reaction object) { // ('(' (triggers+=TriggerRef (',' triggers+=TriggerRef)*)? ')') // ( => sources+=VarRef (',' sources+=VarRef)*)? // ('->' effects+=VarRefOrModeTransition (',' effects+=VarRefOrModeTransition)*)? - // (code=Code)? (stp=STP)? (deadline=Deadline)? (delimited?=';')? + // (code=Code)? (maxwait=MaxWait)? (deadline=Deadline)? (delimited?=';')? // ; return sList( "reaction", @@ -479,7 +485,7 @@ public SExpr caseReaction(Reaction object) { sList("sources", object.getSources()), sList("effects", object.getEffects()), object.getCode(), - object.getStp(), + object.getMaxWait(), object.getDeadline(), sList("is-delimited", object.isDelimited())); } @@ -524,10 +530,10 @@ public SExpr caseWatchdog(Watchdog object) { } @Override - public SExpr caseSTP(STP object) { - // STP: - // 'STP' '(' value=Expression ')' code=Code; - return sList("stp", object.getValue(), object.getCode()); + public SExpr caseMaxWait(MaxWait object) { + // maxwait: + // 'maxwait' ()'(' value=Expression ')')? code=Code; + return sList("maxwait", object.getValue(), object.getCode()); } @Override @@ -626,20 +632,20 @@ public SExpr caseElement(Element object) { // keyvalue=KeyValuePairs // | array=Array // | literal=Literal - // | (time=INT unit=TimeUnit) - // | id=Path; + // | time=Time + // | id=Path; return sList( "element", object.getKeyvalue(), object.getArray(), object.getLiteral(), - object.getTime() == 0 - && (object.getKeyvalue() != null - || object.getArray() != null - || object.getLiteral() != null - || object.getId() != null) + object.getTime() == null ? null - : sList("time", object.getTime(), object.getUnit()), + : object.getTime().getForever() != null + ? "forever" + : object.getTime().getNever() != null + ? "never" + : sList("time", object.getTime().getInterval(), object.getTime().getUnit()), object.getId()); } diff --git a/lfc/core/src/main/java/org/lflang/target/property/type/PrimitiveType.java b/lfc/core/src/main/java/org/lflang/target/property/type/PrimitiveType.java index 95427f97..2925c88d 100644 --- a/lfc/core/src/main/java/org/lflang/target/property/type/PrimitiveType.java +++ b/lfc/core/src/main/java/org/lflang/target/property/type/PrimitiveType.java @@ -45,7 +45,7 @@ public enum PrimitiveType implements TargetPropertyType { && v.getArray() == null && v.getLiteral() == null && v.getId() == null - && (v.getTime() == 0 || v.getUnit() != null)), + && v.getTime() != null), STRING( "a string", v -> v.getLiteral() != null && !isCharLiteral(v.getLiteral()) || v.getId() != null), diff --git a/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcReactionGenerator.kt b/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcReactionGenerator.kt index 82c39ae0..7aa079d8 100644 --- a/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcReactionGenerator.kt +++ b/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcReactionGenerator.kt @@ -186,7 +186,7 @@ class UcReactionGenerator(private val reactor: Reactor) { private fun generateReactionCtor(reaction: Reaction) = """| - |${if (reaction.stp != null) "LF_DEFINE_REACTION_STP_VIOLATION_HANDLER(${reactor.codeType}, ${reaction.codeName});" else ""} + |${if (reaction.maxWait != null) "LF_DEFINE_REACTION_STP_VIOLATION_HANDLER(${reactor.codeType}, ${reaction.codeName});" else ""} |${if (reaction.deadline != null) "LF_DEFINE_REACTION_DEADLINE_VIOLATION_HANDLER(${reactor.codeType}, ${reaction.codeName});" else ""} |LF_DEFINE_REACTION_CTOR(${reactor.codeType}, ${reaction.codeName}, ${reaction.index}, ${reaction.ctorDeadlineArgs}, ${reaction.ctorStpArgs}); """ From b51f4a0c16dc3b40328e875bc9e4467ec1a8d8be Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Thu, 18 Sep 2025 14:45:53 -0700 Subject: [PATCH 2/2] Spotless --- .../src/main/java/org/lflang/ast/IsEqual.java | 10 +++++----- lfc/core/src/main/java/org/lflang/ast/ToLf.java | 16 +++++++--------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/lfc/core/src/main/java/org/lflang/ast/IsEqual.java b/lfc/core/src/main/java/org/lflang/ast/IsEqual.java index 8f1164b6..1deed7cc 100644 --- a/lfc/core/src/main/java/org/lflang/ast/IsEqual.java +++ b/lfc/core/src/main/java/org/lflang/ast/IsEqual.java @@ -67,8 +67,8 @@ /** * Switch class that checks if subtrees of the AST are semantically equivalent to each other. Return - * `false` if they are not equivalent; return `true` or `false` (but preferably - * `true`) if they are equivalent. + * `false` if they are not equivalent; return `true` or `false` (but preferably `true`) if they are + * equivalent. * * @ingroup Utilities */ @@ -657,7 +657,8 @@ ComparisonMachine equalAsObjects(Function propertyGetter) { /** * Conclude false if the two properties are not equal as objects, given that - * `projectionToClassRepresentatives` maps each object to some semantically equivalent object. If either or both of the objects are null, also conclude false. + * `projectionToClassRepresentatives` maps each object to some semantically equivalent object. + * If either or both of the objects are null, also conclude false. */ ComparisonMachine equalAsObjectsModulo( Function propertyGetter, Function projectionToClassRepresentatives) { @@ -678,8 +679,7 @@ ComparisonMachine equivalent(Function propertyGette /** * Conclude false if the two properties are not semantically equivalent parse nodes, given that - * `projectionToClassRepresentatives` maps each parse node to some semantically equivalent - * node. + * `projectionToClassRepresentatives` maps each parse node to some semantically equivalent node. */ ComparisonMachine equivalentModulo( Function propertyGetter, Function projectionToClassRepresentatives) { diff --git a/lfc/core/src/main/java/org/lflang/ast/ToLf.java b/lfc/core/src/main/java/org/lflang/ast/ToLf.java index 13c9bc4e..6b72f469 100644 --- a/lfc/core/src/main/java/org/lflang/ast/ToLf.java +++ b/lfc/core/src/main/java/org/lflang/ast/ToLf.java @@ -177,9 +177,7 @@ static ICompositeNode getNextCompositeSibling(INode node, Function return null; } - /** - * Return the siblings following `node` up to (and not including) the next non-leaf sibling. - */ + /** Return the siblings following `node` up to (and not including) the next non-leaf sibling. */ private static Stream getFollowingNonCompositeSiblings(ICompositeNode node) { INode sibling = node; List ret = new ArrayList<>(); @@ -190,8 +188,8 @@ private static Stream getFollowingNonCompositeSiblings(ICompositeNode nod } /** - * Return comments that follow `node` in the source code and that either satisfy `filter` - * or that cannot belong to any following sibling of `node`. + * Return comments that follow `node` in the source code and that either satisfy `filter` or that + * cannot belong to any following sibling of `node`. */ private static Stream getFollowingComments( ICompositeNode node, Predicate precedingFilter, Predicate followingFilter) { @@ -207,8 +205,8 @@ private static Stream getFollowingComments( } /** - * Return comments contained by `node` that logically belong to this node (and not to any of - * its children). + * Return comments contained by `node` that logically belong to this node (and not to any of its + * children). */ private static List getContainedCodeComments(INode node) { ArrayList ret = new ArrayList<>(); @@ -231,8 +229,8 @@ private static List getContainedCodeComments(INode node) { } /** - * Return all comments that are part of `node`, regardless of where they appear relative to - * the main content of the node. + * Return all comments that are part of `node`, regardless of where they appear relative to the + * main content of the node. */ private static List getContainedComments(INode node) { var ret = new ArrayList();