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 f59b78799e..7131da51cd 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java @@ -1127,7 +1127,7 @@ private String createParameterLabel(ParameterInstance param, boolean bullet) { } if (!IterableExtensions.isNullOrEmpty(param.getInitialValue())) { b.append("("); - b.append(IterableExtensions.join(param.getInitialValue(), ", ", _utilityExtensions::toText)); + b.append(IterableExtensions.join(param.getInitialValue(), ", ", ASTUtils::toText)); b.append(")"); } return b.toString(); @@ -1163,7 +1163,7 @@ private String createStateVariableLabel(StateVar variable, boolean bullet) { } if (!IterableExtensions.isNullOrEmpty(variable.getInit())) { b.append("("); - b.append(IterableExtensions.join(variable.getInit(), ", ", _utilityExtensions::toText)); + b.append(IterableExtensions.join(variable.getInit(), ", ", ASTUtils::toText)); b.append(")"); } return b.toString(); diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.java b/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.java index dfc0d1ab60..a7f13df7f6 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.java +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.java @@ -68,6 +68,7 @@ import org.eclipse.xtext.xbase.lib.IterableExtensions; import org.eclipse.xtext.xbase.lib.Pair; import org.eclipse.xtext.xbase.lib.StringExtensions; +import org.lflang.ASTUtils; import org.lflang.diagram.synthesis.AbstractSynthesisExtensions; import org.lflang.diagram.synthesis.LinguaFrancaSynthesis; import org.lflang.diagram.synthesis.postprocessor.ReactionPortAdjustment; @@ -175,7 +176,7 @@ public KRoundedRectangle addMainReactorFigure(KNode node, ReactorInstance reacto if (reactorInstance.reactorDefinition.getHost() != null && getBooleanValue(LinguaFrancaSynthesis.SHOW_REACTOR_HOST)) { KText hostNameText = _kContainerRenderingExtensions.addText(childContainer, - _utilityExtensions.toText(reactorInstance.reactorDefinition.getHost())); + ASTUtils.toText(reactorInstance.reactorDefinition.getHost())); DiagramSyntheses.suppressSelectability(hostNameText); _linguaFrancaStyleExtensions.underlineSelectionStyle(hostNameText); setGridPlacementDataFromPointToPoint(hostNameText, @@ -245,7 +246,7 @@ public ReactorFigureComponents addReactorFigure(KNode node, ReactorInstance reac if (getBooleanValue(LinguaFrancaSynthesis.SHOW_REACTOR_HOST)) { KText reactorHostText = _kContainerRenderingExtensions.addText(childContainer, - _utilityExtensions.toText(reactorInstance.getDefinition().getHost())); + ASTUtils.toText(reactorInstance.getDefinition().getHost())); DiagramSyntheses.suppressSelectability(reactorHostText); _linguaFrancaStyleExtensions.underlineSelectionStyle(reactorHostText); setGridPlacementDataFromPointToPoint(reactorHostText, diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/UtilityExtensions.java b/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/UtilityExtensions.java index fe88a612f7..772071756d 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/UtilityExtensions.java +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/UtilityExtensions.java @@ -39,9 +39,12 @@ import org.lflang.diagram.synthesis.AbstractSynthesisExtensions; import org.lflang.generator.ReactorInstance; import org.lflang.lf.Code; +import org.lflang.lf.Expression; import org.lflang.lf.Host; +import org.lflang.lf.Literal; +import org.lflang.lf.ParameterReference; import org.lflang.lf.Reactor; -import org.lflang.lf.Value; +import org.lflang.lf.Time; import org.lflang.util.StringUtil; import de.cau.cs.kieler.klighd.internal.util.KlighdInternalProperties; @@ -61,44 +64,7 @@ public class UtilityExtensions extends AbstractSynthesisExtensions { @Extension private KGraphFactory _kGraphFactory = KGraphFactory.eINSTANCE; - - /** - * Converts a timing value into readable text - */ - public String toText(Value value) { - if (value != null) { - if (value.getParameter() != null) { - return value.getParameter().getName(); - } else if (value.getTime() != null) { - return value.getTime().getInterval() + - value.getTime().getUnit().toString(); - } else if (value.getLiteral() != null) { - return value.getLiteral(); - } else if (value.getCode() != null) { - return StringUtil.trimCodeBlock(value.getCode().getBody()); - } - } - return ""; - } - - /** - * Converts a host value into readable text - */ - public String toText(Host host) { - StringBuilder sb = new StringBuilder(); - if (host != null) { - if (!StringExtensions.isNullOrEmpty(host.getUser())) { - sb.append(host.getUser()).append("@"); - } - if (!StringExtensions.isNullOrEmpty(host.getAddr())) { - sb.append(host.getAddr()); - } - if (host.getPort() != 0) { - sb.append(":").append(host.getPort()); - } - } - return sb.toString(); - } + /** * Returns true if the reactor is the primary reactor diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java index b7ccf6f592..09a0091972 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java @@ -33,7 +33,6 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; - import javax.inject.Inject; import org.eclipse.xtext.testing.InjectWith; @@ -42,8 +41,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; + import org.lflang.ASTUtils; import org.lflang.lf.Instantiation; +import org.lflang.lf.Literal; import org.lflang.lf.Model; import org.lflang.lf.Parameter; import org.lflang.lf.StateVar; @@ -222,50 +223,47 @@ public void initialValue() throws Exception { Parameter parameter = (Parameter)obj; if (parameter.getName() == "x") { var values = ASTUtils.initialValue(parameter, null); - Assertions.assertEquals(values.get(0).getLiteral(), "1"); + Assertions.assertInstanceOf(Literal.class, values.get(0)); + Assertions.assertEquals(((Literal)values.get(0)).getLiteral(), "1"); - values = ASTUtils.initialValue(parameter, - List.of(map.get("a1"))); - Assertions.assertEquals(values.get(0).getLiteral(), "2"); + values = ASTUtils.initialValue(parameter, List.of(map.get("a1"))); + Assertions.assertInstanceOf(Literal.class, values.get(0)); + Assertions.assertEquals(((Literal)values.get(0)).getLiteral(), "2"); - values = ASTUtils.initialValue(parameter, - List.of(map.get("a2"))); - Assertions.assertEquals(values.get(0).getLiteral(), "-1"); + values = ASTUtils.initialValue(parameter, List.of(map.get("a2"))); + Assertions.assertInstanceOf(Literal.class, values.get(0)); + Assertions.assertEquals(((Literal)values.get(0)).getLiteral(), "-1"); - values = ASTUtils.initialValue(parameter, - List.of(map.get("a1"), map.get("b1"))); - Assertions.assertEquals(values.get(0).getLiteral(), "3"); + values = ASTUtils.initialValue(parameter, List.of(map.get("a1"), map.get("b1"))); + Assertions.assertInstanceOf(Literal.class, values.get(0)); + Assertions.assertEquals(((Literal)values.get(0)).getLiteral(), "3"); - values = ASTUtils.initialValue(parameter, - List.of(map.get("a2"), map.get("b1"))); - Assertions.assertEquals(values.get(0).getLiteral(), "-1"); + values = ASTUtils.initialValue(parameter, List.of(map.get("a2"), map.get("b1"))); + Assertions.assertInstanceOf(Literal.class, values.get(0)); + Assertions.assertEquals(((Literal)values.get(0)).getLiteral(), "-1"); - values = ASTUtils.initialValue(parameter, - List.of(map.get("a1"), map.get("b2"))); - Assertions.assertEquals(values.get(0).getLiteral(), "-2"); + values = ASTUtils.initialValue(parameter, List.of(map.get("a1"), map.get("b2"))); + Assertions.assertInstanceOf(Literal.class, values.get(0)); + Assertions.assertEquals(((Literal)values.get(0)).getLiteral(), "-2"); - values = ASTUtils.initialValue(parameter, - List.of(map.get("a2"), map.get("b2"))); - Assertions.assertEquals(values.get(0).getLiteral(), "-1"); + values = ASTUtils.initialValue(parameter, List.of(map.get("a2"), map.get("b2"))); + Assertions.assertInstanceOf(Literal.class, values.get(0)); + Assertions.assertEquals(((Literal)values.get(0)).getLiteral(), "-1"); } else if (parameter.getName() == "y") { var values = ASTUtils.initialValue(parameter, null); - Assertions.assertEquals(values.get(0).getLiteral(), "2"); + Assertions.assertInstanceOf(Literal.class, values.get(0)); + Assertions.assertEquals(((Literal)values.get(0)).getLiteral(), "2"); - try { - values = ASTUtils.initialValue(parameter, - List.of(map.get("a1"))); - } catch (IllegalArgumentException ex) { - Assertions.assertTrue(ex.getMessage() - .startsWith("Parameter y is not")); - } + Assertions.assertThrows(IllegalArgumentException.class, + () -> ASTUtils.initialValue(parameter, List.of(map.get("a1")))); - values = ASTUtils.initialValue(parameter, - List.of(map.get("b1"))); - Assertions.assertEquals(values.get(0).getLiteral(), "3"); + values = ASTUtils.initialValue(parameter, List.of(map.get("b1"))); + Assertions.assertInstanceOf(Literal.class, values.get(0)); + Assertions.assertEquals(((Literal)values.get(0)).getLiteral(), "3"); - values = ASTUtils.initialValue(parameter, - List.of(map.get("b2"))); - Assertions.assertEquals(values.get(0).getLiteral(), "-2"); + values = ASTUtils.initialValue(parameter, List.of(map.get("b2"))); + Assertions.assertInstanceOf(Literal.class, values.get(0)); + Assertions.assertEquals(((Literal)values.get(0)).getLiteral(), "-2"); } } }); 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 c53607bc21..86f872f406 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java @@ -26,8 +26,6 @@ ***************/ package org.lflang.tests.compiler; -import static org.lflang.ASTUtils.withoutQuotes; - import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -40,6 +38,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; + import org.lflang.Target; import org.lflang.TargetProperty; import org.lflang.TargetProperty.ArrayType; @@ -53,6 +52,7 @@ import org.lflang.lf.Model; import org.lflang.lf.Visibility; import org.lflang.tests.LFInjectorProvider; +import org.lflang.util.StringUtil; import com.google.inject.Inject; @@ -906,8 +906,8 @@ public void nonzeroAfterMustHaveUnits() throws Exception { " b = new X()", " a.y -> b.x after 1", "}"); - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getTime(), - null, "Missing or invalid time unit."); + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getConnection(), + null, "Missing time unit."); } @@ -936,7 +936,7 @@ public void nonZeroTimeValueWithoutUnits() throws Exception { " printf(\"Hello World.\\n\");", " =}", "}"); - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getValue(), null, "Missing time unit."); + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getTimer(), null, "Missing time unit."); } /** @@ -963,8 +963,8 @@ public void parameterTypeMismatch() throws Exception { " printf(\"Hello World.\\n\");", " =}", "}"); - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getValue(), - null, "Parameter is not of time type"); + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getTimer(), + null, "Parameter is not of time type."); } /** @@ -991,8 +991,8 @@ public void targetCodeInTimeArgument() throws Exception { " printf(\"Hello World.\\n\");", " =}", "}"); - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getValue(), - null, "Invalid time literal"); + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getTimer(), + null, "Invalid time literal."); } @@ -1250,12 +1250,14 @@ public void stateAndParameterDeclarationsInC() throws Exception { validator.assertError(model, LfPackage.eINSTANCE.getParameter(), null, "Parameter cannot be initialized using parameter."); validator.assertError(model, LfPackage.eINSTANCE.getStateVar(), null, - "Referenced parameter does not denote a time."); + "Missing time unit."); validator.assertError(model, LfPackage.eINSTANCE.getStateVar(), null, - "Invalid time literal."); + "Parameter is not of time type."); + validator.assertError(model, LfPackage.eINSTANCE.getStateVar(), null, + "Invalid time literal."); validator.assertError(model, LfPackage.eINSTANCE.getParameter(), null, "Uninitialized parameter."); - validator.assertError(model, LfPackage.eINSTANCE.getValue(), null, + validator.assertError(model, LfPackage.eINSTANCE.getTimer(), null, "Missing time unit."); } @@ -1696,7 +1698,7 @@ public void checkTargetProperties() throws Exception { // Also make sure warnings are produced when files are not present. if (prop.type == PrimitiveType.FILE) { validator.assertWarning(model, LfPackage.eINSTANCE.getKeyValuePair(), - null, String.format("Could not find file: '%s'.", withoutQuotes(it))); + null, String.format("Could not find file: '%s'.", StringUtil.removeQuotes(it))); } } diff --git a/org.lflang.tests/src/org/lflang/tests/lsp/ErrorInserter.java b/org.lflang.tests/src/org/lflang/tests/lsp/ErrorInserter.java index 5018b41c38..69f7736ae8 100644 --- a/org.lflang.tests/src/org/lflang/tests/lsp/ErrorInserter.java +++ b/org.lflang.tests/src/org/lflang/tests/lsp/ErrorInserter.java @@ -12,6 +12,7 @@ import java.util.ListIterator; import java.util.Random; import java.util.function.BiFunction; +import java.util.function.BiPredicate; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; @@ -28,7 +29,8 @@ class ErrorInserter { /** A basic error inserter builder on which more specific error inserters can be built. */ private static final Builder BASE_ERROR_INSERTER = new Builder() - .insertCondition(s -> Stream.of(";", "}", "{").anyMatch(s::endsWith)) + .insertCondition((s0, s1) -> Stream.of(s0, s1).allMatch(it -> Stream.of(";", "}", "{").anyMatch(it::endsWith))) + .insertCondition((s0, s1) -> !s1.trim().startsWith("else")) .insertable(" 0 = 1;").insertable("some_undeclared_var1524263 = 9;").insertable(" ++;"); public static final Builder C = BASE_ERROR_INSERTER .replacer("lf_set(", "UNDEFINED_NAME2828376(") @@ -79,7 +81,7 @@ private boolean get() { * @param insertCondition Whether the error inserter is permitted to insert a line after a given line. * @throws IOException if the content of {@code originalTest} cannot be read. */ - private AlteredTest(Path originalTest, Predicate insertCondition) throws IOException { + private AlteredTest(Path originalTest, BiPredicate insertCondition) throws IOException { this.badLines = new ArrayList<>(); this.path = originalTest; this.lines = new LinkedList<>(); // Constant-time insertion during iteration is desired. @@ -88,10 +90,9 @@ private AlteredTest(Path originalTest, Predicate insertCondition) throws boolean ret = true; it.previous(); if (it.hasPrevious()) { - ret = insertCondition.test(it.previous()); + ret = insertCondition.test(it.previous(), it.next()); } it.next(); - it.next(); return ret; }; } @@ -246,18 +247,18 @@ public T next() { } private final Node> replacers; private final Node insertables; - private final Predicate insertCondition; + private final BiPredicate insertCondition; /** Initializes a builder for error inserters. */ public Builder() { - this(null, null, s -> true); + this(null, null, (s0, s1) -> true); } /** Construct a builder with the given replacers and insertables. */ private Builder( Node> replacers, Node insertables, - Predicate insertCondition + BiPredicate insertCondition ) { this.replacers = replacers; this.insertables = insertables; @@ -295,9 +296,10 @@ public Builder insertable(String line) { } /** - * Record that for any line X, insertCondition(X) is a necessary condition that a line may be inserted after X. + * Record that for any lines X, Y, insertCondition(X, Y) is a necessary condition that a line may be inserted + * between X and Y. */ - public Builder insertCondition(Predicate insertCondition) { + public Builder insertCondition(BiPredicate insertCondition) { return new Builder(replacers, insertables, insertCondition.and(insertCondition)); } @@ -317,13 +319,13 @@ public ErrorInserter get(Random random) { private final Random random; private final ImmutableList> replacers; private final ImmutableList insertables; - private final Predicate insertCondition; + private final BiPredicate insertCondition; private ErrorInserter( Random random, ImmutableList> replacers, ImmutableList insertables, - Predicate insertCondition + BiPredicate insertCondition ) { this.random = random; this.replacers = replacers; diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index 7a31ab9366..404fbf1b0e 100644 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ b/org.lflang/src/org/lflang/ASTUtils.java @@ -45,7 +45,6 @@ import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.xtext.TerminalRule; import org.eclipse.xtext.nodemodel.ICompositeNode; -import org.eclipse.xtext.nodemodel.ILeafNode; import org.eclipse.xtext.nodemodel.INode; import org.eclipse.xtext.nodemodel.impl.CompositeNode; import org.eclipse.xtext.nodemodel.impl.HiddenLeafNode; @@ -57,26 +56,28 @@ import org.eclipse.xtext.xbase.lib.IteratorExtensions; import org.eclipse.xtext.xbase.lib.StringExtensions; +import org.lflang.ast.ToText; import org.lflang.generator.CodeMap; import org.lflang.generator.GeneratorBase; import org.lflang.generator.InvalidSourceException; import org.lflang.lf.Action; import org.lflang.lf.ActionOrigin; -import org.lflang.lf.ArraySpec; import org.lflang.lf.Assignment; import org.lflang.lf.Code; import org.lflang.lf.Connection; -import org.lflang.lf.Delay; import org.lflang.lf.Element; +import org.lflang.lf.Expression; import org.lflang.lf.ImportedReactor; import org.lflang.lf.Input; import org.lflang.lf.Instantiation; import org.lflang.lf.LfFactory; import org.lflang.lf.LfPackage; +import org.lflang.lf.Literal; import org.lflang.lf.Mode; import org.lflang.lf.Model; import org.lflang.lf.Output; import org.lflang.lf.Parameter; +import org.lflang.lf.ParameterReference; import org.lflang.lf.Port; import org.lflang.lf.Reaction; import org.lflang.lf.Reactor; @@ -87,7 +88,6 @@ import org.lflang.lf.Timer; import org.lflang.lf.Type; import org.lflang.lf.TypeParm; -import org.lflang.lf.Value; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; import org.lflang.lf.WidthSpec; @@ -412,7 +412,7 @@ private static List rerouteViaDelay(Connection connection, */ private static Instantiation getDelayInstance(Reactor delayClass, Connection connection, String generic, Boolean defineWidthFromConnection) { - Delay delay = connection.getDelay(); + Expression delay = connection.getDelay(); Instantiation delayInstance = factory.createInstantiation(); delayInstance.setReactorClass(delayClass); if (!StringExtensions.isNullOrEmpty(generic)) { @@ -439,13 +439,7 @@ private static Instantiation getDelayInstance(Reactor delayClass, } Assignment assignment = factory.createAssignment(); assignment.setLhs(delayClass.getParameters().get(0)); - Value value = factory.createValue(); - if (delay.getParameter() != null) { - value.setParameter(delay.getParameter()); - } else { - value.setTime(delay.getTime()); - } - assignment.getRhs().add(value); + assignment.getRhs().add(delay); delayInstance.getParameters().add(assignment); delayInstance.setName("delay"); // This has to be overridden. return delayInstance; @@ -495,14 +489,13 @@ private static Reactor getDelayClass(Type type, GeneratorBase generator) { Time defaultTime = factory.createTime(); defaultTime.setUnit(null); defaultTime.setInterval(0); - Value defaultValue = factory.createValue(); - defaultValue.setTime(defaultTime); - delayParameter.getInit().add(defaultValue); + delayParameter.getInit().add(defaultTime); // Name the newly created action; set its delay and type. action.setName("act"); - action.setMinDelay(factory.createValue()); - action.getMinDelay().setParameter(delayParameter); + var paramRef = factory.createParameterReference(); + paramRef.setParameter(delayParameter); + action.setMinDelay(paramRef); action.setOrigin(ActionOrigin.LOGICAL); if (generator.getTargetTypes().supportsGenerics()) { @@ -769,77 +762,30 @@ public static List collectElements(Reactor definition, EStructuralFeature //// Utility functions for translating AST nodes into text /** - * Translate the given code into its textual representation. - * @param code AST node to render as string. + * Translate the given code into its textual representation + * with {@code CodeMap.Correspondence} tags inserted, or + * return the empty string if {@code node} is {@code null}. + * This method should be used to generate code. + * @param node AST node to render as string. * @return Textual representation of the given argument. */ - public static String toText(Code code) { - return CodeMap.Correspondence.tag(code, toUntaggedText(code), true); + public static String toText(EObject node) { + if (node == null) return ""; + return CodeMap.Correspondence.tag(node, toOriginalText(node), node instanceof Code); } /** * Translate the given code into its textual representation - * without any {@code CodeMap.Correspondence} tags inserted. - * @param code AST node to render as string. + * without {@code CodeMap.Correspondence} tags, or return + * the empty string if {@code node} is {@code null}. + * This method should be used for analyzing AST nodes in + * cases where they are easiest to analyze as strings. + * @param node AST node to render as string. * @return Textual representation of the given argument. */ - private static String toUntaggedText(Code code) { - // FIXME: This function should not be necessary, but it is because we currently inspect the - // content of code blocks in the validator and generator (using regexes, etc.). See #810, #657. - String text = ""; - if (code != null) { - ICompositeNode node = NodeModelUtils.getNode(code); - if (node != null) { - StringBuilder builder = new StringBuilder(Math.max(node.getTotalLength(), 1)); - for (ILeafNode leaf : node.getLeafNodes()) { - builder.append(leaf.getText()); - } - String str = builder.toString().trim(); - // Remove the code delimiters (and any surrounding comments). - // This assumes any comment before {= does not include {=. - int start = str.indexOf("{="); - int end = str.indexOf("=}", start); - if (start == -1 || end == -1) { - // Silent failure is needed here because toText is needed to create the intermediate representation, - // which the validator uses. - return str; - } - str = str.substring(start + 2, end); - if (str.split("\n").length > 1) { - // multi line code - text = StringUtil.trimCodeBlock(str); - } else { - // single line code - text = str.trim(); - } - } else if (code.getBody() != null) { - // Code must have been added as a simple string. - text = code.getBody(); - } - } - return text; - } - - public static String toText(TypeParm t) { - return !StringExtensions.isNullOrEmpty(t.getLiteral()) ? t.getLiteral() : toText(t.getCode()); - } - - /** - * Return a textual representation of the given element, - * without quotes if there are any. Leading or trailing - * whitespace is removed. - * - * @param e The element to be rendered as a string. - */ - public static String toText(Element e) { - String str = ""; - if (e.getLiteral() != null) { - str = withoutQuotes(e.getLiteral()).trim(); - } - if (e.getId() != null) { - str = e.getId(); - } - return str; + public static String toOriginalText(EObject node) { + if (node == null) return ""; + return ToText.instance.doSwitch(node); } /** @@ -880,92 +826,27 @@ public static TimeValue toTimeValue(Time e) { * @param e The element to be rendered as a boolean. */ public static boolean toBoolean(Element e) { - return toText(e).equalsIgnoreCase("true"); - } - - /** - * Convert a time to its textual representation as it would - * appear in LF code. - * - * @param t The time to be converted - * @return A textual representation - */ - public static String toText(Time t) { - return toTimeValue(t).toString(); - } - - /** - * Convert a value to its textual representation as it would - * appear in LF code. - * - * @param v The value to be converted - * @return A textual representation - */ - public static String toText(Value v) { - if (v.getParameter() != null) { - return v.getParameter().getName(); - } - if (v.getTime()!= null) { - return toText(v.getTime()); - } - if (v.getLiteral() != null) { - return v.getLiteral(); - } - if (v.getCode() != null) { - return toText(v.getCode()); - } - return ""; - } - - public static String toText(Delay d) { - if (d.getParameter() != null) { - return d.getParameter().getName(); - } - return toText(d.getTime()); - } - - /** - * Return a string of the form either "name" or "container.name" depending - * on in which form the variable reference was given. - * @param v The variable reference. - */ - public static String toText(VarRef v) { - if (v.getContainer() != null) { - return String.format("%s.%s", v.getClass().getName(), v.getVariable().getName()); - } else { - return v.getVariable().getName(); - } + return elementToSingleString(e).equalsIgnoreCase("true"); } - - /** - * Convert an array specification to its textual representation as it would - * appear in LF code. - * - * @param spec The array spec to be converted - * @return A textual representation - */ - public static String toText(ArraySpec spec) { - if (spec != null) { - return (spec.isOfVariableLength()) ? "[]" : "[" + spec.getLength() + "]"; - } - return ""; - } - + /** - * Translate the given type into its textual representation, including - * any array specifications. - * @param type AST node to render as string. - * @return Textual representation of the given argument. + * Given the right-hand side of a target property, return a string that + * represents the given value/ + * + * If the given value is not a literal or and id (but for instance and array or dict), + * an empty string is returned. If the element is a string, any quotes are removed. + * + * @param e The right-hand side of a target property. */ - public static String toText(Type type) { - if (type != null) { - String base = baseType(type); - String arr = (type.getArraySpec() != null) ? toText(type.getArraySpec()) : ""; - return base + arr; + public static String elementToSingleString(Element e) { + if (e.getLiteral() != null) { + return StringUtil.removeQuotes(e.getLiteral()).trim(); + } else if (e.getId() != null) { + return e.getId(); } return ""; } - + /** * Given the right-hand side of a target property, return a list with all * the strings that the property lists. @@ -974,17 +855,17 @@ public static String toText(Type type) { * are ignored; they are not added to the list. * @param value The right-hand side of a target property. */ - public static List toListOfStrings(Element value) { + public static List elementToListOfStrings(Element value) { List elements = new ArrayList<>(); if (value.getArray() != null) { for (Element element : value.getArray().getElements()) { - elements.addAll(toListOfStrings(element)); + elements.addAll(elementToListOfStrings(element)); } return elements; } else { - String v = toText(value); + String v = elementToSingleString(value); if (!v.isEmpty()) { - elements.add(toText(value)); + elements.add(v); } } return elements; @@ -1041,24 +922,24 @@ public static boolean isZero(String literal) { } public static boolean isZero(Code code) { - return code != null && isZero(toUntaggedText(code)); + return code != null && isZero(toOriginalText(code)); } - + /** - * Report whether the given value is zero or not. - * @param value AST node to inspect. + * Report whether the given expression is zero or not. + * + * @param expr AST node to inspect. * @return True if the given value denotes the constant `0`, false otherwise. */ - public static boolean isZero(Value value) { - if (value.getLiteral() != null) { - return isZero(value.getLiteral()); - } else if (value.getCode() != null) { - return isZero(value.getCode()); + public static boolean isZero(Expression expr) { + if (expr instanceof Literal) { + return isZero(((Literal) expr).getLiteral()); + } else if (expr instanceof Code) { + return isZero((Code) expr); } return false; } - - + /** * Report whether the given string literal is an integer number or not. * @param literal AST node to inspect. @@ -1080,40 +961,38 @@ public static boolean isInteger(String literal) { * @return True if the given code is an integer, false otherwise. */ public static boolean isInteger(Code code) { - return isInteger(toUntaggedText(code)); + return isInteger(toText(code)); } /** - * Report whether the given value is an integer number or not. - * @param value AST node to inspect. + * Report whether the given expression is an integer number or not. + * @param expr AST node to inspect. * @return True if the given value is an integer, false otherwise. */ - public static boolean isInteger(Value value) { - if (value.getLiteral() != null) { - return isInteger(value.getLiteral()); - } else if (value.getCode() != null) { - return isInteger(value.getCode()); + public static boolean isInteger(Expression expr) { + if (expr instanceof Literal) { + return isInteger(((Literal) expr).getLiteral()); + } else if (expr instanceof Code) { + return isInteger((Code) expr); } return false; } /** - * Report whether the given value denotes a valid time or not. - * @param value AST node to inspect. + * Report whether the given expression denotes a valid time or not. + * @param expr AST node to inspect. * @return True if the argument denotes a valid time, false otherwise. */ - public static boolean isValidTime(Value value) { - if (value != null) { - if (value.getParameter() != null) { - return isOfTimeType(value.getParameter()); - } else if (value.getTime() != null) { - return isValidTime(value.getTime()); - } else if (value.getLiteral() != null) { - return isZero(value.getLiteral()); - } else if (value.getCode() != null) { - return isZero(value.getCode()); + public static boolean isValidTime(Expression expr) { + if (expr instanceof ParameterReference) { + return isOfTimeType(((ParameterReference)expr).getParameter()); + } else if (expr instanceof Time) { + return isValidTime((Time) expr); + } else if (expr instanceof Literal) { + return isZero(((Literal) expr).getLiteral()); + } else if (expr instanceof Code) { + return isZero((Code) expr); } - } return false; } @@ -1138,11 +1017,11 @@ public static boolean isValidTime(Time t) { * "undefined" type if neither can be inferred. * * @param type Explicit type declared on the declaration - * @param initList A list of values used to initialize a parameter or + * @param initList A list of expressions used to initialize a parameter or * state variable. * @return The inferred type, or "undefined" if none could be inferred. */ - public static InferredType getInferredType(Type type, List initList) { + public static InferredType getInferredType(Type type, List initList) { if (type != null) { return InferredType.fromAST(type); } else if (initList == null) { @@ -1152,10 +1031,10 @@ public static InferredType getInferredType(Type type, List initList) { if (initList.size() == 1) { // If there is a single element in the list, and it is a proper // time value with units, we infer the type "time". - Value init = initList.get(0); - if (init.getParameter() != null) { - return getInferredType(init.getParameter()); - } else if (ASTUtils.isValidTime(init) && !ASTUtils.isZero(init)) { + Expression expr = initList.get(0); + if (expr instanceof ParameterReference) { + return getInferredType(((ParameterReference)expr).getParameter()); + } else if (ASTUtils.isValidTime(expr) && !ASTUtils.isZero(expr)) { return InferredType.time(); } } else if (initList.size() > 1) { @@ -1166,11 +1045,11 @@ public static InferredType getInferredType(Type type, List initList) { var allValidTime = true; var foundNonZero = false; - for (var init : initList) { - if (!ASTUtils.isValidTime(init)) { + for (var expr : initList) { + if (!ASTUtils.isValidTime(expr)) { allValidTime = false; } - if (!ASTUtils.isZero(init)) { + if (!ASTUtils.isZero(expr)) { foundNonZero = true; } } @@ -1276,13 +1155,13 @@ public static String generateVarRef(VarRef reference) { } /** - * Assuming that the given value denotes a valid time literal, + * Assuming that the given expression denotes a valid time literal, * return a time value. */ - public static TimeValue getLiteralTimeValue(Value v) { - if (v.getTime() != null) { - return toTimeValue(v.getTime()); - } else if (v.getLiteral() != null && v.getLiteral().equals("0")) { + 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 { return null; @@ -1326,8 +1205,7 @@ public static boolean isOfTimeType(Parameter param) { /** * Given a parameter, return its initial value. - * The initial value is a list of instances of Value, where each - * Value is either an instance of Time, Literal, or Code. + * The initial value is a list of instances of Expressions. * * If the instantiations argument is null or an empty list, then the * value returned is simply the default value given when the parameter @@ -1398,7 +1276,7 @@ public static boolean isOfTimeType(Parameter param) { * instantiation of the reactor class that is parameterized by the * respective parameter or if the chain of instantiations is not nested. */ - public static List initialValue(Parameter parameter, List instantiations) { + public static List initialValue(Parameter parameter, List instantiations) { // If instantiations are given, then check to see whether this parameter gets overridden in // the first of those instantiations. if (instantiations != null && instantiations.size() > 0) { @@ -1424,9 +1302,9 @@ public static List initialValue(Parameter parameter, List } if (lastAssignment != null) { // Right hand side can be a list. Collect the entries. - List result = new ArrayList<>(); - for (Value value: lastAssignment.getRhs()) { - if (value.getParameter() != null) { + List result = new ArrayList<>(); + for (Expression expr: lastAssignment.getRhs()) { + if (expr instanceof ParameterReference) { if (instantiations.size() > 1 && instantiation.eContainer() != instantiations.get(1).getReactorClass() ) { @@ -1437,10 +1315,10 @@ public static List initialValue(Parameter parameter, List + "." ); } - result.addAll(initialValue(value.getParameter(), + result.addAll(initialValue(((ParameterReference)expr).getParameter(), instantiations.subList(1, instantiations.size()))); } else { - result.add(value); + result.add(expr); } } return result; @@ -1492,21 +1370,21 @@ public static boolean belongsTo(EObject eobject, Reactor reactor) { * @param parameter The parameter. * @param instantiations The (optional) list of instantiations. * - * @return The integer value of the parameter, or null if does not have an integer value. + * @return The integer value of the parameter, or null if it does not have an integer value. * * @throws IllegalArgumentException If an instantiation provided is not an * instantiation of the reactor class that is parameterized by the * respective parameter or if the chain of instantiations is not nested. */ public static Integer initialValueInt(Parameter parameter, List instantiations) { - List values = initialValue(parameter, instantiations); + List expressions = initialValue(parameter, instantiations); int result = 0; - for (Value value: values) { - if (value.getLiteral() == null) { + for (Expression expr: expressions) { + if (!(expr instanceof Literal)) { return null; } try { - result += Integer.decode(value.getLiteral()); + result += Integer.decode(((Literal) expr).getLiteral()); } catch (NumberFormatException ex) { return null; } @@ -1767,7 +1645,7 @@ public static boolean isInitialized(StateVar v) { */ public static boolean isParameterized(StateVar s) { return s.getInit() != null && - IterableExtensions.exists(s.getInit(), it -> it.getParameter() != null); + IterableExtensions.exists(s.getInit(), it -> it instanceof ParameterReference); } /** @@ -1859,21 +1737,7 @@ public static String findAnnotationInComments(EObject object, String key) { } return null; } - - /** - * Remove quotation marks surrounding the specified string. - */ - public static String withoutQuotes(String s) { - String result = s; - if (s.startsWith("\"") || s.startsWith("'")) { - result = s.substring(1); - } - if (result.endsWith("\"") || result.endsWith("'")) { - result = result.substring(0, result.length() - 1); - } - return result; - } - + /** * Search for an `@label` annotation for a given reaction. * diff --git a/org.lflang/src/org/lflang/AstExtensions.kt b/org.lflang/src/org/lflang/AstExtensions.kt index 8549818c81..eb53d40f3d 100644 --- a/org.lflang/src/org/lflang/AstExtensions.kt +++ b/org.lflang/src/org/lflang/AstExtensions.kt @@ -28,7 +28,6 @@ import org.eclipse.emf.ecore.EObject import org.eclipse.emf.ecore.resource.Resource import org.eclipse.xtext.nodemodel.util.NodeModelUtils import org.lflang.lf.* -import java.nio.file.Path /** * If this reactor declaration is an import, then @@ -138,103 +137,13 @@ val Parameter.isOfTimeType: Boolean get() = ASTUtils.isOfTimeType(this) val StateVar.isOfTimeType: Boolean get() = ASTUtils.isOfTimeType(this) /** - * Translate this code element into its textual representation. + * Translate this code element into its textual representation + * with {@code CodeMap.Correspondence} tags inserted. * @see ASTUtils.toText */ -fun Code.toText(): String = ASTUtils.toText(this) +fun EObject.toText(): String = ASTUtils.toText(this) -/** - * Translate this code element into its textual representation. - * @see ASTUtils.toText - */ -fun TypeParm.toText(): String = - if (!literal.isNullOrEmpty()) literal - else code.toText() - - -/** - * Return a textual representation of this element, - * without quotes if there are any. Leading or trailing - * whitespace is removed. - * - * @receiver The element to be rendered as a string. - */ -fun Element.toText(): String = - literal?.withoutQuotes()?.trim() ?: id ?: "" - - -fun Delay.toText(): String = ASTUtils.toText(this) - -fun Time.toTimeValue(): TimeValue = TimeValue(interval.toLong(), TimeUnit.fromName(this.unit)) - - -/** - * Return a string of the form either "name" or "container.name" depending - * on in which form the variable reference was given. - * @receiver The variable reference. - */ -fun TriggerRef.toText(): String = - when { - this is VarRef && container != null -> "${container.name}.${variable.name}" - this is VarRef -> variable.name - isStartup -> "startup" - isShutdown -> "shutdown" - else -> throw UnsupportedOperationException("What's this ref: $this") - } - - -/** - * Convert a value to its textual representation as it would - * appear in LF code. - * - * @receiver The value to be converted - * @return A textual representation - */ -fun Value.toText(): String = - parameter?.name - ?: time?.toText() - ?: literal - ?: code?.toText() - ?: "" - - -/** - * Convert a time to its textual representation as it would - * appear in LF code. - * @receiver The time to be converted - */ -fun Time.toText(): String = "$interval $unit" - - -/** - * Convert an array specification to its textual representation as it would - * appear in LF code. - * - * @receiver The array spec to be converted - * @return A textual representation - */ -fun ArraySpec.toText(): String = - if (isOfVariableLength) "[]" - else "[$length]" - - -/** - * Translate the given type into its textual representation, including - * any array specifications. - * @receiver AST node to render as string. - * @return Textual representation of the given argument. - */ -fun Type.toText(): String = baseType + arraySpec?.toText().orEmpty() - -/** - * Produce a unique identifier within a reactor based on a - * given based name. If the name does not exists, it is returned; - * if does exist, an index is appended that makes the name unique. - * @receiver The reactor to find a unique identifier within. - * @param name The name to base the returned identifier on. - */ -fun Reactor.getUniqueIdentifier(name: String): String = - ASTUtils.getUniqueIdentifier(this, name) +fun Time.toTimeValue(): TimeValue = ASTUtils.toTimeValue(this) /** * Translate the given type into its textual representation, but @@ -265,11 +174,7 @@ val Code.isZero: Boolean get() = this.toText().isZero * @receiver AST node to inspect. * @return True if the given value denotes the constant `0`, false otherwise. */ -val Value.isZero: Boolean - get() = - this.literal?.isZero - ?: this.code?.isZero - ?: false +val Expression.isZero: Boolean get() = ASTUtils.isZero(this) /** * Given a parameter, return an inferred type. Only two types can be @@ -396,7 +301,6 @@ val Reaction.containingReactor get() = this.eContainer() as Reactor val Port.isInput get() = this is Input val Assignment.isInitWithBraces get() = braces.isNotEmpty() -val StateVar.isInitWithBraces get() = braces.isNotEmpty() val Parameter.isInitWithBraces get() = braces.isNotEmpty() /** diff --git a/org.lflang/src/org/lflang/LinguaFranca.xtext b/org.lflang/src/org/lflang/LinguaFranca.xtext index 6e071503cd..78252b9a8c 100644 --- a/org.lflang/src/org/lflang/LinguaFranca.xtext +++ b/org.lflang/src/org/lflang/LinguaFranca.xtext @@ -117,10 +117,10 @@ TargetDecl: /** * Declaration of a state variable. Types are optional, but may be required * during validation (depending on the target language). Initialization is also - * optional. A state variable can be initialized by assigning a `Value` or list - * of these. Note that a `Value` may also be a reference to a parameter. + * optional. A state variable can be initialized by assigning a `Expression` or list + * of these. Note that a `Expression` may also be a reference to a parameter. * The following checks must be carried out during validation: - * - if the list of initialization values has more than one element in it, a + * - if the list of initialization expressions has more than one element in it, a * type must be specified; * - if the `time` type is specified, there can only be a single initialization * element, which has to denote a time or a reference to a parameter that @@ -131,8 +131,8 @@ TargetDecl: StateVar: 'state' name=ID ( (':' (type=Type))? - ((parens+='(' (init+=Value (',' init+=Value)*)? parens+=')') - | (braces+='{' (init+=Value (',' init+=Value)*)? braces+='}') + ((parens+='(' (init+=Expression (',' init+=Expression)*)? parens+=')') + | (braces+='{' (init+=Expression (',' init+=Expression)*)? braces+='}') )? ) ';'? ; @@ -160,7 +160,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)? ')')? ';'?; + 'timer' name=ID ('(' offset=Expression (',' period=Expression)? ')')? ';'?; Boolean: TRUE | FALSE @@ -189,7 +189,7 @@ Mode: // the tags of two subsequently scheduled events. Action: (origin=ActionOrigin)? 'action' name=ID - ('(' minDelay=Value (',' minSpacing=Value (',' policy=STRING)? )? ')')? + ('(' minDelay=Expression (',' minSpacing=Expression (',' policy=STRING)? )? ')')? (':' type=Type)? ';'?; Reaction: @@ -205,10 +205,10 @@ TriggerRef: VarRef | startup?='startup' | shutdown?='shutdown'; Deadline: - 'deadline' '(' delay=Value ')' code=Code; + 'deadline' '(' delay=Expression ')' code=Code; STP: - 'STP' '(' value=Value ')' code=Code; + 'STP' '(' value=Expression ')' code=Code; Mutation: ('mutation') @@ -231,17 +231,9 @@ Connection: | ( '(' leftPorts += VarRef (',' leftPorts += VarRef)* ')' iterated ?= '+'?)) ('->' | physical?='~>') rightPorts += VarRef (',' rightPorts += VarRef)* - (delay=Delay)? + ('after' delay=Expression)? (serializer=Serializer)? ';'? - ; - -// After clause with a delay that can either be specified as a parameter or as -// a literal value. If no units are given (which implies that the time interval -// must be zero), the clause must be ended with a semicolon. If units are given, -// the semicolon is optional. -Delay: - 'after' (parameter=[Parameter] | time=TypedTime) ; // Chooses the serializer to use for the connection @@ -285,10 +277,10 @@ VarRefOrModeTransition returns VarRef: Assignment: (lhs=[Parameter] ( - (equals='=' rhs+=Value) + (equals='=' rhs+=Expression) | ((equals='=')? ( - parens+='(' (rhs+=Value (',' rhs+=Value)*)? parens+=')' - | braces+='{' (rhs+=Value (',' rhs+=Value)*)? braces+='}')) + parens+='(' (rhs+=Expression (',' rhs+=Expression)*)? parens+=')' + | braces+='{' (rhs+=Expression (',' rhs+=Expression)*)? braces+='}')) )); /** @@ -296,22 +288,24 @@ Assignment: */ Parameter: name=ID (':' (type=Type))? - ((parens+='(' (init+=Value (',' init+=Value)*)? parens+=')') - | (braces+='{' (init+=Value (',' init+=Value)*)? braces+='}') + ((parens+='(' (init+=Expression (',' init+=Expression)*)? parens+=')') + | (braces+='{' (init+=Expression (',' init+=Expression)*)? braces+='}') )? ; -Value: - (parameter=[Parameter] | time=Time | literal=Literal | code=Code); +Expression: + {Literal} literal = Literal + | Time + | ParameterReference + | Code +; -Time: - (interval=INT unit=TimeUnit) +ParameterReference: + parameter=[Parameter] ; -// This production is used when we know integer literals are -// to be interpreted as time. -TypedTime returns Time: - (interval=INT unit=TimeUnit?) +Time: + (interval=INT unit=TimeUnit) ; Port: diff --git a/org.lflang/src/org/lflang/ModelInfo.java b/org.lflang/src/org/lflang/ModelInfo.java index dda094baaf..84de93d598 100644 --- a/org.lflang/src/org/lflang/ModelInfo.java +++ b/org.lflang/src/org/lflang/ModelInfo.java @@ -39,9 +39,11 @@ import org.lflang.graph.InstantiationGraph; import org.lflang.lf.Assignment; import org.lflang.lf.Deadline; +import org.lflang.lf.Expression; import org.lflang.lf.Instantiation; import org.lflang.lf.Model; import org.lflang.lf.Parameter; +import org.lflang.lf.ParameterReference; import org.lflang.lf.Reactor; import org.lflang.lf.STP; @@ -162,7 +164,9 @@ private void collectOverflowingNodes() { } // If any of the upstream parameters overflow, report this deadline. - if (detectOverflow(new HashSet<>(), deadline.getDelay().getParameter())) { + final var delay = deadline.getDelay(); + if (delay instanceof ParameterReference + && detectOverflow(new HashSet<>(), ((ParameterReference) deadline.getDelay()).getParameter())) { this.overflowingDeadlines.add(deadline); } } @@ -211,10 +215,10 @@ private boolean detectOverflow(Set visited, Parameter current) { // Find assignments that override the current parameter. for (var assignment : instantiation.getParameters()) { if (assignment.getLhs().equals(current)) { - Parameter parameter = assignment.getRhs().get(0).getParameter(); - if (parameter != null) { + Expression expr = assignment.getRhs().get(0); + if (expr instanceof ParameterReference) { // Check for overflow in the referenced parameter. - overflow = detectOverflow(visited, parameter) || overflow; + overflow = detectOverflow(visited, ((ParameterReference)expr).getParameter()) || overflow; } else { // The right-hand side of the assignment is a // constant; check whether it is too large. diff --git a/org.lflang/src/org/lflang/TargetProperty.java b/org.lflang/src/org/lflang/TargetProperty.java index efc10903c9..a34ddeb78a 100644 --- a/org.lflang/src/org/lflang/TargetProperty.java +++ b/org.lflang/src/org/lflang/TargetProperty.java @@ -44,6 +44,7 @@ import org.lflang.lf.KeyValuePair; import org.lflang.lf.KeyValuePairs; import org.lflang.util.FileUtil; +import org.lflang.util.StringUtil; import org.lflang.validation.LFValidator; /** @@ -59,7 +60,7 @@ public enum TargetProperty { */ BUILD("build", UnionType.STRING_OR_STRING_ARRAY, Arrays.asList(Target.C, Target.CCPP), (config, value, err) -> { - config.buildCommands = ASTUtils.toListOfStrings(value); + config.buildCommands = ASTUtils.elementToListOfStrings(value); }), /** @@ -69,7 +70,7 @@ public enum TargetProperty { BUILD_TYPE("build-type", UnionType.BUILD_TYPE_UNION, Arrays.asList(Target.C, Target.CCPP, Target.CPP, Target.Rust), (config, value, err) -> { config.cmakeBuildType = (BuildType) UnionType.BUILD_TYPE_UNION - .forName(ASTUtils.toText(value)); + .forName(ASTUtils.elementToSingleString(value)); // set it there too, because the default is different. config.rust.setBuildType(config.cmakeBuildType); }), @@ -80,7 +81,7 @@ public enum TargetProperty { CLOCK_SYNC("clock-sync", UnionType.CLOCK_SYNC_UNION, Arrays.asList(Target.C, Target.CCPP, Target.Python), (config, value, err) -> { config.clockSync = (ClockSyncMode) UnionType.CLOCK_SYNC_UNION - .forName(ASTUtils.toText(value)); + .forName(ASTUtils.elementToSingleString(value)); }), /** @@ -132,14 +133,14 @@ public enum TargetProperty { */ CMAKE_INCLUDE("cmake-include", UnionType.FILE_OR_FILE_ARRAY, Arrays.asList(Target.CPP, Target.C, Target.CCPP), (config, value, err) -> { - config.cmakeIncludes = ASTUtils.toListOfStrings(value); + config.cmakeIncludes = ASTUtils.elementToListOfStrings(value); }, // FIXME: This merging of lists is potentially dangerous since // the incoming list of cmake-includes can belong to a .lf file that is // located in a different location, and keeping just filename // strings like this without absolute paths is incorrect. (config, value, err) -> { - config.cmakeIncludes.addAll(ASTUtils.toListOfStrings(value)); + config.cmakeIncludes.addAll(ASTUtils.elementToListOfStrings(value)); }), /** @@ -157,7 +158,7 @@ public enum TargetProperty { */ COMPILER("compiler", PrimitiveType.STRING, Target.ALL, (config, value, err) -> { - config.compiler = ASTUtils.toText(value); + config.compiler = ASTUtils.elementToSingleString(value); }), /** @@ -179,7 +180,7 @@ public enum TargetProperty { .forName(entry.getName()); switch (option) { case FROM: - config.dockerOptions.from = ASTUtils.toText(entry.getValue()); + config.dockerOptions.from = ASTUtils.elementToSingleString(entry.getValue()); break; default: break; @@ -194,7 +195,7 @@ public enum TargetProperty { */ EXTERNAL_RUNTIME_PATH("external-runtime-path", PrimitiveType.STRING, Arrays.asList(Target.CPP), (config, value, err) -> { - config.externalRuntimePath = ASTUtils.toText(value); + config.externalRuntimePath = ASTUtils.elementToSingleString(value); }), /** @@ -212,14 +213,14 @@ public enum TargetProperty { */ FILES("files", UnionType.FILE_OR_FILE_ARRAY, List.of(Target.C, Target.CCPP, Target.Python), (config, value, err) -> { - config.fileNames = ASTUtils.toListOfStrings(value); + config.fileNames = ASTUtils.elementToListOfStrings(value); }, // FIXME: This merging of lists is potentially dangerous since // the incoming list of files can belong to a .lf file that is // located in a different location, and keeping just filename // strings like this without absolute paths is incorrect. (config, value, err) -> { - config.fileNames.addAll(ASTUtils.toListOfStrings(value)); + config.fileNames.addAll(ASTUtils.elementToListOfStrings(value)); }), /** @@ -227,7 +228,7 @@ public enum TargetProperty { */ FLAGS("flags", UnionType.STRING_OR_STRING_ARRAY, Arrays.asList(Target.C, Target.CCPP), (config, value, err) -> { - config.compilerFlags = ASTUtils.toListOfStrings(value); + config.compilerFlags = ASTUtils.elementToListOfStrings(value); }), /** @@ -237,7 +238,7 @@ public enum TargetProperty { Arrays.asList(Target.C, Target.CCPP, Target.Python), (config, value, err) -> { config.coordination = (CoordinationType) UnionType.COORDINATION_UNION - .forName(ASTUtils.toText(value)); + .forName(ASTUtils.elementToSingleString(value)); }), /** @@ -275,7 +276,7 @@ public enum TargetProperty { LOGGING("logging", UnionType.LOGGING_UNION, Target.ALL, (config, value, err) -> { config.logLevel = (LogLevel) UnionType.LOGGING_UNION - .forName(ASTUtils.toText(value)); + .forName(ASTUtils.elementToSingleString(value)); }), /** @@ -302,7 +303,7 @@ public enum TargetProperty { PROTOBUFS("protobufs", UnionType.FILE_OR_FILE_ARRAY, Arrays.asList(Target.C, Target.CCPP, Target.TS, Target.Python), (config, value, err) -> { - config.protoFiles = ASTUtils.toListOfStrings(value); + config.protoFiles = ASTUtils.elementToListOfStrings(value); }), @@ -319,7 +320,7 @@ public enum TargetProperty { */ RUNTIME_VERSION("runtime-version", PrimitiveType.STRING, Arrays.asList(Target.CPP), (config, value, err) -> { - config.runtimeVersion = ASTUtils.toText(value); + config.runtimeVersion = ASTUtils.elementToSingleString(value); }), @@ -329,7 +330,7 @@ public enum TargetProperty { SCHEDULER("scheduler", UnionType.SCHEDULER_UNION, Arrays.asList(Target.C, Target.CCPP, Target.Python), (config, value, err) -> { config.schedulerType = (SchedulerOption) UnionType.SCHEDULER_UNION - .forName(ASTUtils.toText(value)); + .forName(ASTUtils.elementToSingleString(value)); }), /** @@ -385,7 +386,7 @@ public enum TargetProperty { .forName(entry.getName()); switch (option) { case TRACE_FILE_NAME: - config.tracing.traceFileName = ASTUtils.toText(entry.getValue()); + config.tracing.traceFileName = ASTUtils.elementToSingleString(entry.getValue()); break; default: break; @@ -440,12 +441,12 @@ public enum TargetProperty { // are as expected. if (value.getLiteral() != null) { - Path resolved = referencePath.resolveSibling(ASTUtils.withoutQuotes(value.getLiteral())); + Path resolved = referencePath.resolveSibling(StringUtil.removeQuotes(value.getLiteral())); config.rust.addAndCheckTopLevelModule(resolved, value, err); } else if (value.getArray() != null) { for (Element element : value.getArray().getElements()) { - String literal = ASTUtils.withoutQuotes(element.getLiteral()); + String literal = StringUtil.removeQuotes(element.getLiteral()); Path resolved = referencePath.resolveSibling(literal); config.rust.addAndCheckTopLevelModule(resolved, element, err); } @@ -458,7 +459,7 @@ public enum TargetProperty { */ CARGO_FEATURES("cargo-features", ArrayType.STRING_ARRAY, List.of(Target.Rust), (config, value, err) -> { - config.rust.setCargoFeatures(ASTUtils.toListOfStrings(value)); + config.rust.setCargoFeatures(ASTUtils.elementToListOfStrings(value)); }), /** @@ -865,7 +866,7 @@ private Optional> match(Element e) { if (option instanceof TargetPropertyType) { return ((TargetPropertyType) option).validate(e); } else { - return ASTUtils.toText(e) + return ASTUtils.elementToSingleString(e) .equalsIgnoreCase(option.toString()); } }).findAny(); @@ -1083,11 +1084,11 @@ public static void produceError(String name, String description, */ public enum PrimitiveType implements TargetPropertyType { BOOLEAN("'true' or 'false'", - v -> ASTUtils.toText(v).equalsIgnoreCase("true") - || ASTUtils.toText(v).equalsIgnoreCase("false")), + v -> ASTUtils.elementToSingleString(v).equalsIgnoreCase("true") + || ASTUtils.elementToSingleString(v).equalsIgnoreCase("false")), INTEGER("an integer", v -> { try { - Integer.parseInt(ASTUtils.toText(v)); + Integer.parseInt(ASTUtils.elementToSingleString(v)); } catch (NumberFormatException e) { return false; } @@ -1095,7 +1096,7 @@ public enum PrimitiveType implements TargetPropertyType { }), NON_NEGATIVE_INTEGER("a non-negative integer", v -> { try { - int result = Integer.parseInt(ASTUtils.toText(v)); + int result = Integer.parseInt(ASTUtils.elementToSingleString(v)); if (result < 0) return false; } catch (NumberFormatException e) { @@ -1159,7 +1160,7 @@ public void check(Element e, String name, LFValidator v) { // Looking in the same directory is too restrictive. Disabling this check for now. /* if (this == FILE) { - String file = ASTUtils.toText(e); + String file = ASTUtils.toSingleString(e); if (!FileConfig.fileExists(file, FileConfig.toPath(e.eResource().getURI()).toFile().getParent())) { v.targetPropertyWarnings diff --git a/org.lflang/src/org/lflang/ast/ToText.java b/org.lflang/src/org/lflang/ast/ToText.java new file mode 100644 index 0000000000..3f862a240e --- /dev/null +++ b/org.lflang/src/org/lflang/ast/ToText.java @@ -0,0 +1,128 @@ +package org.lflang.ast; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.nodemodel.ICompositeNode; +import org.eclipse.xtext.nodemodel.ILeafNode; +import org.eclipse.xtext.nodemodel.util.NodeModelUtils; +import org.eclipse.xtext.xbase.lib.StringExtensions; + +import org.lflang.ASTUtils; +import org.lflang.lf.ArraySpec; +import org.lflang.lf.Code; +import org.lflang.lf.Host; +import org.lflang.lf.Literal; +import org.lflang.lf.ParameterReference; +import org.lflang.lf.Time; +import org.lflang.lf.Type; +import org.lflang.lf.TypeParm; +import org.lflang.lf.VarRef; +import org.lflang.lf.util.LfSwitch; +import org.lflang.util.StringUtil; + + +/** + * Switch class for converting AST nodes to their textual representation as + * it would appear in LF code. + */ +public class ToText extends LfSwitch { + + /// public instance initialized when loading the class + public static final ToText instance = new ToText(); + + // private constructor + private ToText() { super(); } + + @Override + public String caseArraySpec(ArraySpec spec) { + return (spec.isOfVariableLength()) ? "[]" : "[" + spec.getLength() + "]"; + } + + @Override + public String caseCode(Code code) { + ICompositeNode node = NodeModelUtils.getNode(code); + if (node != null) { + StringBuilder builder = new StringBuilder(Math.max(node.getTotalLength(), 1)); + for (ILeafNode leaf : node.getLeafNodes()) { + builder.append(leaf.getText()); + } + String str = builder.toString().trim(); + // Remove the code delimiters (and any surrounding comments). + // This assumes any comment before {= does not include {=. + int start = str.indexOf("{="); + int end = str.indexOf("=}", start); + if (start == -1 || end == -1) { + // Silent failure is needed here because toText is needed to create the intermediate representation, + // which the validator uses. + return str; + } + str = str.substring(start + 2, end); + if (str.split("\n").length > 1) { + // multi line code + return StringUtil.trimCodeBlock(str); + } else { + // single line code + return str.trim(); + } + } else if (code.getBody() != null) { + // Code must have been added as a simple string. + return code.getBody(); + } + return ""; + } + + @Override + public String caseHost(Host host) { + StringBuilder sb = new StringBuilder(); + if (!StringExtensions.isNullOrEmpty(host.getUser())) { + sb.append(host.getUser()).append("@"); + } + if (!StringExtensions.isNullOrEmpty(host.getAddr())) { + sb.append(host.getAddr()); + } + if (host.getPort() != 0) { + sb.append(":").append(host.getPort()); + } + return sb.toString(); + } + + @Override + public String caseLiteral(Literal l) { + return l.getLiteral(); + } + + @Override + public String caseParameterReference(ParameterReference p) { + return p.getParameter().getName(); + } + + @Override + public String caseTime(Time t) { + return ASTUtils.toTimeValue(t).toString(); + } + + @Override + public String caseType(Type type) { + String base = ASTUtils.baseType(type); + String arr = (type.getArraySpec() != null) ? doSwitch(type.getArraySpec()) : ""; + return base + arr; + } + + @Override + public String caseTypeParm(TypeParm t) { + return !StringExtensions.isNullOrEmpty(t.getLiteral()) ? t.getLiteral() : doSwitch(t.getCode()); + } + + @Override + public String caseVarRef(VarRef v) { + if (v.getContainer() != null) { + return String.format("%s.%s", v.getContainer().getName(), v.getVariable().getName()); + } else { + return v.getVariable().getName(); + } + } + + @Override + public String defaultCase(EObject object) { + throw new UnsupportedOperationException("ToText has no case for " + object.getClass().getName()); + } +} diff --git a/org.lflang/src/org/lflang/federated/CGeneratorExtension.java b/org.lflang/src/org/lflang/federated/CGeneratorExtension.java index 42518375b7..f02533d1a4 100644 --- a/org.lflang/src/org/lflang/federated/CGeneratorExtension.java +++ b/org.lflang/src/org/lflang/federated/CGeneratorExtension.java @@ -34,9 +34,10 @@ import org.lflang.generator.c.CGenerator; import org.lflang.generator.c.CUtil; import org.lflang.lf.Action; -import org.lflang.lf.Delay; +import org.lflang.lf.Expression; import org.lflang.lf.Input; import org.lflang.lf.Parameter; +import org.lflang.lf.ParameterReference; import org.lflang.lf.Reactor; import org.lflang.lf.ReactorDecl; import org.lflang.lf.VarRef; @@ -230,17 +231,16 @@ public static String createPortStatusFieldForInput(Input input) { * @param generator * @return */ - public static String getNetworkDelayLiteral(Delay delay) { + public static String getNetworkDelayLiteral(Expression delay) { String additionalDelayString = "NEVER"; if (delay != null) { - Parameter p = delay.getParameter(); TimeValue tv; - if (delay.getParameter() != null) { + if (delay instanceof ParameterReference) { // The parameter has to be parameter of the main reactor. // And that value has to be a Time. - tv = ASTUtils.getDefaultAsTimeValue(p); + tv = ASTUtils.getDefaultAsTimeValue(((ParameterReference)delay).getParameter()); } else { - tv = ASTUtils.toTimeValue(delay.getTime()); + tv = ASTUtils.getLiteralTimeValue(delay); } additionalDelayString = Long.toString(tv.toNanoSeconds()); } diff --git a/org.lflang/src/org/lflang/federated/FedASTUtils.java b/org.lflang/src/org/lflang/federated/FedASTUtils.java index 55a05f2bc2..e16e5f70b8 100644 --- a/org.lflang/src/org/lflang/federated/FedASTUtils.java +++ b/org.lflang/src/org/lflang/federated/FedASTUtils.java @@ -38,7 +38,6 @@ import org.eclipse.emf.ecore.util.EcoreUtil; import org.lflang.ASTUtils; import org.lflang.InferredType; -import org.lflang.ASTUtils; import org.lflang.TargetProperty.CoordinationType; import org.lflang.TimeValue; import org.lflang.federated.serialization.SupportedSerializers; @@ -47,14 +46,13 @@ import org.lflang.lf.Action; import org.lflang.lf.ActionOrigin; import org.lflang.lf.Connection; -import org.lflang.lf.Delay; +import org.lflang.lf.Expression; import org.lflang.lf.Instantiation; import org.lflang.lf.LfFactory; -import org.lflang.lf.Parameter; +import org.lflang.lf.ParameterReference; import org.lflang.lf.Reaction; import org.lflang.lf.Reactor; import org.lflang.lf.Type; -import org.lflang.lf.Value; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; @@ -126,8 +124,7 @@ private static Action createNetworkAction( // provided using after is enforced by setting // the minDelay. if (connection.getDelay() != null) { - action.setMinDelay(factory.createValue()); - action.getMinDelay().setTime(connection.getDelay().getTime()); + action.setMinDelay(connection.getDelay()); } } else { action.setOrigin(ActionOrigin.LOGICAL); @@ -367,7 +364,7 @@ private static TimeValue findMaxSTP(Variable port, FederateInstance instance, GeneratorBase generator, Reactor reactor) { // Find a list of STP offsets (if any exists) - List STPList = new LinkedList<>(); + List STPList = new LinkedList<>(); // First, check if there are any connections to contained reactors that // need to be handled @@ -405,10 +402,11 @@ private static TimeValue findMaxSTP(Variable port, // If STP offset is determined, add it // If not, assume it is zero if (r.getStp() != null) { - if (r.getStp().getValue().getParameter() != null) { + if (r.getStp().getValue() instanceof ParameterReference) { List instantList = new ArrayList<>(); instantList.add(instance.instantiation); - STPList.addAll(ASTUtils.initialValue(r.getStp().getValue().getParameter(), instantList)); + final var param = ((ParameterReference)r.getStp().getValue()).getParameter(); + STPList.addAll(ASTUtils.initialValue(param, instantList)); } else { STPList.add(r.getStp().getValue()); } @@ -442,10 +440,11 @@ private static TimeValue findMaxSTP(Variable port, // If STP offset is determined, add it // If not, assume it is zero if (r.getStp() != null) { - if (r.getStp().getValue() instanceof Parameter) { + if (r.getStp().getValue() instanceof ParameterReference) { List instantList = new ArrayList<>(); instantList.add(childPort.getContainer()); - STPList.addAll(ASTUtils.initialValue(r.getStp().getValue().getParameter(), instantList)); + final var param = ((ParameterReference)r.getStp().getValue()).getParameter(); + STPList.addAll(ASTUtils.initialValue(param, instantList)); } else { STPList.add(r.getStp().getValue()); } @@ -580,7 +579,7 @@ private static void addNetworkOutputControlReaction( int channelIndex, int receivingFedID, GeneratorBase generator, - Delay delay + Expression delay ) { LfFactory factory = LfFactory.eINSTANCE; Reaction reaction = factory.createReaction(); diff --git a/org.lflang/src/org/lflang/federated/FederateInstance.java b/org.lflang/src/org/lflang/federated/FederateInstance.java index 1da57efe66..fa1ada37bf 100644 --- a/org.lflang/src/org/lflang/federated/FederateInstance.java +++ b/org.lflang/src/org/lflang/federated/FederateInstance.java @@ -47,7 +47,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.TriggerInstance; import org.lflang.lf.Action; import org.lflang.lf.ActionOrigin; -import org.lflang.lf.Delay; +import org.lflang.lf.Expression; import org.lflang.lf.Input; import org.lflang.lf.Instantiation; import org.lflang.lf.Output; @@ -119,7 +119,7 @@ public FederateInstance( /** * A list of outputs that can be triggered directly or indirectly by physical actions. */ - public Set outputsConnectedToPhysicalActions = new LinkedHashSet<>(); + public Set outputsConnectedToPhysicalActions = new LinkedHashSet<>(); /** * The host, if specified using the 'at' keyword. @@ -137,7 +137,7 @@ public FederateInstance( * may may include null, meaning that there is a connection * from the federate instance that has no delay. */ - public Map> dependsOn = new LinkedHashMap<>(); + public Map> dependsOn = new LinkedHashMap<>(); /** * The directory, if specified using the 'at' keyword. @@ -155,7 +155,7 @@ public FederateInstance( * may may include null, meaning that there is a connection * from the federate instance that has no delay. */ - public Map> sendsTo = new LinkedHashMap<>(); + public Map> sendsTo = new LinkedHashMap<>(); /** * The user, if specified using the 'at' keyword. diff --git a/org.lflang/src/org/lflang/federated/PythonGeneratorExtension.java b/org.lflang/src/org/lflang/federated/PythonGeneratorExtension.java index 8da0a9abc4..d0b09cad6b 100644 --- a/org.lflang/src/org/lflang/federated/PythonGeneratorExtension.java +++ b/org.lflang/src/org/lflang/federated/PythonGeneratorExtension.java @@ -34,7 +34,7 @@ import org.lflang.federated.serialization.SupportedSerializers; import org.lflang.generator.c.CUtil; import org.lflang.lf.Action; -import org.lflang.lf.Delay; +import org.lflang.lf.Expression; import org.lflang.lf.VarRef; /** @@ -71,7 +71,7 @@ public static String generateNetworkSenderBody( FederateInstance receivingFed, InferredType type, boolean isPhysical, - Delay delay, + Expression delay, SupportedSerializers serializer, CoordinationType coordinationType ) { diff --git a/org.lflang/src/org/lflang/generator/CodeMap.java b/org.lflang/src/org/lflang/generator/CodeMap.java index 541135de91..01af28f1d5 100644 --- a/org.lflang/src/org/lflang/generator/CodeMap.java +++ b/org.lflang/src/org/lflang/generator/CodeMap.java @@ -11,10 +11,13 @@ import java.util.regex.Pattern; import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.nodemodel.INode; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; import org.eclipse.xtext.util.LineAndColumn; +import org.lflang.lf.impl.ParameterReferenceImpl; + /** * Encapsulates data about the correspondence between * ranges of generated code and ranges of a Lingua Franca @@ -27,16 +30,16 @@ public static class Correspondence { // represent any serious effort to embed the string representation of this object in generated code // without introducing a syntax error. Instead, it is done simply because it is easy. private static final Pattern PATTERN = Pattern.compile(String.format( - "/\\*Correspondence: (?%s) \\-> (?%s) \\(src=(?%s)\\)\\*/", + "/\\*Correspondence: (?%s) \\-> (?%s) \\(verbatim=(?true|false); src=(?%s)\\)\\*/", Position.removeNamedCapturingGroups(Range.PATTERN), Position.removeNamedCapturingGroups(Range.PATTERN), ".*?" )); - // TODO(peter): Add "private final boolean verbatim;" and make corresponding enhancements private final Path path; private final Range lfRange; private final Range generatedRange; + private final boolean verbatim; /** * Instantiates a Correspondence between @@ -49,10 +52,11 @@ public static class Correspondence { * associated with * {@code lfRange} */ - public Correspondence(Path path, Range lfRange, Range generatedRange) { + private Correspondence(Path path, Range lfRange, Range generatedRange, boolean verbatim) { this.path = path; this.lfRange = lfRange; this.generatedRange = generatedRange; + this.verbatim = verbatim; } /** @@ -86,25 +90,11 @@ public Range getGeneratedRange() { @Override public String toString() { return String.format( - "/*Correspondence: %s -> %s (src=%s)*/", - lfRange.toString(), generatedRange.toString(), path.toString() + "/*Correspondence: %s -> %s (verbatim=%b; src=%s)*/", + lfRange.toString(), generatedRange.toString(), verbatim, path.toString() ); } - /** - * Returns the Correspondence represented by - * {@code s}. - * @param s a String that represents a - * Correspondence, formatted like the - * output of - * {@code Correspondence::toString} - * @return the Correspondence represented by - * {@code s} - */ - public static Correspondence fromString(String s) { - return fromString(s, Position.ORIGIN); - } - /** * Returns the Correspondence represented by * {@code s}. @@ -123,7 +113,9 @@ public static Correspondence fromString(String s, Position relativeTo) { Range generatedRange = Range.fromString(matcher.group("generatedRange"), relativeTo); return new Correspondence( Path.of(matcher.group("path")), - lfRange, generatedRange + lfRange, + generatedRange, + Boolean.parseBoolean(matcher.group("verbatim")) ); } throw new IllegalArgumentException(String.format("Could not parse %s as a Correspondence.", s)); @@ -153,15 +145,29 @@ public static String tag(EObject astNode, String representation, boolean verbati Position lfStart = Position.fromOneBased( oneBasedLfLineAndColumn.getLine(), oneBasedLfLineAndColumn.getColumn() ); - final Path lfPath = Path.of(astNode.eResource().getURI().path()); + final Path lfPath = Path.of(bestEffortGetEResource(astNode).getURI().path()); if (verbatim) lfStart = lfStart.plus(node.getText().substring(0, indexOf(node.getText(), representation))); return new Correspondence( lfPath, new Range(lfStart, lfStart.plus(verbatim ? representation : node.getText())), - new Range(Position.ORIGIN, Position.displacementOf(representation)) + new Range(Position.ORIGIN, Position.displacementOf(representation)), + verbatim ) + representation; } + /** + * Return the {@code eResource} associated with the given AST node. + * This is a dangerous operation which can cause an unrecoverable error. + */ + private static Resource bestEffortGetEResource(EObject astNode) { + if (astNode instanceof ParameterReferenceImpl pri) return pri.getParameter().eResource(); + Resource ret = astNode.eResource(); + if (ret != null) return ret; + throw new RuntimeException( + "Every non-null AST node should have an EResource, but \"" + astNode + "\" does not." + ); + } + /** * Make a best-effort attempt to find the index of * a near substring whose first line is expected to @@ -190,6 +196,13 @@ private static int indexOf(String s, String imperfectSubstring) { * to ranges in Lingua Franca files. */ private final Map> map; + /** + * A mapping from Lingua Franca source paths to mappings + * from ranges in the generated file represented by this + * to whether those ranges are copied verbatim from the + * source. + */ + private final Map> isVerbatimByLfSourceByRange; /* ------------------------- PUBLIC METHODS -------------------------- */ @@ -208,13 +221,14 @@ private static int indexOf(String s, String imperfectSubstring) { */ public static CodeMap fromGeneratedCode(String internalGeneratedCode) { Map> map = new HashMap<>(); + Map> isVerbatimByLfSourceByRange = new HashMap<>(); StringBuilder generatedCode = new StringBuilder(); Iterator it = internalGeneratedCode.lines().iterator(); int zeroBasedLine = 0; while (it.hasNext()) { - generatedCode.append(processGeneratedLine(it.next(), zeroBasedLine++, map)).append('\n'); + generatedCode.append(processGeneratedLine(it.next(), zeroBasedLine++, map, isVerbatimByLfSourceByRange)).append('\n'); } - return new CodeMap(generatedCode.toString(), map); + return new CodeMap(generatedCode.toString(), map, isVerbatimByLfSourceByRange); } /** @@ -251,8 +265,12 @@ public Set lfSourcePaths() { */ public Position adjusted(Path lfFile, Position generatedFilePosition) { NavigableMap mapOfInterest = map.get(lfFile); + Map isVerbatimByRange = isVerbatimByLfSourceByRange.get(lfFile); Map.Entry nearestEntry = mapOfInterest.floorEntry(Range.degenerateRange(generatedFilePosition)); if (nearestEntry == null) return Position.ORIGIN; + if (!isVerbatimByRange.get(nearestEntry.getKey())) { + return nearestEntry.getValue().getStartInclusive(); + } if (nearestEntry.getKey().contains(generatedFilePosition)) { return nearestEntry.getValue().getStartInclusive().plus( generatedFilePosition.minus(nearestEntry.getKey().getStartInclusive()) @@ -281,9 +299,14 @@ public Range adjusted(Path lfFile, Range generatedFileRange) { /* ------------------------- PRIVATE METHODS ------------------------- */ - private CodeMap(String generatedCode, Map> map) { + private CodeMap( + String generatedCode, Map> map, + Map> isVerbatimByLfSourceByRange + ) { this.generatedCode = generatedCode; this.map = map; + this.isVerbatimByLfSourceByRange = isVerbatimByLfSourceByRange; } /** @@ -299,7 +322,8 @@ private CodeMap(String generatedCode, Map> map) private static String processGeneratedLine( String line, int zeroBasedLineIndex, - Map> map + Map> map, + Map> isVerbatimByLfSourceByRange ) { Matcher matcher = Correspondence.PATTERN.matcher(line); StringBuilder cleanedLine = new StringBuilder(); @@ -312,6 +336,8 @@ private static String processGeneratedLine( ); if (!map.containsKey(c.path)) map.put(c.path, new TreeMap<>()); map.get(c.path).put(c.generatedRange, c.lfRange); + if (!isVerbatimByLfSourceByRange.containsKey(c.path)) isVerbatimByLfSourceByRange.put(c.path, new HashMap<>()); + isVerbatimByLfSourceByRange.get(c.path).put(c.generatedRange, c.verbatim); lastEnd = matcher.end(); } cleanedLine.append(line.substring(lastEnd)); diff --git a/org.lflang/src/org/lflang/generator/ValueGenerator.java b/org.lflang/src/org/lflang/generator/ExpressionGenerator.java similarity index 70% rename from org.lflang/src/org/lflang/generator/ValueGenerator.java rename to org.lflang/src/org/lflang/generator/ExpressionGenerator.java index 2272c24884..df89b1b2cf 100644 --- a/org.lflang/src/org/lflang/generator/ValueGenerator.java +++ b/org.lflang/src/org/lflang/generator/ExpressionGenerator.java @@ -4,23 +4,22 @@ import java.util.ArrayList; import java.util.stream.Collectors; -import org.lflang.ASTUtils; import org.lflang.ASTUtils; import org.lflang.TimeValue; import org.lflang.lf.Assignment; -import org.lflang.lf.Delay; +import org.lflang.lf.Expression; import org.lflang.lf.Instantiation; import org.lflang.lf.Parameter; +import org.lflang.lf.ParameterReference; import org.lflang.lf.StateVar; import org.lflang.lf.Time; import org.lflang.TimeUnit; -import org.lflang.lf.Value; /** * Encapsulates logic for representing {@code Value}s in a * target language. */ -public final class ValueGenerator { +public final class ExpressionGenerator { /** * A {@code TimeInTargetLanguage} is a @@ -46,10 +45,10 @@ public interface GetTargetReference { /** * Instantiates a target-language-specific - * ValueGenerator parameterized by {@code f}. + * ExpressionGenerator parameterized by {@code f}. * @param f a time representation strategy */ - public ValueGenerator(TimeInTargetLanguage f, GetTargetReference g) { + public ExpressionGenerator(TimeInTargetLanguage f, GetTargetReference g) { this.timeInTargetLanguage = f; this.getTargetReference = g; } @@ -65,11 +64,11 @@ public List getInitializerList(StateVar state) { // FIXME: Previously, we returned null if it was not initialized, which would have caused an // NPE in TSStateGenerator. Is this the desired behavior? if (!ASTUtils.isInitialized(state)) return list; - for (Value v : state.getInit()) { - if (v.getParameter() != null) { - list.add(getTargetReference.apply(v.getParameter())); + for (Expression expr : state.getInit()) { + if (expr instanceof ParameterReference) { + list.add(getTargetReference.apply(((ParameterReference)expr).getParameter())); } else { - list.add(getTargetValue(v, ASTUtils.isOfTimeType(state))); + list.add(getTargetValue(expr, ASTUtils.isOfTimeType(state))); } } return list; @@ -84,8 +83,8 @@ public List getInitializerList(StateVar state) { public List getInitializerList(Parameter param) { List list = new ArrayList<>(); if (param == null) return list; - for (Value v : param.getInit()) - list.add(getTargetValue(v, ASTUtils.isOfTimeType(param))); + for (Expression expr : param.getInit()) + list.add(getTargetValue(expr, ASTUtils.isOfTimeType(param))); return list; } @@ -109,8 +108,8 @@ public List getInitializerList(Parameter param, Instantiation i) { // Case 1: The parameter was overwritten in the instantiation List list = new ArrayList<>(); if (assignments.get(0) == null) return list; - for (Value init : assignments.get(0).getRhs()) - list.add(getTargetValue(init, ASTUtils.isOfTimeType(param))); + for (Expression expr : assignments.get(0).getRhs()) + list.add(getTargetValue(expr, ASTUtils.isOfTimeType(param))); return list; } @@ -130,46 +129,36 @@ public String getTargetTime(Time t) { return timeInTargetLanguage.apply(new TimeValue(t.getInterval(), TimeUnit.fromName(t.getUnit()))); } - /** - * Return the time specified by {@code d}, expressed as - * code that is valid for some target languages. - */ - public String getTargetTime(Delay d) { - return d.getParameter() != null ? ASTUtils.toText(d) : timeInTargetLanguage.apply( - ASTUtils.toTimeValue(d.getTime()) // The time is given as a parameter reference. - ); - } - /** * Return the time specified by {@code v}, expressed as * code that is valid for some target languages. */ - public String getTargetTime(Value v) { - return getTargetValue(v, true); + public String getTargetTime(Expression expr) { + return getTargetValue(expr, true); } /** - * Get textual representation of a value in the target language. + * Get textual representation of an expression in the target language. * - * If the value evaluates to 0, it is interpreted as a normal value. + * If the value evaluates to 0, it is interpreted as a literal. * - * @param v A time AST node + * @param expr A time AST node * @return A time string in the target language */ - public String getTargetValue(Value v) { - return ASTUtils.toText(v); + public String getTargetValue(Expression expr) { + return ASTUtils.toText(expr); } /** - * Get textual representation of a value in the target language. + * Get textual representation of an expression in the target language. * - * @param v A time AST node + * @param expr A time AST node * @param isTime Whether {@code v} is expected to be a time * @return A time string in the target language */ - public String getTargetValue(Value v, boolean isTime) { - if (v.getTime() != null) return getTargetTime(v.getTime()); - if (isTime && ASTUtils.isZero(v)) return timeInTargetLanguage.apply(TimeValue.ZERO); - return ASTUtils.toText(v); + public String getTargetValue(Expression expr, boolean isTime) { + if (expr instanceof Time) return getTargetTime((Time)expr); + if (isTime && ASTUtils.isZero(expr)) return timeInTargetLanguage.apply(TimeValue.ZERO); + return ASTUtils.toText(expr); } } diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.java b/org.lflang/src/org/lflang/generator/GeneratorBase.java index 3bb1524aeb..00158d835b 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.java +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.java @@ -62,7 +62,7 @@ import org.lflang.graph.InstantiationGraph; import org.lflang.lf.Action; import org.lflang.lf.Connection; -import org.lflang.lf.Delay; +import org.lflang.lf.Expression; import org.lflang.lf.Instantiation; import org.lflang.lf.LfFactory; import org.lflang.lf.Mode; @@ -71,7 +71,6 @@ import org.lflang.lf.Reaction; import org.lflang.lf.Reactor; import org.lflang.lf.Time; -import org.lflang.lf.Value; import org.lflang.lf.VarRef; import org.lflang.validation.AbstractLFValidator; @@ -707,7 +706,7 @@ public String generateNetworkSenderBody( FederateInstance receivingFed, InferredType type, boolean isPhysical, - Delay delay, + Expression delay, SupportedSerializers serializer ) { throw new UnsupportedOperationException("This target does not support network connections between federates."); @@ -745,7 +744,7 @@ public String generateNetworkOutputControlReactionBody( int receivingFederateID, int sendingBankIndex, int sendingChannelIndex, - Delay delay + Expression delay ) { throw new UnsupportedOperationException("This target does not support network connections between federates."); } @@ -1179,10 +1178,10 @@ private void replaceConnectionFromFederate( && targetConfig.coordination != CoordinationType.DECENTRALIZED) { // Map the delays on connections between federates. // First see if the cache has been created. - Set dependsOnDelays = dstFederate.dependsOn.get(srcFederate); + Set dependsOnDelays = dstFederate.dependsOn.get(srcFederate); if (dependsOnDelays == null) { // If not, create it. - dependsOnDelays = new LinkedHashSet(); + dependsOnDelays = new LinkedHashSet(); dstFederate.dependsOn.put(srcFederate, dependsOnDelays); } // Put the delay on the cache. @@ -1193,9 +1192,9 @@ private void replaceConnectionFromFederate( dependsOnDelays.add(null); } // Map the connections between federates. - Set sendsToDelays = srcFederate.sendsTo.get(dstFederate); + Set sendsToDelays = srcFederate.sendsTo.get(dstFederate); if (sendsToDelays == null) { - sendsToDelays = new LinkedHashSet(); + sendsToDelays = new LinkedHashSet(); srcFederate.sendsTo.put(dstFederate, sendsToDelays); } if (connection.getDelay() != null) { @@ -1270,6 +1269,7 @@ public void printInfo(LFGeneratorContext.Mode mode) { * @param t A time AST node * @return A time string in the target language */ + // FIXME: this should be placed in ExpressionGenerator public static String getTargetTime(Time t) { TimeValue value = new TimeValue(t.getInterval(), TimeUnit.fromName(t.getUnit())); return timeInTargetLanguage(value); @@ -1280,39 +1280,33 @@ public static String getTargetTime(Time t) { * * If the value evaluates to 0, it is interpreted as a normal value. * - * @param v A time AST node - * @return A time string in the target language + * @param expr An AST node + * @return A string in the target language */ - public static String getTargetValue(Value v) { - if (v.getTime() != null) { - return getTargetTime(v.getTime()); + // FIXME: this should be placed in ExpressionGenerator + public static String getTargetValue(Expression expr) { + if (expr instanceof Time) { + return getTargetTime((Time)expr); } - return ASTUtils.toText(v); + return ASTUtils.toText(expr); } /** * Get textual representation of a value in the target language. * * If the value evaluates to 0, it is interpreted as a time. - * - * @param v A time AST node + * + * @param expr A time AST node * @return A time string in the target language */ - public static String getTargetTime(Value v) { - if (v.getTime() != null) { - return getTargetTime(v.getTime()); - } else if (ASTUtils.isZero(v)) { + // FIXME: this should be placed in ExpressionGenerator + public static String getTargetTime(Expression expr) { + if (expr instanceof Time) { + return getTargetTime((Time)expr); + } else if (ASTUtils.isZero(expr)) { TimeValue value = TimeValue.ZERO; return timeInTargetLanguage(value); } - return ASTUtils.toText(v); - } - - public static String getTargetTime(Delay d) { - if (d.getParameter() != null) { - return ASTUtils.toText(d); - } else { - return getTargetTime(d.getTime()); - } + return ASTUtils.toText(expr); } } diff --git a/org.lflang/src/org/lflang/generator/ParameterInstance.java b/org.lflang/src/org/lflang/generator/ParameterInstance.java index 84c3ccac7b..1b0a09c69d 100644 --- a/org.lflang/src/org/lflang/generator/ParameterInstance.java +++ b/org.lflang/src/org/lflang/generator/ParameterInstance.java @@ -33,8 +33,8 @@ import org.lflang.InferredType; import org.lflang.ASTUtils; import org.lflang.lf.Assignment; +import org.lflang.lf.Expression; import org.lflang.lf.Parameter; -import org.lflang.lf.Value; /** * Representation of a compile-time instance of a parameter. @@ -76,7 +76,7 @@ public ParameterInstance(Parameter definition, ReactorInstance parent) { * of Time, Literal, or Code. That is, references to other * parameters have been replaced with their initial values. */ - public List getInitialValue() { + public List getInitialValue() { return parent.initialParameterValue(this.definition); } diff --git a/org.lflang/src/org/lflang/generator/Range.java b/org.lflang/src/org/lflang/generator/Range.java index a24d7959ca..ffca1104d9 100644 --- a/org.lflang/src/org/lflang/generator/Range.java +++ b/org.lflang/src/org/lflang/generator/Range.java @@ -58,8 +58,7 @@ public Position getEndExclusive() { @Override public boolean equals(Object o) { - if (!(o instanceof Range)) return false; - Range r = (Range) o; + if (!(o instanceof Range r)) return false; return start.equals(r.start); } diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index a0f9d53432..352f616ebe 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -82,7 +82,7 @@ public ReactionInstance( // UNORDERED_REACTION_MARKER, then mark it unordered, // overriding the argument. String body = ASTUtils.toText(definition.getCode()); - if (body != null && body.contains(UNORDERED_REACTION_MARKER)) { + if (body.contains(UNORDERED_REACTION_MARKER)) { this.isUnordered = true; } diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 1f88acd6e9..869806b303 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -39,19 +39,20 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.TriggerInstance.BuiltinTriggerVariable; import org.lflang.lf.Action; import org.lflang.lf.Connection; -import org.lflang.lf.Delay; +import org.lflang.lf.Expression; import org.lflang.lf.Input; import org.lflang.lf.Instantiation; import org.lflang.lf.Mode; import org.lflang.lf.Output; import org.lflang.lf.Parameter; +import org.lflang.lf.ParameterReference; import org.lflang.lf.Port; import org.lflang.lf.Reaction; import org.lflang.lf.Reactor; import org.lflang.lf.ReactorDecl; +import org.lflang.lf.Time; import org.lflang.lf.Timer; import org.lflang.lf.TriggerRef; -import org.lflang.lf.Value; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; import org.lflang.lf.WidthSpec; @@ -432,7 +433,7 @@ public Integer initialIntParameterValue(Parameter parameter) { * a Time if a time value was given, or a Code, if a code value was * given (text in the target language delimited by {= ... =} */ - public List initialParameterValue(Parameter parameter) { + public List initialParameterValue(Parameter parameter) { return ASTUtils.initialValue(parameter, instantiations()); } @@ -640,32 +641,17 @@ public String toString() { } /** - * Assuming that the given value denotes a valid time, return a time value. + * Assuming that the given expression denotes a valid time, return a time value. * * If the value is given as a parameter reference, this will look up the * precise time value assigned to this reactor instance. */ - public TimeValue getTimeValue(Value v) { - Parameter p = v.getParameter(); - if (p != null) { - return ASTUtils.getLiteralTimeValue(lookupParameterInstance(p).getInitialValue().get(0)); + public TimeValue getTimeValue(Expression expr) { + if (expr instanceof ParameterReference) { + final var param = ((ParameterReference)expr).getParameter(); + return ASTUtils.getLiteralTimeValue(lookupParameterInstance(param).getInitialValue().get(0)); } else { - return ASTUtils.getLiteralTimeValue(v); - } - } - - /** - * Assuming that the given delay denotes a valid time, return a time value. - * - * If the delay is given as a parameter reference, this will look up the - * precise time value assigned to this reactor instance. - */ - public TimeValue getTimeValue(Delay d) { - Parameter p = d.getParameter(); - if (p != null) { - return ASTUtils.getLiteralTimeValue(lookupParameterInstance(p).getInitialValue().get(0)); - } else { - return ASTUtils.toTimeValue(d.getTime()); + return ASTUtils.getLiteralTimeValue(expr); } } diff --git a/org.lflang/src/org/lflang/generator/TargetTypes.java b/org.lflang/src/org/lflang/generator/TargetTypes.java index 82925c4a95..c667d45a20 100644 --- a/org.lflang/src/org/lflang/generator/TargetTypes.java +++ b/org.lflang/src/org/lflang/generator/TargetTypes.java @@ -6,23 +6,25 @@ import org.lflang.ASTUtils; import org.lflang.InferredType; -import org.lflang.ASTUtils; import org.lflang.TimeUnit; import org.lflang.TimeValue; import org.lflang.lf.Action; +import org.lflang.lf.Code; +import org.lflang.lf.Expression; +import org.lflang.lf.Literal; import org.lflang.lf.Parameter; +import org.lflang.lf.ParameterReference; import org.lflang.lf.Port; import org.lflang.lf.StateVar; import org.lflang.lf.Time; import org.lflang.lf.Type; -import org.lflang.lf.Value; /** * Information about the types of a target language. Contains * utilities to convert LF expressions and types to the target * language. Each code generator is expected to use at least one * language-specific instance of this interface. - * + *

* TODO currently, {@link GeneratorBase} implements this interface, * it should instead contain an instance. * @@ -128,7 +130,7 @@ default String getMissingExpr(InferredType type) { * initializer list. If both are absent, then the undefined * type is returned. */ - default String getTargetType(Type type, List init) { + default String getTargetType(Type type, List init) { return getTargetType(ASTUtils.getInferredType(type, init)); } @@ -205,7 +207,7 @@ default String getTargetType(Port p) { * @param type Declared type of the expression (nullable) * @param initWithBraces Whether the initializer uses the braced form. */ - default String getTargetInitializer(List init, Type type, boolean initWithBraces) { + default String getTargetInitializer(List init, Type type, boolean initWithBraces) { Objects.requireNonNull(init); var inferredType = ASTUtils.getInferredType(type, init); if (init.size() == 1) { @@ -223,22 +225,22 @@ default String getTargetInitializer(List init, Type type, boolean initWit /** - * Returns the representation of the given value in target code. + * Returns the representation of the given expression in target code. * The given type, if non-null, may inform the code generation. */ - default String getTargetExpr(Value value, InferredType type) { - if (ASTUtils.isZero(value) && type != null && type.isTime) { + default String getTargetExpr(Expression expr, InferredType type) { + if (ASTUtils.isZero(expr) && type != null && type.isTime) { return getTargetTimeExpr(TimeValue.ZERO); - } else if (value.getParameter() != null) { - return escapeIdentifier(value.getParameter().getName()); - } else if (value.getTime() != null) { - return getTargetTimeExpr(value.getTime()); - } else if (value.getLiteral() != null) { - return ASTUtils.addZeroToLeadingDot(value.getLiteral()); // here we don't escape - } else if (value.getCode() != null) { - return ASTUtils.toText(value.getCode()); + } else if (expr instanceof ParameterReference) { + return escapeIdentifier(((ParameterReference) expr).getParameter().getName()); + } else if (expr instanceof Time) { + return getTargetTimeExpr((Time) expr); + } else if (expr instanceof Literal) { + return ASTUtils.addZeroToLeadingDot(((Literal) expr).getLiteral()); // here we don't escape + } else if (expr instanceof Code) { + return ASTUtils.toText(expr); } else { - throw new IllegalStateException("Invalid value " + value); + throw new IllegalStateException("Invalid value " + expr); } } diff --git a/org.lflang/src/org/lflang/generator/TimerInstance.java b/org.lflang/src/org/lflang/generator/TimerInstance.java index f1250ea05f..6683b37bdb 100644 --- a/org.lflang/src/org/lflang/generator/TimerInstance.java +++ b/org.lflang/src/org/lflang/generator/TimerInstance.java @@ -70,7 +70,7 @@ public TimerInstance(Timer definition, ReactorInstance parent) { try { this.period = parent.getTimeValue(definition.getPeriod()); } catch (IllegalArgumentException ex) { - parent.reporter.reportError(definition.getOffset(), "Invalid time."); + parent.reporter.reportError(definition.getPeriod(), "Invalid time."); } } } diff --git a/org.lflang/src/org/lflang/generator/c/CFederateGenerator.java b/org.lflang/src/org/lflang/generator/c/CFederateGenerator.java index 7537e56424..1f8f00f1f8 100644 --- a/org.lflang/src/org/lflang/generator/c/CFederateGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CFederateGenerator.java @@ -1,16 +1,11 @@ package org.lflang.generator.c; -import java.util.Map; - import org.lflang.ASTUtils; -import org.lflang.TargetConfig; -import org.lflang.TargetProperty.CoordinationType; import org.lflang.federated.FederateInstance; import org.lflang.generator.CodeBuilder; import org.lflang.generator.GeneratorBase; -import org.lflang.generator.ParameterInstance; -import org.lflang.generator.ReactorInstance; -import org.lflang.lf.Delay; +import org.lflang.lf.Expression; +import org.lflang.lf.ParameterReference; /** * Generate code for federate related functionality @@ -76,15 +71,16 @@ public static String generateFederateNeighborStructure(FederateInstance federate // There is at least one delay, so find the minimum. // If there is no delay at all, this is encoded as NEVER. code.pr("candidate_tmp = FOREVER;"); - for (Delay delay : delays) { + for (Expression delay : delays) { if (delay == null) { // Use NEVER to encode no delay at all. code.pr("candidate_tmp = NEVER;"); } else { var delayTime = GeneratorBase.getTargetTime(delay); - if (delay.getParameter() != null) { + if (delay instanceof ParameterReference) { // The delay is given as a parameter reference. Find its value. - delayTime = GeneratorBase.timeInTargetLanguage(ASTUtils.getDefaultAsTimeValue(delay.getParameter())); + final var param = ((ParameterReference)delay).getParameter(); + delayTime = GeneratorBase.timeInTargetLanguage(ASTUtils.getDefaultAsTimeValue(param)); } code.pr(String.join("\n", "if ("+delayTime+" < candidate_tmp) {", diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index e7800d5199..2eb2b45907 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -76,7 +76,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.TriggerInstance; import org.lflang.lf.Action; import org.lflang.lf.ActionOrigin; -import org.lflang.lf.Delay; +import org.lflang.lf.Expression; import org.lflang.lf.Input; import org.lflang.lf.Instantiation; import org.lflang.lf.Mode; @@ -2386,7 +2386,7 @@ public String generateNetworkSenderBody( FederateInstance receivingFed, InferredType type, boolean isPhysical, - Delay delay, + Expression delay, SupportedSerializers serializer ) { return CNetworkGenerator.generateNetworkSenderBody( @@ -2447,7 +2447,7 @@ public String generateNetworkOutputControlReactionBody( int receivingFederateID, int sendingBankIndex, int sendingChannelIndex, - Delay delay + Expression delay ) { return CNetworkGenerator.generateNetworkOutputControlReactionBody( port, diff --git a/org.lflang/src/org/lflang/generator/c/CNetworkGenerator.java b/org.lflang/src/org/lflang/generator/c/CNetworkGenerator.java index 432d79c988..150a3e6835 100644 --- a/org.lflang/src/org/lflang/generator/c/CNetworkGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CNetworkGenerator.java @@ -2,9 +2,9 @@ import org.lflang.federated.CGeneratorExtension; import org.lflang.federated.FederateInstance; +import org.lflang.lf.Expression; import org.lflang.lf.VarRef; import org.lflang.lf.Action; -import org.lflang.lf.Delay; import org.lflang.lf.Port; import java.util.regex.Pattern; @@ -33,7 +33,7 @@ private static boolean isSharedPtrType(InferredType type, CTypes types) { } // Regular expression pattern for shared_ptr types. - static final Pattern sharedPointerVariable = Pattern.compile("^std::shared_ptr<(\\S+)>$"); + static final Pattern sharedPointerVariable = Pattern.compile("^(/\\*.*?\\*/)?std::shared_ptr<(?((/\\*.*?\\*/)?(\\S+))+)>$"); /** * Generate code for the body of a reaction that handles the @@ -120,7 +120,7 @@ public static String generateNetworkReceiverBody( } else if (isSharedPtrType(portType, types)) { var matcher = sharedPointerVariable.matcher(portTypeStr); if (matcher.find()) { - portTypeStr = matcher.group(1); + portTypeStr = matcher.group("type"); } } var ROSDeserializer = new FedROS2CPPSerialization(); @@ -168,7 +168,7 @@ public static String generateNetworkSenderBody( FederateInstance receivingFed, InferredType type, boolean isPhysical, - Delay delay, + Expression delay, SupportedSerializers serializer, CTypes types, CoordinationType coordinationType @@ -266,7 +266,7 @@ public static String generateNetworkSenderBody( } else if (isSharedPtrType(type, types)) { var matcher = sharedPointerVariable.matcher(typeStr); if (matcher.find()) { - typeStr = matcher.group(1); + typeStr = matcher.group("type"); } } var ROSSerializer = new FedROS2CPPSerialization(); @@ -334,7 +334,7 @@ public static String generateNetworkOutputControlReactionBody( int receivingFederateID, int sendingBankIndex, int sendingChannelIndex, - Delay delay + Expression delay ) { // Store the code var result = new CodeBuilder(); diff --git a/org.lflang/src/org/lflang/generator/c/CParameterGenerator.java b/org.lflang/src/org/lflang/generator/c/CParameterGenerator.java index 915d5d0861..7261182a75 100644 --- a/org.lflang/src/org/lflang/generator/c/CParameterGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CParameterGenerator.java @@ -7,9 +7,10 @@ import org.lflang.generator.CodeBuilder; import org.lflang.generator.GeneratorBase; import org.lflang.lf.Assignment; +import org.lflang.lf.Expression; +import org.lflang.lf.ParameterReference; import org.lflang.lf.Parameter; import org.lflang.lf.Reactor; -import org.lflang.lf.Value; /** * Generates C code to declare and initialize parameters. @@ -44,24 +45,25 @@ public static String getInitializer(ParameterInstance p) { if (lastAssignment != null) { // The parameter has an assignment. // Right hand side can be a list. Collect the entries. - for (Value value: lastAssignment.getRhs()) { - if (value.getParameter() != null) { + for (Expression expr: lastAssignment.getRhs()) { + if (expr instanceof ParameterReference) { // The parameter is being assigned a parameter value. // Assume that parameter belongs to the parent's parent. // This should have been checked by the validator. - list.add(CUtil.reactorRef(p.getParent().getParent()) + "->" + value.getParameter().getName()); + final var param = ((ParameterReference) expr).getParameter(); + list.add(CUtil.reactorRef(p.getParent().getParent()) + "->" + param.getName()); } else { - list.add(GeneratorBase.getTargetTime(value)); + list.add(GeneratorBase.getTargetTime(expr)); } } } else { // there was no assignment in the instantiation. So just use the // parameter's initial value. - for (Value i : p.getParent().initialParameterValue(p.getDefinition())) { + for (Expression expr : p.getParent().initialParameterValue(p.getDefinition())) { if (ASTUtils.isOfTimeType(p.getDefinition())) { - list.add(GeneratorBase.getTargetTime(i)); + list.add(GeneratorBase.getTargetTime(expr)); } else { - list.add(GeneratorBase.getTargetTime(i)); + list.add(GeneratorBase.getTargetTime(expr)); } } } diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index a69caea0a1..384b15fec6 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java @@ -11,12 +11,9 @@ import org.lflang.ASTUtils; import org.lflang.ErrorReporter; import org.lflang.InferredType; -import org.lflang.Target; import org.lflang.federated.CGeneratorExtension; import org.lflang.federated.FederateInstance; import org.lflang.generator.CodeBuilder; -import org.lflang.generator.ReactionInstance; -import org.lflang.generator.TriggerInstance; import org.lflang.generator.ModeInstance.ModeTransitionType; import org.lflang.lf.Action; import org.lflang.lf.ActionOrigin; diff --git a/org.lflang/src/org/lflang/generator/c/CStateGenerator.java b/org.lflang/src/org/lflang/generator/c/CStateGenerator.java index 55ae3093a3..970b2bc0d4 100644 --- a/org.lflang/src/org/lflang/generator/c/CStateGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CStateGenerator.java @@ -7,10 +7,10 @@ import org.lflang.generator.GeneratorBase; import org.lflang.generator.ModeInstance; import org.lflang.generator.ReactorInstance; -import org.lflang.lf.Mode; +import org.lflang.lf.Expression; +import org.lflang.lf.ParameterReference; import org.lflang.lf.Reactor; import org.lflang.lf.StateVar; -import org.lflang.lf.Value; public class CStateGenerator { /** @@ -153,11 +153,12 @@ private static String generateModalPropertyInitializer( */ private static String getInitializerExpr(StateVar state, ReactorInstance parent) { var list = new LinkedList(); - for (Value i : state.getInit()) { - if (i.getParameter() != null) { - list.add(CUtil.reactorRef(parent) + "->" + i.getParameter().getName()); + for (Expression expr : state.getInit()) { + if (expr instanceof ParameterReference) { + final var param = ((ParameterReference)expr).getParameter(); + list.add(CUtil.reactorRef(parent) + "->" + param.getName()); } else { - list.add(GeneratorBase.getTargetTime(i)); + list.add(GeneratorBase.getTargetTime(expr)); } } return list.size() == 1 ? diff --git a/org.lflang/src/org/lflang/generator/cpp/CppExtensions.kt b/org.lflang/src/org/lflang/generator/cpp/CppExtensions.kt index 31eb1356af..03cd752049 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppExtensions.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppExtensions.kt @@ -1,10 +1,26 @@ package org.lflang.generator.cpp import org.eclipse.emf.ecore.resource.Resource -import org.lflang.* -import org.lflang.lf.* -import java.time.LocalDateTime -import java.time.format.DateTimeFormatter +import org.lflang.InferredType +import org.lflang.TargetProperty +import org.lflang.TimeValue +import org.lflang.indexInContainer +import org.lflang.isBank +import org.lflang.isGeneric +import org.lflang.isMultiport +import org.lflang.lf.Expression +import org.lflang.lf.LfPackage +import org.lflang.lf.ParameterReference +import org.lflang.lf.Port +import org.lflang.lf.Preamble +import org.lflang.lf.Reaction +import org.lflang.lf.Reactor +import org.lflang.lf.TriggerRef +import org.lflang.lf.VarRef +import org.lflang.lf.Visibility +import org.lflang.lf.WidthSpec +import org.lflang.toText +import org.lflang.unreachable /************* * Copyright (c) 2019-2021, TU Dresden. @@ -58,8 +74,8 @@ fun TimeValue.toCppCode() = CppTypes.getTargetTimeExpr(this) * @param outerContext A flag indicating whether to generate code for the scope of the outer reactor class. * This should be set to false if called from code generators for the inner class. */ -fun Value.toTime(outerContext: Boolean = false): String = - if (outerContext && this.parameter != null) "__lf_inner.${parameter.name}" +fun Expression.toTime(outerContext: Boolean = false): String = + if (outerContext && this is ParameterReference) "__lf_inner.${this.parameter.name}" else CppTypes.getTargetExpr(this, InferredType.time()) /** @@ -67,7 +83,7 @@ fun Value.toTime(outerContext: Boolean = false): String = * * If the value evaluates to 0, it is interpreted as a normal value. */ -fun Value.toCppCode(): String = CppTypes.getTargetExpr(this, null) +fun Expression.toCppCode(): String = CppTypes.getTargetExpr(this, null) /** Get the textual representation of a width in C++ code */ fun WidthSpec.toCppCode(): String = terms.joinToString(" + ") { @@ -78,7 +94,7 @@ fun WidthSpec.toCppCode(): String = terms.joinToString(" + ") { if ((variable as Port).isMultiport) "(${container.name}.size() * ${container.name}[0]->${variable.name}.size())" else "${container.name}.size()" } else { - if ((variable as Port).isMultiport) "${name}.size()" + if ((variable as Port).isMultiport) "$name.size()" else "1" } } diff --git a/org.lflang/src/org/lflang/generator/cpp/CppParameterGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppParameterGenerator.kt index c330a83fcc..09a6135337 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppParameterGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppParameterGenerator.kt @@ -37,7 +37,7 @@ class CppParameterGenerator(private val reactor: Reactor) { /** * Create a list of initializers for the given parameter * - * TODO This is redundant to ValueGenerator.getInitializerList + * TODO This is redundant to ExpressionGenerator.getInitializerList */ private fun Parameter.getInitializerList() = init.map { if (isOfTimeType) it.toTime() diff --git a/org.lflang/src/org/lflang/generator/cpp/CppStateGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppStateGenerator.kt index d7e62102dd..ad510d3964 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppStateGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppStateGenerator.kt @@ -27,6 +27,7 @@ package org.lflang.generator.cpp import org.lflang.inferredType import org.lflang.isInitialized import org.lflang.isOfTimeType +import org.lflang.lf.ParameterReference import org.lflang.lf.Reactor import org.lflang.lf.StateVar @@ -36,13 +37,13 @@ class CppStateGenerator(private val reactor: Reactor) { /** * Create a list of state initializers in target code. * - * TODO This is redundant to ValueGenerator.getInitializerList + * TODO This is redundant to ExpressionGenerator.getInitializerList */ private fun getInitializerList(state: StateVar) = state.init.map { when { - it.parameter != null -> it.parameter.name - state.isOfTimeType -> it.toTime() - else -> it.toCppCode() + it is ParameterReference -> it.parameter.name + state.isOfTimeType -> it.toTime() + else -> it.toCppCode() } } diff --git a/org.lflang/src/org/lflang/generator/python/PyUtil.java b/org.lflang/src/org/lflang/generator/python/PyUtil.java index 51503d2c08..3db866dfef 100644 --- a/org.lflang/src/org/lflang/generator/python/PyUtil.java +++ b/org.lflang/src/org/lflang/generator/python/PyUtil.java @@ -29,7 +29,8 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.ReactorInstance; import org.lflang.generator.GeneratorBase; import org.lflang.generator.c.CUtil; -import org.lflang.lf.Value; +import org.lflang.lf.Expression; +import org.lflang.lf.ParameterReference; import org.lflang.ASTUtils; @@ -138,29 +139,29 @@ public static String generateGILReleaseCode() { * Python equivalent. * Examples: * true/false -> True/False - * @param v A value + * @param expr A value * @return A value string in the target language */ - protected static String getPythonTargetValue(Value v) { - String returnValue = ""; - switch (ASTUtils.toText(v)) { + protected static String getPythonTargetValue(Expression expr) { + String returnValue; + switch (ASTUtils.toOriginalText(expr)) { case "false": returnValue = "False"; break; case "true": returnValue = "True"; break; - default: - returnValue = GeneratorBase.getTargetValue(v); + default: + returnValue = GeneratorBase.getTargetValue(expr); } // Parameters in Python are always prepended with a 'self.' // predicate. Therefore, we need to append the returned value // if it is a parameter. - if (v.getParameter() != null) { + if (expr instanceof ParameterReference) { returnValue = "self." + returnValue; } return returnValue; } -} \ No newline at end of file +} diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonGenerator.java index 816adb8fc7..2acae28110 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.java @@ -69,7 +69,7 @@ import org.lflang.generator.c.CGenerator; import org.lflang.generator.c.CUtil; import org.lflang.lf.Action; -import org.lflang.lf.Delay; +import org.lflang.lf.Expression; import org.lflang.lf.Input; import org.lflang.lf.Model; import org.lflang.lf.Output; @@ -507,7 +507,7 @@ public String generateNetworkSenderBody( FederateInstance receivingFed, InferredType type, boolean isPhysical, - Delay delay, + Expression delay, SupportedSerializers serializer ) { return PythonNetworkGenerator.generateNetworkSenderBody( diff --git a/org.lflang/src/org/lflang/generator/python/PythonNetworkGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonNetworkGenerator.java index e82af143aa..1bf7ea5982 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonNetworkGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonNetworkGenerator.java @@ -2,9 +2,9 @@ import org.lflang.federated.FederateInstance; import org.lflang.federated.PythonGeneratorExtension; +import org.lflang.lf.Expression; import org.lflang.lf.VarRef; import org.lflang.lf.Action; -import org.lflang.lf.Delay; import org.lflang.InferredType; import org.lflang.TargetProperty.CoordinationType; import org.lflang.federated.serialization.SupportedSerializers; @@ -93,7 +93,7 @@ public static String generateNetworkSenderBody( FederateInstance receivingFed, InferredType type, boolean isPhysical, - Delay delay, + Expression delay, SupportedSerializers serializer, CoordinationType coordinationType ) { diff --git a/org.lflang/src/org/lflang/generator/python/PythonParameterGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonParameterGenerator.java index b8bc202cd3..68695ace44 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonParameterGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonParameterGenerator.java @@ -8,16 +8,11 @@ import com.google.common.base.Objects; import org.lflang.ASTUtils; -import org.lflang.ASTUtils; -import org.lflang.generator.CodeBuilder; import org.lflang.generator.GeneratorBase; import org.lflang.generator.ParameterInstance; -import org.lflang.generator.ReactorInstance; -import org.lflang.generator.c.CUtil; -import org.lflang.generator.c.CParameterGenerator; +import org.lflang.lf.Expression; +import org.lflang.lf.ParameterReference; import org.lflang.lf.ReactorDecl; -import org.lflang.lf.Value; -import org.lflang.lf.Reactor; import org.lflang.lf.Assignment; import org.lflang.lf.Parameter; @@ -120,19 +115,20 @@ public static String generatePythonInitializer(ParameterInstance p) { if (lastAssignment != null) { // The parameter has an assignment. // Right hand side can be a list. Collect the entries. - for (Value value : lastAssignment.getRhs()) { - if (value.getParameter() != null) { + for (Expression expr : lastAssignment.getRhs()) { + if (expr instanceof ParameterReference) { // The parameter is being assigned a parameter value. // Assume that parameter belongs to the parent's parent. // This should have been checked by the validator. - list.add(PyUtil.reactorRef(p.getParent().getParent()) + "." + value.getParameter().getName()); + final var param = ((ParameterReference) expr).getParameter(); + list.add(PyUtil.reactorRef(p.getParent().getParent()) + "." + param.getName()); } else { - list.add(GeneratorBase.getTargetTime(value)); + list.add(GeneratorBase.getTargetTime(expr)); } } } else { - for (Value i : p.getParent().initialParameterValue(p.getDefinition())) { - list.add(PyUtil.getPythonTargetValue(i)); + for (Expression expr : p.getParent().initialParameterValue(p.getDefinition())) { + list.add(PyUtil.getPythonTargetValue(expr)); } } return list.size() > 1 ? "(" + String.join(", ", list) + ")" : list.get(0); diff --git a/org.lflang/src/org/lflang/generator/python/PythonStateGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonStateGenerator.java index 78745d24d5..07a6a2c1e5 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonStateGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonStateGenerator.java @@ -5,11 +5,8 @@ import java.util.stream.Collectors; import org.lflang.ASTUtils; -import org.lflang.ASTUtils; -import org.lflang.generator.GeneratorBase; import org.lflang.lf.ReactorDecl; import org.lflang.lf.StateVar; -import org.lflang.lf.Value; public class PythonStateGenerator { /** diff --git a/org.lflang/src/org/lflang/generator/rust/CargoDependencySpec.java b/org.lflang/src/org/lflang/generator/rust/CargoDependencySpec.java index 0b48f1a2e2..11783a41b3 100644 --- a/org.lflang/src/org/lflang/generator/rust/CargoDependencySpec.java +++ b/org.lflang/src/org/lflang/generator/rust/CargoDependencySpec.java @@ -166,7 +166,7 @@ private static CargoDependencySpec parseValue(Element element, boolean isRuntime "Expected an array of strings for key '" + name + "'"); } features = array.getElements().stream() - .map(ASTUtils::toText) + .map(ASTUtils::elementToSingleString) .map(StringUtil::removeQuotes) .collect(Collectors.toList()); continue; diff --git a/org.lflang/src/org/lflang/generator/rust/RustModel.kt b/org.lflang/src/org/lflang/generator/rust/RustModel.kt index 12de9accfb..c3ec7bac97 100644 --- a/org.lflang/src/org/lflang/generator/rust/RustModel.kt +++ b/org.lflang/src/org/lflang/generator/rust/RustModel.kt @@ -310,13 +310,13 @@ sealed class ReactorComponent { * Since there's no reasonable common supertype we use [Variable], but maybe we should * have another interface. */ - fun from(v: Variable): ReactorComponent? = when (v) { + fun from(v: Variable): ReactorComponent = when (v) { is Port -> PortData.from(v) is Action -> ActionData( lfName = v.name, isLogical = v.isLogical, dataType = RustTypes.getTargetType(v.type), - minDelay = v.minDelay?.time?.let(RustTypes::getTargetTimeExpr) + minDelay = (v.minDelay as? Time)?.let(RustTypes::getTargetTimeExpr) ) is Timer -> TimerData( lfName = v.name, @@ -326,17 +326,16 @@ sealed class ReactorComponent { else -> throw UnsupportedGeneratorFeatureException("Dependency on ${v.javaClass.simpleName} $v") } - private fun Value?.toTimerTimeValue(): TargetCode = + private fun Expression?.toTimerTimeValue(): TargetCode = when { - this == null -> "Duration::from_millis(0)" - parameter != null -> "${parameter.name}.clone()" - literal != null -> - literal.toIntOrNull() - ?.let { TimeValue(it.toLong(), DEFAULT_TIME_UNIT_IN_TIMER).toRustTimeExpr() } - ?: throw InvalidLfSourceException("Not an integer literal", this) - time != null -> time.toRustTimeExpr() - code != null -> code.toText().inBlock() - else -> RustTypes.getTargetExpr(this, InferredType.time()) + this == null -> "Duration::from_millis(0)" + this is ParameterReference -> "${parameter.name}.clone()" + this is Literal -> literal.toIntOrNull() + ?.let { TimeValue(it.toLong(), DEFAULT_TIME_UNIT_IN_TIMER).toRustTimeExpr() } + ?: throw InvalidLfSourceException("Not an integer literal", this) + this is Time -> toRustTimeExpr() + this is Code -> toText().inBlock() + else -> RustTypes.getTargetExpr(this, InferredType.time()) } } } @@ -518,7 +517,7 @@ object RustModelBuilder { val components = mutableMapOf() val allComponents: List = reactor.allComponents() for (component in allComponents) { - val irObj = ReactorComponent.from(component) ?: continue + val irObj = ReactorComponent.from(component) components[irObj.lfName] = irObj } diff --git a/org.lflang/src/org/lflang/generator/rust/RustTypes.kt b/org.lflang/src/org/lflang/generator/rust/RustTypes.kt index 2401c966e4..f555aff7fb 100644 --- a/org.lflang/src/org/lflang/generator/rust/RustTypes.kt +++ b/org.lflang/src/org/lflang/generator/rust/RustTypes.kt @@ -24,13 +24,14 @@ package org.lflang.generator.rust +import org.lflang.ASTUtils.toText import org.lflang.InferredType import org.lflang.TimeValue import org.lflang.generator.TargetCode import org.lflang.generator.TargetTypes import org.lflang.inBlock -import org.lflang.lf.Value -import org.lflang.toText +import org.lflang.lf.Code +import org.lflang.lf.Expression object RustTypes : TargetTypes { @@ -52,10 +53,9 @@ object RustTypes : TargetTypes { if (ident in RustKeywords) "r#$ident" else ident - override fun getTargetExpr(value: Value, type: InferredType?): String = when { - // wrap in a block to enable writing several statements - value.code != null -> value.code.toText().inBlock() - else -> super.getTargetExpr(value, type) + override fun getTargetExpr(expr: Expression, type: InferredType?): String = when (expr) { + is Code -> toText(expr).inBlock() + else -> super.getTargetExpr(expr, type) } override fun getTargetTimeExpr(timeValue: TimeValue): TargetCode = with(timeValue) { diff --git a/org.lflang/src/org/lflang/generator/ts/TSActionGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSActionGenerator.kt index afc6736c87..73fec6a7f9 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSActionGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSActionGenerator.kt @@ -2,8 +2,9 @@ package org.lflang.generator.ts import org.lflang.federated.FederateInstance import org.lflang.lf.Action +import org.lflang.lf.Expression +import org.lflang.lf.ParameterReference import org.lflang.lf.Type -import org.lflang.lf.Value import java.util.* /** @@ -15,7 +16,7 @@ class TSActionGenerator ( private val actions: List, private val federate: FederateInstance ) { - private fun Value.getTargetValue(): String = tsGenerator.getTargetValueW(this) + private fun Expression.getTargetValue(): String = tsGenerator.getTargetValueW(this) private fun Type.getTargetType(): String = tsGenerator.getTargetTypeW(this) fun generateClassProperties(): String { @@ -44,8 +45,8 @@ class TSActionGenerator ( if (action.minDelay != null) { // Actions in the TypeScript target are constructed // with an optional minDelay argument which defaults to 0. - if (action.minDelay.parameter != null) { - actionArgs+= ", " + action.minDelay.parameter.name + if (action.minDelay is ParameterReference) { + actionArgs+= ", " + (action.minDelay as ParameterReference).parameter.name } else { actionArgs+= ", " + action.minDelay.getTargetValue() } diff --git a/org.lflang/src/org/lflang/generator/ts/TSConnectionGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSConnectionGenerator.kt index 166774537f..9083fc049e 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSConnectionGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSConnectionGenerator.kt @@ -3,13 +3,8 @@ package org.lflang.generator.ts import org.lflang.ErrorReporter import org.lflang.hasMultipleConnections import org.lflang.isBank -import org.lflang.isInput -import org.lflang.isMultiport import org.lflang.lf.Connection -import org.lflang.lf.Port -import org.lflang.lf.Reactor import org.lflang.lf.VarRef -import org.lflang.toText import java.util.* /** diff --git a/org.lflang/src/org/lflang/generator/ts/TSExtensions.kt b/org.lflang/src/org/lflang/generator/ts/TSExtensions.kt index 300966b797..9cf24511cb 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSExtensions.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSExtensions.kt @@ -1,6 +1,5 @@ package org.lflang.generator.ts -import org.lflang.federated.FederateInstance import org.lflang.isBank import org.lflang.isMultiport import org.lflang.lf.Action @@ -17,7 +16,7 @@ import org.lflang.toText */ fun WidthSpec.toTSCode(): String = terms.joinToString(" + ") { when { - it.parameter != null -> "${it.parameter.name}" + it.parameter != null -> it.parameter.name it.port != null -> with(it.port) { if (container?.isBank == true) { if ((variable as Port).isMultiport) "this.${container.name}.all().length * this.${container.name}.all()[0].${variable.name}.width()" diff --git a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt index 333c9560bd..6ccbd85744 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt @@ -44,16 +44,15 @@ import org.lflang.generator.LFGeneratorContext import org.lflang.generator.PrependOperator import org.lflang.generator.SubContext import org.lflang.generator.TargetTypes -import org.lflang.generator.ValueGenerator +import org.lflang.generator.ExpressionGenerator import org.lflang.generator.GeneratorUtils.canGenerate import org.lflang.inferredType import org.lflang.lf.Action -import org.lflang.lf.Delay +import org.lflang.lf.Expression import org.lflang.lf.Instantiation import org.lflang.lf.Parameter import org.lflang.lf.StateVar import org.lflang.lf.Type -import org.lflang.lf.Value import org.lflang.lf.VarRef import org.lflang.scoping.LFGlobalScopeProvider import org.lflang.util.FileUtil @@ -100,7 +99,8 @@ class TSGenerator( "reaction.ts", "reactor.ts", "microtime.d.ts", "multiport.ts", "nanotimer.d.ts", "port.ts", "state.ts", "strings.ts", "time.ts", "trigger.ts", "types.ts", "ulog.d.ts", "util.ts") - private val VG = ValueGenerator(::timeInTargetLanguage) { param -> "this.${param.name}.get()" } + private val VG = + ExpressionGenerator(::timeInTargetLanguage) { param -> "this.${param.name}.get()" } private fun timeInTargetLanguage(value: TimeValue): String { return if (value.unit != null) { @@ -127,7 +127,7 @@ class TSGenerator( // Wrappers to expose GeneratorBase methods. fun federationRTIPropertiesW() = federationRTIProperties - fun getTargetValueW(v: Value): String = VG.getTargetValue(v, false) + fun getTargetValueW(expr: Expression): String = VG.getTargetValue(expr, false) fun getTargetTypeW(p: Parameter): String = TSTypes.getTargetType(p.inferredType) fun getTargetTypeW(state: StateVar): String = TSTypes.getTargetType(state) fun getTargetTypeW(t: Type): String = TSTypes.getTargetType(t) @@ -566,7 +566,7 @@ class TSGenerator( receivingFed: FederateInstance, type: InferredType, isPhysical: Boolean, - delay: Delay?, + delay: Expression?, serializer: SupportedSerializers ): String { return with(PrependOperator) {""" @@ -594,7 +594,7 @@ class TSGenerator( receivingFederateID: Int, sendingBankIndex: Int, sendingChannelIndex: Int, - delay: Delay? + delay: Expression? ): String? { return with(PrependOperator) {""" |// TODO(hokeun): Figure out what to do for generateNetworkOutputControlReactionBody diff --git a/org.lflang/src/org/lflang/generator/ts/TSReactionGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSReactionGenerator.kt index 71b3294462..51d2178039 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSReactionGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSReactionGenerator.kt @@ -28,7 +28,7 @@ class TSReactionGenerator( private val reactor : Reactor, private val federate: FederateInstance ) { - private fun Value.getTargetValue(): String = tsGenerator.getTargetValueW(this) + private fun Expression.getTargetExpression(): String = tsGenerator.getTargetValueW(this) private fun Parameter.getTargetType(): String = tsGenerator.getTargetTypeW(this) private fun StateVar.getTargetType(): String = tsGenerator.getTargetTypeW(this) private fun Type.getTargetType(): String = tsGenerator.getTargetTypeW(this) @@ -71,10 +71,11 @@ class TSReactionGenerator( reactSignature: StringJoiner ): String { var deadlineArgs = "" - if (reaction.deadline.delay.parameter != null) { - deadlineArgs += "this.${reaction.deadline.delay.parameter.name}.get()"; + val delay = reaction.deadline.delay + if (delay is ParameterReference) { + deadlineArgs += "this.${delay.parameter.name}.get()"; } else { - deadlineArgs += reaction.deadline.delay.getTargetValue() + deadlineArgs += delay.getTargetExpression() } return with(PrependOperator) { diff --git a/org.lflang/src/org/lflang/generator/ts/TSTimerGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSTimerGenerator.kt index 4039716297..0d47872142 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSTimerGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSTimerGenerator.kt @@ -1,8 +1,7 @@ -package org.lflang.generator.ts; +package org.lflang.generator.ts -import org.lflang.generator.PrependOperator +import org.lflang.lf.Expression import org.lflang.lf.Timer -import org.lflang.lf.Value import java.util.* /** @@ -13,7 +12,7 @@ class TSTimerGenerator ( private val tsGenerator: TSGenerator, private val timers: List ) { - private fun Value.getTargetValue(): String = tsGenerator.getTargetValueW(this) + private fun Expression.getTargetValue(): String = tsGenerator.getTargetValueW(this) fun generateClassProperties(): String { val timerClassProperties = LinkedList() diff --git a/org.lflang/src/org/lflang/validation/BaseLFValidator.java b/org.lflang/src/org/lflang/validation/BaseLFValidator.java index 1ce506efb3..6b6e7e91ab 100644 --- a/org.lflang/src/org/lflang/validation/BaseLFValidator.java +++ b/org.lflang/src/org/lflang/validation/BaseLFValidator.java @@ -44,7 +44,7 @@ public class BaseLFValidator extends AbstractLFValidator { @Check(CheckType.FAST) public void checkTime(Time time) { if (!ASTUtils.isValidTime(time)) { - error("Missing or invalid time unit. " + + error("Invalid time unit. " + "Should be one of " + TimeUnit.list() + ".", Literals.TIME__UNIT); } diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 1a45cdc141..c07456d2a7 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -30,11 +30,9 @@ import static org.lflang.ASTUtils.isGeneric; import static org.lflang.ASTUtils.isInteger; import static org.lflang.ASTUtils.isOfTimeType; -import static org.lflang.ASTUtils.isParameterized; -import static org.lflang.ASTUtils.isValidTime; import static org.lflang.ASTUtils.isZero; import static org.lflang.ASTUtils.toDefinition; -import static org.lflang.ASTUtils.toText; +import static org.lflang.ASTUtils.toOriginalText; import java.io.IOException; import java.util.ArrayList; @@ -51,10 +49,12 @@ import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.xtext.validation.Check; import org.eclipse.xtext.validation.CheckType; import org.eclipse.xtext.validation.ValidationMessageAcceptor; + import org.lflang.ASTUtils; import org.lflang.ModelInfo; import org.lflang.Target; @@ -68,6 +68,7 @@ import org.lflang.lf.Assignment; import org.lflang.lf.Connection; import org.lflang.lf.Deadline; +import org.lflang.lf.Expression; import org.lflang.lf.Host; import org.lflang.lf.IPV4Host; import org.lflang.lf.IPV6Host; @@ -78,11 +79,13 @@ import org.lflang.lf.KeyValuePair; import org.lflang.lf.KeyValuePairs; import org.lflang.lf.LfPackage.Literals; +import org.lflang.lf.Literal; import org.lflang.lf.Mode; import org.lflang.lf.Model; import org.lflang.lf.NamedHost; import org.lflang.lf.Output; import org.lflang.lf.Parameter; +import org.lflang.lf.ParameterReference; import org.lflang.lf.Port; import org.lflang.lf.Preamble; import org.lflang.lf.Reaction; @@ -92,11 +95,11 @@ import org.lflang.lf.Serializer; import org.lflang.lf.StateVar; import org.lflang.lf.TargetDecl; +import org.lflang.lf.Time; import org.lflang.lf.Timer; import org.lflang.lf.TriggerRef; import org.lflang.lf.Type; import org.lflang.lf.TypedVariable; -import org.lflang.lf.Value; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; import org.lflang.lf.Visibility; @@ -151,6 +154,8 @@ public void checkAction(Action action) { String.join(", ", SPACING_VIOLATION_POLICIES) + ".", Literals.ACTION__POLICY); } + checkExpressionAsTime(action.getMinDelay(), Literals.ACTION__MIN_DELAY); + checkExpressionAsTime(action.getMinSpacing(), Literals.ACTION__MIN_SPACING); } @Check(CheckType.FAST) @@ -158,24 +163,10 @@ public void checkAssignment(Assignment assignment) { // If the left-hand side is a time parameter, make sure the assignment has units if (isOfTimeType(assignment.getLhs())) { if (assignment.getRhs().size() > 1) { - error("Incompatible type.", Literals.ASSIGNMENT__RHS); + error("Incompatible type.", Literals.ASSIGNMENT__RHS); } else if (assignment.getRhs().size() > 0) { - Value v = assignment.getRhs().get(0); - if (!isValidTime(v)) { - if (v.getParameter() == null) { - // This is a value. Check that units are present. - error( - "Missing time unit.", Literals.ASSIGNMENT__RHS); - } else { - // This is a reference to another parameter. Report problem. - error( - "Cannot assign parameter: " + - v.getParameter().getName() + " to " + - assignment.getLhs().getName() + - ". The latter is a time parameter, but the former is not.", - Literals.ASSIGNMENT__RHS); - } - } + Expression expr = assignment.getRhs().get(0); + checkExpressionAsTime(expr, Literals.ASSIGNMENT__RHS); } // If this assignment overrides a parameter that is used in a deadline, // report possible overflow. @@ -223,7 +214,7 @@ public void checkConnection(Connection connection) { Reactor reactor = ASTUtils.getEnclosingReactor(connection); String reactorName = reactor.getName(); error(String.format("Connection in reactor %s creates", reactorName) + - String.format("a cyclic dependency between %s and %s.", toText(lp), toText(rp)), + String.format("a cyclic dependency between %s and %s.", toOriginalText(lp), toOriginalText(rp)), Literals.CONNECTION__DELAY); } } @@ -354,6 +345,17 @@ public void checkConnection(Connection connection) { } } } + + // Check the after delay + if (connection.getDelay() != null) { + final var delay = connection.getDelay(); + if (delay instanceof ParameterReference || delay instanceof Time || delay instanceof Literal) { + checkExpressionAsTime(delay, Literals.CONNECTION__DELAY); + } else { + error("After delays can only be given by time literals or parameters.", + Literals.CONNECTION__DELAY); + } + } } @Check(CheckType.FAST) @@ -365,6 +367,7 @@ public void checkDeadline(Deadline deadline) { TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", Literals.DEADLINE__DELAY); } + checkExpressionAsTime(deadline.getDelay(), Literals.DEADLINE__DELAY); } @Check(CheckType.FAST) @@ -586,8 +589,8 @@ public void checkOutput(Output output) { public void checkParameter(Parameter param) { checkName(param.getName(), Literals.PARAMETER__NAME); - for (Value it : param.getInit()) { - if (it.getParameter() != null) { + for (Expression expr : param.getInit()) { + if (expr instanceof ParameterReference) { // Initialization using parameters is forbidden. error("Parameter cannot be initialized using parameter.", Literals.PARAMETER__INIT); @@ -608,10 +611,10 @@ public void checkParameter(Parameter param) { Literals.PARAMETER__INIT); } else { // The parameter is a singleton time. - Value init = param.getInit().get(0); - if (init.getTime() == null) { - if (init != null && !isZero(init)) { - if (isInteger(init)) { + Expression expr = param.getInit().get(0); + if (!(expr instanceof Time)) { + if (!ASTUtils.isZero(expr)) { + if (ASTUtils.isInteger(expr)) { error("Missing time unit.", Literals.PARAMETER__INIT); } else { error("Invalid time literal.", @@ -753,6 +756,7 @@ public void checkReaction(Reaction reaction) { for (NamedInstance it : cycles) { if (it.getDefinition().equals(reaction)) { reactionInCycle = true; + break; } } if (reactionInCycle) { @@ -771,7 +775,7 @@ public void checkReaction(Reaction reaction) { } } if (triggerExistsInCycle) { - trigs.add(toText(tVarRef)); + trigs.add(toOriginalText(tVarRef)); } } if (trigs.size() > 0) { @@ -790,7 +794,7 @@ public void checkReaction(Reaction reaction) { } } if (sourceExistInCycle) { - sources.add(toText(t)); + sources.add(toOriginalText(t)); } } if (sources.size() > 0) { @@ -809,7 +813,7 @@ public void checkReaction(Reaction reaction) { } } if (effectExistInCycle) { - effects.add(toText(t)); + effects.add(toOriginalText(t)); } } if (effects.size() > 0) { @@ -983,28 +987,11 @@ public void checkState(StateVar stateVar) { // If the state is declared to be a time, // make sure that it is initialized correctly. if (stateVar.getInit() != null) { - for (Value init : stateVar.getInit()) { - if (stateVar.getType() != null && stateVar.getType().isTime() && - !isValidTime(init)) { - if (isParameterized(stateVar)) { - error( - "Referenced parameter does not denote a time.", - Literals.STATE_VAR__INIT); - } else { - if (init != null && !isZero(init)) { - if (isInteger(init)) { - error( - "Missing time unit.", Literals.STATE_VAR__INIT); - } else { - error("Invalid time literal.", - Literals.STATE_VAR__INIT); - } - } - } - } + for (Expression expr : stateVar.getInit()) { + checkExpressionAsTime(expr, Literals.STATE_VAR__INIT); } } - } else if (this.target.requiresTypes && (ASTUtils.getInferredType(stateVar)).isUndefined()) { + } else if (this.target.requiresTypes && ASTUtils.getInferredType(stateVar).isUndefined()) { // Report if a type is missing error("State must have a type.", Literals.STATE_VAR__TYPE); } @@ -1012,8 +999,8 @@ public void checkState(StateVar stateVar) { if (isCBasedTarget() && stateVar.getInit().size() > 1) { // In C, if initialization is done with a list, elements cannot // refer to parameters. - for (Value it : stateVar.getInit()) { - if (it.getParameter() != null) { + for (Expression expr : stateVar.getInit()) { + if (expr instanceof ParameterReference) { error("List items cannot refer to a parameter.", Literals.STATE_VAR__INIT); break; @@ -1125,25 +1112,30 @@ public void checkTargetProperties(KeyValuePairs targetProperties) { KeyValuePair schedulerTargetProperty = schedulerTargetProperties .size() > 0 ? schedulerTargetProperties.get(0) : null; if (schedulerTargetProperty != null) { - String schedulerName = schedulerTargetProperty.getValue().getId(); - if (!TargetProperty.SchedulerOption.valueOf(schedulerName) - .prioritizesDeadline()) { - // Check if a deadline is assigned to any reaction - if (info.model.getReactors().stream().filter(reactor -> { - // Filter reactors that contain at least one reaction that - // has a deadline handler. - return ASTUtils.allReactions(reactor).stream() - .filter(reaction -> { - return reaction.getDeadline() != null; - }).count() > 0; - }).count() > 0) { - warning("This program contains deadlines, but the chosen " - + schedulerName - + " scheduler does not prioritize reaction execution " - + "based on deadlines. This might result in a sub-optimal " - + "scheduling.", schedulerTargetProperty, - Literals.KEY_VALUE_PAIR__VALUE); + String schedulerName = ASTUtils.elementToSingleString(schedulerTargetProperty.getValue()); + try { + if (!TargetProperty.SchedulerOption.valueOf(schedulerName) + .prioritizesDeadline()) { + // Check if a deadline is assigned to any reaction + if (info.model.getReactors().stream().filter(reactor -> { + // Filter reactors that contain at least one reaction that + // has a deadline handler. + return ASTUtils.allReactions(reactor).stream() + .filter(reaction -> { + return reaction.getDeadline() != null; + }).count() > 0; + }).count() > 0) { + warning("This program contains deadlines, but the chosen " + + schedulerName + + " scheduler does not prioritize reaction execution " + + "based on deadlines. This might result in a sub-optimal " + + "scheduling.", schedulerTargetProperty, + Literals.KEY_VALUE_PAIR__VALUE); + } } + } catch (IllegalArgumentException e) { + // the given scheduler is invalid, but this is already checked by + // checkTargetProperties } } } @@ -1151,6 +1143,8 @@ public void checkTargetProperties(KeyValuePairs targetProperties) { @Check(CheckType.FAST) public void checkTimer(Timer timer) { checkName(timer.getName(), Literals.VARIABLE__NAME); + checkExpressionAsTime(timer.getOffset(), Literals.TIMER__OFFSET); + checkExpressionAsTime(timer.getPeriod(), Literals.TIMER__PERIOD); } @Check(CheckType.FAST) @@ -1177,33 +1171,6 @@ else if (this.target == Target.Python) { } } } - - @Check(CheckType.FAST) - public void checkValueAsTime(Value value) { - EObject container = value.eContainer(); - - if (container instanceof Timer || container instanceof Action || - container instanceof Connection || container instanceof Deadline) { - // If parameter is referenced, check that it is of the correct type. - if (value.getParameter() != null) { - if (!isOfTimeType(value.getParameter()) && target.requiresTypes) { - error("Parameter is not of time type", - Literals.VALUE__PARAMETER); - } - } else if (value.getTime() == null) { - if (value.getLiteral() != null && !isZero(value.getLiteral())) { - if (isInteger(value.getLiteral())) { - error("Missing time unit.", Literals.VALUE__LITERAL); - } else { - error("Invalid time literal.", - Literals.VALUE__LITERAL); - } - } else if (value.getCode() != null) { - error("Invalid time literal.", Literals.VALUE__CODE); - } - } - } - } @Check(CheckType.FAST) public void checkVarRef(VarRef varRef) { @@ -1433,6 +1400,30 @@ private void checkName(String name, EStructuralFeature feature) { } } + /** + * Check if an expressions denotes a valid time. + * @param expr the expression to check + * @param eReference the eReference to report errors on + */ + private void checkExpressionAsTime(Expression expr, EReference eReference) { + if (expr == null) { + return; + } + // If parameter is referenced, check that it is of the correct type. + if (expr instanceof ParameterReference) { + final var param = ((ParameterReference) expr).getParameter(); + if (!isOfTimeType(param) && target.requiresTypes) { + error("Parameter is not of time type.", eReference); + } + } else if(expr instanceof Literal && isInteger(expr)) { + if (!isZero(expr)) { + error("Missing time unit.", eReference); + } + } else if (!(expr instanceof Time)) { + error("Invalid time literal.", eReference); + } + } + /** * Return the number of main or federated reactors declared. * @param iter An iterator over all objects in the resource. diff --git a/test/C/src/AfterZero.lf b/test/C/src/AfterZero.lf new file mode 100644 index 0000000000..b40ea77079 --- /dev/null +++ b/test/C/src/AfterZero.lf @@ -0,0 +1,55 @@ +// This checks that the after keyword adjusts logical time, not +// using physical time. +target C { + fast: false, + timeout: 3 sec +}; +reactor foo { + input x:int; + output y:int; + reaction(x) -> y {= + SET(y, 2*x->value); + =} +} +reactor print { + state expected_time:time(0); + state received:int(0); + input x:int; + reaction(x) {= + self->received++; + interval_t elapsed_time = get_elapsed_logical_time(); + printf("Result is %d\n", x->value); + if (x->value != 84) { + printf("ERROR: Expected result to be 84.\n"); + exit(1); + } + printf("Current logical time is: %lld\n", elapsed_time); + printf("Current microstep is: %lld\n", get_microstep()); + printf("Current physical time is: %lld\n", get_elapsed_physical_time()); + if (elapsed_time != self->expected_time) { + printf("ERROR: Expected logical time to be %lld.\n", self->expected_time); + exit(2); + } + if (get_microstep() != 1) { + printf("ERROR: Expected microstep to be 1\n"); + exit(3); + } + self->expected_time += SEC(1); + =} + reaction(shutdown) {= + if (self->received == 0) { + printf("ERROR: Final reactor received no data.\n"); + exit(3); + } + =} +} + +main reactor { + f = new foo(); + p = new print(); + timer t(0, 1 sec); + reaction(t) -> f.x {= + SET(f.x, 42); + =} + f.y -> p.x after 0; +} diff --git a/test/Cpp/src/AfterZero.lf b/test/Cpp/src/AfterZero.lf new file mode 100644 index 0000000000..82450c71e4 --- /dev/null +++ b/test/Cpp/src/AfterZero.lf @@ -0,0 +1,55 @@ +// This checks that the after keyword adjusts logical time, not +// using physical time. +target Cpp { + fast: false, + timeout: 3 sec +}; +reactor foo { + input x:int; + output y:int; + reaction(x) -> y {= + y.set(2*(*x.get())); + =} +} +reactor print { + state expected_time:time(0); + state i:int(0); + input x:int; + reaction(x) {= + i++; + auto elapsed_time = get_elapsed_logical_time(); + std::cout << "Result is " << *x.get() << '\n'; + if (*x.get() != 84) { + std::cerr << "ERROR: Expected result to be 84.\n"; + exit(1); + } + std::cout << "Current logical time is: " << elapsed_time << '\n'; + std::cout << "Current microstep is: " << get_microstep() << '\n'; + std::cout << "Current physical time is: " << get_elapsed_physical_time() << '\n'; + if (elapsed_time != expected_time) { + std::cerr << "ERROR: Expected logical time to be " << expected_time << '\n'; + exit(2); + } + if (get_microstep() != 1) { + std::cerr << "Expected microstrp to be 1\n"; + exit(3); + } + expected_time += 1s; + =} + reaction(shutdown) {= + if (i == 0) { + std::cerr << "ERROR: Final reactor received no data.\n"; + exit(3); + } + =} +} +main reactor { + f = new foo(); + p = new print(); + timer t(0, 1 sec); + reaction(t) -> f.x {= + f.x.set(42); + std::cout << "Timer!\n"; + =} + f.y -> p.x after 0; +}