From 7a32327b8569c4e449bac2c430714bac6ef6903d Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Mon, 24 Jan 2022 13:24:58 -0800 Subject: [PATCH 01/23] port checkImportedReactor, checkImport, checkName, dependsOnCycle, isUnused --- .../org/lflang/validation/LFValidator.java | 1344 +++++++++++++++++ 1 file changed, 1344 insertions(+) create mode 100644 org.lflang/src/org/lflang/validation/LFValidator.java diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java new file mode 100644 index 0000000000..f9b0c0f2ef --- /dev/null +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -0,0 +1,1344 @@ +/* Validation checks for Lingua Franca code. */ + +/************* + * Copyright (c) 2019-2020, The University of California at Berkeley. + + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ***************/ +package org.lflang.validation; + +import com.google.inject.Inject; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.common.util.TreeIterator; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.validation.Check; +import org.eclipse.xtext.validation.ValidationMessageAcceptor; +import org.eclipse.xtend.lib.annotations.Accessors; +import org.eclipse.xtend.lib.annotations.AccessorType; + +import org.lflang.FileConfig; +import org.lflang.ModelInfo; +import org.lflang.Target; +import org.lflang.TargetProperty; +import org.lflang.TimeValue; +import org.lflang.lf.Action; +import org.lflang.lf.ActionOrigin; +import org.lflang.lf.Assignment; +import org.lflang.lf.Connection; +import org.lflang.lf.Deadline; +import org.lflang.lf.Host; +import org.lflang.lf.IPV4Host; +import org.lflang.lf.IPV6Host; +import org.lflang.lf.Import; +import org.lflang.lf.ImportedReactor; +import org.lflang.lf.Input; +import org.lflang.lf.Instantiation; +import org.lflang.lf.KeyValuePair; +import org.lflang.lf.KeyValuePairs; +import org.lflang.lf.LfPackage.Literals; +import org.lflang.lf.Model; +import org.lflang.lf.NamedHost; +import org.lflang.lf.Output; +import org.lflang.lf.Parameter; +import org.lflang.lf.Port; +import org.lflang.lf.Preamble; +import org.lflang.lf.Reaction; +import org.lflang.lf.Reactor; +import org.lflang.lf.Serializer; +import org.lflang.lf.STP; +import org.lflang.lf.StateVar; +import org.lflang.lf.TargetDecl; +import org.lflang.lf.Timer; +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; +import org.lflang.lf.WidthSpec; +import org.lflang.lf.ReactorDecl; + +import static org.lflang.ASTUtils.*; +import static org.lflang.JavaAstUtils.*; +import org.lflang.federated.serialization.SupportedSerializers; + +/** + * Custom validation checks for Lingua Franca programs. + * + * Also see: https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#validation + * + * @author{Edward A. Lee } + * @author{Marten Lohstroh } + * @author{Matt Weber } + * @author(Christian Menard } + * + */ +class LFValidator extends BaseLFValidator { + + private Target target; + + public ModelInfo info = new ModelInfo(); + + @Accessors(AccessorType.PUBLIC_GETTER) + private ValidatorErrorReporter errorReporter = new ValidatorErrorReporter(getMessageAcceptor(), + new ValidatorStateAccess()); + + @Inject(optional = true) + ValidationMessageAcceptor messageAcceptor; + + /** + * Regular expression to check the validity of IPV4 addresses (due to David M. Syzdek). + */ + static String ipv4Regex = "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}" + + "(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"; + + /** + * Regular expression to check the validity of IPV6 addresses (due to David M. Syzdek), + * with minor adjustment to allow up to six IPV6 segments (without truncation) in front + * of an embedded IPv4-address. + **/ + static String ipv6Regex = + "(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" + + "([0-9a-fA-F]{1,4}:){1,7}:|" + + "([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" + + "([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" + + "([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" + + "([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" + + "([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" + + "[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" + + ":((:[0-9a-fA-F]{1,4}){1,7}|:)|" + + "fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|" + + "::(ffff(:0{1,4}){0,1}:){0,1}" + ipv4Regex + "|" + + "([0-9a-fA-F]{1,4}:){1,4}:" + ipv4Regex + "|" + + "([0-9a-fA-F]{1,4}:){1,6}" + ipv4Regex + ")"; + + static String usernameRegex = "^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\\$)$"; + + static String hostOrFQNRegex = "^([a-z0-9]+(-[a-z0-9]+)*)|(([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,})$"; + + public static String GLOBALLY_DUPLICATE_NAME = "GLOBALLY_DUPLICATE_NAME"; + + static List spacingViolationPolicies = List.of("defer", "drop", "replace"); + + private List targetPropertyErrors = new ArrayList<>(); + + private List targetPropertyWarnings = new ArrayList<>(); + + public List getTargetPropertyErrors() { + return this.targetPropertyErrors; + } + + @Override + public ValidationMessageAcceptor getMessageAcceptor() { + return messageAcceptor == null ? this : messageAcceptor; + } + + /** + * Returns true if target is C or a C-based target like CCpp. + */ + private boolean isCBasedTarget() { + return (this.target == Target.C || this.target == Target.CCPP); + } + + @Check + public void checkImportedReactor(ImportedReactor reactor) { + if (isUnused(reactor)) { + warning("Unused reactor class.", + Literals.IMPORTED_REACTOR__REACTOR_CLASS); + } + + if (info.instantiationGraph.hasCycles()) { + Set cycleSet = new HashSet<>(); + for (Set cycle : info.instantiationGraph.getCycles()) { + cycleSet.addAll(cycle); + } + if (dependsOnCycle(reactor.getReactorClass(), cycleSet, new HashSet<>())) { + error("Imported reactor '" + reactor.getReactorClass().getName() + + "' has cyclic instantiation in it.", Literals.IMPORTED_REACTOR__REACTOR_CLASS); + } + } + } + + @Check + public void checkImport(Import imp) { + if (imp.getReactorClasses().get(0).getReactorClass().eResource().getErrors().size() > 0) { + error("Error loading resource.", Literals.IMPORT__IMPORT_URI); // FIXME: print specifics. + return; + } + + // FIXME: report error if resource cannot be resolved. + for (ImportedReactor reactor : imp.getReactorClasses()) { + if (!isUnused(reactor)) { + return; + } + } + warning("Unused import.", Literals.IMPORT__IMPORT_URI); + } + + // ////////////////////////////////////////////////// + // // Helper functions for checks to be performed on multiple entities + // Check the name of a feature for illegal substrings. + private void checkName(String name, EStructuralFeature feature) { + + // Raises an error if the string starts with two underscores. + if (name.length() >= 2 && name.substring(0, 2).equals("__")) { + error(UNDERSCORE_MESSAGE + name, feature); + } + + if (this.target.isReservedIdent(name)) { + error(RESERVED_MESSAGE + name, feature); + } + + if (this.target == Target.TS) { + // "actions" is a reserved word within a TS reaction + if (name.equals("actions")) { + error(ACTIONS_MESSAGE + name, feature); + } + } + } + + /** + * Report whether a given reactor has dependencies on a cyclic + * instantiation pattern. This means the reactor has an instantiation + * in it -- directly or in one of its contained reactors -- that is + * self-referential. + * @param reactor The reactor definition to find out whether it has any + * dependencies on cyclic instantiations. + * @param cycleSet The set of all reactors that are part of an + * instantiation cycle. + * @param visited The set of nodes already visited in this graph traversal. + */ + private boolean dependsOnCycle(Reactor reactor, Set cycleSet, + Set visited) { + Set origins = info.instantiationGraph.getUpstreamAdjacentNodes(reactor); + if (visited.contains(reactor)) { + return false; + } else { + visited.add(reactor); + for (Reactor it : origins) { + if (cycleSet.contains(it) || dependsOnCycle(it, cycleSet, visited)) { + // Reached a cycle. + return true; + } + } + } + return false; + } + + /** + * Report whether a given imported reactor is used in this resource or not. + * @param reactor The imported reactor to check whether it is used. + */ + private boolean isUnused(ImportedReactor reactor) { + TreeIterator instantiations = reactor.eResource().getAllContents(); + TreeIterator subclasses = reactor.eResource().getAllContents(); + + boolean instantiationsCheck = true; + while (instantiations.hasNext() && instantiationsCheck) { + EObject obj = instantiations.next(); + if (!(obj instanceof Instantiation)) { + continue; + } + Instantiation inst = (Instantiation) obj; + instantiationsCheck &= (inst.getReactorClass() != reactor && inst.getReactorClass() != reactor.getReactorClass()); + } + + boolean subclassesCheck = true; + while (subclasses.hasNext() && subclassesCheck) { + EObject obj = subclasses.next(); + if (!(obj instanceof Reactor)) { + continue; + } + Reactor subclass = (Reactor) obj; + for (ReactorDecl decl : subclass.getSuperClasses()) { + subclassesCheck &= (decl != reactor && decl != reactor.getReactorClass()); + } + } + return instantiationsCheck && subclassesCheck; + } + + +// // ////////////////////////////////////////////////// +// // // Functions to set up data structures for performing checks. +// // FAST ensures that these checks run whenever a file is modified. +// // Alternatives are NORMAL (when saving) and EXPENSIVE (only when right-click, validate). + +// // ////////////////////////////////////////////////// +// // // The following checks are in alphabetical order. +// @Check(FAST) +// def checkAction(Action action) { +// checkName(action.name, Literals.VARIABLE__NAME) +// if (action.origin == ActionOrigin.NONE) { +// error( +// "Action must have modifier `logical` or `physical`.", +// Literals.ACTION__ORIGIN +// ) +// } +// if (action.policy !== null && +// !spacingViolationPolicies.contains(action.policy)) { +// error( +// "Unrecognized spacing violation policy: " + action.policy + +// ". Available policies are: " + +// spacingViolationPolicies.join(", ") + ".", +// Literals.ACTION__POLICY) +// } +// } + +// @Check(FAST) +// def checkAssignment(Assignment assignment) { +// // If the left-hand side is a time parameter, make sure the assignment has units +// if (assignment.lhs.isOfTimeType) { +// if (assignment.rhs.size > 1) { +// error("Incompatible type.", Literals.ASSIGNMENT__RHS) +// } else if (assignment.rhs.size > 0) { +// val v = assignment.rhs.get(0) +// if (!v.isValidTime) { +// if (v.parameter === 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.parameter.name + " to " + +// assignment.lhs.name + +// ". The latter is a time parameter, but the former is not.", +// Literals.ASSIGNMENT__RHS) +// } +// } +// } +// // If this assignment overrides a parameter that is used in a deadline, +// // report possible overflow. +// if (isCBasedTarget && +// this.info.overflowingAssignments.contains(assignment)) { +// error( +// "Time value used to specify a deadline exceeds the maximum of " + +// TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", +// Literals.ASSIGNMENT__RHS) +// } +// } + +// if(!assignment.braces.isNullOrEmpty() && this.target != Target.CPP) { +// error("Brace initializers are only supported for the C++ target", Literals.ASSIGNMENT__BRACES) +// } + +// // FIXME: lhs is list => rhs is list +// // lhs is fixed with size n => rhs is fixed with size n +// // FIXME": similar checks for decl/init +// // Specifically for C: list can only be literal or time lists +// } + +// @Check(FAST) +// def checkWidthSpec(WidthSpec widthSpec) { +// if (!this.target.supportsMultiports()) { +// error("Multiports and banks are currently not supported by the given target.", +// Literals.WIDTH_SPEC__TERMS) +// } else { +// for (term : widthSpec.terms) { +// if (term.parameter !== null) { +// if (!this.target.supportsParameterizedWidths()) { +// error("Parameterized widths are not supported by this target.", Literals.WIDTH_SPEC__TERMS) +// } +// } else if (term.port !== null) { +// // Widths given with `widthof()` are not supported (yet?). +// // This feature is currently only used for after delays. +// error("widthof is not supported.", Literals.WIDTH_SPEC__TERMS) +// } else if (term.code !== null) { +// if (this.target != Target.CPP) { +// error("This target does not support width given as code.", Literals.WIDTH_SPEC__TERMS) +// } +// } else if (term.width < 0) { +// error("Width must be a positive integer.", Literals.WIDTH_SPEC__TERMS) +// } +// } +// } +// } + +// @Check(FAST) +// def checkConnection(Connection connection) { + +// // Report if connection is part of a cycle. +// for (cycle : this.info.topologyCycles()) { +// for (lp : connection.leftPorts) { +// for (rp : connection.rightPorts) { +// var leftInCycle = false +// val reactorName = (connection.eContainer as Reactor).name + +// if ((lp.container === null && cycle.exists [ +// it.definition === lp.variable +// ]) || cycle.exists [ +// (it.definition === lp.variable && it.parent === lp.container) +// ]) { +// leftInCycle = true +// } + +// if ((rp.container === null && cycle.exists [ +// it.definition === rp.variable +// ]) || cycle.exists [ +// (it.definition === rp.variable && it.parent === rp.container) +// ]) { +// if (leftInCycle) { +// // Only report of _both_ reference ports are in the cycle. +// error('''Connection in reactor «reactorName» creates ''' + +// '''a cyclic dependency between «lp.toText» and ''' + +// '''«rp.toText».''', Literals.CONNECTION__DELAY +// ) +// } +// } +// } +// } +// } + +// // FIXME: look up all ReactorInstance objects that have a definition equal to the +// // container of this connection. For each of those occurrences, the widths have to match. +// // For the C target, since C has such a weak type system, check that +// // the types on both sides of every connection match. For other languages, +// // we leave type compatibility that language's compiler or interpreter. +// if (isCBasedTarget) { +// var type = null as Type +// for (port : connection.leftPorts) { +// // If the variable is not a port, then there is some other +// // error. Avoid a class cast exception. +// if (port.variable instanceof Port) { +// if (type === null) { +// type = (port.variable as Port).type +// } else { +// // Unfortunately, xtext does not generate a suitable equals() +// // method for AST types, so we have to manually check the types. +// if (!sameType(type, (port.variable as Port).type)) { +// error("Types do not match.", Literals.CONNECTION__LEFT_PORTS) +// } +// } +// } +// } +// for (port : connection.rightPorts) { +// // If the variable is not a port, then there is some other +// // error. Avoid a class cast exception. +// if (port.variable instanceof Port) { +// if (type === null) { +// type = (port.variable as Port).type +// } else { +// if (!sameType(type, (port.variable as Port).type)) { +// error("Types do not match.", Literals.CONNECTION__RIGHT_PORTS) +// } +// } +// } +// } +// } + +// // Check whether the total width of the left side of the connection +// // matches the total width of the right side. This cannot be determined +// // here if the width is not given as a constant. In that case, it is up +// // to the code generator to check it. +// var leftWidth = 0 +// for (port : connection.leftPorts) { +// val width = inferPortWidth(port, null, null) // null args imply incomplete check. +// if (width < 0 || leftWidth < 0) { +// // Cannot determine the width of the left ports. +// leftWidth = -1 +// } else { +// leftWidth += width +// } +// } +// var rightWidth = 0 +// for (port : connection.rightPorts) { +// val width = inferPortWidth(port, null, null) // null args imply incomplete check. +// if (width < 0 || rightWidth < 0) { +// // Cannot determine the width of the left ports. +// rightWidth = -1 +// } else { +// rightWidth += width +// } +// } + +// if (leftWidth !== -1 && rightWidth !== -1 && leftWidth != rightWidth) { +// if (connection.isIterated) { +// if (leftWidth == 0 || rightWidth % leftWidth != 0) { +// // FIXME: The second argument should be Literals.CONNECTION, but +// // stupidly, xtext will not accept that. There seems to be no way to +// // report an error for the whole connection statement. +// warning('''Left width «leftWidth» does not divide right width «rightWidth»''', +// Literals.CONNECTION__LEFT_PORTS +// ) +// } +// } else { +// // FIXME: The second argument should be Literals.CONNECTION, but +// // stupidly, xtext will not accept that. There seems to be no way to +// // report an error for the whole connection statement. +// warning('''Left width «leftWidth» does not match right width «rightWidth»''', +// Literals.CONNECTION__LEFT_PORTS +// ) +// } +// } + +// val reactor = connection.eContainer as Reactor + +// // Make sure the right port is not already an effect of a reaction. +// for (reaction : reactor.reactions) { +// for (effect : reaction.effects) { +// for (rightPort : connection.rightPorts) { +// if (rightPort.container === effect.container && +// rightPort.variable === effect.variable) { +// error("Cannot connect: Port named '" + effect.variable.name + +// "' is already effect of a reaction.", +// Literals.CONNECTION__RIGHT_PORTS +// ) +// } +// } +// } +// } + +// // Check that the right port does not already have some other +// // upstream connection. +// for (c : reactor.connections) { +// if (c !== connection) { +// for (thisRightPort : connection.rightPorts) { +// for (thatRightPort : c.rightPorts) { +// if (thisRightPort.container === thatRightPort.container && +// thisRightPort.variable === thatRightPort.variable) { +// error( +// "Cannot connect: Port named '" + thisRightPort.variable.name + +// "' may only appear once on the right side of a connection.", +// Literals.CONNECTION__RIGHT_PORTS) +// } +// } +// } +// } +// } +// } + +// /** +// * Return true if the two types match. Unfortunately, xtext does not +// * seem to create a suitable equals() method for Type, so we have to +// * do this manually. +// */ +// private def boolean sameType(Type type1, Type type2) { +// // Most common case first. +// if (type1.id !== null) { +// if (type1.stars !== null) { +// if (type2.stars === null) return false +// if (type1.stars.length != type2.stars.length) return false +// } +// return (type1.id.equals(type2.id)) +// } +// if (type1 === null) { +// if (type2 === null) return true +// return false +// } +// // Type specification in the grammar is: +// // (time?='time' (arraySpec=ArraySpec)?) | ((id=(DottedName) (stars+='*')* ('<' typeParms+=TypeParm (',' typeParms+=TypeParm)* '>')? (arraySpec=ArraySpec)?) | code=Code); +// if (type1.time) { +// if (!type2.time) return false +// // Ignore the arraySpec because that is checked when connection +// // is checked for balance. +// return true +// } +// // Type must be given in a code body. +// return (type1.code.body.equals(type2?.code?.body)) +// } + +// @Check(FAST) +// def checkDeadline(Deadline deadline) { +// if (isCBasedTarget && +// this.info.overflowingDeadlines.contains(deadline)) { +// error( +// "Deadline exceeds the maximum of " + +// TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", +// Literals.DEADLINE__DELAY) +// } +// } +// @Check(FAST) +// def checkSTPOffset(STP stp) { +// if (isCBasedTarget && +// this.info.overflowingDeadlines.contains(stp)) { +// error( +// "STP offset exceeds the maximum of " + +// TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", +// Literals.DEADLINE__DELAY) +// } +// } + +// @Check(FAST) +// def checkInput(Input input) { +// checkName(input.name, Literals.VARIABLE__NAME) +// if (target.requiresTypes) { +// if (input.type === null) { +// error("Input must have a type.", Literals.TYPED_VARIABLE__TYPE) +// } +// } + +// // mutable has no meaning in C++ +// if (input.mutable && this.target == Target.CPP) { +// warning( +// "The mutable qualifier has no meaning for the C++ target and should be removed. " + +// "In C++, any value can be made mutable by calling get_mutable_copy().", +// Literals.INPUT__MUTABLE +// ) +// } + +// // Variable width multiports are not supported (yet?). +// if (input.widthSpec !== null && input.widthSpec.ofVariableLength) { +// error("Variable-width multiports are not supported.", Literals.PORT__WIDTH_SPEC) +// } +// } + +// @Check(FAST) +// def checkInstantiation(Instantiation instantiation) { +// checkName(instantiation.name, Literals.INSTANTIATION__NAME) +// val reactor = instantiation.reactorClass.toDefinition +// if (reactor.isMain || reactor.isFederated) { +// error( +// "Cannot instantiate a main (or federated) reactor: " + +// instantiation.reactorClass.name, +// Literals.INSTANTIATION__REACTOR_CLASS +// ) +// } + +// // Report error if this instantiation is part of a cycle. +// // FIXME: improve error message. +// // FIXME: Also report if there exists a cycle within. +// if (this.info.instantiationGraph.cycles.size > 0) { +// for (cycle : this.info.instantiationGraph.cycles) { +// val container = instantiation.eContainer as Reactor +// if (cycle.contains(container) && cycle.contains(reactor)) { +// error( +// "Instantiation is part of a cycle: " + +// cycle.fold(newArrayList, [ list, r | +// list.add(r.name); +// list +// ]).join(', ') + ".", +// Literals.INSTANTIATION__REACTOR_CLASS +// ) +// } +// } +// } +// // Variable width multiports are not supported (yet?). +// if (instantiation.widthSpec !== null +// && instantiation.widthSpec.ofVariableLength +// ) { +// if (isCBasedTarget) { +// warning("Variable-width banks are for internal use only.", +// Literals.INSTANTIATION__WIDTH_SPEC +// ) +// } else { +// error("Variable-width banks are not supported.", +// Literals.INSTANTIATION__WIDTH_SPEC +// ) +// } +// } +// } + +// /** Check target parameters, which are key-value pairs. */ +// @Check(FAST) +// def checkKeyValuePair(KeyValuePair param) { +// // Check only if the container's container is a Target. +// if (param.eContainer.eContainer instanceof TargetDecl) { + +// val prop = TargetProperty.forName(param.name) + +// // Make sure the key is valid. +// if (prop === null) { +// warning( +// "Unrecognized target parameter: " + param.name + +// ". Recognized parameters are: " + +// TargetProperty.getOptions().join(", ") + ".", +// Literals.KEY_VALUE_PAIR__NAME) +// } + +// // Check whether the property is supported by the target. +// if (!prop.supportedBy.contains(this.target)) { +// warning( +// "The target parameter: " + param.name + +// " is not supported by the " + this.target + +// " target and will thus be ignored.", +// Literals.KEY_VALUE_PAIR__NAME) +// } + +// // Report problem with the assigned value. +// prop.type.check(param.value, param.name, this) +// targetPropertyErrors.forEach [ +// error(it, Literals.KEY_VALUE_PAIR__VALUE) +// ] +// targetPropertyErrors.clear() +// targetPropertyWarnings.forEach [ +// warning(it, Literals.KEY_VALUE_PAIR__VALUE) +// ] +// targetPropertyWarnings.clear() +// } +// } + +// @Check(FAST) +// def checkOutput(Output output) { +// checkName(output.name, Literals.VARIABLE__NAME) +// if (this.target.requiresTypes) { +// if (output.type === null) { +// error("Output must have a type.", Literals.TYPED_VARIABLE__TYPE) +// } +// } + +// // Variable width multiports are not supported (yet?). +// if (output.widthSpec !== null && output.widthSpec.ofVariableLength) { +// error("Variable-width multiports are not supported.", Literals.PORT__WIDTH_SPEC) +// } +// } + +// @Check(FAST) +// def checkModel(Model model) { +// // Since we're doing a fast check, we only want to update +// // if the model info hasn't been initialized yet. If it has, +// // we use the old information and update it during a normal +// // check (see below). +// if (!info.updated) { +// info.update(model, errorReporter) +// } +// } + +// @Check(NORMAL) +// def updateModelInfo(Model model) { +// info.update(model, errorReporter) +// } + +// @Check(FAST) +// def checkParameter(Parameter param) { +// checkName(param.name, Literals.PARAMETER__NAME) + +// if (param.init.exists[it.parameter !== null]) { +// // Initialization using parameters is forbidden. +// error("Parameter cannot be initialized using parameter.", +// Literals.PARAMETER__INIT) +// } + +// if (param.init === null || param.init.size == 0) { +// // All parameters must be initialized. +// error("Uninitialized parameter.", Literals.PARAMETER__INIT) +// } else if (param.isOfTimeType) { +// // We do additional checks on types because we can make stronger +// // assumptions about them. + +// // If the parameter is not a list, cannot be initialized +// // using a one. +// if (param.init.size > 1 && param.type.arraySpec === null) { +// error("Time parameter cannot be initialized using a list.", +// Literals.PARAMETER__INIT) +// } else { +// // The parameter is a singleton time. +// val init = param.init.get(0) +// if (init.time === null) { +// if (init !== null && !init.isZero) { +// if (init.isInteger) { +// error("Missing time unit.", Literals.PARAMETER__INIT) +// } else { +// error("Invalid time literal.", +// Literals.PARAMETER__INIT) +// } +// } +// } // If time is not null, we know that a unit is also specified. +// } +// } else if (this.target.requiresTypes) { +// // Report missing target type. +// if (param.inferredType.isUndefined()) { +// error("Type declaration missing.", Literals.PARAMETER__TYPE) +// } +// } + +// if (isCBasedTarget && +// this.info.overflowingParameters.contains(param)) { +// error( +// "Time value used to specify a deadline exceeds the maximum of " + +// TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", +// Literals.PARAMETER__INIT) +// } + +// if(!param.braces.isNullOrEmpty && this.target != Target.CPP) { +// error("Brace initializers are only supported for the C++ target", Literals.PARAMETER__BRACES) +// } + +// } + +// @Check(FAST) +// def checkPreamble(Preamble preamble) { +// if (this.target == Target.CPP) { +// if (preamble.visibility == Visibility.NONE) { +// error( +// "Preambles for the C++ target need a visibility qualifier (private or public)!", +// Literals.PREAMBLE__VISIBILITY +// ) +// } else if (preamble.visibility == Visibility.PRIVATE) { +// val container = preamble.eContainer +// if (container !== null && container instanceof Reactor) { +// val reactor = container as Reactor +// if (reactor.isGeneric) { +// warning( +// "Private preambles in generic reactors are not truly private. " + +// "Since the generated code is placed in a *_impl.hh file, it will " + +// "be visible on the public interface. Consider using a public " + +// "preamble within the reactor or a private preamble on file scope.", +// Literals.PREAMBLE__VISIBILITY) +// } +// } +// } +// } else if (preamble.visibility != Visibility.NONE) { +// warning( +// '''The «preamble.visibility» qualifier has no meaning for the «this.target.name» target. It should be removed.''', +// Literals.PREAMBLE__VISIBILITY +// ) +// } +// } + +// @Check(FAST) +// def checkReaction(Reaction reaction) { + +// if (reaction.triggers === null || reaction.triggers.size == 0) { +// warning("Reaction has no trigger.", Literals.REACTION__TRIGGERS) +// } +// val triggers = new HashSet +// // Make sure input triggers have no container and output sources do. +// for (trigger : reaction.triggers) { +// if (trigger instanceof VarRef) { +// triggers.add(trigger.variable) +// if (trigger.variable instanceof Input) { +// if (trigger.container !== null) { +// error('''Cannot have an input of a contained reactor as a trigger: «trigger.container.name».«trigger.variable.name»''', +// Literals.REACTION__TRIGGERS) +// } +// } else if (trigger.variable instanceof Output) { +// if (trigger.container === null) { +// error('''Cannot have an output of this reactor as a trigger: «trigger.variable.name»''', +// Literals.REACTION__TRIGGERS) +// } +// } +// } +// } + +// // Make sure input sources have no container and output sources do. +// // Also check that a source is not already listed as a trigger. +// for (source : reaction.sources) { +// if (triggers.contains(source.variable)) { +// error('''Source is already listed as a trigger: «source.variable.name»''', +// Literals.REACTION__SOURCES) +// } +// if (source.variable instanceof Input) { +// if (source.container !== null) { +// error('''Cannot have an input of a contained reactor as a source: «source.container.name».«source.variable.name»''', +// Literals.REACTION__SOURCES) +// } +// } else if (source.variable instanceof Output) { +// if (source.container === null) { +// error('''Cannot have an output of this reactor as a source: «source.variable.name»''', +// Literals.REACTION__SOURCES) +// } +// } +// } + +// // Make sure output effects have no container and input effects do. +// for (effect : reaction.effects) { +// if (effect.variable instanceof Input) { +// if (effect.container === null) { +// error('''Cannot have an input of this reactor as an effect: «effect.variable.name»''', +// Literals.REACTION__EFFECTS) +// } +// } else if (effect.variable instanceof Output) { +// if (effect.container !== null) { +// error('''Cannot have an output of a contained reactor as an effect: «effect.container.name».«effect.variable.name»''', +// Literals.REACTION__EFFECTS) +// } +// } +// } + +// // Report error if this reaction is part of a cycle. +// for (cycle : this.info.topologyCycles()) { +// val reactor = (reaction.eContainer) as Reactor +// if (cycle.exists[it.definition === reaction]) { +// // Report involved triggers. +// val trigs = new ArrayList() +// reaction.triggers.forEach [ t | +// (t instanceof VarRef && cycle.exists [ c | +// c.definition === (t as VarRef).variable +// ]) ? trigs.add((t as VarRef).toText) : { +// } +// ] +// if (trigs.size > 0) { +// error('''Reaction triggers involved in cyclic dependency in reactor «reactor.name»: «trigs.join(', ')».''', +// Literals.REACTION__TRIGGERS) +// } + +// // Report involved sources. +// val sources = new ArrayList() +// reaction.sources.forEach [ t | +// (cycle.exists[c|c.definition === t.variable]) +// ? sources.add(t.toText) +// : { +// } +// ] +// if (sources.size > 0) { +// error('''Reaction sources involved in cyclic dependency in reactor «reactor.name»: «sources.join(', ')».''', +// Literals.REACTION__SOURCES) +// } + +// // Report involved effects. +// val effects = new ArrayList() +// reaction.effects.forEach [ t | +// (cycle.exists[c|c.definition === t.variable]) +// ? effects.add(t.toText) +// : { +// } +// ] +// if (effects.size > 0) { +// error('''Reaction effects involved in cyclic dependency in reactor «reactor.name»: «effects.join(', ')».''', +// Literals.REACTION__EFFECTS) +// } + +// if (trigs.size + sources.size == 0) { +// error( +// '''Cyclic dependency due to preceding reaction. Consider reordering reactions within reactor «reactor.name» to avoid causality loop.''', +// reaction.eContainer, +// Literals.REACTOR__REACTIONS, +// reactor.reactions.indexOf(reaction)) +// } else if (effects.size == 0) { +// error( +// '''Cyclic dependency due to succeeding reaction. Consider reordering reactions within reactor «reactor.name» to avoid causality loop.''', +// reaction.eContainer, +// Literals.REACTOR__REACTIONS, +// reactor.reactions.indexOf(reaction)) +// } +// // Not reporting reactions that are part of cycle _only_ due to reaction ordering. +// // Moving them won't help solve the problem. +// } +// } +// // FIXME: improve error message. +// } + +// @Check(FAST) +// def checkReactor(Reactor reactor) { +// val name = FileConfig.nameWithoutExtension(reactor.eResource) +// if (reactor.name === null) { +// if (!reactor.isFederated && !reactor.isMain) { +// error( +// "Reactor must be named.", +// Literals.REACTOR_DECL__NAME +// ) +// } +// // Prevent NPE in tests below. +// return +// } else { +// if (reactor.isFederated || reactor.isMain) { +// if(!reactor.name.equals(name)) { +// // Make sure that if the name is omitted, the reactor is indeed main. +// error( +// "Name of main reactor must match the file name (or be omitted).", +// Literals.REACTOR_DECL__NAME +// ) +// } +// // Do not allow multiple main/federated reactors. +// if (reactor.eResource.allContents.filter(Reactor).filter[it.isMain || it.isFederated].size > 1) { +// var attribute = Literals.REACTOR__MAIN +// if (reactor.isFederated) { +// attribute = Literals.REACTOR__FEDERATED +// } +// if (reactor.isMain || reactor.isFederated) { +// error( +// "Multiple definitions of main or federated reactor.", +// attribute +// ) +// } +// } +// } else if (reactor.eResource.allContents.filter(Reactor).exists[it.isMain || it.isFederated] && reactor.name.equals(name)) { +// // Make sure that if a main reactor is specified, there are no +// // ordinary reactors that clash with it. +// error( +// "Name conflict with main reactor.", +// Literals.REACTOR_DECL__NAME +// ) +// } +// } + +// // If there is a main reactor (with no name) then disallow other (non-main) reactors +// // matching the file name. + +// checkName(reactor.name, Literals.REACTOR_DECL__NAME) + +// // C++ reactors may not be called 'preamble' +// if (this.target == Target.CPP && reactor.name.equalsIgnoreCase("preamble")) { +// error( +// "Reactor cannot be named '" + reactor.name + "'", +// Literals.REACTOR_DECL__NAME +// ) +// } + +// if (reactor.host !== null) { +// if (!reactor.isFederated) { +// error( +// "Cannot assign a host to reactor '" + reactor.name + +// "' because it is not federated.", +// Literals.REACTOR__HOST +// ) +// } +// } + +// var variables = new ArrayList() +// variables.addAll(reactor.inputs) +// variables.addAll(reactor.outputs) +// variables.addAll(reactor.actions) +// variables.addAll(reactor.timers) + +// // Perform checks on super classes. +// for (superClass : reactor.superClasses ?: emptyList) { +// var conflicts = new HashSet() + +// // Detect input conflicts +// checkConflict(superClass.toDefinition.inputs, reactor.inputs, variables, conflicts) +// // Detect output conflicts +// checkConflict(superClass.toDefinition.outputs, reactor.outputs, variables, conflicts) +// // Detect output conflicts +// checkConflict(superClass.toDefinition.actions, reactor.actions, variables, conflicts) +// // Detect conflicts +// for (timer : superClass.toDefinition.timers) { +// if (timer.hasNameConflict(variables.filter[it | !reactor.timers.contains(it)])) { +// conflicts.add(timer) +// } else { +// variables.add(timer) +// } +// } + +// // Report conflicts. +// if (conflicts.size > 0) { +// val names = new ArrayList(); +// conflicts.forEach[it | names.add(it.name)] +// error( +// '''Cannot extend «superClass.name» due to the following conflicts: «names.join(',')».''', +// Literals.REACTOR__SUPER_CLASSES +// ) +// } +// } +// } +// /** +// * For each input, report a conflict if: +// * 1) the input exists and the type doesn't match; or +// * 2) the input has a name clash with variable that is not an input. +// * @param superVars List of typed variables of a particular kind (i.e., +// * inputs, outputs, or actions), found in a super class. +// * @param sameKind Typed variables of the same kind, found in the subclass. +// * @param allOwn Accumulator of non-conflicting variables incorporated in the +// * subclass. +// * @param conflicts Set of variables that are in conflict, to be used by this +// * function to report conflicts. +// */ +// def checkConflict (EList superVars, +// EList sameKind, List allOwn, +// HashSet conflicts) { +// for (superVar : superVars) { +// val match = sameKind.findFirst [ it | +// it.name.equals(superVar.name) +// ] +// val rest = allOwn.filter[it|!sameKind.contains(it)] +// if ((match !== null && superVar.type !== match.type) || superVar.hasNameConflict(rest)) { +// conflicts.add(superVar) +// } else { +// allOwn.add(superVar) +// } +// } +// } + +// /** +// * Report whether the name of the given element matches any variable in +// * the ones to check against. +// * @param element The element to compare against all variables in the given iterable. +// * @param toCheckAgainst Iterable variables to compare the given element against. +// */ +// def boolean hasNameConflict(Variable element, +// Iterable toCheckAgainst) { +// if (toCheckAgainst.filter[it|it.name.equals(element.name)].size > 0) { +// return true +// } +// return false +// } + +// @Check(FAST) +// def checkHost(Host host) { +// val addr = host.addr +// val user = host.user +// if (user !== null && !user.matches(usernameRegex)) { +// warning( +// "Invalid user name.", +// Literals.HOST__USER +// ) +// } +// if (host instanceof IPV4Host && !addr.matches(ipv4Regex)) { +// warning( +// "Invalid IP address.", +// Literals.HOST__ADDR +// ) +// } else if (host instanceof IPV6Host && !addr.matches(ipv6Regex)) { +// warning( +// "Invalid IP address.", +// Literals.HOST__ADDR +// ) +// } else if (host instanceof NamedHost && !addr.matches(hostOrFQNRegex)) { +// warning( +// "Invalid host name or fully qualified domain name.", +// Literals.HOST__ADDR +// ) +// } +// } + +// /** +// * Check if the requested serialization is supported. +// */ +// @Check(FAST) +// def checkSerializer(Serializer serializer) { +// var boolean isValidSerializer = false; +// for (SupportedSerializers method : SupportedSerializers.values()) { +// if (method.name().equalsIgnoreCase(serializer.type)){ +// isValidSerializer = true; +// } +// } + +// if (!isValidSerializer) { +// error( +// "Serializer can be " + SupportedSerializers.values.toList, +// Literals.SERIALIZER__TYPE +// ); +// } +// } + +// @Check(FAST) +// def checkState(StateVar stateVar) { +// checkName(stateVar.name, Literals.STATE_VAR__NAME) + +// if (stateVar.isOfTimeType) { +// // If the state is declared to be a time, +// // make sure that it is initialized correctly. +// if (stateVar.init !== null) { +// for (init : stateVar.init) { +// if (stateVar.type !== null && stateVar.type.isTime && +// !init.isValidTime) { +// if (stateVar.isParameterized) { +// error( +// "Referenced parameter does not denote a time.", +// Literals.STATE_VAR__INIT) +// } else { +// if (init !== null && !init.isZero) { +// if (init.isInteger) { +// error( +// "Missing time unit.", Literals.STATE_VAR__INIT) +// } else { +// error("Invalid time literal.", +// Literals.STATE_VAR__INIT) +// } +// } +// } +// } +// } +// } +// } else if (this.target.requiresTypes && stateVar.inferredType.isUndefined) { +// // Report if a type is missing +// error("State must have a type.", Literals.STATE_VAR__TYPE) +// } + +// if (isCBasedTarget && stateVar.init.size > 1) { +// // In C, if initialization is done with a list, elements cannot +// // refer to parameters. +// if (stateVar.init.exists[it.parameter !== null]) { +// error("List items cannot refer to a parameter.", +// Literals.STATE_VAR__INIT) +// } +// } + +// if(!stateVar.braces.isNullOrEmpty && this.target != Target.CPP) { +// error("Brace initializers are only supported for the C++ target", Literals.STATE_VAR__BRACES) +// } +// } + +// @Check(FAST) +// def checkTargetDecl(TargetDecl target) { +// val targetOpt = Target.forName(target.name); +// if (targetOpt.isEmpty()) { +// error("Unrecognized target: " + target.name, +// Literals.TARGET_DECL__NAME) +// } else { +// this.target = targetOpt.get(); +// } +// } + +// /** +// * Check for consistency of the target properties, which are +// * defined as KeyValuePairs. +// * +// * @param targetProperties The target properties defined +// * in the current Lingua Franca program. +// */ +// @Check(EXPENSIVE) +// def checkTargetProperties(KeyValuePairs targetProperties) { + +// val fastTargetProperties = targetProperties.pairs.filter( +// pair | +// // Check to see if fast is defined +// TargetProperty.forName(pair.name) == TargetProperty.FAST +// ) + +// val fastTargetProperty = fastTargetProperties.findFirst[t | true]; + +// if (fastTargetProperty !== null) { +// // Check for federated +// if (info.model.reactors.exists( +// reactor | +// // Check to see if the program has a federated reactor +// reactor.isFederated +// )) { +// error( +// "The fast target property is incompatible with federated programs.", +// fastTargetProperty, +// Literals.KEY_VALUE_PAIR__NAME +// ) +// } + +// // Check for physical actions +// if (info.model.reactors.exists( +// reactor | +// // Check to see if the program has a physical action in a reactor +// reactor.actions.exists(action|(action.origin == ActionOrigin.PHYSICAL)) +// )) { +// error( +// "The fast target property is incompatible with physical actions.", +// fastTargetProperty, +// Literals.KEY_VALUE_PAIR__NAME +// ) +// } + +// } + +// val clockSyncTargetProperties = targetProperties.pairs.filter( +// pair | +// // Check to see if clock-sync is defined +// TargetProperty.forName(pair.name) == TargetProperty.CLOCK_SYNC +// ) + +// val clockSyncTargetProperty = clockSyncTargetProperties.findFirst[t | true]; +// if (clockSyncTargetProperty !== null) { +// if (info.model.reactors.exists( +// reactor | +// // Check to see if the program has a federated reactor defined. +// reactor.isFederated +// ) == false) { +// warning( +// "The clock-sync target property is incompatible with non-federated programs.", +// clockSyncTargetProperty, +// Literals.KEY_VALUE_PAIR__NAME +// ) +// } +// } +// } + +// @Check(FAST) +// def checkValueAsTime(Value value) { +// val 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.parameter !== null) { +// if (!value.parameter.isOfTimeType && target.requiresTypes === true) { +// error("Parameter is not of time type", +// Literals.VALUE__PARAMETER) +// } +// } else if (value.time === null) { +// if (value.literal !== null && !value.literal.isZero) { +// if (value.literal.isInteger) { +// error("Missing time unit.", Literals.VALUE__LITERAL) +// } else { +// error("Invalid time literal.", +// Literals.VALUE__LITERAL) +// } +// } else if (value.code !== null) { +// error("Invalid time literal.", Literals.VALUE__CODE) +// } +// } +// } +// } + +// @Check(FAST) +// def checkTimer(Timer timer) { +// checkName(timer.name, Literals.VARIABLE__NAME) +// } + +// @Check(FAST) +// def checkType(Type type) { +// // FIXME: disallow the use of generics in C +// if (this.target == Target.CPP) { +// if (type.stars.size > 0) { +// warning( +// "Raw pointers should be avoided in conjunction with LF. Ports " + +// "and actions implicitly use smart pointers. In this case, " + +// "the pointer here is likely not needed. For parameters and state " + +// "smart pointers should be used explicitly if pointer semantics " + +// "are really needed.", +// Literals.TYPE__STARS +// ) +// } +// } +// else if (this.target == Target.Python) { +// if (type !== null) { +// error( +// "Types are not allowed in the Python target", +// Literals.TYPE__ID +// ) +// } +// } +// } + +// @Check(FAST) +// def checkVarRef(VarRef varRef) { +// // check correct usage of interleaved +// if (varRef.isInterleaved) { +// if (this.target != Target.CPP && !isCBasedTarget && this.target != Target.Python) { +// error("This target does not support interleaved port references.", Literals.VAR_REF__INTERLEAVED) +// } +// if (!(varRef.eContainer instanceof Connection)) { +// error("interleaved can only be used in connections.", Literals.VAR_REF__INTERLEAVED) +// } + +// if (varRef.variable instanceof Port) { +// // This test only works correctly if the variable is actually a port. If it is not a port, other +// // validator rules will produce error messages. +// if (varRef.container === null || varRef.container.widthSpec === null || +// (varRef.variable as Port).widthSpec === null +// ) { +// error("interleaved can only be used for multiports contained within banks.", Literals.VAR_REF__INTERLEAVED) +// } +// } +// } +// } + + static String UNDERSCORE_MESSAGE = "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": "; + static String ACTIONS_MESSAGE = "\"actions\" is a reserved word for the TypeScript target for objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation): "; + static String RESERVED_MESSAGE = "Reserved words in the target language are not allowed for objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation): "; + +} From a54d8911d3f87d92159e862830928a63ae1642dd Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Tue, 25 Jan 2022 11:23:48 -0800 Subject: [PATCH 02/23] port up to sameType --- .../org/lflang/validation/LFValidator.java | 559 +++++++++--------- 1 file changed, 270 insertions(+), 289 deletions(-) diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index f9b0c0f2ef..99a487f7e2 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -38,6 +38,7 @@ import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.validation.Check; +import org.eclipse.xtext.validation.CheckType; import org.eclipse.xtext.validation.ValidationMessageAcceptor; import org.eclipse.xtend.lib.annotations.Accessors; import org.eclipse.xtend.lib.annotations.AccessorType; @@ -82,7 +83,10 @@ import org.lflang.lf.Variable; import org.lflang.lf.Visibility; import org.lflang.lf.WidthSpec; +import org.lflang.lf.WidthTerm; import org.lflang.lf.ReactorDecl; +import org.lflang.generator.ReactorInstance; +import org.lflang.generator.NamedInstance; import static org.lflang.ASTUtils.*; import static org.lflang.JavaAstUtils.*; @@ -284,287 +288,264 @@ private boolean isUnused(ImportedReactor reactor) { } -// // ////////////////////////////////////////////////// -// // // Functions to set up data structures for performing checks. -// // FAST ensures that these checks run whenever a file is modified. -// // Alternatives are NORMAL (when saving) and EXPENSIVE (only when right-click, validate). - -// // ////////////////////////////////////////////////// -// // // The following checks are in alphabetical order. -// @Check(FAST) -// def checkAction(Action action) { -// checkName(action.name, Literals.VARIABLE__NAME) -// if (action.origin == ActionOrigin.NONE) { -// error( -// "Action must have modifier `logical` or `physical`.", -// Literals.ACTION__ORIGIN -// ) -// } -// if (action.policy !== null && -// !spacingViolationPolicies.contains(action.policy)) { -// error( -// "Unrecognized spacing violation policy: " + action.policy + -// ". Available policies are: " + -// spacingViolationPolicies.join(", ") + ".", -// Literals.ACTION__POLICY) -// } -// } + // ////////////////////////////////////////////////// + // // Functions to set up data structures for performing checks. + // FAST ensures that these checks run whenever a file is modified. + // Alternatives are NORMAL (when saving) and EXPENSIVE (only when right-click, validate). -// @Check(FAST) -// def checkAssignment(Assignment assignment) { -// // If the left-hand side is a time parameter, make sure the assignment has units -// if (assignment.lhs.isOfTimeType) { -// if (assignment.rhs.size > 1) { -// error("Incompatible type.", Literals.ASSIGNMENT__RHS) -// } else if (assignment.rhs.size > 0) { -// val v = assignment.rhs.get(0) -// if (!v.isValidTime) { -// if (v.parameter === 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.parameter.name + " to " + -// assignment.lhs.name + -// ". The latter is a time parameter, but the former is not.", -// Literals.ASSIGNMENT__RHS) -// } -// } -// } -// // If this assignment overrides a parameter that is used in a deadline, -// // report possible overflow. -// if (isCBasedTarget && -// this.info.overflowingAssignments.contains(assignment)) { -// error( -// "Time value used to specify a deadline exceeds the maximum of " + -// TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", -// Literals.ASSIGNMENT__RHS) -// } -// } + // ////////////////////////////////////////////////// + // // The following checks are in alphabetical order. + @Check(CheckType.FAST) + public void checkAction(Action action) { + checkName(action.getName(), Literals.VARIABLE__NAME); + if (action.getOrigin() == ActionOrigin.NONE) { + error( + "Action must have modifier `logical` or `physical`.", + Literals.ACTION__ORIGIN + ); + } + if (action.getPolicy() != null && + !spacingViolationPolicies.contains(action.getPolicy())) { + error( + "Unrecognized spacing violation policy: " + action.getPolicy() + + ". Available policies are: " + + String.join(", ", spacingViolationPolicies) + ".", + Literals.ACTION__POLICY); + } + } -// if(!assignment.braces.isNullOrEmpty() && this.target != Target.CPP) { -// error("Brace initializers are only supported for the C++ target", Literals.ASSIGNMENT__BRACES) -// } + @Check(CheckType.FAST) + 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); + } 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); + } + } + } + // If this assignment overrides a parameter that is used in a deadline, + // report possible overflow. + if (isCBasedTarget() && + this.info.overflowingAssignments.contains(assignment)) { + error( + "Time value used to specify a deadline exceeds the maximum of " + + TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", + Literals.ASSIGNMENT__RHS); + } + } -// // FIXME: lhs is list => rhs is list -// // lhs is fixed with size n => rhs is fixed with size n -// // FIXME": similar checks for decl/init -// // Specifically for C: list can only be literal or time lists -// } + EList braces = assignment.getBraces(); + if(!(braces == null || braces.isEmpty()) && this.target != Target.CPP) { + error("Brace initializers are only supported for the C++ target", Literals.ASSIGNMENT__BRACES); + } -// @Check(FAST) -// def checkWidthSpec(WidthSpec widthSpec) { -// if (!this.target.supportsMultiports()) { -// error("Multiports and banks are currently not supported by the given target.", -// Literals.WIDTH_SPEC__TERMS) -// } else { -// for (term : widthSpec.terms) { -// if (term.parameter !== null) { -// if (!this.target.supportsParameterizedWidths()) { -// error("Parameterized widths are not supported by this target.", Literals.WIDTH_SPEC__TERMS) -// } -// } else if (term.port !== null) { -// // Widths given with `widthof()` are not supported (yet?). -// // This feature is currently only used for after delays. -// error("widthof is not supported.", Literals.WIDTH_SPEC__TERMS) -// } else if (term.code !== null) { -// if (this.target != Target.CPP) { -// error("This target does not support width given as code.", Literals.WIDTH_SPEC__TERMS) -// } -// } else if (term.width < 0) { -// error("Width must be a positive integer.", Literals.WIDTH_SPEC__TERMS) -// } -// } -// } -// } + // FIXME: lhs is list => rhs is list + // lhs is fixed with size n => rhs is fixed with size n + // FIXME": similar checks for decl/init + // Specifically for C: list can only be literal or time lists + } -// @Check(FAST) -// def checkConnection(Connection connection) { + @Check(CheckType.FAST) + public void checkConnection(Connection connection) { + + // Report if connection is part of a cycle. + Set> cycles = this.info.topologyCycles(); + for (VarRef lp : connection.getLeftPorts()) { + for (VarRef rp : connection.getRightPorts()) { + boolean leftInCycle = false; + + for (NamedInstance it : cycles) { + if ((lp.getContainer() == null && it.getDefinition() == lp.getVariable()) + || (it.getDefinition() == lp.getVariable() && it.getParent() == lp.getContainer())) { + leftInCycle = true; + break; + } + } -// // Report if connection is part of a cycle. -// for (cycle : this.info.topologyCycles()) { -// for (lp : connection.leftPorts) { -// for (rp : connection.rightPorts) { -// var leftInCycle = false -// val reactorName = (connection.eContainer as Reactor).name - -// if ((lp.container === null && cycle.exists [ -// it.definition === lp.variable -// ]) || cycle.exists [ -// (it.definition === lp.variable && it.parent === lp.container) -// ]) { -// leftInCycle = true -// } + for (NamedInstance it : cycles) { + if ((rp.getContainer() == null && it.getDefinition() == rp.getVariable()) + || (it.getDefinition() == rp.getVariable() && it.getParent() == rp.getContainer())) { + if (leftInCycle) { + String reactorName = ((Reactor) connection.eContainer()).getName(); + error(String.format("Connection in reactor %s creates", reactorName) + + String.format("a cyclic dependency between %s and %s.", lp.toString(), rp.toString()), + Literals.CONNECTION__DELAY); + } + } + } + } + } -// if ((rp.container === null && cycle.exists [ -// it.definition === rp.variable -// ]) || cycle.exists [ -// (it.definition === rp.variable && it.parent === rp.container) -// ]) { -// if (leftInCycle) { -// // Only report of _both_ reference ports are in the cycle. -// error('''Connection in reactor «reactorName» creates ''' + -// '''a cyclic dependency between «lp.toText» and ''' + -// '''«rp.toText».''', Literals.CONNECTION__DELAY -// ) -// } -// } -// } -// } -// } + // FIXME: look up all ReactorInstance objects that have a definition equal to the + // container of this connection. For each of those occurrences, the widths have to match. + // For the C target, since C has such a weak type system, check that + // the types on both sides of every connection match. For other languages, + // we leave type compatibility that language's compiler or interpreter. + if (isCBasedTarget()) { + Type type = (Type) null; + for (VarRef port : connection.getLeftPorts()) { + // If the variable is not a port, then there is some other + // error. Avoid a class cast exception. + if (port.getVariable() instanceof Port) { + if (type == null) { + type = ((Port) port.getVariable()).getType(); + } else { + // Unfortunately, xtext does not generate a suitable equals() + // method for AST types, so we have to manually check the types. + if (!sameType(type, ((Port) port.getVariable()).getType())) { + error("Types do not match.", Literals.CONNECTION__LEFT_PORTS); + } + } + } + } + for (VarRef port : connection.getRightPorts()) { + // If the variable is not a port, then there is some other + // error. Avoid a class cast exception. + if (port.getVariable() instanceof Port) { + if (type == null) { + type = ((Port) port.getVariable()).getType(); + } else { + if (!sameType(type, type = ((Port) port.getVariable()).getType())) { + error("Types do not match.", Literals.CONNECTION__RIGHT_PORTS); + } + } + } + } + } -// // FIXME: look up all ReactorInstance objects that have a definition equal to the -// // container of this connection. For each of those occurrences, the widths have to match. -// // For the C target, since C has such a weak type system, check that -// // the types on both sides of every connection match. For other languages, -// // we leave type compatibility that language's compiler or interpreter. -// if (isCBasedTarget) { -// var type = null as Type -// for (port : connection.leftPorts) { -// // If the variable is not a port, then there is some other -// // error. Avoid a class cast exception. -// if (port.variable instanceof Port) { -// if (type === null) { -// type = (port.variable as Port).type -// } else { -// // Unfortunately, xtext does not generate a suitable equals() -// // method for AST types, so we have to manually check the types. -// if (!sameType(type, (port.variable as Port).type)) { -// error("Types do not match.", Literals.CONNECTION__LEFT_PORTS) -// } -// } -// } -// } -// for (port : connection.rightPorts) { -// // If the variable is not a port, then there is some other -// // error. Avoid a class cast exception. -// if (port.variable instanceof Port) { -// if (type === null) { -// type = (port.variable as Port).type -// } else { -// if (!sameType(type, (port.variable as Port).type)) { -// error("Types do not match.", Literals.CONNECTION__RIGHT_PORTS) -// } -// } -// } -// } -// } + // Check whether the total width of the left side of the connection + // matches the total width of the right side. This cannot be determined + // here if the width is not given as a constant. In that case, it is up + // to the code generator to check it. + int leftWidth = 0; + for (VarRef port : connection.getLeftPorts()) { + int width = inferPortWidth(port, null, null); // null args imply incomplete check. + if (width < 0 || leftWidth < 0) { + // Cannot determine the width of the left ports. + leftWidth = -1; + } else { + leftWidth += width; + } + } + int rightWidth = 0; + for (VarRef port : connection.getRightPorts()) { + int width = inferPortWidth(port, null, null); // null args imply incomplete check. + if (width < 0 || rightWidth < 0) { + // Cannot determine the width of the left ports. + rightWidth = -1; + } else { + rightWidth += width; + } + } -// // Check whether the total width of the left side of the connection -// // matches the total width of the right side. This cannot be determined -// // here if the width is not given as a constant. In that case, it is up -// // to the code generator to check it. -// var leftWidth = 0 -// for (port : connection.leftPorts) { -// val width = inferPortWidth(port, null, null) // null args imply incomplete check. -// if (width < 0 || leftWidth < 0) { -// // Cannot determine the width of the left ports. -// leftWidth = -1 -// } else { -// leftWidth += width -// } -// } -// var rightWidth = 0 -// for (port : connection.rightPorts) { -// val width = inferPortWidth(port, null, null) // null args imply incomplete check. -// if (width < 0 || rightWidth < 0) { -// // Cannot determine the width of the left ports. -// rightWidth = -1 -// } else { -// rightWidth += width -// } -// } + if (leftWidth != -1 && rightWidth != -1 && leftWidth != rightWidth) { + if (connection.isIterated()) { + if (leftWidth == 0 || rightWidth % leftWidth != 0) { + // FIXME: The second argument should be Literals.CONNECTION, but + // stupidly, xtext will not accept that. There seems to be no way to + // report an error for the whole connection statement. + warning(String.format("Left width %s does not divide right width %s", leftWidth, rightWidth), + Literals.CONNECTION__LEFT_PORTS + ); + } + } else { + // FIXME: The second argument should be Literals.CONNECTION, but + // stupidly, xtext will not accept that. There seems to be no way to + // report an error for the whole connection statement. + warning(String.format("Left width %s does not match right width %s", leftWidth, rightWidth), + Literals.CONNECTION__LEFT_PORTS + ); + } + } -// if (leftWidth !== -1 && rightWidth !== -1 && leftWidth != rightWidth) { -// if (connection.isIterated) { -// if (leftWidth == 0 || rightWidth % leftWidth != 0) { -// // FIXME: The second argument should be Literals.CONNECTION, but -// // stupidly, xtext will not accept that. There seems to be no way to -// // report an error for the whole connection statement. -// warning('''Left width «leftWidth» does not divide right width «rightWidth»''', -// Literals.CONNECTION__LEFT_PORTS -// ) -// } -// } else { -// // FIXME: The second argument should be Literals.CONNECTION, but -// // stupidly, xtext will not accept that. There seems to be no way to -// // report an error for the whole connection statement. -// warning('''Left width «leftWidth» does not match right width «rightWidth»''', -// Literals.CONNECTION__LEFT_PORTS -// ) -// } -// } + Reactor reactor = (Reactor) connection.eContainer(); + + // Make sure the right port is not already an effect of a reaction. + for (Reaction reaction : reactor.getReactions()) { + for (VarRef effect : reaction.getEffects()) { + for (VarRef rightPort : connection.getRightPorts()) { + if (rightPort.getContainer() == effect.getContainer() && + rightPort.getVariable() == effect.getVariable()) { + error("Cannot connect: Port named '" + effect.getVariable().getName() + + "' is already effect of a reaction.", + Literals.CONNECTION__RIGHT_PORTS + ); + } + } + } + } -// val reactor = connection.eContainer as Reactor - -// // Make sure the right port is not already an effect of a reaction. -// for (reaction : reactor.reactions) { -// for (effect : reaction.effects) { -// for (rightPort : connection.rightPorts) { -// if (rightPort.container === effect.container && -// rightPort.variable === effect.variable) { -// error("Cannot connect: Port named '" + effect.variable.name + -// "' is already effect of a reaction.", -// Literals.CONNECTION__RIGHT_PORTS -// ) -// } -// } -// } -// } + // Check that the right port does not already have some other + // upstream connection. + for (Connection c : reactor.getConnections()) { + if (c != connection) { + for (VarRef thisRightPort : connection.getRightPorts()) { + for (VarRef thatRightPort : c.getRightPorts()) { + if (thisRightPort.getContainer() == thatRightPort.getContainer() && + thisRightPort.getVariable() == thatRightPort.getVariable()) { + error( + "Cannot connect: Port named '" + thisRightPort.getVariable().getName() + + "' may only appear once on the right side of a connection.", + Literals.CONNECTION__RIGHT_PORTS); + } + } + } + } + } + } -// // Check that the right port does not already have some other -// // upstream connection. -// for (c : reactor.connections) { -// if (c !== connection) { -// for (thisRightPort : connection.rightPorts) { -// for (thatRightPort : c.rightPorts) { -// if (thisRightPort.container === thatRightPort.container && -// thisRightPort.variable === thatRightPort.variable) { -// error( -// "Cannot connect: Port named '" + thisRightPort.variable.name + -// "' may only appear once on the right side of a connection.", -// Literals.CONNECTION__RIGHT_PORTS) -// } -// } -// } -// } -// } -// } + /** + * Return true if the two types match. Unfortunately, xtext does not + * seem to create a suitable equals() method for Type, so we have to + * do this manually. + */ + private boolean sameType(Type type1, Type type2) { + if (type1 == null) { + return type2 == null; + } + if (type2 == null) { + return type1 == null; + } + // Most common case first. + if (type1.getId() != null) { + if (type1.getStars() != null) { + if (type2.getStars() == null) return false; + if (type1.getStars().size() != type2.getStars().size()) return false; + } + return (type1.getId().equals(type2.getId())); + } -// /** -// * Return true if the two types match. Unfortunately, xtext does not -// * seem to create a suitable equals() method for Type, so we have to -// * do this manually. -// */ -// private def boolean sameType(Type type1, Type type2) { -// // Most common case first. -// if (type1.id !== null) { -// if (type1.stars !== null) { -// if (type2.stars === null) return false -// if (type1.stars.length != type2.stars.length) return false -// } -// return (type1.id.equals(type2.id)) -// } -// if (type1 === null) { -// if (type2 === null) return true -// return false -// } -// // Type specification in the grammar is: -// // (time?='time' (arraySpec=ArraySpec)?) | ((id=(DottedName) (stars+='*')* ('<' typeParms+=TypeParm (',' typeParms+=TypeParm)* '>')? (arraySpec=ArraySpec)?) | code=Code); -// if (type1.time) { -// if (!type2.time) return false -// // Ignore the arraySpec because that is checked when connection -// // is checked for balance. -// return true -// } -// // Type must be given in a code body. -// return (type1.code.body.equals(type2?.code?.body)) -// } + // Type specification in the grammar is: + // (time?='time' (arraySpec=ArraySpec)?) | ((id=(DottedName) (stars+='*')* ('<' typeParms+=TypeParm (',' typeParms+=TypeParm)* '>')? (arraySpec=ArraySpec)?) | code=Code); + if (type1.isTime()) { + if (!type2.isTime()) return false; + // Ignore the arraySpec because that is checked when connection + // is checked for balance. + return true; + } + // Type must be given in a code body + if (type2.getCode() != null) { + return type1.getCode().getBody().equals(type2.getCode().getBody()); + } + return false; + } -// @Check(FAST) +// @Check(CheckType.FAST) // def checkDeadline(Deadline deadline) { // if (isCBasedTarget && // this.info.overflowingDeadlines.contains(deadline)) { @@ -574,7 +555,7 @@ private boolean isUnused(ImportedReactor reactor) { // Literals.DEADLINE__DELAY) // } // } -// @Check(FAST) +// @Check(CheckType.FAST) // def checkSTPOffset(STP stp) { // if (isCBasedTarget && // this.info.overflowingDeadlines.contains(stp)) { @@ -585,7 +566,7 @@ private boolean isUnused(ImportedReactor reactor) { // } // } -// @Check(FAST) +// @Check(CheckType.FAST) // def checkInput(Input input) { // checkName(input.name, Literals.VARIABLE__NAME) // if (target.requiresTypes) { @@ -609,7 +590,7 @@ private boolean isUnused(ImportedReactor reactor) { // } // } -// @Check(FAST) +// @Check(CheckType.FAST) // def checkInstantiation(Instantiation instantiation) { // checkName(instantiation.name, Literals.INSTANTIATION__NAME) // val reactor = instantiation.reactorClass.toDefinition @@ -656,7 +637,7 @@ private boolean isUnused(ImportedReactor reactor) { // } // /** Check target parameters, which are key-value pairs. */ -// @Check(FAST) +// @Check(CheckType.FAST) // def checkKeyValuePair(KeyValuePair param) { // // Check only if the container's container is a Target. // if (param.eContainer.eContainer instanceof TargetDecl) { @@ -694,7 +675,7 @@ private boolean isUnused(ImportedReactor reactor) { // } // } -// @Check(FAST) +// @Check(CheckType.FAST) // def checkOutput(Output output) { // checkName(output.name, Literals.VARIABLE__NAME) // if (this.target.requiresTypes) { @@ -709,7 +690,7 @@ private boolean isUnused(ImportedReactor reactor) { // } // } -// @Check(FAST) +// @Check(CheckType.FAST) // def checkModel(Model model) { // // Since we're doing a fast check, we only want to update // // if the model info hasn't been initialized yet. If it has, @@ -725,11 +706,11 @@ private boolean isUnused(ImportedReactor reactor) { // info.update(model, errorReporter) // } -// @Check(FAST) +// @Check(CheckType.FAST) // def checkParameter(Parameter param) { // checkName(param.name, Literals.PARAMETER__NAME) -// if (param.init.exists[it.parameter !== null]) { +// if (param.init.exists[it.getParameter() !== null]) { // // Initialization using parameters is forbidden. // error("Parameter cannot be initialized using parameter.", // Literals.PARAMETER__INIT) @@ -782,7 +763,7 @@ private boolean isUnused(ImportedReactor reactor) { // } -// @Check(FAST) +// @Check(CheckType.FAST) // def checkPreamble(Preamble preamble) { // if (this.target == Target.CPP) { // if (preamble.visibility == Visibility.NONE) { @@ -812,7 +793,7 @@ private boolean isUnused(ImportedReactor reactor) { // } // } -// @Check(FAST) +// @Check(CheckType.FAST) // def checkReaction(Reaction reaction) { // if (reaction.triggers === null || reaction.triggers.size == 0) { @@ -935,7 +916,7 @@ private boolean isUnused(ImportedReactor reactor) { // // FIXME: improve error message. // } -// @Check(FAST) +// @Check(CheckType.FAST) // def checkReactor(Reactor reactor) { // val name = FileConfig.nameWithoutExtension(reactor.eResource) // if (reactor.name === null) { @@ -1080,7 +1061,7 @@ private boolean isUnused(ImportedReactor reactor) { // return false // } -// @Check(FAST) +// @Check(CheckType.FAST) // def checkHost(Host host) { // val addr = host.addr // val user = host.user @@ -1111,7 +1092,7 @@ private boolean isUnused(ImportedReactor reactor) { // /** // * Check if the requested serialization is supported. // */ -// @Check(FAST) +// @Check(CheckType.FAST) // def checkSerializer(Serializer serializer) { // var boolean isValidSerializer = false; // for (SupportedSerializers method : SupportedSerializers.values()) { @@ -1128,7 +1109,7 @@ private boolean isUnused(ImportedReactor reactor) { // } // } -// @Check(FAST) +// @Check(CheckType.FAST) // def checkState(StateVar stateVar) { // checkName(stateVar.name, Literals.STATE_VAR__NAME) @@ -1165,7 +1146,7 @@ private boolean isUnused(ImportedReactor reactor) { // if (isCBasedTarget && stateVar.init.size > 1) { // // In C, if initialization is done with a list, elements cannot // // refer to parameters. -// if (stateVar.init.exists[it.parameter !== null]) { +// if (stateVar.init.exists[it.getParameter() !== null]) { // error("List items cannot refer to a parameter.", // Literals.STATE_VAR__INIT) // } @@ -1176,7 +1157,7 @@ private boolean isUnused(ImportedReactor reactor) { // } // } -// @Check(FAST) +// @Check(CheckType.FAST) // def checkTargetDecl(TargetDecl target) { // val targetOpt = Target.forName(target.name); // if (targetOpt.isEmpty()) { @@ -1256,7 +1237,7 @@ private boolean isUnused(ImportedReactor reactor) { // } // } -// @Check(FAST) +// @Check(CheckType.FAST) // def checkValueAsTime(Value value) { // val container = value.eContainer @@ -1264,8 +1245,8 @@ private boolean isUnused(ImportedReactor reactor) { // container instanceof Connection || container instanceof Deadline) { // // If parameter is referenced, check that it is of the correct type. -// if (value.parameter !== null) { -// if (!value.parameter.isOfTimeType && target.requiresTypes === true) { +// if (value.getParameter() !== null) { +// if (!value.getParameter().isOfTimeType && target.requiresTypes === true) { // error("Parameter is not of time type", // Literals.VALUE__PARAMETER) // } @@ -1284,12 +1265,12 @@ private boolean isUnused(ImportedReactor reactor) { // } // } -// @Check(FAST) +// @Check(CheckType.FAST) // def checkTimer(Timer timer) { // checkName(timer.name, Literals.VARIABLE__NAME) // } -// @Check(FAST) +// @Check(CheckType.FAST) // def checkType(Type type) { // // FIXME: disallow the use of generics in C // if (this.target == Target.CPP) { @@ -1314,7 +1295,7 @@ private boolean isUnused(ImportedReactor reactor) { // } // } -// @Check(FAST) +// @Check(CheckType.FAST) // def checkVarRef(VarRef varRef) { // // check correct usage of interleaved // if (varRef.isInterleaved) { From 3fdc3eb6ae3639222cd30dcd0e4364272e89d0bb Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Tue, 25 Jan 2022 11:50:06 -0800 Subject: [PATCH 03/23] port until checkPreamble --- .../org/lflang/validation/LFValidator.java | 473 +++++++++--------- 1 file changed, 242 insertions(+), 231 deletions(-) diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 99a487f7e2..2cdda2d43a 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -44,6 +44,7 @@ import org.eclipse.xtend.lib.annotations.AccessorType; import org.lflang.FileConfig; +import org.lflang.InferredType; import org.lflang.ModelInfo; import org.lflang.Target; import org.lflang.TargetProperty; @@ -545,253 +546,263 @@ private boolean sameType(Type type1, Type type2) { return false; } -// @Check(CheckType.FAST) -// def checkDeadline(Deadline deadline) { -// if (isCBasedTarget && -// this.info.overflowingDeadlines.contains(deadline)) { -// error( -// "Deadline exceeds the maximum of " + -// TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", -// Literals.DEADLINE__DELAY) -// } -// } -// @Check(CheckType.FAST) -// def checkSTPOffset(STP stp) { -// if (isCBasedTarget && -// this.info.overflowingDeadlines.contains(stp)) { -// error( -// "STP offset exceeds the maximum of " + -// TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", -// Literals.DEADLINE__DELAY) -// } -// } - -// @Check(CheckType.FAST) -// def checkInput(Input input) { -// checkName(input.name, Literals.VARIABLE__NAME) -// if (target.requiresTypes) { -// if (input.type === null) { -// error("Input must have a type.", Literals.TYPED_VARIABLE__TYPE) -// } -// } - -// // mutable has no meaning in C++ -// if (input.mutable && this.target == Target.CPP) { -// warning( -// "The mutable qualifier has no meaning for the C++ target and should be removed. " + -// "In C++, any value can be made mutable by calling get_mutable_copy().", -// Literals.INPUT__MUTABLE -// ) -// } + @Check(CheckType.FAST) + public void checkDeadline(Deadline deadline) { + if (isCBasedTarget() && + this.info.overflowingDeadlines.contains(deadline)) { + error( + "Deadline exceeds the maximum of " + + TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", + Literals.DEADLINE__DELAY); + } + } -// // Variable width multiports are not supported (yet?). -// if (input.widthSpec !== null && input.widthSpec.ofVariableLength) { -// error("Variable-width multiports are not supported.", Literals.PORT__WIDTH_SPEC) -// } -// } + @Check(CheckType.FAST) + public void checkSTPOffset(STP stp) { + if (isCBasedTarget() && + this.info.overflowingDeadlines.contains((Deadline) stp)) { + error( + "STP offset exceeds the maximum of " + + TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", + Literals.DEADLINE__DELAY); + } + } -// @Check(CheckType.FAST) -// def checkInstantiation(Instantiation instantiation) { -// checkName(instantiation.name, Literals.INSTANTIATION__NAME) -// val reactor = instantiation.reactorClass.toDefinition -// if (reactor.isMain || reactor.isFederated) { -// error( -// "Cannot instantiate a main (or federated) reactor: " + -// instantiation.reactorClass.name, -// Literals.INSTANTIATION__REACTOR_CLASS -// ) -// } + @Check(CheckType.FAST) + public void checkInput(Input input) { + checkName(input.getName(), Literals.VARIABLE__NAME); + if (target.requiresTypes) { + if (input.getType() == null) { + error("Input must have a type.", Literals.TYPED_VARIABLE__TYPE); + } + } -// // Report error if this instantiation is part of a cycle. -// // FIXME: improve error message. -// // FIXME: Also report if there exists a cycle within. -// if (this.info.instantiationGraph.cycles.size > 0) { -// for (cycle : this.info.instantiationGraph.cycles) { -// val container = instantiation.eContainer as Reactor -// if (cycle.contains(container) && cycle.contains(reactor)) { -// error( -// "Instantiation is part of a cycle: " + -// cycle.fold(newArrayList, [ list, r | -// list.add(r.name); -// list -// ]).join(', ') + ".", -// Literals.INSTANTIATION__REACTOR_CLASS -// ) -// } -// } -// } -// // Variable width multiports are not supported (yet?). -// if (instantiation.widthSpec !== null -// && instantiation.widthSpec.ofVariableLength -// ) { -// if (isCBasedTarget) { -// warning("Variable-width banks are for internal use only.", -// Literals.INSTANTIATION__WIDTH_SPEC -// ) -// } else { -// error("Variable-width banks are not supported.", -// Literals.INSTANTIATION__WIDTH_SPEC -// ) -// } -// } -// } + // mutable has no meaning in C++ + if (input.isMutable() && this.target == Target.CPP) { + warning( + "The mutable qualifier has no meaning for the C++ target and should be removed. " + + "In C++, any value can be made mutable by calling get_mutable_copy().", + Literals.INPUT__MUTABLE + ); + } -// /** Check target parameters, which are key-value pairs. */ -// @Check(CheckType.FAST) -// def checkKeyValuePair(KeyValuePair param) { -// // Check only if the container's container is a Target. -// if (param.eContainer.eContainer instanceof TargetDecl) { + // Variable width multiports are not supported (yet?). + if (input.getWidthSpec() != null && input.getWidthSpec().isOfVariableLength()) { + error("Variable-width multiports are not supported.", Literals.PORT__WIDTH_SPEC); + } + } -// val prop = TargetProperty.forName(param.name) + @Check(CheckType.FAST) + public void checkInstantiation(Instantiation instantiation) { + checkName(instantiation.getName(), Literals.INSTANTIATION__NAME); + Reactor reactor = (Reactor) instantiation.getReactorClass(); + if (reactor.isMain() || reactor.isFederated()) { + error( + "Cannot instantiate a main (or federated) reactor: " + + instantiation.getReactorClass().getName(), + Literals.INSTANTIATION__REACTOR_CLASS + ); + } -// // Make sure the key is valid. -// if (prop === null) { -// warning( -// "Unrecognized target parameter: " + param.name + -// ". Recognized parameters are: " + -// TargetProperty.getOptions().join(", ") + ".", -// Literals.KEY_VALUE_PAIR__NAME) -// } + // Report error if this instantiation is part of a cycle. + // FIXME: improve error message. + // FIXME: Also report if there exists a cycle within. + if (this.info.instantiationGraph.getCycles().size() > 0) { + for (Set cycle : this.info.instantiationGraph.getCycles()) { + Reactor container = (Reactor) instantiation.eContainer(); + if (cycle.contains(container) && cycle.contains(reactor)) { + List names = new ArrayList<>(); + for (Reactor r : cycle) { + names.add(r.getName()); + } + + error( + "Instantiation is part of a cycle: " + String.join(", ", names) + ".", + Literals.INSTANTIATION__REACTOR_CLASS + ); + } + } + } + // Variable width multiports are not supported (yet?). + if (instantiation.getWidthSpec() != null + && instantiation.getWidthSpec().isOfVariableLength() + ) { + if (isCBasedTarget()) { + warning("Variable-width banks are for internal use only.", + Literals.INSTANTIATION__WIDTH_SPEC + ); + } else { + error("Variable-width banks are not supported.", + Literals.INSTANTIATION__WIDTH_SPEC + ); + } + } + } -// // Check whether the property is supported by the target. -// if (!prop.supportedBy.contains(this.target)) { -// warning( -// "The target parameter: " + param.name + -// " is not supported by the " + this.target + -// " target and will thus be ignored.", -// Literals.KEY_VALUE_PAIR__NAME) -// } + /** Check target parameters, which are key-value pairs. */ + @Check(CheckType.FAST) + public void checkKeyValuePair(KeyValuePair param) { + // Check only if the container's container is a Target. + if (param.eContainer().eContainer() instanceof TargetDecl) { + TargetProperty prop = TargetProperty.forName(param.getName()); + + // Make sure the key is valid. + if (prop == null) { + List optionNames = new ArrayList<>(); + for (TargetProperty p : TargetProperty.getOptions()) { + optionNames.add(p.name()); + } + warning( + "Unrecognized target parameter: " + param.getName() + + ". Recognized parameters are: " + + String.join(", ", optionNames) + ".", + Literals.KEY_VALUE_PAIR__NAME); + } -// // Report problem with the assigned value. -// prop.type.check(param.value, param.name, this) -// targetPropertyErrors.forEach [ -// error(it, Literals.KEY_VALUE_PAIR__VALUE) -// ] -// targetPropertyErrors.clear() -// targetPropertyWarnings.forEach [ -// warning(it, Literals.KEY_VALUE_PAIR__VALUE) -// ] -// targetPropertyWarnings.clear() -// } -// } + // Check whether the property is supported by the target. + if (!prop.supportedBy.contains(this.target)) { + warning( + "The target parameter: " + param.getName() + + " is not supported by the " + this.target + + " target and will thus be ignored.", + Literals.KEY_VALUE_PAIR__NAME); + } -// @Check(CheckType.FAST) -// def checkOutput(Output output) { -// checkName(output.name, Literals.VARIABLE__NAME) -// if (this.target.requiresTypes) { -// if (output.type === null) { -// error("Output must have a type.", Literals.TYPED_VARIABLE__TYPE) -// } -// } + // Report problem with the assigned value. + prop.type.check(param.getValue(), param.getName(), this); + + for (String it : targetPropertyErrors) { + error(it, Literals.KEY_VALUE_PAIR__VALUE); + } + targetPropertyErrors.clear(); + + for (String it : targetPropertyWarnings) { + error(it, Literals.KEY_VALUE_PAIR__VALUE); + } + targetPropertyWarnings.clear(); + } + } -// // Variable width multiports are not supported (yet?). -// if (output.widthSpec !== null && output.widthSpec.ofVariableLength) { -// error("Variable-width multiports are not supported.", Literals.PORT__WIDTH_SPEC) -// } -// } + @Check(CheckType.FAST) + public void checkOutput(Output output) { + checkName(output.getName(), Literals.VARIABLE__NAME); + if (this.target.requiresTypes) { + if (output.getType() == null) { + error("Output must have a type.", Literals.TYPED_VARIABLE__TYPE); + } + } -// @Check(CheckType.FAST) -// def checkModel(Model model) { -// // Since we're doing a fast check, we only want to update -// // if the model info hasn't been initialized yet. If it has, -// // we use the old information and update it during a normal -// // check (see below). -// if (!info.updated) { -// info.update(model, errorReporter) -// } -// } + // Variable width multiports are not supported (yet?). + if (output.getWidthSpec() != null && output.getWidthSpec().isOfVariableLength()) { + error("Variable-width multiports are not supported.", Literals.PORT__WIDTH_SPEC); + } + } -// @Check(NORMAL) -// def updateModelInfo(Model model) { -// info.update(model, errorReporter) -// } + @Check(CheckType.FAST) + public void checkModel(Model model) { + // Since we're doing a fast check, we only want to update + // if the model info hasn't been initialized yet. If it has, + // we use the old information and update it during a normal + // check (see below). + if (!info.updated) { + info.update(model, errorReporter); + } + } -// @Check(CheckType.FAST) -// def checkParameter(Parameter param) { -// checkName(param.name, Literals.PARAMETER__NAME) + @Check(CheckType.NORMAL) + public void updateModelInfo(Model model) { + info.update(model, errorReporter); + } -// if (param.init.exists[it.getParameter() !== null]) { -// // Initialization using parameters is forbidden. -// error("Parameter cannot be initialized using parameter.", -// Literals.PARAMETER__INIT) -// } + @Check(CheckType.FAST) + public void checkParameter(Parameter param) { + checkName(param.getName(), Literals.PARAMETER__NAME); + + for (Value it : param.getInit()) { + if (it.getParameter() != null) { + // Initialization using parameters is forbidden. + error("Parameter cannot be initialized using parameter.", + Literals.PARAMETER__INIT); + } + } -// if (param.init === null || param.init.size == 0) { -// // All parameters must be initialized. -// error("Uninitialized parameter.", Literals.PARAMETER__INIT) -// } else if (param.isOfTimeType) { -// // We do additional checks on types because we can make stronger -// // assumptions about them. - -// // If the parameter is not a list, cannot be initialized -// // using a one. -// if (param.init.size > 1 && param.type.arraySpec === null) { -// error("Time parameter cannot be initialized using a list.", -// Literals.PARAMETER__INIT) -// } else { -// // The parameter is a singleton time. -// val init = param.init.get(0) -// if (init.time === null) { -// if (init !== null && !init.isZero) { -// if (init.isInteger) { -// error("Missing time unit.", Literals.PARAMETER__INIT) -// } else { -// error("Invalid time literal.", -// Literals.PARAMETER__INIT) -// } -// } -// } // If time is not null, we know that a unit is also specified. -// } -// } else if (this.target.requiresTypes) { -// // Report missing target type. -// if (param.inferredType.isUndefined()) { -// error("Type declaration missing.", Literals.PARAMETER__TYPE) -// } -// } + if (param.getInit() == null || param.getInit().size() == 0) { + // All parameters must be initialized. + error("Uninitialized parameter.", Literals.PARAMETER__INIT); + } else if (isOfTimeType(param)) { + // We do additional checks on types because we can make stronger + // assumptions about them. + + // If the parameter is not a list, cannot be initialized + // using a one. + if (param.getInit().size() > 1 && param.getType().getArraySpec() == null) { + error("Time parameter cannot be initialized using a list.", + 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)) { + error("Missing time unit.", Literals.PARAMETER__INIT); + } else { + error("Invalid time literal.", + Literals.PARAMETER__INIT); + } + } + } // If time is not null, we know that a unit is also specified. + } + } else if (this.target.requiresTypes) { + // Report missing target type. + if (InferredType.fromAST(param.getType()).isUndefined()) { + error("Type declaration missing.", Literals.PARAMETER__TYPE); + } + } -// if (isCBasedTarget && -// this.info.overflowingParameters.contains(param)) { -// error( -// "Time value used to specify a deadline exceeds the maximum of " + -// TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", -// Literals.PARAMETER__INIT) -// } - -// if(!param.braces.isNullOrEmpty && this.target != Target.CPP) { -// error("Brace initializers are only supported for the C++ target", Literals.PARAMETER__BRACES) -// } + if (isCBasedTarget() && + this.info.overflowingParameters.contains(param)) { + error( + "Time value used to specify a deadline exceeds the maximum of " + + TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", + Literals.PARAMETER__INIT); + } -// } + EList braces = param.getBraces(); + if(!(braces == null || braces.isEmpty()) && this.target != Target.CPP) { + error("Brace initializers are only supported for the C++ target", Literals.PARAMETER__BRACES); + } + } -// @Check(CheckType.FAST) -// def checkPreamble(Preamble preamble) { -// if (this.target == Target.CPP) { -// if (preamble.visibility == Visibility.NONE) { -// error( -// "Preambles for the C++ target need a visibility qualifier (private or public)!", -// Literals.PREAMBLE__VISIBILITY -// ) -// } else if (preamble.visibility == Visibility.PRIVATE) { -// val container = preamble.eContainer -// if (container !== null && container instanceof Reactor) { -// val reactor = container as Reactor -// if (reactor.isGeneric) { -// warning( -// "Private preambles in generic reactors are not truly private. " + -// "Since the generated code is placed in a *_impl.hh file, it will " + -// "be visible on the public interface. Consider using a public " + -// "preamble within the reactor or a private preamble on file scope.", -// Literals.PREAMBLE__VISIBILITY) -// } -// } -// } -// } else if (preamble.visibility != Visibility.NONE) { -// warning( -// '''The «preamble.visibility» qualifier has no meaning for the «this.target.name» target. It should be removed.''', -// Literals.PREAMBLE__VISIBILITY -// ) -// } -// } + @Check(CheckType.FAST) + public void checkPreamble(Preamble preamble) { + if (this.target == Target.CPP) { + if (preamble.getVisibility() == Visibility.NONE) { + error( + "Preambles for the C++ target need a visibility qualifier (private or public)!", + Literals.PREAMBLE__VISIBILITY + ); + } else if (preamble.getVisibility() == Visibility.PRIVATE) { + EObject container = preamble.eContainer(); + if (container != null && container instanceof Reactor) { + Reactor reactor = (Reactor) container; + if (isGeneric(reactor)) { + warning( + "Private preambles in generic reactors are not truly private. " + + "Since the generated code is placed in a *_impl.hh file, it will " + + "be visible on the public interface. Consider using a public " + + "preamble within the reactor or a private preamble on file scope.", + Literals.PREAMBLE__VISIBILITY); + } + } + } + } else if (preamble.getVisibility() != Visibility.NONE) { + warning( + String.format("The %s qualifier has no meaning for the %s target. It should be removed.", + preamble.getVisibility(), this.target.name()), + Literals.PREAMBLE__VISIBILITY + ); + } + } // @Check(CheckType.FAST) // def checkReaction(Reaction reaction) { @@ -1143,7 +1154,7 @@ private boolean sameType(Type type1, Type type2) { // error("State must have a type.", Literals.STATE_VAR__TYPE) // } -// if (isCBasedTarget && stateVar.init.size > 1) { +// if (isCBasedTarget() && stateVar.init.size > 1) { // // In C, if initialization is done with a list, elements cannot // // refer to parameters. // if (stateVar.init.exists[it.getParameter() !== null]) { @@ -1299,7 +1310,7 @@ private boolean sameType(Type type1, Type type2) { // def checkVarRef(VarRef varRef) { // // check correct usage of interleaved // if (varRef.isInterleaved) { -// if (this.target != Target.CPP && !isCBasedTarget && this.target != Target.Python) { +// if (this.target != Target.CPP && !isCBasedTarget() && this.target != Target.Python) { // error("This target does not support interleaved port references.", Literals.VAR_REF__INTERLEAVED) // } // if (!(varRef.eContainer instanceof Connection)) { From 49f386de540ec12fefcb2ee1bf44da3e83955032 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Wed, 26 Jan 2022 12:00:21 -0800 Subject: [PATCH 04/23] port more functions to java --- .../org/lflang/validation/LFValidator.java | 802 +++++++++--------- 1 file changed, 422 insertions(+), 380 deletions(-) diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 2cdda2d43a..c9bcfca488 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -28,14 +28,19 @@ import com.google.inject.Inject; +import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.Optional; +import org.eclipse.emf.common.util.BasicEList; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.common.util.TreeIterator; +import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.validation.Check; import org.eclipse.xtext.validation.CheckType; @@ -754,7 +759,7 @@ public void checkParameter(Parameter param) { } } else if (this.target.requiresTypes) { // Report missing target type. - if (InferredType.fromAST(param.getType()).isUndefined()) { + if (((InferredType) param).isUndefined()) { error("Type declaration missing.", Literals.PARAMETER__TYPE); } } @@ -927,407 +932,444 @@ public void checkPreamble(Preamble preamble) { // // FIXME: improve error message. // } -// @Check(CheckType.FAST) -// def checkReactor(Reactor reactor) { -// val name = FileConfig.nameWithoutExtension(reactor.eResource) -// if (reactor.name === null) { -// if (!reactor.isFederated && !reactor.isMain) { -// error( -// "Reactor must be named.", -// Literals.REACTOR_DECL__NAME -// ) -// } -// // Prevent NPE in tests below. -// return -// } else { -// if (reactor.isFederated || reactor.isMain) { -// if(!reactor.name.equals(name)) { -// // Make sure that if the name is omitted, the reactor is indeed main. -// error( -// "Name of main reactor must match the file name (or be omitted).", -// Literals.REACTOR_DECL__NAME -// ) -// } -// // Do not allow multiple main/federated reactors. -// if (reactor.eResource.allContents.filter(Reactor).filter[it.isMain || it.isFederated].size > 1) { -// var attribute = Literals.REACTOR__MAIN -// if (reactor.isFederated) { -// attribute = Literals.REACTOR__FEDERATED -// } -// if (reactor.isMain || reactor.isFederated) { -// error( -// "Multiple definitions of main or federated reactor.", -// attribute -// ) -// } -// } -// } else if (reactor.eResource.allContents.filter(Reactor).exists[it.isMain || it.isFederated] && reactor.name.equals(name)) { -// // Make sure that if a main reactor is specified, there are no -// // ordinary reactors that clash with it. -// error( -// "Name conflict with main reactor.", -// Literals.REACTOR_DECL__NAME -// ) -// } -// } + private int countMain(TreeIterator iter) { + int nMain = 0; + while (iter.hasNext()) { + EObject obj = iter.next(); + if (!(obj instanceof Reactor)) { + continue; + } + Reactor r = (Reactor) obj; + if (r.isMain() || r.isFederated()) { + nMain++; + } + } + return nMain; + } -// // If there is a main reactor (with no name) then disallow other (non-main) reactors -// // matching the file name. + @Check(CheckType.FAST) + public void checkReactor(Reactor reactor) throws IOException { + String name = FileConfig.nameWithoutExtension(reactor.eResource()); + if (reactor.getName() == null) { + if (!reactor.isFederated() && !reactor.isMain()) { + error( + "Reactor must be named.", + Literals.REACTOR_DECL__NAME + ); + } + // Prevent NPE in tests below. + return; + } else { + TreeIterator iter = reactor.eResource().getAllContents(); + if (reactor.isFederated() || reactor.isMain()) { + if(!reactor.getName().equals(name)) { + // Make sure that if the name is omitted, the reactor is indeed main. + error( + "Name of main reactor must match the file name (or be omitted).", + Literals.REACTOR_DECL__NAME + ); + } + + // Do not allow multiple main/federated reactors. + int nMain = countMain(iter); + if (nMain > 1) { + EAttribute attribute = Literals.REACTOR__MAIN; + if (reactor.isFederated()) { + attribute = Literals.REACTOR__FEDERATED; + } + if (reactor.isMain() || reactor.isFederated()) { + error( + "Multiple definitions of main or federated reactor.", + attribute + ); + } + } + } else { + int nMain = countMain(iter); + if (nMain > 0 && reactor.getName().equals(name)) { + error( + "Name conflict with main reactor.", + Literals.REACTOR_DECL__NAME + ); + } + } + } -// checkName(reactor.name, Literals.REACTOR_DECL__NAME) + // If there is a main reactor (with no name) then disallow other (non-main) reactors + // matching the file name. -// // C++ reactors may not be called 'preamble' -// if (this.target == Target.CPP && reactor.name.equalsIgnoreCase("preamble")) { -// error( -// "Reactor cannot be named '" + reactor.name + "'", -// Literals.REACTOR_DECL__NAME -// ) -// } + checkName(reactor.getName(), Literals.REACTOR_DECL__NAME); -// if (reactor.host !== null) { -// if (!reactor.isFederated) { -// error( -// "Cannot assign a host to reactor '" + reactor.name + -// "' because it is not federated.", -// Literals.REACTOR__HOST -// ) -// } -// } + // C++ reactors may not be called 'preamble' + if (this.target == Target.CPP && reactor.getName().equalsIgnoreCase("preamble")) { + error( + "Reactor cannot be named '" + reactor.getName() + "'", + Literals.REACTOR_DECL__NAME + ); + } -// var variables = new ArrayList() -// variables.addAll(reactor.inputs) -// variables.addAll(reactor.outputs) -// variables.addAll(reactor.actions) -// variables.addAll(reactor.timers) - -// // Perform checks on super classes. -// for (superClass : reactor.superClasses ?: emptyList) { -// var conflicts = new HashSet() - -// // Detect input conflicts -// checkConflict(superClass.toDefinition.inputs, reactor.inputs, variables, conflicts) -// // Detect output conflicts -// checkConflict(superClass.toDefinition.outputs, reactor.outputs, variables, conflicts) -// // Detect output conflicts -// checkConflict(superClass.toDefinition.actions, reactor.actions, variables, conflicts) -// // Detect conflicts -// for (timer : superClass.toDefinition.timers) { -// if (timer.hasNameConflict(variables.filter[it | !reactor.timers.contains(it)])) { -// conflicts.add(timer) -// } else { -// variables.add(timer) -// } -// } + if (reactor.getHost() != null) { + if (!reactor.isFederated()) { + error( + "Cannot assign a host to reactor '" + reactor.getName() + + "' because it is not federated.", + Literals.REACTOR__HOST + ); + } + } -// // Report conflicts. -// if (conflicts.size > 0) { -// val names = new ArrayList(); -// conflicts.forEach[it | names.add(it.name)] -// error( -// '''Cannot extend «superClass.name» due to the following conflicts: «names.join(',')».''', -// Literals.REACTOR__SUPER_CLASSES -// ) -// } -// } -// } -// /** -// * For each input, report a conflict if: -// * 1) the input exists and the type doesn't match; or -// * 2) the input has a name clash with variable that is not an input. -// * @param superVars List of typed variables of a particular kind (i.e., -// * inputs, outputs, or actions), found in a super class. -// * @param sameKind Typed variables of the same kind, found in the subclass. -// * @param allOwn Accumulator of non-conflicting variables incorporated in the -// * subclass. -// * @param conflicts Set of variables that are in conflict, to be used by this -// * function to report conflicts. -// */ -// def checkConflict (EList superVars, -// EList sameKind, List allOwn, -// HashSet conflicts) { -// for (superVar : superVars) { -// val match = sameKind.findFirst [ it | -// it.name.equals(superVar.name) -// ] -// val rest = allOwn.filter[it|!sameKind.contains(it)] -// if ((match !== null && superVar.type !== match.type) || superVar.hasNameConflict(rest)) { -// conflicts.add(superVar) -// } else { -// allOwn.add(superVar) -// } -// } -// } + List variables = new ArrayList<>(); + variables.addAll(reactor.getInputs()); + variables.addAll(reactor.getOutputs()); + variables.addAll(reactor.getActions()); + variables.addAll(reactor.getTimers()); + + // Perform checks on super classes. + EList superClasses = reactor.getSuperClasses() != null ? reactor.getSuperClasses() : new BasicEList<>(); + for (ReactorDecl superClass : superClasses) { + HashSet conflicts = new HashSet<>(); + + // Detect input conflicts + checkConflict(toDefinition(superClass).getInputs(), reactor.getInputs(), variables, conflicts); + // Detect output conflicts + checkConflict(toDefinition(superClass).getOutputs(), reactor.getOutputs(), variables, conflicts); + // Detect output conflicts + checkConflict(toDefinition(superClass).getActions(), reactor.getActions(), variables, conflicts); + // Detect conflicts + for (Timer timer : toDefinition(superClass).getTimers()) { + List filteredVariables = new ArrayList<>(variables); + filteredVariables.removeIf(it -> reactor.getTimers().contains(it)); + if (hasNameConflict(timer, filteredVariables)) { + conflicts.add(timer); + } else { + variables.add(timer); + } + } -// /** -// * Report whether the name of the given element matches any variable in -// * the ones to check against. -// * @param element The element to compare against all variables in the given iterable. -// * @param toCheckAgainst Iterable variables to compare the given element against. -// */ -// def boolean hasNameConflict(Variable element, -// Iterable toCheckAgainst) { -// if (toCheckAgainst.filter[it|it.name.equals(element.name)].size > 0) { -// return true -// } -// return false -// } + // Report conflicts. + if (conflicts.size() > 0) { + List names = new ArrayList<>(); + for (Variable it : conflicts) { + names.add(it.getName()); + } + error( + String.format("Cannot extend %s due to the following conflicts: %s.", superClass.getName(), String.join(",", names)), + Literals.REACTOR__SUPER_CLASSES + ); + } + } + } -// @Check(CheckType.FAST) -// def checkHost(Host host) { -// val addr = host.addr -// val user = host.user -// if (user !== null && !user.matches(usernameRegex)) { -// warning( -// "Invalid user name.", -// Literals.HOST__USER -// ) -// } -// if (host instanceof IPV4Host && !addr.matches(ipv4Regex)) { -// warning( -// "Invalid IP address.", -// Literals.HOST__ADDR -// ) -// } else if (host instanceof IPV6Host && !addr.matches(ipv6Regex)) { -// warning( -// "Invalid IP address.", -// Literals.HOST__ADDR -// ) -// } else if (host instanceof NamedHost && !addr.matches(hostOrFQNRegex)) { -// warning( -// "Invalid host name or fully qualified domain name.", -// Literals.HOST__ADDR -// ) -// } -// } + /** + * For each input, report a conflict if: + * 1) the input exists and the type doesn't match; or + * 2) the input has a name clash with variable that is not an input. + * @param superVars List of typed variables of a particular kind (i.e., + * inputs, outputs, or actions), found in a super class. + * @param sameKind Typed variables of the same kind, found in the subclass. + * @param allOwn Accumulator of non-conflicting variables incorporated in the + * subclass. + * @param conflicts Set of variables that are in conflict, to be used by this + * function to report conflicts. + */ + private void checkConflict (EList superVars, + EList sameKind, List allOwn, + HashSet conflicts) { + for (T superVar : superVars) { + T match = null; + for (T it : sameKind) { + if (it.getName().equals(superVar.getName())) { + match = it; + break; + } + } + List rest = new ArrayList<>(allOwn); + rest.removeIf(it -> sameKind.contains(it)); + + if ((match != null && superVar.getType() != match.getType()) || hasNameConflict(superVar, rest)) { + conflicts.add(superVar); + } else { + allOwn.add(superVar); + } + } + } + + /** + * Report whether the name of the given element matches any variable in + * the ones to check against. + * @param element The element to compare against all variables in the given iterable. + * @param toCheckAgainst Iterable variables to compare the given element against. + */ + private boolean hasNameConflict(Variable element, + Iterable toCheckAgainst) { + int numNameConflicts = 0; + for (Variable it : toCheckAgainst) { + if (it.getName().equals(element.getName())) { + numNameConflicts++; + } + } + return numNameConflicts > 0; + } + + @Check(CheckType.FAST) + public void checkHost(Host host) { + String addr = host.getAddr(); + String user = host.getUser(); + if (user != null && !user.matches(usernameRegex)) { + warning( + "Invalid user name.", + Literals.HOST__USER + ); + } + if (host instanceof IPV4Host && !addr.matches(ipv4Regex)) { + warning( + "Invalid IP address.", + Literals.HOST__ADDR + ); + } else if (host instanceof IPV6Host && !addr.matches(ipv6Regex)) { + warning( + "Invalid IP address.", + Literals.HOST__ADDR + ); + } else if (host instanceof NamedHost && !addr.matches(hostOrFQNRegex)) { + warning( + "Invalid host name or fully qualified domain name.", + Literals.HOST__ADDR + ); + } + } -// /** -// * Check if the requested serialization is supported. -// */ -// @Check(CheckType.FAST) -// def checkSerializer(Serializer serializer) { -// var boolean isValidSerializer = false; -// for (SupportedSerializers method : SupportedSerializers.values()) { -// if (method.name().equalsIgnoreCase(serializer.type)){ -// isValidSerializer = true; -// } -// } + /** + * Check if the requested serialization is supported. + */ + @Check(CheckType.FAST) + public void checkSerializer(Serializer serializer) { + boolean isValidSerializer = false; + for (SupportedSerializers method : SupportedSerializers.values()) { + if (method.name().equalsIgnoreCase(serializer.getType())){ + isValidSerializer = true; + } + } -// if (!isValidSerializer) { -// error( -// "Serializer can be " + SupportedSerializers.values.toList, -// Literals.SERIALIZER__TYPE -// ); -// } -// } + if (!isValidSerializer) { + error( + "Serializer can be " + Arrays.asList(SupportedSerializers.values()), + Literals.SERIALIZER__TYPE + ); + } + } -// @Check(CheckType.FAST) -// def checkState(StateVar stateVar) { -// checkName(stateVar.name, Literals.STATE_VAR__NAME) - -// if (stateVar.isOfTimeType) { -// // If the state is declared to be a time, -// // make sure that it is initialized correctly. -// if (stateVar.init !== null) { -// for (init : stateVar.init) { -// if (stateVar.type !== null && stateVar.type.isTime && -// !init.isValidTime) { -// if (stateVar.isParameterized) { -// error( -// "Referenced parameter does not denote a time.", -// Literals.STATE_VAR__INIT) -// } else { -// if (init !== null && !init.isZero) { -// if (init.isInteger) { -// error( -// "Missing time unit.", Literals.STATE_VAR__INIT) -// } else { -// error("Invalid time literal.", -// Literals.STATE_VAR__INIT) -// } -// } -// } -// } -// } -// } -// } else if (this.target.requiresTypes && stateVar.inferredType.isUndefined) { -// // Report if a type is missing -// error("State must have a type.", Literals.STATE_VAR__TYPE) -// } + @Check(CheckType.FAST) + public void checkState(StateVar stateVar) { + checkName(stateVar.getName(), Literals.STATE_VAR__NAME); + + if (isOfTimeType(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); + } + } + } + } + } + } + } else if (this.target.requiresTypes && ((InferredType) stateVar).isUndefined()) { + // Report if a type is missing + error("State must have a type.", Literals.STATE_VAR__TYPE); + } -// if (isCBasedTarget() && stateVar.init.size > 1) { -// // In C, if initialization is done with a list, elements cannot -// // refer to parameters. -// if (stateVar.init.exists[it.getParameter() !== null]) { -// error("List items cannot refer to a parameter.", -// Literals.STATE_VAR__INIT) -// } -// } + 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) { + error("List items cannot refer to a parameter.", + Literals.STATE_VAR__INIT); + break; + } + } + } -// if(!stateVar.braces.isNullOrEmpty && this.target != Target.CPP) { -// error("Brace initializers are only supported for the C++ target", Literals.STATE_VAR__BRACES) -// } -// } + EList braces = stateVar.getBraces(); + if(!(braces == null || braces.isEmpty()) && this.target != Target.CPP) { + error("Brace initializers are only supported for the C++ target", Literals.STATE_VAR__BRACES); + } + } -// @Check(CheckType.FAST) -// def checkTargetDecl(TargetDecl target) { -// val targetOpt = Target.forName(target.name); -// if (targetOpt.isEmpty()) { -// error("Unrecognized target: " + target.name, -// Literals.TARGET_DECL__NAME) -// } else { -// this.target = targetOpt.get(); -// } -// } + @Check(CheckType.FAST) + public void checkTargetDecl(TargetDecl target) throws IOException { + Optional targetOpt = Target.forName(target.getName()); + if (targetOpt.isEmpty()) { + error("Unrecognized target: " + target.getName(), + Literals.TARGET_DECL__NAME); + } else { + this.target = targetOpt.get(); + } + String lfFileName = FileConfig.nameWithoutExtension(target.eResource()); + if (Character.isDigit(lfFileName.charAt(0))) { + errorReporter.reportError("LF file names must not start with a number"); + } + } -// /** -// * Check for consistency of the target properties, which are -// * defined as KeyValuePairs. -// * -// * @param targetProperties The target properties defined -// * in the current Lingua Franca program. -// */ -// @Check(EXPENSIVE) -// def checkTargetProperties(KeyValuePairs targetProperties) { - -// val fastTargetProperties = targetProperties.pairs.filter( -// pair | -// // Check to see if fast is defined -// TargetProperty.forName(pair.name) == TargetProperty.FAST -// ) - -// val fastTargetProperty = fastTargetProperties.findFirst[t | true]; - -// if (fastTargetProperty !== null) { -// // Check for federated -// if (info.model.reactors.exists( -// reactor | -// // Check to see if the program has a federated reactor -// reactor.isFederated -// )) { -// error( -// "The fast target property is incompatible with federated programs.", -// fastTargetProperty, -// Literals.KEY_VALUE_PAIR__NAME -// ) -// } + /** + * Check for consistency of the target properties, which are + * defined as KeyValuePairs. + * + * @param targetProperties The target properties defined + * in the current Lingua Franca program. + */ + @Check(CheckType.EXPENSIVE) + public void checkTargetProperties(KeyValuePairs targetProperties) { + EList fastTargetProperties = new BasicEList<>(targetProperties.getPairs()); + fastTargetProperties.removeIf(pair -> TargetProperty.forName(pair.getName()) != TargetProperty.FAST); + KeyValuePair fastTargetProperty = fastTargetProperties.size() > 0 ? fastTargetProperties.get(0) : null; + + if (fastTargetProperty != null) { + // Check for federated + for (Reactor reactor : info.model.getReactors()) { + // Check to see if the program has a federated reactor + if (reactor.isFederated()) { + error( + "The fast target property is incompatible with federated programs.", + fastTargetProperty, + Literals.KEY_VALUE_PAIR__NAME + ); + break; + } + } -// // Check for physical actions -// if (info.model.reactors.exists( -// reactor | -// // Check to see if the program has a physical action in a reactor -// reactor.actions.exists(action|(action.origin == ActionOrigin.PHYSICAL)) -// )) { -// error( -// "The fast target property is incompatible with physical actions.", -// fastTargetProperty, -// Literals.KEY_VALUE_PAIR__NAME -// ) -// } - -// } - -// val clockSyncTargetProperties = targetProperties.pairs.filter( -// pair | -// // Check to see if clock-sync is defined -// TargetProperty.forName(pair.name) == TargetProperty.CLOCK_SYNC -// ) + // Check for physical actions + for (Reactor reactor : info.model.getReactors()) { + // Check to see if the program has a physical action in a reactor + for (Action action : reactor.getActions()) { + if (action.getOrigin() == ActionOrigin.PHYSICAL) { + error( + "The fast target property is incompatible with physical actions.", + fastTargetProperty, + Literals.KEY_VALUE_PAIR__NAME + ); + break; + } + } + } + } -// val clockSyncTargetProperty = clockSyncTargetProperties.findFirst[t | true]; -// if (clockSyncTargetProperty !== null) { -// if (info.model.reactors.exists( -// reactor | -// // Check to see if the program has a federated reactor defined. -// reactor.isFederated -// ) == false) { -// warning( -// "The clock-sync target property is incompatible with non-federated programs.", -// clockSyncTargetProperty, -// Literals.KEY_VALUE_PAIR__NAME -// ) -// } -// } -// } - -// @Check(CheckType.FAST) -// def checkValueAsTime(Value value) { -// val container = value.eContainer - -// if (container instanceof Timer || container instanceof Action || -// container instanceof Connection || container instanceof Deadline) { + EList clockSyncTargetProperties = new BasicEList<>(targetProperties.getPairs()); + // Check to see if clock-sync is defined + clockSyncTargetProperties.removeIf(pair -> TargetProperty.forName(pair.getName()) != TargetProperty.CLOCK_SYNC); + KeyValuePair clockSyncTargetProperty = clockSyncTargetProperties.size() > 0 ? clockSyncTargetProperties.get(0) : null; + + if (clockSyncTargetProperty != null) { + boolean federatedExists = false; + for (Reactor reactor : info.model.getReactors()) { + if (reactor.isFederated()) { + federatedExists = true; + } + } + if (!federatedExists) { + warning( + "The clock-sync target property is incompatible with non-federated programs.", + clockSyncTargetProperty, + Literals.KEY_VALUE_PAIR__NAME + ); + } + } + } -// // If parameter is referenced, check that it is of the correct type. -// if (value.getParameter() !== null) { -// if (!value.getParameter().isOfTimeType && target.requiresTypes === true) { -// error("Parameter is not of time type", -// Literals.VALUE__PARAMETER) -// } -// } else if (value.time === null) { -// if (value.literal !== null && !value.literal.isZero) { -// if (value.literal.isInteger) { -// error("Missing time unit.", Literals.VALUE__LITERAL) -// } else { -// error("Invalid time literal.", -// Literals.VALUE__LITERAL) -// } -// } else if (value.code !== null) { -// error("Invalid time literal.", Literals.VALUE__CODE) -// } -// } -// } -// } + @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 == true) { + 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) -// def checkTimer(Timer timer) { -// checkName(timer.name, Literals.VARIABLE__NAME) -// } + @Check(CheckType.FAST) + public void checkTimer(Timer timer) { + checkName(timer.getName(), Literals.VARIABLE__NAME); + } -// @Check(CheckType.FAST) -// def checkType(Type type) { -// // FIXME: disallow the use of generics in C -// if (this.target == Target.CPP) { -// if (type.stars.size > 0) { -// warning( -// "Raw pointers should be avoided in conjunction with LF. Ports " + -// "and actions implicitly use smart pointers. In this case, " + -// "the pointer here is likely not needed. For parameters and state " + -// "smart pointers should be used explicitly if pointer semantics " + -// "are really needed.", -// Literals.TYPE__STARS -// ) -// } -// } -// else if (this.target == Target.Python) { -// if (type !== null) { -// error( -// "Types are not allowed in the Python target", -// Literals.TYPE__ID -// ) -// } -// } -// } + @Check(CheckType.FAST) + public void checkType(Type type) { + // FIXME: disallow the use of generics in C + if (this.target == Target.CPP) { + if (type.getStars().size() > 0) { + warning( + "Raw pointers should be avoided in conjunction with LF. Ports " + + "and actions implicitly use smart pointers. In this case, " + + "the pointer here is likely not needed. For parameters and state " + + "smart pointers should be used explicitly if pointer semantics " + + "are really needed.", + Literals.TYPE__STARS + ); + } + } + else if (this.target == Target.Python) { + if (type != null) { + error( + "Types are not allowed in the Python target", + Literals.TYPE__ID + ); + } + } + } -// @Check(CheckType.FAST) -// def checkVarRef(VarRef varRef) { -// // check correct usage of interleaved -// if (varRef.isInterleaved) { -// if (this.target != Target.CPP && !isCBasedTarget() && this.target != Target.Python) { -// error("This target does not support interleaved port references.", Literals.VAR_REF__INTERLEAVED) -// } -// if (!(varRef.eContainer instanceof Connection)) { -// error("interleaved can only be used in connections.", Literals.VAR_REF__INTERLEAVED) -// } + @Check(CheckType.FAST) + public void checkVarRef(VarRef varRef) { + // check correct usage of interleaved + if (varRef.isInterleaved()) { + if (this.target != Target.CPP && !isCBasedTarget() && this.target != Target.Python) { + error("This target does not support interleaved port references.", Literals.VAR_REF__INTERLEAVED); + } + if (!(varRef.eContainer() instanceof Connection)) { + error("interleaved can only be used in connections.", Literals.VAR_REF__INTERLEAVED); + } -// if (varRef.variable instanceof Port) { -// // This test only works correctly if the variable is actually a port. If it is not a port, other -// // validator rules will produce error messages. -// if (varRef.container === null || varRef.container.widthSpec === null || -// (varRef.variable as Port).widthSpec === null -// ) { -// error("interleaved can only be used for multiports contained within banks.", Literals.VAR_REF__INTERLEAVED) -// } -// } -// } -// } + if (varRef.getVariable() instanceof Port) { + // This test only works correctly if the variable is actually a port. If it is not a port, other + // validator rules will produce error messages. + if (varRef.getContainer() == null || varRef.getContainer().getWidthSpec() == null || + ((Port) varRef.getVariable()).getWidthSpec() == null + ) { + error("interleaved can only be used for multiports contained within banks.", Literals.VAR_REF__INTERLEAVED); + } + } + } + } static String UNDERSCORE_MESSAGE = "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": "; static String ACTIONS_MESSAGE = "\"actions\" is a reserved word for the TypeScript target for objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation): "; From ce6346e1671fc2cdd1178ef6d43d2360188065bb Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Wed, 26 Jan 2022 13:38:58 -0800 Subject: [PATCH 05/23] finish converting LFValidator --- org.lflang/src/org/lflang/validation/LFValidator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index c9bcfca488..86f37d7834 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -109,7 +109,7 @@ * @author(Christian Menard } * */ -class LFValidator extends BaseLFValidator { +public class LFValidator extends BaseLFValidator { private Target target; From a08f29fe06d6c89b5511acac8c0bd5d8403659f2 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Wed, 26 Jan 2022 13:40:27 -0800 Subject: [PATCH 06/23] finish converting LFValidator for real --- .../org/lflang/validation/LFValidator.java | 285 +++++++++--------- 1 file changed, 149 insertions(+), 136 deletions(-) diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 86f37d7834..1780d6f298 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -82,16 +82,14 @@ import org.lflang.lf.StateVar; import org.lflang.lf.TargetDecl; 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; -import org.lflang.lf.WidthSpec; -import org.lflang.lf.WidthTerm; import org.lflang.lf.ReactorDecl; -import org.lflang.generator.ReactorInstance; import org.lflang.generator.NamedInstance; import static org.lflang.ASTUtils.*; @@ -376,20 +374,20 @@ public void checkConnection(Connection connection) { boolean leftInCycle = false; for (NamedInstance it : cycles) { - if ((lp.getContainer() == null && it.getDefinition() == lp.getVariable()) - || (it.getDefinition() == lp.getVariable() && it.getParent() == lp.getContainer())) { + if ((lp.getContainer() == null && it.getDefinition().equals(lp.getVariable())) + || (it.getDefinition().equals(lp.getVariable()) && it.getParent().equals(lp.getContainer()))) { leftInCycle = true; break; } } for (NamedInstance it : cycles) { - if ((rp.getContainer() == null && it.getDefinition() == rp.getVariable()) - || (it.getDefinition() == rp.getVariable() && it.getParent() == rp.getContainer())) { + if ((rp.getContainer() == null && it.getDefinition().equals(rp.getVariable())) + || (it.getDefinition().equals(rp.getVariable()) && it.getParent().equals(rp.getContainer()))) { if (leftInCycle) { String reactorName = ((Reactor) connection.eContainer()).getName(); error(String.format("Connection in reactor %s creates", reactorName) + - String.format("a cyclic dependency between %s and %s.", lp.toString(), rp.toString()), + String.format("a cyclic dependency between %s and %s.", toText(lp), toText(rp)), Literals.CONNECTION__DELAY); } } @@ -485,8 +483,8 @@ public void checkConnection(Connection connection) { for (Reaction reaction : reactor.getReactions()) { for (VarRef effect : reaction.getEffects()) { for (VarRef rightPort : connection.getRightPorts()) { - if (rightPort.getContainer() == effect.getContainer() && - rightPort.getVariable() == effect.getVariable()) { + if (rightPort.getContainer().equals(effect.getContainer()) && + rightPort.getVariable().equals(effect.getVariable())) { error("Cannot connect: Port named '" + effect.getVariable().getName() + "' is already effect of a reaction.", Literals.CONNECTION__RIGHT_PORTS @@ -502,8 +500,8 @@ public void checkConnection(Connection connection) { if (c != connection) { for (VarRef thisRightPort : connection.getRightPorts()) { for (VarRef thatRightPort : c.getRightPorts()) { - if (thisRightPort.getContainer() == thatRightPort.getContainer() && - thisRightPort.getVariable() == thatRightPort.getVariable()) { + if (thisRightPort.getContainer().equals(thatRightPort.getContainer()) && + thisRightPort.getVariable().equals(thatRightPort.getVariable())) { error( "Cannot connect: Port named '" + thisRightPort.getVariable().getName() + "' may only appear once on the right side of a connection.", @@ -809,128 +807,143 @@ public void checkPreamble(Preamble preamble) { } } -// @Check(CheckType.FAST) -// def checkReaction(Reaction reaction) { - -// if (reaction.triggers === null || reaction.triggers.size == 0) { -// warning("Reaction has no trigger.", Literals.REACTION__TRIGGERS) -// } -// val triggers = new HashSet -// // Make sure input triggers have no container and output sources do. -// for (trigger : reaction.triggers) { -// if (trigger instanceof VarRef) { -// triggers.add(trigger.variable) -// if (trigger.variable instanceof Input) { -// if (trigger.container !== null) { -// error('''Cannot have an input of a contained reactor as a trigger: «trigger.container.name».«trigger.variable.name»''', -// Literals.REACTION__TRIGGERS) -// } -// } else if (trigger.variable instanceof Output) { -// if (trigger.container === null) { -// error('''Cannot have an output of this reactor as a trigger: «trigger.variable.name»''', -// Literals.REACTION__TRIGGERS) -// } -// } -// } -// } - -// // Make sure input sources have no container and output sources do. -// // Also check that a source is not already listed as a trigger. -// for (source : reaction.sources) { -// if (triggers.contains(source.variable)) { -// error('''Source is already listed as a trigger: «source.variable.name»''', -// Literals.REACTION__SOURCES) -// } -// if (source.variable instanceof Input) { -// if (source.container !== null) { -// error('''Cannot have an input of a contained reactor as a source: «source.container.name».«source.variable.name»''', -// Literals.REACTION__SOURCES) -// } -// } else if (source.variable instanceof Output) { -// if (source.container === null) { -// error('''Cannot have an output of this reactor as a source: «source.variable.name»''', -// Literals.REACTION__SOURCES) -// } -// } -// } - -// // Make sure output effects have no container and input effects do. -// for (effect : reaction.effects) { -// if (effect.variable instanceof Input) { -// if (effect.container === null) { -// error('''Cannot have an input of this reactor as an effect: «effect.variable.name»''', -// Literals.REACTION__EFFECTS) -// } -// } else if (effect.variable instanceof Output) { -// if (effect.container !== null) { -// error('''Cannot have an output of a contained reactor as an effect: «effect.container.name».«effect.variable.name»''', -// Literals.REACTION__EFFECTS) -// } -// } -// } - -// // Report error if this reaction is part of a cycle. -// for (cycle : this.info.topologyCycles()) { -// val reactor = (reaction.eContainer) as Reactor -// if (cycle.exists[it.definition === reaction]) { -// // Report involved triggers. -// val trigs = new ArrayList() -// reaction.triggers.forEach [ t | -// (t instanceof VarRef && cycle.exists [ c | -// c.definition === (t as VarRef).variable -// ]) ? trigs.add((t as VarRef).toText) : { -// } -// ] -// if (trigs.size > 0) { -// error('''Reaction triggers involved in cyclic dependency in reactor «reactor.name»: «trigs.join(', ')».''', -// Literals.REACTION__TRIGGERS) -// } - -// // Report involved sources. -// val sources = new ArrayList() -// reaction.sources.forEach [ t | -// (cycle.exists[c|c.definition === t.variable]) -// ? sources.add(t.toText) -// : { -// } -// ] -// if (sources.size > 0) { -// error('''Reaction sources involved in cyclic dependency in reactor «reactor.name»: «sources.join(', ')».''', -// Literals.REACTION__SOURCES) -// } - -// // Report involved effects. -// val effects = new ArrayList() -// reaction.effects.forEach [ t | -// (cycle.exists[c|c.definition === t.variable]) -// ? effects.add(t.toText) -// : { -// } -// ] -// if (effects.size > 0) { -// error('''Reaction effects involved in cyclic dependency in reactor «reactor.name»: «effects.join(', ')».''', -// Literals.REACTION__EFFECTS) -// } - -// if (trigs.size + sources.size == 0) { -// error( -// '''Cyclic dependency due to preceding reaction. Consider reordering reactions within reactor «reactor.name» to avoid causality loop.''', -// reaction.eContainer, -// Literals.REACTOR__REACTIONS, -// reactor.reactions.indexOf(reaction)) -// } else if (effects.size == 0) { -// error( -// '''Cyclic dependency due to succeeding reaction. Consider reordering reactions within reactor «reactor.name» to avoid causality loop.''', -// reaction.eContainer, -// Literals.REACTOR__REACTIONS, -// reactor.reactions.indexOf(reaction)) -// } -// // Not reporting reactions that are part of cycle _only_ due to reaction ordering. -// // Moving them won't help solve the problem. -// } -// } -// // FIXME: improve error message. -// } + @Check(CheckType.FAST) + public void checkReaction(Reaction reaction) { + + if (reaction.getTriggers() == null || reaction.getTriggers().size() == 0) { + warning("Reaction has no trigger.", Literals.REACTION__TRIGGERS); + } + HashSet triggers = new HashSet<>(); + // Make sure input triggers have no container and output sources do. + for (TriggerRef trigger : reaction.getTriggers()) { + if (trigger instanceof VarRef) { + VarRef triggerVarRef = (VarRef) trigger; + triggers.add(triggerVarRef.getVariable()); + if (triggerVarRef instanceof Input) { + if (triggerVarRef.getContainer() != null) { + error(String.format("Cannot have an input of a contained reactor as a trigger: %s.%s", triggerVarRef.getContainer().getName(), triggerVarRef.getVariable().getName()), + Literals.REACTION__TRIGGERS); + } + } else if (triggerVarRef.getVariable() instanceof Output) { + if (triggerVarRef.getContainer() == null) { + error(String.format("Cannot have an output of this reactor as a trigger: %s", triggerVarRef.getVariable().getName()), + Literals.REACTION__TRIGGERS); + } + } + } + } + + // Make sure input sources have no container and output sources do. + // Also check that a source is not already listed as a trigger. + for (VarRef source : reaction.getSources()) { + if (triggers.contains(source.getVariable())) { + error(String.format("Source is already listed as a trigger: %s", source.getVariable().getName()), + Literals.REACTION__SOURCES); + } + if (source.getVariable() instanceof Input) { + if (source.getContainer() != null) { + error(String.format("Cannot have an input of a contained reactor as a source: %s.%s", source.getContainer().getName(), source.getVariable().getName()), + Literals.REACTION__SOURCES); + } + } else if (source.getVariable() instanceof Output) { + if (source.getContainer() == null) { + error(String.format("Cannot have an output of this reactor as a source: %s", source.getVariable().getName()), + Literals.REACTION__SOURCES); + } + } + } + + // Make sure output effects have no container and input effects do. + for (VarRef effect : reaction.getEffects()) { + if (effect.getVariable() instanceof Input) { + if (effect.getContainer() == null) { + error(String.format("Cannot have an input of this reactor as an effect: %s", effect.getVariable().getName()), + Literals.REACTION__EFFECTS); + } + } else if (effect.getVariable() instanceof Output) { + if (effect.getContainer() != null) { + error(String.format("Cannot have an output of a contained reactor as an effect: %s.%s", effect.getContainer().getName(), effect.getVariable().getName()), + Literals.REACTION__EFFECTS); + } + } + } + + // // Report error if this reaction is part of a cycle. + Set> cycles = this.info.topologyCycles(); + Reactor reactor = (Reactor) reaction.eContainer(); + boolean reactionInCycle = false; + for (NamedInstance it : cycles) { + if (it.getDefinition().equals(reaction)) { + reactionInCycle = true; + } + } + if (reactionInCycle) { + // Report involved triggers. + List trigs = new ArrayList<>(); + for (TriggerRef t : reaction.getTriggers()) { + if (!(t instanceof VarRef)) { + continue; + } + VarRef tVarRef = (VarRef) t; + for (NamedInstance c : cycles) { + if (c.getDefinition().equals(tVarRef.getVariable())) { + trigs.add(toText(tVarRef)); + } + } + } + if (trigs.size() > 0) { + error(String.format("Reaction triggers involved in cyclic dependency in reactor %s: %s.", reactor.getName(), String.join(",", trigs)), + Literals.REACTION__TRIGGERS); + } + + // Report involved sources. + List sources = new ArrayList<>(); + for (VarRef t : reaction.getSources()) { + for (NamedInstance c : cycles) { + if (c.getDefinition().equals(t.getVariable())) { + sources.add(toText(t)); + } + } + } + if (sources.size() > 0) { + error(String.format("Reaction sources involved in cyclic dependency in reactor %s: %s.", reactor.getName(), String.join(", ", sources)), + Literals.REACTION__SOURCES); + } + + // Report involved effects. + List effects = new ArrayList<>(); + for (VarRef t : reaction.getEffects()) { + for (NamedInstance c : cycles) { + if (c.getDefinition().equals(t.getVariable())) { + sources.add(toText(t)); + } + } + } + if (effects.size() > 0) { + error(String.format("Reaction effects involved in cyclic dependency in reactor %s: %s.", reactor.getName(), String.join(", ", effects)), + Literals.REACTION__EFFECTS); + } + + if (trigs.size() + sources.size() == 0) { + error( + String.format("Cyclic dependency due to preceding reaction. Consider reordering reactions within reactor %s to avoid causality loop.", reactor.getName()), + reaction.eContainer(), + Literals.REACTOR__REACTIONS, + reactor.getReactions().indexOf(reaction) + ); + } else if (effects.size() == 0) { + error( + String.format("Cyclic dependency due to succeeding reaction. Consider reordering reactions within reactor %s to avoid causality loop.", reactor.getName()), + reaction.eContainer(), + Literals.REACTOR__REACTIONS, + reactor.getReactions().indexOf(reaction) + ); + } + // Not reporting reactions that are part of cycle _only_ due to reaction ordering. + // Moving them won't help solve the problem. + } + // FIXME: improve error message. + } private int countMain(TreeIterator iter) { int nMain = 0; @@ -1257,7 +1270,7 @@ public void checkTargetProperties(KeyValuePairs targetProperties) { for (Reactor reactor : info.model.getReactors()) { // Check to see if the program has a physical action in a reactor for (Action action : reactor.getActions()) { - if (action.getOrigin() == ActionOrigin.PHYSICAL) { + if (action.getOrigin().equals(ActionOrigin.PHYSICAL)) { error( "The fast target property is incompatible with physical actions.", fastTargetProperty, @@ -1299,7 +1312,7 @@ public void checkValueAsTime(Value value) { 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 == true) { + if (!isOfTimeType(value.getParameter()) && target.requiresTypes) { error("Parameter is not of time type", Literals.VALUE__PARAMETER); } From c43082b991b40b1938c447709f372b90ccf1d451 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Wed, 26 Jan 2022 13:42:53 -0800 Subject: [PATCH 07/23] delete LFValidator.xtend --- .../org/lflang/validation/LFValidator.xtend | 1328 ----------------- 1 file changed, 1328 deletions(-) delete mode 100644 org.lflang/src/org/lflang/validation/LFValidator.xtend diff --git a/org.lflang/src/org/lflang/validation/LFValidator.xtend b/org.lflang/src/org/lflang/validation/LFValidator.xtend deleted file mode 100644 index cecc376392..0000000000 --- a/org.lflang/src/org/lflang/validation/LFValidator.xtend +++ /dev/null @@ -1,1328 +0,0 @@ -/* Validation checks for Lingua Franca code. */ - -/************* - * Copyright (c) 2019-2020, The University of California at Berkeley. - - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ***************/ -package org.lflang.validation - -import com.google.inject.Inject - -import java.util.ArrayList -import java.util.HashSet -import java.util.List -import java.util.Set - -import org.eclipse.emf.common.util.EList -import org.eclipse.emf.ecore.EStructuralFeature -import org.eclipse.xtext.validation.Check -import org.eclipse.xtext.validation.ValidationMessageAcceptor; -import org.eclipse.xtend.lib.annotations.Accessors - -import org.lflang.FileConfig -import org.lflang.ModelInfo -import org.lflang.Target -import org.lflang.TargetProperty -import org.lflang.TimeValue -import org.lflang.lf.Action -import org.lflang.lf.ActionOrigin -import org.lflang.lf.Assignment -import org.lflang.lf.Connection -import org.lflang.lf.Deadline -import org.lflang.lf.Host -import org.lflang.lf.IPV4Host -import org.lflang.lf.IPV6Host -import org.lflang.lf.Import -import org.lflang.lf.ImportedReactor -import org.lflang.lf.Input -import org.lflang.lf.Instantiation -import org.lflang.lf.KeyValuePair -import org.lflang.lf.KeyValuePairs -import org.lflang.lf.LfPackage.Literals -import org.lflang.lf.Model -import org.lflang.lf.NamedHost -import org.lflang.lf.Output -import org.lflang.lf.Parameter -import org.lflang.lf.Port -import org.lflang.lf.Preamble -import org.lflang.lf.Reaction -import org.lflang.lf.Reactor -import org.lflang.lf.Serializer -import org.lflang.lf.STP -import org.lflang.lf.StateVar -import org.lflang.lf.TargetDecl -import org.lflang.lf.Timer -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 -import org.lflang.lf.WidthSpec - -import static extension org.lflang.ASTUtils.* -import static extension org.lflang.JavaAstUtils.* -import org.lflang.federated.serialization.SupportedSerializers - -/** - * Custom validation checks for Lingua Franca programs. - * - * Also see: https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#validation - * - * @author{Edward A. Lee } - * @author{Marten Lohstroh } - * @author{Matt Weber } - * @author(Christian Menard } - * - */ -class LFValidator extends BaseLFValidator { - - var Target target - - public var info = new ModelInfo() - - @Accessors(PUBLIC_GETTER) - val ValidatorErrorReporter errorReporter = new ValidatorErrorReporter(getMessageAcceptor(), - new ValidatorStateAccess()) - - @Inject(optional = true) - ValidationMessageAcceptor messageAcceptor - - /** - * Regular expression to check the validity of IPV4 addresses (due to David M. Syzdek). - */ - static val ipv4Regex = "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}" + - "(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])" - - /** - * Regular expression to check the validity of IPV6 addresses (due to David M. Syzdek), - * with minor adjustment to allow up to six IPV6 segments (without truncation) in front - * of an embedded IPv4-address. - **/ - static val ipv6Regex = - "(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" + - "([0-9a-fA-F]{1,4}:){1,7}:|" + - "([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" + - "([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" + - "([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" + - "([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" + - "([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" + - "[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" + - ":((:[0-9a-fA-F]{1,4}){1,7}|:)|" + - "fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|" + - "::(ffff(:0{1,4}){0,1}:){0,1}" + ipv4Regex + "|" + - "([0-9a-fA-F]{1,4}:){1,4}:" + ipv4Regex + "|" + - "([0-9a-fA-F]{1,4}:){1,6}" + ipv4Regex + ")" - - static val usernameRegex = "^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\\$)$" - - static val hostOrFQNRegex = "^([a-z0-9]+(-[a-z0-9]+)*)|(([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,})$" - - public static val GLOBALLY_DUPLICATE_NAME = 'GLOBALLY_DUPLICATE_NAME' - - static val spacingViolationPolicies = #['defer', 'drop', 'replace'] - - val List targetPropertyErrors = new ArrayList - - val List targetPropertyWarnings = new ArrayList - - def List getTargetPropertyErrors() { - this.targetPropertyErrors - } - - override ValidationMessageAcceptor getMessageAcceptor() { - return messageAcceptor === null ? this : messageAcceptor - } - - /** - * Returns true if target is C or a C-based target like CCpp. - */ - def boolean isCBasedTarget() { - return (this.target == Target.C || this.target == Target.CCPP); - } - - @Check - def checkImportedReactor(ImportedReactor reactor) { - if (reactor.unused) { - warning("Unused reactor class.", - Literals.IMPORTED_REACTOR__REACTOR_CLASS) - } - - if (info.instantiationGraph.hasCycles) { - val cycleSet = newHashSet - info.instantiationGraph.cycles.forEach[forEach[cycleSet.add(it)]] - if (dependsOnCycle(reactor.toDefinition, cycleSet, newHashSet)) { - error("Imported reactor '" + reactor.toDefinition.name + - "' has cyclic instantiation in it.", - Literals.IMPORTED_REACTOR__REACTOR_CLASS) - } - } - } - - @Check - def checkImport(Import imp) { - if (imp.reactorClasses.get(0).toDefinition.eResource.errors.size > 0) { - error("Error loading resource.", Literals.IMPORT__IMPORT_URI) // FIXME: print specifics. - return - } - - // FIXME: report error if resource cannot be resolved. - - for (reactor : imp.reactorClasses) { - if (!reactor.unused) { - return - } - } - warning("Unused import.", Literals.IMPORT__IMPORT_URI) - } - - // ////////////////////////////////////////////////// - // // Helper functions for checks to be performed on multiple entities - // Check the name of a feature for illegal substrings. - private def checkName(String name, EStructuralFeature feature) { - - // Raises an error if the string starts with two underscores. - if (name.length() >= 2 && name.substring(0, 2).equals("__")) { - error(UNDERSCORE_MESSAGE + name, feature) - } - - if (this.target.isReservedIdent(name)) { - error(RESERVED_MESSAGE + name, feature) - } - - if (this.target == Target.TS) { - // "actions" is a reserved word within a TS reaction - if (name.equals("actions")) { - error(ACTIONS_MESSAGE + name, feature) - } - } - - } - - /** - * Report whether a given reactor has dependencies on a cyclic - * instantiation pattern. This means the reactor has an instantiation - * in it -- directly or in one of its contained reactors -- that is - * self-referential. - * @param reactor The reactor definition to find out whether it has any - * dependencies on cyclic instantiations. - * @param cycleSet The set of all reactors that are part of an - * instantiation cycle. - * @param visited The set of nodes already visited in this graph traversal. - */ - private def boolean dependsOnCycle(Reactor reactor, Set cycleSet, - Set visited) { - val origins = info.instantiationGraph.getUpstreamAdjacentNodes(reactor) - if (visited.contains(reactor)) { - return false - } else { - visited.add(reactor) - if (origins.exists[cycleSet.contains(it)] || origins.exists [ - it.dependsOnCycle(cycleSet, visited) - ]) { - // Reached a cycle. - return true - } - } - return false - } - - /** - * Report whether a given imported reactor is used in this resource or not. - * @param reactor The imported reactor to check whether it is used. - */ - private def boolean isUnused(ImportedReactor reactor) { - val instantiations = reactor.eResource.allContents.filter(Instantiation) - val subclasses = reactor.eResource.allContents.filter(Reactor) - if (instantiations. - forall[it.reactorClass !== reactor && it.reactorClass !== reactor.reactorClass] && - subclasses.forall [ - it.superClasses.forall [ - it !== reactor && it !== reactor.reactorClass - ] - ]) { - return true - } - return false - } - - - // ////////////////////////////////////////////////// - // // Functions to set up data structures for performing checks. - // FAST ensures that these checks run whenever a file is modified. - // Alternatives are NORMAL (when saving) and EXPENSIVE (only when right-click, validate). - - // ////////////////////////////////////////////////// - // // The following checks are in alphabetical order. - @Check(FAST) - def checkAction(Action action) { - checkName(action.name, Literals.VARIABLE__NAME) - if (action.origin == ActionOrigin.NONE) { - error( - "Action must have modifier `logical` or `physical`.", - Literals.ACTION__ORIGIN - ) - } - if (action.policy !== null && - !spacingViolationPolicies.contains(action.policy)) { - error( - "Unrecognized spacing violation policy: " + action.policy + - ". Available policies are: " + - spacingViolationPolicies.join(", ") + ".", - Literals.ACTION__POLICY) - } - } - - @Check(FAST) - def checkAssignment(Assignment assignment) { - // If the left-hand side is a time parameter, make sure the assignment has units - if (assignment.lhs.isOfTimeType) { - if (assignment.rhs.size > 1) { - error("Incompatible type.", Literals.ASSIGNMENT__RHS) - } else if (assignment.rhs.size > 0) { - val v = assignment.rhs.get(0) - if (!v.isValidTime) { - if (v.parameter === 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.parameter.name + " to " + - assignment.lhs.name + - ". The latter is a time parameter, but the former is not.", - Literals.ASSIGNMENT__RHS) - } - } - } - // If this assignment overrides a parameter that is used in a deadline, - // report possible overflow. - if (isCBasedTarget && - this.info.overflowingAssignments.contains(assignment)) { - error( - "Time value used to specify a deadline exceeds the maximum of " + - TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", - Literals.ASSIGNMENT__RHS) - } - } - - if(!assignment.braces.isNullOrEmpty() && this.target != Target.CPP) { - error("Brace initializers are only supported for the C++ target", Literals.ASSIGNMENT__BRACES) - } - - // FIXME: lhs is list => rhs is list - // lhs is fixed with size n => rhs is fixed with size n - // FIXME": similar checks for decl/init - // Specifically for C: list can only be literal or time lists - } - - @Check(FAST) - def checkWidthSpec(WidthSpec widthSpec) { - if (!this.target.supportsMultiports()) { - error("Multiports and banks are currently not supported by the given target.", - Literals.WIDTH_SPEC__TERMS) - } else { - for (term : widthSpec.terms) { - if (term.parameter !== null) { - if (!this.target.supportsParameterizedWidths()) { - error("Parameterized widths are not supported by this target.", Literals.WIDTH_SPEC__TERMS) - } - } else if (term.port !== null) { - // Widths given with `widthof()` are not supported (yet?). - // This feature is currently only used for after delays. - error("widthof is not supported.", Literals.WIDTH_SPEC__TERMS) - } else if (term.code !== null) { - if (this.target != Target.CPP) { - error("This target does not support width given as code.", Literals.WIDTH_SPEC__TERMS) - } - } else if (term.width < 0) { - error("Width must be a positive integer.", Literals.WIDTH_SPEC__TERMS) - } - } - } - } - - @Check(FAST) - def checkConnection(Connection connection) { - - // Report if connection is part of a cycle. - val cycles = this.info.topologyCycles(); - for (lp : connection.leftPorts) { - for (rp : connection.rightPorts) { - var leftInCycle = false - if ((lp.container === null && cycles.exists [ - it.definition === lp.variable - ]) || cycles.exists [ - (it.definition === lp.variable && it.parent === lp.container) - ]) { - leftInCycle = true - } - - if ((rp.container === null && cycles.exists [ - it.definition === rp.variable - ]) || cycles.exists [ - (it.definition === rp.variable && it.parent === rp.container) - ]) { - if (leftInCycle) { - val reactorName = (connection.eContainer as Reactor).name - // Only report of _both_ reference ports are in the cycle. - error('''Connection in reactor «reactorName» creates ''' + - '''a cyclic dependency between «lp.toText» and ''' + - '''«rp.toText».''', Literals.CONNECTION__DELAY - ) - } - } - } - } - - // FIXME: look up all ReactorInstance objects that have a definition equal to the - // container of this connection. For each of those occurrences, the widths have to match. - // For the C target, since C has such a weak type system, check that - // the types on both sides of every connection match. For other languages, - // we leave type compatibility that language's compiler or interpreter. - if (isCBasedTarget) { - var type = null as Type - for (port : connection.leftPorts) { - // If the variable is not a port, then there is some other - // error. Avoid a class cast exception. - if (port.variable instanceof Port) { - if (type === null) { - type = (port.variable as Port).type - } else { - // Unfortunately, xtext does not generate a suitable equals() - // method for AST types, so we have to manually check the types. - if (!sameType(type, (port.variable as Port).type)) { - error("Types do not match.", Literals.CONNECTION__LEFT_PORTS) - } - } - } - } - for (port : connection.rightPorts) { - // If the variable is not a port, then there is some other - // error. Avoid a class cast exception. - if (port.variable instanceof Port) { - if (type === null) { - type = (port.variable as Port).type - } else { - if (!sameType(type, (port.variable as Port).type)) { - error("Types do not match.", Literals.CONNECTION__RIGHT_PORTS) - } - } - } - } - } - - // Check whether the total width of the left side of the connection - // matches the total width of the right side. This cannot be determined - // here if the width is not given as a constant. In that case, it is up - // to the code generator to check it. - var leftWidth = 0 - for (port : connection.leftPorts) { - val width = inferPortWidth(port, null, null) // null args imply incomplete check. - if (width < 0 || leftWidth < 0) { - // Cannot determine the width of the left ports. - leftWidth = -1 - } else { - leftWidth += width - } - } - var rightWidth = 0 - for (port : connection.rightPorts) { - val width = inferPortWidth(port, null, null) // null args imply incomplete check. - if (width < 0 || rightWidth < 0) { - // Cannot determine the width of the left ports. - rightWidth = -1 - } else { - rightWidth += width - } - } - - if (leftWidth !== -1 && rightWidth !== -1 && leftWidth != rightWidth) { - if (connection.isIterated) { - if (leftWidth == 0 || rightWidth % leftWidth != 0) { - // FIXME: The second argument should be Literals.CONNECTION, but - // stupidly, xtext will not accept that. There seems to be no way to - // report an error for the whole connection statement. - warning('''Left width «leftWidth» does not divide right width «rightWidth»''', - Literals.CONNECTION__LEFT_PORTS - ) - } - } else { - // FIXME: The second argument should be Literals.CONNECTION, but - // stupidly, xtext will not accept that. There seems to be no way to - // report an error for the whole connection statement. - warning('''Left width «leftWidth» does not match right width «rightWidth»''', - Literals.CONNECTION__LEFT_PORTS - ) - } - } - - val reactor = connection.eContainer as Reactor - - // Make sure the right port is not already an effect of a reaction. - for (reaction : reactor.reactions) { - for (effect : reaction.effects) { - for (rightPort : connection.rightPorts) { - if (rightPort.container === effect.container && - rightPort.variable === effect.variable) { - error("Cannot connect: Port named '" + effect.variable.name + - "' is already effect of a reaction.", - Literals.CONNECTION__RIGHT_PORTS - ) - } - } - } - } - - // Check that the right port does not already have some other - // upstream connection. - for (c : reactor.connections) { - if (c !== connection) { - for (thisRightPort : connection.rightPorts) { - for (thatRightPort : c.rightPorts) { - if (thisRightPort.container === thatRightPort.container && - thisRightPort.variable === thatRightPort.variable) { - error( - "Cannot connect: Port named '" + thisRightPort.variable.name + - "' may only appear once on the right side of a connection.", - Literals.CONNECTION__RIGHT_PORTS) - } - } - } - } - } - } - - /** - * Return true if the two types match. Unfortunately, xtext does not - * seem to create a suitable equals() method for Type, so we have to - * do this manually. - */ - private def boolean sameType(Type type1, Type type2) { - // Most common case first. - if (type1.id !== null) { - if (type1.stars !== null) { - if (type2.stars === null) return false - if (type1.stars.length != type2.stars.length) return false - } - return (type1.id.equals(type2.id)) - } - if (type1 === null) { - if (type2 === null) return true - return false - } - // Type specification in the grammar is: - // (time?='time' (arraySpec=ArraySpec)?) | ((id=(DottedName) (stars+='*')* ('<' typeParms+=TypeParm (',' typeParms+=TypeParm)* '>')? (arraySpec=ArraySpec)?) | code=Code); - if (type1.time) { - if (!type2.time) return false - // Ignore the arraySpec because that is checked when connection - // is checked for balance. - return true - } - // Type must be given in a code body. - return (type1.code.body.equals(type2?.code?.body)) - } - - @Check(FAST) - def checkDeadline(Deadline deadline) { - if (isCBasedTarget && - this.info.overflowingDeadlines.contains(deadline)) { - error( - "Deadline exceeds the maximum of " + - TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", - Literals.DEADLINE__DELAY) - } - } -@Check(FAST) - def checkSTPOffset(STP stp) { - if (isCBasedTarget && - this.info.overflowingDeadlines.contains(stp)) { - error( - "STP offset exceeds the maximum of " + - TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", - Literals.DEADLINE__DELAY) - } - } - - @Check(FAST) - def checkInput(Input input) { - checkName(input.name, Literals.VARIABLE__NAME) - if (target.requiresTypes) { - if (input.type === null) { - error("Input must have a type.", Literals.TYPED_VARIABLE__TYPE) - } - } - - // mutable has no meaning in C++ - if (input.mutable && this.target == Target.CPP) { - warning( - "The mutable qualifier has no meaning for the C++ target and should be removed. " + - "In C++, any value can be made mutable by calling get_mutable_copy().", - Literals.INPUT__MUTABLE - ) - } - - // Variable width multiports are not supported (yet?). - if (input.widthSpec !== null && input.widthSpec.ofVariableLength) { - error("Variable-width multiports are not supported.", Literals.PORT__WIDTH_SPEC) - } - } - - @Check(FAST) - def checkInstantiation(Instantiation instantiation) { - checkName(instantiation.name, Literals.INSTANTIATION__NAME) - val reactor = instantiation.reactorClass.toDefinition - if (reactor.isMain || reactor.isFederated) { - error( - "Cannot instantiate a main (or federated) reactor: " + - instantiation.reactorClass.name, - Literals.INSTANTIATION__REACTOR_CLASS - ) - } - - // Report error if this instantiation is part of a cycle. - // FIXME: improve error message. - // FIXME: Also report if there exists a cycle within. - if (this.info.instantiationGraph.cycles.size > 0) { - for (cycle : this.info.instantiationGraph.cycles) { - val container = instantiation.eContainer as Reactor - if (cycle.contains(container) && cycle.contains(reactor)) { - error( - "Instantiation is part of a cycle: " + - cycle.fold(newArrayList, [ list, r | - list.add(r.name); - list - ]).join(', ') + ".", - Literals.INSTANTIATION__REACTOR_CLASS - ) - } - } - } - // Variable width multiports are not supported (yet?). - if (instantiation.widthSpec !== null - && instantiation.widthSpec.ofVariableLength - ) { - if (isCBasedTarget) { - warning("Variable-width banks are for internal use only.", - Literals.INSTANTIATION__WIDTH_SPEC - ) - } else { - error("Variable-width banks are not supported.", - Literals.INSTANTIATION__WIDTH_SPEC - ) - } - } - } - - /** Check target parameters, which are key-value pairs. */ - @Check(FAST) - def checkKeyValuePair(KeyValuePair param) { - // Check only if the container's container is a Target. - if (param.eContainer.eContainer instanceof TargetDecl) { - - val prop = TargetProperty.forName(param.name) - - // Make sure the key is valid. - if (prop === null) { - warning( - "Unrecognized target parameter: " + param.name + - ". Recognized parameters are: " + - TargetProperty.getOptions().join(", ") + ".", - Literals.KEY_VALUE_PAIR__NAME) - } - - // Check whether the property is supported by the target. - if (!prop.supportedBy.contains(this.target)) { - warning( - "The target parameter: " + param.name + - " is not supported by the " + this.target + - " target and will thus be ignored.", - Literals.KEY_VALUE_PAIR__NAME) - } - - // Report problem with the assigned value. - prop.type.check(param.value, param.name, this) - targetPropertyErrors.forEach [ - error(it, Literals.KEY_VALUE_PAIR__VALUE) - ] - targetPropertyErrors.clear() - targetPropertyWarnings.forEach [ - warning(it, Literals.KEY_VALUE_PAIR__VALUE) - ] - targetPropertyWarnings.clear() - } - } - - @Check(FAST) - def checkOutput(Output output) { - checkName(output.name, Literals.VARIABLE__NAME) - if (this.target.requiresTypes) { - if (output.type === null) { - error("Output must have a type.", Literals.TYPED_VARIABLE__TYPE) - } - } - - // Variable width multiports are not supported (yet?). - if (output.widthSpec !== null && output.widthSpec.ofVariableLength) { - error("Variable-width multiports are not supported.", Literals.PORT__WIDTH_SPEC) - } - } - - @Check(FAST) - def checkModel(Model model) { - // Since we're doing a fast check, we only want to update - // if the model info hasn't been initialized yet. If it has, - // we use the old information and update it during a normal - // check (see below). - if (!info.updated) { - info.update(model, errorReporter) - } - } - - @Check(NORMAL) - def updateModelInfo(Model model) { - info.update(model, errorReporter) - } - - @Check(FAST) - def checkParameter(Parameter param) { - checkName(param.name, Literals.PARAMETER__NAME) - - if (param.init.exists[it.parameter !== null]) { - // Initialization using parameters is forbidden. - error("Parameter cannot be initialized using parameter.", - Literals.PARAMETER__INIT) - } - - if (param.init === null || param.init.size == 0) { - // All parameters must be initialized. - error("Uninitialized parameter.", Literals.PARAMETER__INIT) - } else if (param.isOfTimeType) { - // We do additional checks on types because we can make stronger - // assumptions about them. - - // If the parameter is not a list, cannot be initialized - // using a one. - if (param.init.size > 1 && param.type.arraySpec === null) { - error("Time parameter cannot be initialized using a list.", - Literals.PARAMETER__INIT) - } else { - // The parameter is a singleton time. - val init = param.init.get(0) - if (init.time === null) { - if (init !== null && !init.isZero) { - if (init.isInteger) { - error("Missing time unit.", Literals.PARAMETER__INIT) - } else { - error("Invalid time literal.", - Literals.PARAMETER__INIT) - } - } - } // If time is not null, we know that a unit is also specified. - } - } else if (this.target.requiresTypes) { - // Report missing target type. - if (param.inferredType.isUndefined()) { - error("Type declaration missing.", Literals.PARAMETER__TYPE) - } - } - - if (isCBasedTarget && - this.info.overflowingParameters.contains(param)) { - error( - "Time value used to specify a deadline exceeds the maximum of " + - TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", - Literals.PARAMETER__INIT) - } - - if(!param.braces.isNullOrEmpty && this.target != Target.CPP) { - error("Brace initializers are only supported for the C++ target", Literals.PARAMETER__BRACES) - } - - } - - @Check(FAST) - def checkPreamble(Preamble preamble) { - if (this.target == Target.CPP) { - if (preamble.visibility == Visibility.NONE) { - error( - "Preambles for the C++ target need a visibility qualifier (private or public)!", - Literals.PREAMBLE__VISIBILITY - ) - } else if (preamble.visibility == Visibility.PRIVATE) { - val container = preamble.eContainer - if (container !== null && container instanceof Reactor) { - val reactor = container as Reactor - if (reactor.isGeneric) { - warning( - "Private preambles in generic reactors are not truly private. " + - "Since the generated code is placed in a *_impl.hh file, it will " + - "be visible on the public interface. Consider using a public " + - "preamble within the reactor or a private preamble on file scope.", - Literals.PREAMBLE__VISIBILITY) - } - } - } - } else if (preamble.visibility != Visibility.NONE) { - warning( - '''The «preamble.visibility» qualifier has no meaning for the «this.target.name» target. It should be removed.''', - Literals.PREAMBLE__VISIBILITY - ) - } - } - - @Check(FAST) - def checkReaction(Reaction reaction) { - - if (reaction.triggers === null || reaction.triggers.size == 0) { - warning("Reaction has no trigger.", Literals.REACTION__TRIGGERS) - } - val triggers = new HashSet - // Make sure input triggers have no container and output sources do. - for (trigger : reaction.triggers) { - if (trigger instanceof VarRef) { - triggers.add(trigger.variable) - if (trigger.variable instanceof Input) { - if (trigger.container !== null) { - error('''Cannot have an input of a contained reactor as a trigger: «trigger.container.name».«trigger.variable.name»''', - Literals.REACTION__TRIGGERS) - } - } else if (trigger.variable instanceof Output) { - if (trigger.container === null) { - error('''Cannot have an output of this reactor as a trigger: «trigger.variable.name»''', - Literals.REACTION__TRIGGERS) - } - } - } - } - - // Make sure input sources have no container and output sources do. - // Also check that a source is not already listed as a trigger. - for (source : reaction.sources) { - if (triggers.contains(source.variable)) { - error('''Source is already listed as a trigger: «source.variable.name»''', - Literals.REACTION__SOURCES) - } - if (source.variable instanceof Input) { - if (source.container !== null) { - error('''Cannot have an input of a contained reactor as a source: «source.container.name».«source.variable.name»''', - Literals.REACTION__SOURCES) - } - } else if (source.variable instanceof Output) { - if (source.container === null) { - error('''Cannot have an output of this reactor as a source: «source.variable.name»''', - Literals.REACTION__SOURCES) - } - } - } - - // Make sure output effects have no container and input effects do. - for (effect : reaction.effects) { - if (effect.variable instanceof Input) { - if (effect.container === null) { - error('''Cannot have an input of this reactor as an effect: «effect.variable.name»''', - Literals.REACTION__EFFECTS) - } - } else if (effect.variable instanceof Output) { - if (effect.container !== null) { - error('''Cannot have an output of a contained reactor as an effect: «effect.container.name».«effect.variable.name»''', - Literals.REACTION__EFFECTS) - } - } - } - - // Report error if this reaction is part of a cycle. - val cycles = this.info.topologyCycles(); - val reactor = (reaction.eContainer) as Reactor - if (cycles.exists[it.definition === reaction]) { - // Report involved triggers. - val trigs = new ArrayList() - reaction.triggers.forEach [ t | - (t instanceof VarRef && cycles.exists [ c | - c.definition === (t as VarRef).variable - ]) ? trigs.add((t as VarRef).toText) : { - } - ] - if (trigs.size > 0) { - error('''Reaction triggers involved in cyclic dependency in reactor «reactor.name»: «trigs.join(', ')».''', - Literals.REACTION__TRIGGERS) - } - - // Report involved sources. - val sources = new ArrayList() - reaction.sources.forEach [ t | - (cycles.exists[c|c.definition === t.variable]) - ? sources.add(t.toText) - : { - } - ] - if (sources.size > 0) { - error('''Reaction sources involved in cyclic dependency in reactor «reactor.name»: «sources.join(', ')».''', - Literals.REACTION__SOURCES) - } - - // Report involved effects. - val effects = new ArrayList() - reaction.effects.forEach [ t | - (cycles.exists[c|c.definition === t.variable]) - ? effects.add(t.toText) - : { - } - ] - if (effects.size > 0) { - error('''Reaction effects involved in cyclic dependency in reactor «reactor.name»: «effects.join(', ')».''', - Literals.REACTION__EFFECTS) - } - - if (trigs.size + sources.size == 0) { - error( - '''Cyclic dependency due to preceding reaction. Consider reordering reactions within reactor «reactor.name» to avoid causality loop.''', - reaction.eContainer, - Literals.REACTOR__REACTIONS, - reactor.reactions.indexOf(reaction)) - } else if (effects.size == 0) { - error( - '''Cyclic dependency due to succeeding reaction. Consider reordering reactions within reactor «reactor.name» to avoid causality loop.''', - reaction.eContainer, - Literals.REACTOR__REACTIONS, - reactor.reactions.indexOf(reaction)) - } - // Not reporting reactions that are part of cycle _only_ due to reaction ordering. - // Moving them won't help solve the problem. - } - } - - @Check(FAST) - def checkReactor(Reactor reactor) { - val name = FileConfig.nameWithoutExtension(reactor.eResource) - if (reactor.name === null) { - if (!reactor.isFederated && !reactor.isMain) { - error( - "Reactor must be named.", - Literals.REACTOR_DECL__NAME - ) - } - // Prevent NPE in tests below. - return - } else { - if (reactor.isFederated || reactor.isMain) { - if(!reactor.name.equals(name)) { - // Make sure that if the name is omitted, the reactor is indeed main. - error( - "Name of main reactor must match the file name (or be omitted).", - Literals.REACTOR_DECL__NAME - ) - } - // Do not allow multiple main/federated reactors. - if (reactor.eResource.allContents.filter(Reactor).filter[it.isMain || it.isFederated].size > 1) { - var attribute = Literals.REACTOR__MAIN - if (reactor.isFederated) { - attribute = Literals.REACTOR__FEDERATED - } - if (reactor.isMain || reactor.isFederated) { - error( - "Multiple definitions of main or federated reactor.", - attribute - ) - } - } - } else if (reactor.eResource.allContents.filter(Reactor).exists[it.isMain || it.isFederated] && reactor.name.equals(name)) { - // Make sure that if a main reactor is specified, there are no - // ordinary reactors that clash with it. - error( - "Name conflict with main reactor.", - Literals.REACTOR_DECL__NAME - ) - } - } - - // If there is a main reactor (with no name) then disallow other (non-main) reactors - // matching the file name. - - checkName(reactor.name, Literals.REACTOR_DECL__NAME) - - // C++ reactors may not be called 'preamble' - if (this.target == Target.CPP && reactor.name.equalsIgnoreCase("preamble")) { - error( - "Reactor cannot be named '" + reactor.name + "'", - Literals.REACTOR_DECL__NAME - ) - } - - if (reactor.host !== null) { - if (!reactor.isFederated) { - error( - "Cannot assign a host to reactor '" + reactor.name + - "' because it is not federated.", - Literals.REACTOR__HOST - ) - } - } - - var variables = new ArrayList() - variables.addAll(reactor.inputs) - variables.addAll(reactor.outputs) - variables.addAll(reactor.actions) - variables.addAll(reactor.timers) - - // Perform checks on super classes. - for (superClass : reactor.superClasses ?: emptyList) { - var conflicts = new HashSet() - - // Detect input conflicts - checkConflict(superClass.toDefinition.inputs, reactor.inputs, variables, conflicts) - // Detect output conflicts - checkConflict(superClass.toDefinition.outputs, reactor.outputs, variables, conflicts) - // Detect output conflicts - checkConflict(superClass.toDefinition.actions, reactor.actions, variables, conflicts) - // Detect conflicts - for (timer : superClass.toDefinition.timers) { - if (timer.hasNameConflict(variables.filter[it | !reactor.timers.contains(it)])) { - conflicts.add(timer) - } else { - variables.add(timer) - } - } - - // Report conflicts. - if (conflicts.size > 0) { - val names = new ArrayList(); - conflicts.forEach[it | names.add(it.name)] - error( - '''Cannot extend «superClass.name» due to the following conflicts: «names.join(',')».''', - Literals.REACTOR__SUPER_CLASSES - ) - } - } - } - /** - * For each input, report a conflict if: - * 1) the input exists and the type doesn't match; or - * 2) the input has a name clash with variable that is not an input. - * @param superVars List of typed variables of a particular kind (i.e., - * inputs, outputs, or actions), found in a super class. - * @param sameKind Typed variables of the same kind, found in the subclass. - * @param allOwn Accumulator of non-conflicting variables incorporated in the - * subclass. - * @param conflicts Set of variables that are in conflict, to be used by this - * function to report conflicts. - */ - def checkConflict (EList superVars, - EList sameKind, List allOwn, - HashSet conflicts) { - for (superVar : superVars) { - val match = sameKind.findFirst [ it | - it.name.equals(superVar.name) - ] - val rest = allOwn.filter[it|!sameKind.contains(it)] - if ((match !== null && superVar.type !== match.type) || superVar.hasNameConflict(rest)) { - conflicts.add(superVar) - } else { - allOwn.add(superVar) - } - } - } - - /** - * Report whether the name of the given element matches any variable in - * the ones to check against. - * @param element The element to compare against all variables in the given iterable. - * @param toCheckAgainst Iterable variables to compare the given element against. - */ - def boolean hasNameConflict(Variable element, - Iterable toCheckAgainst) { - if (toCheckAgainst.filter[it|it.name.equals(element.name)].size > 0) { - return true - } - return false - } - - @Check(FAST) - def checkHost(Host host) { - val addr = host.addr - val user = host.user - if (user !== null && !user.matches(usernameRegex)) { - warning( - "Invalid user name.", - Literals.HOST__USER - ) - } - if (host instanceof IPV4Host && !addr.matches(ipv4Regex)) { - warning( - "Invalid IP address.", - Literals.HOST__ADDR - ) - } else if (host instanceof IPV6Host && !addr.matches(ipv6Regex)) { - warning( - "Invalid IP address.", - Literals.HOST__ADDR - ) - } else if (host instanceof NamedHost && !addr.matches(hostOrFQNRegex)) { - warning( - "Invalid host name or fully qualified domain name.", - Literals.HOST__ADDR - ) - } - } - - /** - * Check if the requested serialization is supported. - */ - @Check(FAST) - def checkSerializer(Serializer serializer) { - var boolean isValidSerializer = false; - for (SupportedSerializers method : SupportedSerializers.values()) { - if (method.name().equalsIgnoreCase(serializer.type)){ - isValidSerializer = true; - } - } - - if (!isValidSerializer) { - error( - "Serializer can be " + SupportedSerializers.values.toList, - Literals.SERIALIZER__TYPE - ); - } - } - - @Check(FAST) - def checkState(StateVar stateVar) { - checkName(stateVar.name, Literals.STATE_VAR__NAME) - - if (stateVar.isOfTimeType) { - // If the state is declared to be a time, - // make sure that it is initialized correctly. - if (stateVar.init !== null) { - for (init : stateVar.init) { - if (stateVar.type !== null && stateVar.type.isTime && - !init.isValidTime) { - if (stateVar.isParameterized) { - error( - "Referenced parameter does not denote a time.", - Literals.STATE_VAR__INIT) - } else { - if (init !== null && !init.isZero) { - if (init.isInteger) { - error( - "Missing time unit.", Literals.STATE_VAR__INIT) - } else { - error("Invalid time literal.", - Literals.STATE_VAR__INIT) - } - } - } - } - } - } - } else if (this.target.requiresTypes && stateVar.inferredType.isUndefined) { - // Report if a type is missing - error("State must have a type.", Literals.STATE_VAR__TYPE) - } - - if (isCBasedTarget && stateVar.init.size > 1) { - // In C, if initialization is done with a list, elements cannot - // refer to parameters. - if (stateVar.init.exists[it.parameter !== null]) { - error("List items cannot refer to a parameter.", - Literals.STATE_VAR__INIT) - } - } - - if(!stateVar.braces.isNullOrEmpty && this.target != Target.CPP) { - error("Brace initializers are only supported for the C++ target", Literals.STATE_VAR__BRACES) - } - } - - @Check(FAST) - def checkTargetDecl(TargetDecl target) { - val targetOpt = Target.forName(target.name); - if (targetOpt.isEmpty()) { - error("Unrecognized target: " + target.name, - Literals.TARGET_DECL__NAME) - } else { - this.target = targetOpt.get(); - } - - val lfFileName = FileConfig.nameWithoutExtension(target.eResource) - if (Character.isDigit(lfFileName.charAt(0))) { - errorReporter.reportError("LF file names must not start with a number") - } - } - - /** - * Check for consistency of the target properties, which are - * defined as KeyValuePairs. - * - * @param targetProperties The target properties defined - * in the current Lingua Franca program. - */ - @Check(EXPENSIVE) - def checkTargetProperties(KeyValuePairs targetProperties) { - - val fastTargetProperties = targetProperties.pairs.filter( - pair | - // Check to see if fast is defined - TargetProperty.forName(pair.name) == TargetProperty.FAST - ) - - val fastTargetProperty = fastTargetProperties.findFirst[t | true]; - - if (fastTargetProperty !== null) { - // Check for federated - if (info.model.reactors.exists( - reactor | - // Check to see if the program has a federated reactor - reactor.isFederated - )) { - error( - "The fast target property is incompatible with federated programs.", - fastTargetProperty, - Literals.KEY_VALUE_PAIR__NAME - ) - } - - // Check for physical actions - if (info.model.reactors.exists( - reactor | - // Check to see if the program has a physical action in a reactor - reactor.actions.exists(action|(action.origin == ActionOrigin.PHYSICAL)) - )) { - error( - "The fast target property is incompatible with physical actions.", - fastTargetProperty, - Literals.KEY_VALUE_PAIR__NAME - ) - } - - } - - val clockSyncTargetProperties = targetProperties.pairs.filter( - pair | - // Check to see if clock-sync is defined - TargetProperty.forName(pair.name) == TargetProperty.CLOCK_SYNC - ) - - val clockSyncTargetProperty = clockSyncTargetProperties.findFirst[t | true]; - if (clockSyncTargetProperty !== null) { - if (info.model.reactors.exists( - reactor | - // Check to see if the program has a federated reactor defined. - reactor.isFederated - ) == false) { - warning( - "The clock-sync target property is incompatible with non-federated programs.", - clockSyncTargetProperty, - Literals.KEY_VALUE_PAIR__NAME - ) - } - } - } - - @Check(FAST) - def checkValueAsTime(Value value) { - val 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.parameter !== null) { - if (!value.parameter.isOfTimeType && target.requiresTypes === true) { - error("Parameter is not of time type", - Literals.VALUE__PARAMETER) - } - } else if (value.time === null) { - if (value.literal !== null && !value.literal.isZero) { - if (value.literal.isInteger) { - error("Missing time unit.", Literals.VALUE__LITERAL) - } else { - error("Invalid time literal.", - Literals.VALUE__LITERAL) - } - } else if (value.code !== null) { - error("Invalid time literal.", Literals.VALUE__CODE) - } - } - } - } - - @Check(FAST) - def checkTimer(Timer timer) { - checkName(timer.name, Literals.VARIABLE__NAME) - } - - @Check(FAST) - def checkType(Type type) { - // FIXME: disallow the use of generics in C - if (this.target == Target.CPP) { - if (type.stars.size > 0) { - warning( - "Raw pointers should be avoided in conjunction with LF. Ports " + - "and actions implicitly use smart pointers. In this case, " + - "the pointer here is likely not needed. For parameters and state " + - "smart pointers should be used explicitly if pointer semantics " + - "are really needed.", - Literals.TYPE__STARS - ) - } - } - else if (this.target == Target.Python) { - if (type !== null) { - error( - "Types are not allowed in the Python target", - Literals.TYPE__ID - ) - } - } - } - - @Check(FAST) - def checkVarRef(VarRef varRef) { - // check correct usage of interleaved - if (varRef.isInterleaved) { - if (this.target != Target.CPP && !isCBasedTarget && this.target != Target.Python) { - error("This target does not support interleaved port references.", Literals.VAR_REF__INTERLEAVED) - } - if (!(varRef.eContainer instanceof Connection)) { - error("interleaved can only be used in connections.", Literals.VAR_REF__INTERLEAVED) - } - - if (varRef.variable instanceof Port) { - // This test only works correctly if the variable is actually a port. If it is not a port, other - // validator rules will produce error messages. - if (varRef.container === null || varRef.container.widthSpec === null || - (varRef.variable as Port).widthSpec === null - ) { - error("interleaved can only be used for multiports contained within banks.", Literals.VAR_REF__INTERLEAVED) - } - } - } - } - - static val UNDERSCORE_MESSAGE = "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": " - static val ACTIONS_MESSAGE = "\"actions\" is a reserved word for the TypeScript target for objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation): " - static val RESERVED_MESSAGE = "Reserved words in the target language are not allowed for objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation): " - -} From dd34062e9ee0be530f525c0605f1f63b82c03b55 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Wed, 26 Jan 2022 14:11:12 -0800 Subject: [PATCH 08/23] add error reporter getter and dummy xtend file --- org.lflang/src/org/lflang/validation/LFValidator.java | 7 ++++--- org.lflang/src/org/lflang/validation/LFValidator.xtend | 0 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 org.lflang/src/org/lflang/validation/LFValidator.xtend diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 1780d6f298..ecf4f413c5 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -45,8 +45,6 @@ import org.eclipse.xtext.validation.Check; import org.eclipse.xtext.validation.CheckType; import org.eclipse.xtext.validation.ValidationMessageAcceptor; -import org.eclipse.xtend.lib.annotations.Accessors; -import org.eclipse.xtend.lib.annotations.AccessorType; import org.lflang.FileConfig; import org.lflang.InferredType; @@ -113,7 +111,6 @@ public class LFValidator extends BaseLFValidator { public ModelInfo info = new ModelInfo(); - @Accessors(AccessorType.PUBLIC_GETTER) private ValidatorErrorReporter errorReporter = new ValidatorErrorReporter(getMessageAcceptor(), new ValidatorStateAccess()); @@ -161,6 +158,10 @@ public class LFValidator extends BaseLFValidator { public List getTargetPropertyErrors() { return this.targetPropertyErrors; } + + public ValidatorErrorReporter getErrorReporter() { + return this.errorReporter; + } @Override public ValidationMessageAcceptor getMessageAcceptor() { diff --git a/org.lflang/src/org/lflang/validation/LFValidator.xtend b/org.lflang/src/org/lflang/validation/LFValidator.xtend new file mode 100644 index 0000000000..e69de29bb2 From a117db6f5444cf6a34c86b2a0050825bff48bb5f Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Wed, 26 Jan 2022 18:13:27 -0800 Subject: [PATCH 09/23] remove dummy validator --- org.lflang/src/org/lflang/GenerateLinguaFranca.mwe2 | 2 +- org.lflang/src/org/lflang/validation/LFValidator.xtend | 0 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 org.lflang/src/org/lflang/validation/LFValidator.xtend diff --git a/org.lflang/src/org/lflang/GenerateLinguaFranca.mwe2 b/org.lflang/src/org/lflang/GenerateLinguaFranca.mwe2 index 71762cca9a..884edeaa9e 100644 --- a/org.lflang/src/org/lflang/GenerateLinguaFranca.mwe2 +++ b/org.lflang/src/org/lflang/GenerateLinguaFranca.mwe2 @@ -44,7 +44,7 @@ Workflow { //composedCheck = "org.eclipse.xtext.validation.ImportUriValidator" // Generates checks for @Deprecated grammar annotations, an IssueProvider and a corresponding PropertyPage generateDeprecationValidation = true - generateXtendStub = true + generateXtendStub = false } junitSupport = { junitVersion = "5" diff --git a/org.lflang/src/org/lflang/validation/LFValidator.xtend b/org.lflang/src/org/lflang/validation/LFValidator.xtend deleted file mode 100644 index e69de29bb2..0000000000 From 301b7c2bc386c5e75a1fb8f45643939ca570b465 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Wed, 26 Jan 2022 18:40:05 -0800 Subject: [PATCH 10/23] fix bug in checkReaction --- .../org/lflang/validation/LFValidator.java | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index ecf4f413c5..e95a4b2c26 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -886,25 +886,37 @@ public void checkReaction(Reaction reaction) { continue; } VarRef tVarRef = (VarRef) t; + boolean triggerExistsInCycle = false; for (NamedInstance c : cycles) { if (c.getDefinition().equals(tVarRef.getVariable())) { - trigs.add(toText(tVarRef)); + triggerExistsInCycle = true; + break; } } + if (triggerExistsInCycle) { + trigs.add(toText(tVarRef)); + } } + System.out.println(trigs.size()); if (trigs.size() > 0) { - error(String.format("Reaction triggers involved in cyclic dependency in reactor %s: %s.", reactor.getName(), String.join(",", trigs)), + System.out.println(String.format("Reaction triggers involved in cyclic dependency in reactor %s: %s.", reactor.getName(), String.join(", ", trigs))); + error(String.format("Reaction triggers involved in cyclic dependency in reactor %s: %s.", reactor.getName(), String.join(", ", trigs)), Literals.REACTION__TRIGGERS); } // Report involved sources. List sources = new ArrayList<>(); for (VarRef t : reaction.getSources()) { + boolean sourceExistInCycle = false; for (NamedInstance c : cycles) { if (c.getDefinition().equals(t.getVariable())) { - sources.add(toText(t)); + sourceExistInCycle = true; + break; } } + if (sourceExistInCycle) { + sources.add(toText(t)); + } } if (sources.size() > 0) { error(String.format("Reaction sources involved in cyclic dependency in reactor %s: %s.", reactor.getName(), String.join(", ", sources)), @@ -914,11 +926,16 @@ public void checkReaction(Reaction reaction) { // Report involved effects. List effects = new ArrayList<>(); for (VarRef t : reaction.getEffects()) { + boolean effectExistInCycle = false; for (NamedInstance c : cycles) { if (c.getDefinition().equals(t.getVariable())) { - sources.add(toText(t)); + effectExistInCycle = true; + break; } } + if (effectExistInCycle) { + effects.add(toText(t)); + } } if (effects.size() > 0) { error(String.format("Reaction effects involved in cyclic dependency in reactor %s: %s.", reactor.getName(), String.join(", ", effects)), From fd29e0c41f85cad5608859923120422e79b11ded Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Wed, 26 Jan 2022 19:00:14 -0800 Subject: [PATCH 11/23] fix inferred type bug --- org.lflang/src/org/lflang/validation/LFValidator.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index e95a4b2c26..8dcf9c8969 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -48,6 +48,7 @@ import org.lflang.FileConfig; import org.lflang.InferredType; +import org.lflang.JavaAstUtils; import org.lflang.ModelInfo; import org.lflang.Target; import org.lflang.TargetProperty; @@ -757,8 +758,8 @@ public void checkParameter(Parameter param) { } // If time is not null, we know that a unit is also specified. } } else if (this.target.requiresTypes) { - // Report missing target type. - if (((InferredType) param).isUndefined()) { + // Report missing target type. param.inferredType.undefine + if ((JavaAstUtils.getInferredType(param).isUndefined())) { error("Type declaration missing.", Literals.PARAMETER__TYPE); } } @@ -897,9 +898,7 @@ public void checkReaction(Reaction reaction) { trigs.add(toText(tVarRef)); } } - System.out.println(trigs.size()); if (trigs.size() > 0) { - System.out.println(String.format("Reaction triggers involved in cyclic dependency in reactor %s: %s.", reactor.getName(), String.join(", ", trigs))); error(String.format("Reaction triggers involved in cyclic dependency in reactor %s: %s.", reactor.getName(), String.join(", ", trigs)), Literals.REACTION__TRIGGERS); } @@ -1219,7 +1218,7 @@ public void checkState(StateVar stateVar) { } } } - } else if (this.target.requiresTypes && ((InferredType) stateVar).isUndefined()) { + } else if (this.target.requiresTypes && (JavaAstUtils.getInferredType(stateVar)).isUndefined()) { // Report if a type is missing error("State must have a type.", Literals.STATE_VAR__TYPE); } From 8f81777af74705c1f63213dc07a68f21ff25323a Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Wed, 26 Jan 2022 19:04:11 -0800 Subject: [PATCH 12/23] fix checkConnection bug --- org.lflang/src/org/lflang/validation/LFValidator.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 8dcf9c8969..19bb3dd090 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -485,7 +485,8 @@ public void checkConnection(Connection connection) { for (Reaction reaction : reactor.getReactions()) { for (VarRef effect : reaction.getEffects()) { for (VarRef rightPort : connection.getRightPorts()) { - if (rightPort.getContainer().equals(effect.getContainer()) && + if ((rightPort.getContainer() == null && effect.getContainer() == null || + rightPort.getContainer().equals(effect.getContainer())) && rightPort.getVariable().equals(effect.getVariable())) { error("Cannot connect: Port named '" + effect.getVariable().getName() + "' is already effect of a reaction.", @@ -502,7 +503,8 @@ public void checkConnection(Connection connection) { if (c != connection) { for (VarRef thisRightPort : connection.getRightPorts()) { for (VarRef thatRightPort : c.getRightPorts()) { - if (thisRightPort.getContainer().equals(thatRightPort.getContainer()) && + if ((thisRightPort.getContainer() == null && thatRightPort.getContainer() == null || + thisRightPort.getContainer().equals(thatRightPort.getContainer())) && thisRightPort.getVariable().equals(thatRightPort.getVariable())) { error( "Cannot connect: Port named '" + thisRightPort.getVariable().getName() + From f1273fec686c947a2dc5f781fb4f964dc9595a64 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Thu, 27 Jan 2022 10:07:44 -0800 Subject: [PATCH 13/23] fix wrong conversion of toDefinition --- org.lflang/src/org/lflang/validation/LFValidator.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 19bb3dd090..596d29b6c1 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -188,8 +188,8 @@ public void checkImportedReactor(ImportedReactor reactor) { for (Set cycle : info.instantiationGraph.getCycles()) { cycleSet.addAll(cycle); } - if (dependsOnCycle(reactor.getReactorClass(), cycleSet, new HashSet<>())) { - error("Imported reactor '" + reactor.getReactorClass().getName() + + if (dependsOnCycle(toDefinition(reactor), cycleSet, new HashSet<>())) { + error("Imported reactor '" + toDefinition(reactor).getName() + "' has cyclic instantiation in it.", Literals.IMPORTED_REACTOR__REACTOR_CLASS); } } @@ -197,7 +197,7 @@ public void checkImportedReactor(ImportedReactor reactor) { @Check public void checkImport(Import imp) { - if (imp.getReactorClasses().get(0).getReactorClass().eResource().getErrors().size() > 0) { + if (toDefinition(imp.getReactorClasses().get(0)).eResource().getErrors().size() > 0) { error("Error loading resource.", Literals.IMPORT__IMPORT_URI); // FIXME: print specifics. return; } @@ -602,7 +602,7 @@ public void checkInput(Input input) { @Check(CheckType.FAST) public void checkInstantiation(Instantiation instantiation) { checkName(instantiation.getName(), Literals.INSTANTIATION__NAME); - Reactor reactor = (Reactor) instantiation.getReactorClass(); + Reactor reactor = toDefinition(instantiation.getReactorClass()); if (reactor.isMain() || reactor.isFederated()) { error( "Cannot instantiate a main (or federated) reactor: " + From 218190276abfb027b7ae5ff34ab52275ba500e25 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Thu, 27 Jan 2022 10:14:04 -0800 Subject: [PATCH 14/23] fix checkStp bug --- org.lflang/src/org/lflang/validation/LFValidator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 596d29b6c1..7f3ff4b315 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -567,7 +567,7 @@ public void checkDeadline(Deadline deadline) { @Check(CheckType.FAST) public void checkSTPOffset(STP stp) { if (isCBasedTarget() && - this.info.overflowingDeadlines.contains((Deadline) stp)) { + this.info.overflowingDeadlines.contains(stp)) { error( "STP offset exceeds the maximum of " + TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", From 8e5092d23a923c33a252c0a9236862f792acadfd Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Thu, 27 Jan 2022 10:23:48 -0800 Subject: [PATCH 15/23] incorporate changes from PR #804 --- .../src/org/lflang/validation/LFValidator.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 7f3ff4b315..48653d8401 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -766,6 +766,19 @@ public void checkParameter(Parameter param) { } } + if(this.target == Target.CPP) { + EObject container = param.eContainer(); + Reactor reactor = (Reactor) container; + if(reactor.isMain()){ + // we need to check for the cli parameters that are always taken + List cliParams = List.of("t", "threads", "o", "timeout", "k", "keepalive", "f", "fast", "help"); + if(cliParams.contains(param.getName())){ + error("Parameter '" + param.getName() + "' is already in use as command line argument by Lingua Franca,", + Literals.PARAMETER__NAME); + } + } + } + if (isCBasedTarget() && this.info.overflowingParameters.contains(param)) { error( From 459c12ec5c1a7f6bb6beb5cb9c96af4ee4b3731d Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Thu, 27 Jan 2022 14:16:29 -0800 Subject: [PATCH 16/23] add import validation tests --- .../compiler/LinguaFrancaValidationTest.java | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) 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 3e5cc075d2..ff50478967 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java @@ -29,6 +29,13 @@ import com.google.inject.Inject; import java.util.List; import java.util.Map; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.LinkedList; import org.eclipse.xtext.testing.InjectWith; import org.eclipse.xtext.testing.extensions.InjectionExtension; @@ -43,6 +50,7 @@ import org.lflang.lf.LfPackage; import org.lflang.lf.Model; import org.lflang.lf.Visibility; +import org.junit.Ignore; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -1721,6 +1729,84 @@ public void checkCargoDependencyProperty() throws Exception { LfPackage.eINSTANCE.getElement(), null, "Expected an array of strings for key 'features'" ); } + + @Test + public void checkImportedCyclicReactor() throws Exception { + File tempFile = File.createTempFile("lf-validation", ".lf"); + tempFile.deleteOnExit(); + // Java 17: + // String fileToBeImported = """ + // target C; + // reactor A { + // a = new A(); + // } + // """ + // Java 11: + String fileToBeImported = String.join(System.getProperty("line.separator"), + "target C;", + "reactor A {", + " a = new A();", + "}" + ); + BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile)); + writer.write(fileToBeImported); + writer.close(); + + // Java 17: + // String testCase = """ + // target C; + // import A from ... + // main reactor { + // } + // """ + // Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + String.format("import A from \"%s\"", tempFile.getAbsolutePath()), + "main reactor {", + "}" + ); + // TODO: Uncomment the line below and fix self-referential imported reactor not checked error (see Issue #902 on Github). + // Model model = parseWithError(testCase); + // TODO: Uncomment the lines below and resolve the weird error. (java.lang.IllegalArgumentException: resolve against non-hierarchical or relative base) + // validator.assertError(model, LfPackage.eINSTANCE.getImportedReactor(), null, "Imported reactor 'A' has cyclic instantiation in it."); + } + + @Test + public void checkUnusedImport() throws Exception { + File tempFile = File.createTempFile("lf-validation", ".lf"); + tempFile.deleteOnExit(); + // Java 17: + // String fileToBeImported = """ + // target C; + // reactor A {} + // """ + // Java 11: + String fileToBeImported = String.join(System.getProperty("line.separator"), + "target C;", + "reactor A{}" + ); + BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile)); + writer.write(fileToBeImported); + writer.close(); + + // Java 17: + // String testCase = """ + // target C; + // import A from ... + // main reactor {} + // """ + // Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + String.format("import A from \"%s\"", tempFile.getAbsolutePath()), + "main reactor{}" + ); + Model model = parseWithoutError(testCase); + // TODO: Uncomment the lines below and resolve the weird error. (java.lang.IllegalArgumentException: resolve against non-hierarchical or relative base) + // validator.assertWarning(model, LfPackage.eINSTANCE.getImport(), null, "Unused import."); + // validator.assertWarning(parseWithoutError(testCase), LfPackage.eINSTANCE.getImportedReactor(), null, "Unused reactor class."); + } } From 784a270a8d2cf937d48fac8d59b037da3e0cd251 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Thu, 27 Jan 2022 15:02:04 -0800 Subject: [PATCH 17/23] add tests for input type, stp and deadline validation --- .../compiler/LinguaFrancaValidationTest.java | 92 ++++++++++++++++++- 1 file changed, 88 insertions(+), 4 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java index ff50478967..01d05ad83e 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java @@ -1731,7 +1731,7 @@ public void checkCargoDependencyProperty() throws Exception { } @Test - public void checkImportedCyclicReactor() throws Exception { + public void testImportedCyclicReactor() throws Exception { File tempFile = File.createTempFile("lf-validation", ".lf"); tempFile.deleteOnExit(); // Java 17: @@ -1766,14 +1766,13 @@ public void checkImportedCyclicReactor() throws Exception { "main reactor {", "}" ); - // TODO: Uncomment the line below and fix self-referential imported reactor not checked error (see Issue #902 on Github). - // Model model = parseWithError(testCase); + Model model = parseWithoutError(testCase); // TODO: Uncomment the lines below and resolve the weird error. (java.lang.IllegalArgumentException: resolve against non-hierarchical or relative base) // validator.assertError(model, LfPackage.eINSTANCE.getImportedReactor(), null, "Imported reactor 'A' has cyclic instantiation in it."); } @Test - public void checkUnusedImport() throws Exception { + public void testUnusedImport() throws Exception { File tempFile = File.createTempFile("lf-validation", ".lf"); tempFile.deleteOnExit(); // Java 17: @@ -1807,6 +1806,91 @@ public void checkUnusedImport() throws Exception { // validator.assertWarning(model, LfPackage.eINSTANCE.getImport(), null, "Unused import."); // validator.assertWarning(parseWithoutError(testCase), LfPackage.eINSTANCE.getImportedReactor(), null, "Unused reactor class."); } + + @Test + public void testMissingInputType() throws Exception { + // Java 17: + // String testCase = """ + // target C; + // main reactor { + // input i; + // } + // """ + // Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "main reactor {", + " input i;", + "}"); + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getInput(), null, + "Input must have a type."); + } + + @Test + public void testCppMutableInput() throws Exception { + // Java 17: + // String testCase = """ + // target Cpp; + // main reactor { + // mutable input i:int; + // } + // """ + // Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target Cpp;", + "main reactor {", + " mutable input i:int;", + "}"); + validator.assertWarning(parseWithoutError(testCase), LfPackage.eINSTANCE.getInput(), null, + "The mutable qualifier has no meaning for the C++ target and should be removed. " + + "In C++, any value can be made mutable by calling get_mutable_copy()."); + } + + @Test + public void testOverflowingSTP() throws Exception { + // Java 17: + // String testCase = """ + // target C; + // main reactor { + // reaction(startup) {==} STP(2147483648) {==} + // } + // """ + // Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "main reactor {", + " reaction(startup) {==} STP(2147483648) {==}", + "}"); + + // TODO: Uncomment and fix failing test. See issue #903 on Github. + // validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getSTP(), null, + // "STP offset exceeds the maximum of " + TimeValue.MAX_LONG_DEADLINE + " nanoseconds."); + } + + @Test + public void testOverflowingDeadline() throws Exception { + // Java 17: + // String testCase = """ + // target C; + // main reactor { + // reaction(startup) {==} STP(2147483648) {==} + // } + // """ + // Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "main reactor {", + " reaction(startup) {==} deadline(2147483648) {==}", + "}"); + + // TODO: Uncomment and fix failing test. See issue #903 on Github. + // validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getDeadline(), null, + // "Deadline exceeds the maximum of " + TimeValue.MAX_LONG_DEADLINE + " nanoseconds."); + } + + + + } From c71df588b9d5ed102e7d06e4a445b7b02741b197 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Thu, 27 Jan 2022 15:59:34 -0800 Subject: [PATCH 18/23] rename helper function --- org.lflang/src/org/lflang/validation/LFValidator.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 48653d8401..6d6204d064 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -977,7 +977,7 @@ public void checkReaction(Reaction reaction) { // FIXME: improve error message. } - private int countMain(TreeIterator iter) { + private int countMainOrFederated(TreeIterator iter) { int nMain = 0; while (iter.hasNext()) { EObject obj = iter.next(); @@ -1016,7 +1016,7 @@ public void checkReactor(Reactor reactor) throws IOException { } // Do not allow multiple main/federated reactors. - int nMain = countMain(iter); + int nMain = countMainOrFederated(iter); if (nMain > 1) { EAttribute attribute = Literals.REACTOR__MAIN; if (reactor.isFederated()) { @@ -1030,7 +1030,7 @@ public void checkReactor(Reactor reactor) throws IOException { } } } else { - int nMain = countMain(iter); + int nMain = countMainOrFederated(iter); if (nMain > 0 && reactor.getName().equals(name)) { error( "Name conflict with main reactor.", From dbc58db6b6aa5f258eabab308e2626d823fdb056 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Thu, 27 Jan 2022 16:31:17 -0800 Subject: [PATCH 19/23] add more tests --- .../compiler/LinguaFrancaValidationTest.java | 219 ++++++++++++++++-- 1 file changed, 200 insertions(+), 19 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java index 01d05ad83e..eb2def20fb 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java @@ -41,6 +41,7 @@ import org.eclipse.xtext.testing.extensions.InjectionExtension; import org.eclipse.xtext.testing.util.ParseHelper; import org.eclipse.xtext.testing.validation.ValidationTestHelper; +import org.eclipse.xtext.validation.Issue; import org.lflang.Target; import org.lflang.TargetProperty; import org.lflang.TargetProperty.DictionaryType; @@ -1818,14 +1819,55 @@ public void testMissingInputType() throws Exception { // """ // Java 11: String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "main reactor {", - " input i;", - "}"); + "target C;", + "main reactor {", + " input i;", + "}" + ); validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getInput(), null, "Input must have a type."); } + @Test + public void testMissingOutputType() throws Exception { + // Java 17: + // String testCase = """ + // target C; + // main reactor { + // output i; + // } + // """ + // Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "main reactor {", + " output i;", + "}" + ); + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getOutput(), null, + "Output must have a type."); + } + + @Test + public void testMissingStateType() throws Exception { + // Java 17: + // String testCase = """ + // target C; + // main reactor { + // state i; + // } + // """ + // Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "main reactor {", + " state i;", + "}" + ); + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getStateVar(), null, + "State must have a type."); + } + @Test public void testCppMutableInput() throws Exception { // Java 17: @@ -1837,10 +1879,11 @@ public void testCppMutableInput() throws Exception { // """ // Java 11: String testCase = String.join(System.getProperty("line.separator"), - "target Cpp;", - "main reactor {", - " mutable input i:int;", - "}"); + "target Cpp;", + "main reactor {", + " mutable input i:int;", + "}" + ); validator.assertWarning(parseWithoutError(testCase), LfPackage.eINSTANCE.getInput(), null, "The mutable qualifier has no meaning for the C++ target and should be removed. " + "In C++, any value can be made mutable by calling get_mutable_copy()."); @@ -1857,10 +1900,11 @@ public void testOverflowingSTP() throws Exception { // """ // Java 11: String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "main reactor {", - " reaction(startup) {==} STP(2147483648) {==}", - "}"); + "target C;", + "main reactor {", + " reaction(startup) {==} STP(2147483648) {==}", + "}" + ); // TODO: Uncomment and fix failing test. See issue #903 on Github. // validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getSTP(), null, @@ -1878,20 +1922,157 @@ public void testOverflowingDeadline() throws Exception { // """ // Java 11: String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "main reactor {", - " reaction(startup) {==} deadline(2147483648) {==}", - "}"); + "target C;", + "main reactor {", + " reaction(startup) {==} deadline(2147483648) {==}", + "}" + ); // TODO: Uncomment and fix failing test. See issue #903 on Github. // validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getDeadline(), null, // "Deadline exceeds the maximum of " + TimeValue.MAX_LONG_DEADLINE + " nanoseconds."); } - + @Test + public void testInvalidTargetParam() throws Exception { + // Java 17: + // String testCase = """ + // target C { beefyDesktop: true } + // main reactor {} + // """ + // Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C { beefyDesktop: true }", + "main reactor {}" + ); + List issues = validator.validate(parseWithoutError(testCase)); + Assertions.assertTrue(issues.size() == 1 && issues.get(0).getMessage().contains("Unrecognized target parameter: beefyDesktop")); + } - -} + @Test + public void testTargetParamNotSupportedForTarget() throws Exception { + // Java 17: + // String testCase = """ + // target Python { build: "" } + // main reactor {} + // """ + // Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target Python { build: \"\" }", + "main reactor {}" + ); + List issues = validator.validate(parseWithoutError(testCase)); + Assertions.assertTrue(issues.size() == 1 && issues.get(0).getMessage().contains("The target parameter: build" + + " is not supported by the Python target and will thus be ignored.")); + } + + @Test + public void testUnnamedReactor() throws Exception { + // Java 17: + // String testCase = """ + // target C; + // reactor {} + // """ + // Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "reactor {}" + ); + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getReactor(), null, + "Reactor must be named."); + } + + @Test + public void testMultipleMainReactor() throws Exception { + // Java 17: + // String testCase = """ + // target C; + // main reactor A {} + // main reactor A {} + // """ + // Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "main reactor A {}", + "main reactor A {}" + ); + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getReactor(), null, + "Multiple definitions of main or federated reactor."); + } + @Test + public void testMultipleMainReactorUnnamed() throws Exception { + // Java 17: + // String testCase = """ + // target C; + // main reactor {} + // main reactor {} + // """ + // Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "main reactor {}", + "main reactor {}" + ); + // TODO: Uncomment and fix test. See issue #905 on Github. + // validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getReactor(), null, + // "Multiple definitions of main or federated reactor."); + } + + @Test + public void testMultipleFederatedReactor() throws Exception { + // Java 17: + // String testCase = """ + // target C; + // federated reactor A {} + // federated reactor A {} + // """ + // Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "federated reactor A {}", + "federated reactor A {}" + ); + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getReactor(), null, + "Multiple definitions of main or federated reactor."); + } + + @Test + public void testMultipleMainOrFederatedReactor() throws Exception { + // Java 17: + // String testCase = """ + // target C; + // federated reactor A {} + // federated reactor A {} + // """ + // Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "main reactor A {}", + "federated reactor A {}" + ); + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getReactor(), null, + "Multiple definitions of main or federated reactor."); + } + + @Test + public void testMainReactorHasHost() throws Exception { + // Java 17: + // String testCase = """ + // target C; + // main reactor at 127.0.0.1{} + // """ + // Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "main reactor at 127.0.0.1{}" + ); + // TODO: Uncomment and fix test + // List issues = validator.validate(parseWithoutError(testCase)); + // Assertions.assertTrue(issues.size() == 1 && + // issues.get(0).getMessage().contains("Cannot assign a host to reactor '") && + // issues.get(0).getMessage().contains("' because it is not federated.")); + } +} From 86d1ab1a5b02fa14065e416b6893afe961e11de5 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Thu, 27 Jan 2022 16:47:21 -0800 Subject: [PATCH 20/23] add more tests --- .../compiler/LinguaFrancaValidationTest.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) 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 eb2def20fb..b1db81a26f 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java @@ -1868,6 +1868,22 @@ public void testMissingStateType() throws Exception { "State must have a type."); } + @Test + public void testListWithParam() throws Exception { + // Java 17: + // String testCase = """ + // target C; + // main reactor (A:int(1)) { state i:int(A, 2, 3) } + // """ + // Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "main reactor (A:int(1)) { state i:int(A, 2, 3) }" + ); + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getStateVar(), null, + "List items cannot refer to a parameter."); + } + @Test public void testCppMutableInput() throws Exception { // Java 17: @@ -2073,6 +2089,24 @@ public void testMainReactorHasHost() throws Exception { // issues.get(0).getMessage().contains("Cannot assign a host to reactor '") && // issues.get(0).getMessage().contains("' because it is not federated.")); } + + @Test + public void testUnrecognizedTarget() throws Exception { + // Java 17: + // String testCase = """ + // target Pjthon; + // main reactor {} + // """ + // Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target Pjthon;", + "main reactor {}" + ); + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getTargetDecl(), null, + "Unrecognized target: Pjthon"); + } + + } From 6c724b15173434159117b3e05e1b62e82bc27315 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Thu, 27 Jan 2022 16:57:21 -0800 Subject: [PATCH 21/23] comment out import related validation tests for now --- .../compiler/LinguaFrancaValidationTest.java | 128 +++++++++--------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java index b1db81a26f..edabde83a2 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java @@ -1733,76 +1733,76 @@ public void checkCargoDependencyProperty() throws Exception { @Test public void testImportedCyclicReactor() throws Exception { - File tempFile = File.createTempFile("lf-validation", ".lf"); - tempFile.deleteOnExit(); - // Java 17: - // String fileToBeImported = """ - // target C; - // reactor A { - // a = new A(); - // } - // """ - // Java 11: - String fileToBeImported = String.join(System.getProperty("line.separator"), - "target C;", - "reactor A {", - " a = new A();", - "}" - ); - BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile)); - writer.write(fileToBeImported); - writer.close(); - - // Java 17: - // String testCase = """ - // target C; - // import A from ... - // main reactor { - // } - // """ - // Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - String.format("import A from \"%s\"", tempFile.getAbsolutePath()), - "main reactor {", - "}" - ); - Model model = parseWithoutError(testCase); + // File tempFile = File.createTempFile("lf-validation", ".lf"); + // tempFile.deleteOnExit(); + // // Java 17: + // // String fileToBeImported = """ + // // target C; + // // reactor A { + // // a = new A(); + // // } + // // """ + // // Java 11: + // String fileToBeImported = String.join(System.getProperty("line.separator"), + // "target C;", + // "reactor A {", + // " a = new A();", + // "}" + // ); + // BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile)); + // writer.write(fileToBeImported); + // writer.close(); + + // // Java 17: + // // String testCase = """ + // // target C; + // // import A from ... + // // main reactor { + // // } + // // """ + // // Java 11: + // String testCase = String.join(System.getProperty("line.separator"), + // "target C;", + // String.format("import A from \"%s\"", tempFile.getAbsolutePath()), + // "main reactor {", + // "}" + // ); + // Model model = parseWithoutError(testCase); // TODO: Uncomment the lines below and resolve the weird error. (java.lang.IllegalArgumentException: resolve against non-hierarchical or relative base) // validator.assertError(model, LfPackage.eINSTANCE.getImportedReactor(), null, "Imported reactor 'A' has cyclic instantiation in it."); } @Test public void testUnusedImport() throws Exception { - File tempFile = File.createTempFile("lf-validation", ".lf"); - tempFile.deleteOnExit(); - // Java 17: - // String fileToBeImported = """ - // target C; - // reactor A {} - // """ - // Java 11: - String fileToBeImported = String.join(System.getProperty("line.separator"), - "target C;", - "reactor A{}" - ); - BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile)); - writer.write(fileToBeImported); - writer.close(); - - // Java 17: - // String testCase = """ - // target C; - // import A from ... - // main reactor {} - // """ - // Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - String.format("import A from \"%s\"", tempFile.getAbsolutePath()), - "main reactor{}" - ); - Model model = parseWithoutError(testCase); + // File tempFile = File.createTempFile("lf-validation", ".lf"); + // tempFile.deleteOnExit(); + // // Java 17: + // // String fileToBeImported = """ + // // target C; + // // reactor A {} + // // """ + // // Java 11: + // String fileToBeImported = String.join(System.getProperty("line.separator"), + // "target C;", + // "reactor A{}" + // ); + // BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile)); + // writer.write(fileToBeImported); + // writer.close(); + + // // Java 17: + // // String testCase = """ + // // target C; + // // import A from ... + // // main reactor {} + // // """ + // // Java 11: + // String testCase = String.join(System.getProperty("line.separator"), + // "target C;", + // String.format("import A from \"%s\"", tempFile.getAbsolutePath()), + // "main reactor{}" + // ); + // Model model = parseWithoutError(testCase); // TODO: Uncomment the lines below and resolve the weird error. (java.lang.IllegalArgumentException: resolve against non-hierarchical or relative base) // validator.assertWarning(model, LfPackage.eINSTANCE.getImport(), null, "Unused import."); // validator.assertWarning(parseWithoutError(testCase), LfPackage.eINSTANCE.getImportedReactor(), null, "Unused reactor class."); From 112b3beb1e1fcef99a1b6a67fd89199e41a0aa8b Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Fri, 28 Jan 2022 10:03:40 -0800 Subject: [PATCH 22/23] readd checkWidthSpec --- .../org/lflang/validation/LFValidator.java | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 6d6204d064..71a47aea80 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -88,6 +88,8 @@ import org.lflang.lf.VarRef; import org.lflang.lf.Variable; import org.lflang.lf.Visibility; +import org.lflang.lf.WidthSpec; +import org.lflang.lf.WidthTerm; import org.lflang.lf.ReactorDecl; import org.lflang.generator.NamedInstance; @@ -366,6 +368,32 @@ public void checkAssignment(Assignment assignment) { // Specifically for C: list can only be literal or time lists } + @Check(CheckType.FAST) + public void checkWidthSpec(WidthSpec widthSpec) { + if (!this.target.supportsMultiports()) { + error("Multiports and banks are currently not supported by the given target.", + Literals.WIDTH_SPEC__TERMS); + } else { + for (WidthTerm term : widthSpec.getTerms()) { + if (term.getParameter() != null) { + if (!this.target.supportsParameterizedWidths()) { + error("Parameterized widths are not supported by this target.", Literals.WIDTH_SPEC__TERMS); + } + } else if (term.getPort() != null) { + // Widths given with `widthof()` are not supported (yet?). + // This feature is currently only used for after delays. + error("widthof is not supported.", Literals.WIDTH_SPEC__TERMS); + } else if (term.getCode() != null) { + if (this.target != Target.CPP) { + error("This target does not support width given as code.", Literals.WIDTH_SPEC__TERMS); + } + } else if (term.getWidth() < 0) { + error("Width must be a positive integer.", Literals.WIDTH_SPEC__TERMS); + } + } + } + } + @Check(CheckType.FAST) public void checkConnection(Connection connection) { @@ -547,10 +575,7 @@ private boolean sameType(Type type1, Type type2) { return true; } // Type must be given in a code body - if (type2.getCode() != null) { - return type1.getCode().getBody().equals(type2.getCode().getBody()); - } - return false; + return type1.getCode().getBody().equals(type2.getCode().getBody()); } @Check(CheckType.FAST) From f9741cbc6279d3d267d84f1cdd0f50c497d65749 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Fri, 28 Jan 2022 17:03:15 -0800 Subject: [PATCH 23/23] apply code review suggestion --- org.lflang/src/org/lflang/validation/LFValidator.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 71a47aea80..2d7bd1bb25 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -1411,7 +1411,8 @@ public void checkType(Type type) { else if (this.target == Target.Python) { if (type != null) { error( - "Types are not allowed in the Python target", + "Types are not allowed in the Python target (found type " + type.getId() + + " in " + ((Variable) type.eContainer()).getName() +").", Literals.TYPE__ID ); }