diff --git a/core/src/integrationTest/java/org/lflang/tests/lsp/LspTests.java b/core/src/integrationTest/java/org/lflang/tests/lsp/LspTests.java index 6b92b46754..842b8e4dfb 100644 --- a/core/src/integrationTest/java/org/lflang/tests/lsp/LspTests.java +++ b/core/src/integrationTest/java/org/lflang/tests/lsp/LspTests.java @@ -229,8 +229,9 @@ private static Predicate> diagnosticsIncludeText( */ private void runTest(Path test) { MockReportProgress reportProgress = new MockReportProgress(); + MockCancelIndicator cancelIndicator = new MockCancelIndicator(); try { - builder.run(URI.createFileURI(test.toString()), false, reportProgress, () -> false); + builder.run(URI.createFileURI(test.toString()), "", false, reportProgress, cancelIndicator); } catch (Exception e) { e.printStackTrace(); throw e; diff --git a/core/src/integrationTest/java/org/lflang/tests/lsp/MockCancelIndicator.java b/core/src/integrationTest/java/org/lflang/tests/lsp/MockCancelIndicator.java new file mode 100644 index 0000000000..9fe243d481 --- /dev/null +++ b/core/src/integrationTest/java/org/lflang/tests/lsp/MockCancelIndicator.java @@ -0,0 +1,15 @@ +package org.lflang.tests.lsp; + +import org.eclipse.xtext.util.CancelIndicator; + +/** + * Mock the CancelIndicator interface for testing. + * + * @author Erling Jellum + */ +public class MockCancelIndicator implements CancelIndicator { + @Override + public boolean isCanceled() { + return false; + } +} diff --git a/core/src/main/java/org/lflang/LinguaFranca.xtext b/core/src/main/java/org/lflang/LinguaFranca.xtext index d4a892d9e8..7f9e74d491 100644 --- a/core/src/main/java/org/lflang/LinguaFranca.xtext +++ b/core/src/main/java/org/lflang/LinguaFranca.xtext @@ -221,7 +221,7 @@ Watchdog: 'watchdog' name=ID '(' timeout=Expression ')' ('->' effects+=VarRefOrModeTransition (',' effects+=VarRefOrModeTransition)*)? code=Code; - + STP: 'STP' '(' value=Expression ')' code=Code; @@ -232,7 +232,7 @@ Instantiation: (attributes+=Attribute)* name=ID '=' 'new' (widthSpec=WidthSpec)? reactorClass=[ReactorDecl] ('<' typeArgs+=Type (',' typeArgs+=Type)* '>')? '(' - (parameters+=Assignment (',' parameters+=Assignment)*)? + (parameters+=Assignment (',' parameters+=Assignment)*)? ')' (('at' host=Host ';') | ';'?); Connection: diff --git a/core/src/main/java/org/lflang/ast/ToSExpr.java b/core/src/main/java/org/lflang/ast/ToSExpr.java new file mode 100644 index 0000000000..a514d98234 --- /dev/null +++ b/core/src/main/java/org/lflang/ast/ToSExpr.java @@ -0,0 +1,995 @@ +package org.lflang.ast; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.nodemodel.INode; +import org.eclipse.xtext.nodemodel.util.NodeModelUtils; +import org.eclipse.xtext.util.LineAndColumn; +import org.lflang.ast.ToSExpr.SExpr; +import org.lflang.ast.ToSExpr.SList.Fingerprint; +import org.lflang.ast.ToSExpr.SList.Metadata; +import org.lflang.ast.ToSExpr.SList.SAtom; +import org.lflang.ast.ToSExpr.SList.Sym; +import org.lflang.generator.Position; +import org.lflang.generator.Range; +import org.lflang.lf.Action; +import org.lflang.lf.Array; +import org.lflang.lf.Assignment; +import org.lflang.lf.AttrParm; +import org.lflang.lf.Attribute; +import org.lflang.lf.BracedListExpression; +import org.lflang.lf.BracketListExpression; +import org.lflang.lf.BuiltinTriggerRef; +import org.lflang.lf.CStyleArraySpec; +import org.lflang.lf.Code; +import org.lflang.lf.CodeExpr; +import org.lflang.lf.Connection; +import org.lflang.lf.Deadline; +import org.lflang.lf.Element; +import org.lflang.lf.Expression; +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.Initializer; +import org.lflang.lf.Input; +import org.lflang.lf.Instantiation; +import org.lflang.lf.KeyValuePair; +import org.lflang.lf.KeyValuePairs; +import org.lflang.lf.Literal; +import org.lflang.lf.Method; +import org.lflang.lf.MethodArgument; +import org.lflang.lf.Mode; +import org.lflang.lf.Model; +import org.lflang.lf.NamedHost; +import org.lflang.lf.Output; +import org.lflang.lf.Parameter; +import org.lflang.lf.ParameterReference; +import org.lflang.lf.Port; +import org.lflang.lf.Preamble; +import org.lflang.lf.Reaction; +import org.lflang.lf.Reactor; +import org.lflang.lf.ReactorDecl; +import org.lflang.lf.STP; +import org.lflang.lf.Serializer; +import org.lflang.lf.StateVar; +import org.lflang.lf.TargetDecl; +import org.lflang.lf.Time; +import org.lflang.lf.Timer; +import org.lflang.lf.TriggerRef; +import org.lflang.lf.Type; +import org.lflang.lf.TypeParm; +import org.lflang.lf.TypedVariable; +import org.lflang.lf.VarRef; +import org.lflang.lf.Variable; +import org.lflang.lf.Watchdog; +import org.lflang.lf.WidthSpec; +import org.lflang.lf.WidthTerm; +import org.lflang.lf.util.LfSwitch; + +public class ToSExpr extends LfSwitch { + + /** + * The eObjects in the syntax tree on the path from the root up to and including the current + * eObject. + */ + private final List callStack = new ArrayList<>(); + + @Override + public SExpr doSwitch(EObject theEObject) { + callStack.add(theEObject); + var ret = doSwitchHelper(theEObject); + callStack.remove(callStack.size() - 1); + return ret; + } + + private boolean inside(Class tClass) { + return callStack.size() >= 2 && tClass.isInstance(callStack.get(callStack.size() - 2)); + } + + private boolean inVarRef() { + // uses variable, typedvariable, timer, mode, watchdog, port, input, output, action + return inside(VarRef.class); + } + + // private boolean inReaction() { + // // uses triggerref + // return inside(Reaction.class); + // } + + private SExpr doSwitchHelper(EObject theEObject) { + var node = NodeModelUtils.getNode(theEObject); + var range = getLfRange(theEObject); + var metadata = + new Metadata( + theEObject.eResource().getURI(), + node.getTotalOffset(), + range.getStartInclusive().getZeroBasedLine(), + range.getStartInclusive().getZeroBasedColumn(), + range.getEndExclusive().getZeroBasedLine(), + range.getEndExclusive().getZeroBasedColumn()); + var ret = super.doSwitch(theEObject); + ret.setMetadata(metadata); + return ret; + } + + private static Range getLfRange(EObject astNode) { + final INode node = NodeModelUtils.getNode(astNode); + final LineAndColumn oneBasedLfLineAndColumn = + NodeModelUtils.getLineAndColumn(node, node.getTotalOffset()); + Position lfStart = + Position.fromOneBased( + oneBasedLfLineAndColumn.getLine(), oneBasedLfLineAndColumn.getColumn()); + return new Range(lfStart, lfStart.plus(node.getText())); + } + + private SExpr sym(String name) { + return new SAtom<>(new Sym(name)); + } + + private SExpr fingerprint(EObject eObject) { + try { + var md = MessageDigest.getInstance("SHA"); + var range = + getLfRange( + eObject); // No two distinct syntactic elements can have the exact same source code + // range + var locBuffer = ByteBuffer.allocate(16); // 4 32-bit ints + locBuffer.putInt(range.getStartInclusive().getZeroBasedLine()); + locBuffer.putInt(range.getStartInclusive().getZeroBasedColumn()); + locBuffer.putInt(range.getEndExclusive().getZeroBasedLine()); + locBuffer.putInt(range.getEndExclusive().getZeroBasedColumn()); + md.update(locBuffer.array()); + md.update(eObject.eResource().getURI().toString().getBytes(StandardCharsets.UTF_8)); + md.update( + NodeModelUtils.getNode(eObject) + .getText() + .getBytes( + StandardCharsets + .UTF_8)); // FIXME: this is probably redundant, but I am leaving it in for + // good measure until we validate this machinery more rigorously + return new SAtom<>(new Fingerprint(md.digest())); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("should be impossible; SHA is a supported algorithm"); + } + } + + private SExpr sList(String name, Object... parts) { + var ret = new ArrayList(); + ret.add(sym(name)); + for (var part : parts) { + if (part == null) { + continue; + } + if (part instanceof List) { + throw new IllegalArgumentException("List was not expected"); + } + if (part instanceof EObject object) { + ret.add(doSwitch(object)); + } else if (part instanceof SExpr sExpr) { + ret.add(sExpr); + } else { + ret.add(new SAtom<>(part)); + } + } + return new SList(ret); + } + + private SExpr sList(String name, List parts) { + var ret = new ArrayList(); + ret.add(sym(name)); + for (EObject part : parts) { + ret.add(doSwitch(part)); + } + if (ret.size() == 1) { + return null; + } + return new SList(ret); + } + + @Override + public SExpr caseModel(Model object) { + // Model: + // target=TargetDecl + // (imports+=Import)* + // (preambles+=Preamble)* + // (reactors+=Reactor)+ + // ; + return sList( + "model", + doSwitch(object.getTarget()), + sList("imports", object.getImports()), + sList("preambles", object.getPreambles()), + sList("reactors", object.getReactors())); + } + + @Override + public SExpr caseImport(Import object) { + // Import: 'import' reactorClasses+=ImportedReactor (',' + // reactorClasses+=ImportedReactor)* 'from' importURI=STRING ';'?; + return sList( + "import", + new SAtom<>(object.getImportURI()), + sList("reactors", object.getReactorClasses())); + } + + @Override + public SExpr caseReactorDecl(ReactorDecl object) { + // ReactorDecl: Reactor | ImportedReactor; + return sList("reactor-decl", object.getName()); + } + + @Override + public SExpr caseImportedReactor(ImportedReactor object) { + // ImportedReactor: reactorClass=[Reactor] ('as' name=ID)?; + return sList( + "imported-reactor", + object.getName(), + fingerprint(object), + object.getReactorClass().getName(), + fingerprint(object.getReactorClass())); + } + + @Override + public SExpr caseReactor(Reactor object) { + // Reactor: + // {Reactor} (attributes+=Attribute)* ((federated?='federated' | main?='main')? & + // realtime?='realtime'?) 'reactor' (name=ID)? + // ('<' typeParms+=TypeParm (',' typeParms+=TypeParm)* '>')? + // ('(' parameters+=Parameter (',' parameters+=Parameter)* ')')? + // ('at' host=Host)? + // ('extends' (superClasses+=[ReactorDecl] (',' superClasses+=[ReactorDecl])*))? + // '{' + // ( (preambles+=Preamble) + // | (stateVars+=StateVar) + // | (methods+=Method) + // | (inputs+=Input) + // | (outputs+=Output) + // | (timers+=Timer) + // | (actions+=Action) + // | (watchdogs+=Watchdog) + // | (instantiations+=Instantiation) + // | (connections+=Connection) + // | (reactions+=Reaction) + // | (modes+=Mode) + // )* '}'; + return sList( + "reactor", + new SAtom<>(object.getName()), + fingerprint(object), + sList("attributes", object.getAttributes()), + sList("is-main", object.isMain()), + sList("is-federated", object.isFederated()), + sList("is-realtime", object.isRealtime()), + sList("typeParms", object.getTypeParms()), + sList("parameters", object.getParameters()), + sList("host", object.getHost()), + sList("extends", object.getSuperClasses()), + sList("preambles", object.getPreambles()), + sList("state", object.getStateVars()), + sList("methods", object.getMethods()), + sList("inputs", object.getInputs()), + sList("outputs", object.getOutputs()), + sList("timers", object.getTimers()), + sList("actions", object.getActions()), + sList("watchdogs", object.getWatchdogs()), + sList("instantiations", object.getInstantiations()), + sList("connections", object.getConnections()), + sList("reactions", object.getReactions()), + sList("modes", object.getModes())); + } + + @Override + public SExpr caseTypeParm(TypeParm object) { + // TypeParm: + // literal=TypeExpr | code=Code + return sList("type-parm", object.getLiteral(), object.getCode()); + } + + @Override + public SExpr caseTargetDecl(TargetDecl object) { + // TargetDecl: + // 'target' name=ID (config=KeyValuePairs)? ';'?; + return sList("target-decl", object.getName(), object.getConfig()); + } + + @Override + public SExpr caseStateVar(StateVar object) { + // StateVar: + // (attributes+=Attribute)* + // (reset?='reset')? 'state' name=ID + // (':' type=Type)? + // init=Initializer? + // ';'? + // ; + return sList( + "state", + object.getName(), + sList("attributes", object.getAttributes()), + sList("is-reset", object.isReset()), + object.getType(), + object.getInit()); + } + + @Override + public SExpr caseInitializer(Initializer object) { + // Initializer: + // parens?='(' (exprs+=Expression (',' exprs+=Expression)* (trailingComma?=',')?)? ')' + // | braces?='{' (exprs+=Expression (',' exprs+=Expression)* (trailingComma?=',')?)? + // '}' + // | assign?='=' exprs+=Expression + // ; + return sList( + "initializer", sList("expr", object.getExpr()), sList("is-assign", object.isAssign())); + } + + @Override + public SExpr caseMethod(Method object) { + // const?='const'? 'method' name=ID + // '(' (arguments+=MethodArgument (',' arguments+=MethodArgument)*)? ')' + // (':' return=Type)? + // code=Code + // ';'? + return sList( + "method", + object.getName(), + sList("is-const", object.isConst()), + sList("arguments", object.getArguments()), + sList("return", object.getReturn()), + object.getCode()); + } + + @Override + public SExpr caseMethodArgument(MethodArgument object) { + // MethodArgument: + // name=ID (':' type=Type)? + return sList("method-argument", object.getName(), sList("type", object.getType())); + } + + @Override + public SExpr caseInput(Input object) { + // Input: + // (attributes+=Attribute)* mutable?='mutable'? 'input' (widthSpec=WidthSpec)? name=ID + // (':' type=Type)? ';'?; + if (inVarRef()) { + return sList("input", object.getName(), fingerprint(object)); + } + return sList( + "input", + object.getName(), + fingerprint(object), + sList("attributes", object.getAttributes()), + sList("is-mutable", object.isMutable()), + object.getWidthSpec(), + object.getType()); + } + + @Override + public SExpr caseOutput(Output object) { + // Output: + // (attributes+=Attribute)* 'output' (widthSpec=WidthSpec)? name=ID (':' type=Type)? + // ';'?; + if (inVarRef()) { + return sList("output", object.getName(), fingerprint(object)); + } + return sList( + "output", + object.getName(), + fingerprint(object), + sList("attributes", object.getAttributes()), + object.getWidthSpec(), + object.getType()); + } + + @Override + public SExpr caseTimer(Timer object) { + // Timer: + // (attributes+=Attribute)* 'timer' name=ID ('(' offset=Expression (',' + // period=Expression)? ')')? ';'?; + if (inVarRef()) { + return sList("timer", object.getName()); + } + return sList( + "timer", + object.getName(), + fingerprint(object), + sList("attributes", object.getAttributes()), + sList("offset", object.getOffset()), + sList("period", object.getPeriod())); + } + + @Override + public SExpr caseMode(Mode object) { + // Mode: + // {Mode} (initial?='initial')? 'mode' (name=ID)? + // '{' ( + // (stateVars+=StateVar) | + // (timers+=Timer) | + // (actions+=Action) | + // (watchdogs+=Watchdog) | + // (instantiations+=Instantiation) | + // (connections+=Connection) | + // (reactions+=Reaction) + // )* '}'; + if (inVarRef()) { + return sList("mode", object.getName(), fingerprint(object)); + } else { + return sList( + "mode", + object.getName(), + fingerprint(object), + sList("is-initial", object.isInitial()), + sList("state", object.getStateVars()), + sList("timers", object.getTimers()), + sList("actions", object.getActions()), + sList("watchdogs", object.getWatchdogs()), + sList("instantiations", object.getInstantiations()), + sList("connections", object.getConnections()), + sList("reactions", object.getReactions())); + } + } + + @Override + public SExpr caseAction(Action object) { + // Action: + // (attributes+=Attribute)* + // (origin=ActionOrigin)? 'action' name=ID + // ('(' minDelay=Expression (',' minSpacing=Expression (',' policy=STRING)? )? ')')? + // (':' type=Type)? ';'?; + if (inVarRef()) { + return sList("action", object.getName(), fingerprint(object)); + } + return sList( + "action", + object.getName(), + fingerprint(object), + sList("attributes", object.getAttributes()), + object.getOrigin(), + sList("min-delay", object.getMinDelay()), + sList("min-spacing", object.getMinSpacing()), + sList("policy", object.getPolicy()), + object.getType()); + } + + @Override + public SExpr caseReaction(Reaction object) { + // Reaction: + // (attributes+=Attribute)* + // (('reaction') | mutation ?= 'mutation') + // (name=ID)? + // ('(' (triggers+=TriggerRef (',' triggers+=TriggerRef)*)? ')') + // ( => sources+=VarRef (',' sources+=VarRef)*)? + // ('->' effects+=VarRefOrModeTransition (',' effects+=VarRefOrModeTransition)*)? + // (code=Code)? (stp=STP)? (deadline=Deadline)? (delimited?=';')? + // ; + return sList( + "reaction", + object.getName(), + sList("attributes", object.getAttributes()), + sList("is-mutation", object.isMutation()), + sList("triggers", object.getTriggers()), + sList("sources", object.getSources()), + sList("effects", object.getEffects()), + object.getCode(), + object.getStp(), + object.getDeadline(), + sList("is-delimited", object.isDelimited())); + } + + @Override + public SExpr caseTriggerRef(TriggerRef object) { + // TriggerRef: + // BuiltinTriggerRef | VarRef; + throw new RuntimeException("not implemented"); + } + + @Override + public SExpr caseBuiltinTriggerRef(BuiltinTriggerRef object) { + // BuiltinTriggerRef: + // type = BuiltinTrigger; + return sList("builtin-trigger-ref", object.getType()); + } + + @Override + public SExpr caseDeadline(Deadline object) { + // Deadline: + // 'deadline' '(' delay=Expression ')' code=Code; + return sList("deadline", object.getDelay(), object.getCode()); + } + + @Override + public SExpr caseWatchdog(Watchdog object) { + // Watchdog: + // 'watchdog' name=ID '(' timeout=Expression ')' + // ('->' effects+=VarRefOrModeTransition (',' effects+=VarRefOrModeTransition)*)? + // code=Code; + if (inVarRef()) { + return sList("watchdog", object.getName(), fingerprint(object)); + } + return sList( + "watchdog", + object.getName(), + fingerprint(object), + sList("timeout", object.getTimeout()), + sList("effects", object.getEffects()), + object.getCode()); + } + + @Override + public SExpr caseSTP(STP object) { + // STP: + // 'STP' '(' value=Expression ')' code=Code; + return sList("stp", object.getValue(), object.getCode()); + } + + @Override + public SExpr casePreamble(Preamble object) { + // Preamble: + // (visibility=Visibility)? 'preamble' code=Code; + return sList("preamble", object.getVisibility(), object.getCode()); + } + + @Override + public SExpr caseInstantiation(Instantiation object) { + // Instantiation: + // (attributes+=Attribute)* + // name=ID '=' 'new' (widthSpec=WidthSpec)? + // reactorClass=[ReactorDecl] ('<' typeArgs+=Type (',' typeArgs+=Type)* '>')? '(' + // (parameters+=Assignment (',' parameters+=Assignment)*)? + // ')' (('at' host=Host ';') | ';'?); + return sList( + "instantiation", + object.getName(), + sList("attributes", object.getAttributes()), + object.getWidthSpec(), + sList("reactor", object.getReactorClass().getName(), fingerprint(object.getReactorClass())), + sList("typeArgs", object.getTypeArgs()), + sList("parameters", object.getParameters()), + object.getHost()); + } + + @Override + public SExpr caseConnection(Connection object) { + // Connection: + // ((leftPorts += VarRef (',' leftPorts += VarRef)*) + // | ( '(' leftPorts += VarRef (',' leftPorts += VarRef)* ')' iterated ?= '+'?)) + // ('->' | physical?='~>') + // rightPorts += VarRef (',' rightPorts += VarRef)* + // ('after' delay=Expression)? + // (serializer=Serializer)? + // ';'? + return sList( + "connection", + sList("left-ports", object.getLeftPorts()), + sList("right-ports", object.getRightPorts()), + sList("is-iterated", object.isIterated()), + sList("is-physical", object.isPhysical()), + sList("delay", object.getDelay()), + object.getSerializer()); + } + + @Override + public SExpr caseSerializer(Serializer object) { + // Serializer: + // 'serializer' type=STRING + // ; + return sList("serializer", object.getType()); + } + + @Override + public SExpr caseAttribute(Attribute object) { + // Attribute: + // '@' attrName=ID ('(' (attrParms+=AttrParm (',' attrParms+=AttrParm)* ','?)? ')')? + // ; + return sList("attribute", object.getAttrName(), sList("attr-parms", object.getAttrParms())); + } + + @Override + public SExpr caseAttrParm(AttrParm object) { + // AttrParm: + // (name=ID '=')? value=Literal; + return sList("attr-parm", object.getName(), object.getValue()); + } + + @Override + public SExpr caseKeyValuePairs(KeyValuePairs object) { + // KeyValuePairs: + // {KeyValuePairs} '{' (pairs+=KeyValuePair (',' (pairs+=KeyValuePair))* ','?)? '}'; + return sList("key-value-pairs", object.getPairs()); + } + + @Override + public SExpr caseKeyValuePair(KeyValuePair object) { + // KeyValuePair: + // name=(Kebab|STRING) ':' value=Element; + return sList("pair", object.getName(), object.getValue()); + } + + @Override + public SExpr caseArray(Array object) { + // Array: // todo allow empty array in grammar, replace with validator error + // '[' elements+=Element (',' (elements+=Element))* ','? ']'; + return sList("array", object.getElements()); + } + + @Override + public SExpr caseElement(Element object) { + // Element: + // keyvalue=KeyValuePairs + // | array=Array + // | literal=Literal + // | (time=INT unit=TimeUnit) + // | id=Path; + return sList( + "element", + object.getKeyvalue(), + object.getArray(), + object.getLiteral(), + object.getTime() == 0 + && (object.getKeyvalue() != null + || object.getArray() != null + || object.getLiteral() != null + || object.getId() != null) + ? null + : sList("time", object.getTime(), object.getUnit()), + object.getId()); + } + + @Override + public SExpr caseTypedVariable(TypedVariable object) { + // TypedVariable: + // Port | Action + // ; + throw new RuntimeException("not implemented"); + } + + @Override + public SExpr caseVariable(Variable object) { + // Variable: + // TypedVariable | Timer | Mode | Watchdog; + throw new RuntimeException("not implemented"); + } + + @Override + public SExpr caseVarRef(VarRef object) { + // VarRef: + // (variable=[Variable] | container=[Instantiation] '.' variable=[Variable] + // | interleaved?='interleaved' '(' (variable=[Variable] | container=[Instantiation] '.' + // variable=[Variable]) ')') ('as' (alias=ID))? + // ; + return sList( + "var-ref", + object.getVariable(), + object.getContainer(), + sList("is-interleaved", object.isInterleaved()), + sList("alias", object.getAlias())); + } + + @Override + public SExpr caseAssignment(Assignment object) { + // Assignment: + // lhs=[Parameter] + // rhs=AssignmentInitializer + // ; + return sList("assignment", object.getLhs(), object.getRhs()); + } + + @Override + public SExpr caseParameter(Parameter object) { + // Parameter: + // (attributes+=Attribute)* + // name=ID (':' type=Type)? + // init=Initializer? + // ; + return sList( + "parameter", + object.getName(), + sList("attributes", object.getAttributes()), + object.getType(), + object.getInit()); + } + + @Override + public SExpr caseExpression(Expression object) { + // Expression: + // {Literal} literal=Literal + // | Time + // | ParameterReference + // | {CodeExpr} code=Code + // | BracedListExpression + // | BracketListExpression + // ; + throw new RuntimeException("not implemented"); + // return sList("expression", object.getLiteral(), object.getTime(), + // object.getParameter(), object.getCode(), object.getBracedListExpression(), + // object.getBracketListExpression()); + } + + @Override + public SExpr caseBracedListExpression(BracedListExpression object) { + // BracedListExpression: + // '{' {BracedListExpression} (items+=Expression (',' items+=Expression)*)? ','? '}' + // ; + return sList("braced-list-expression", object.getItems()); + } + + @Override + public SExpr caseBracketListExpression(BracketListExpression object) { + // BracketListExpression: + // '[' {BracketListExpression} (items+=Expression (',' items+=Expression)*)? ','? ']' + // ; + return sList("bracket-list-expression", object.getItems()); + } + + @Override + public SExpr caseParameterReference(ParameterReference object) { + // ParameterReference: + // parameter=[Parameter] + // ; + return sList("parameter-reference", object.getParameter()); + } + + @Override + public SExpr caseTime(Time object) { + // Time: + // (interval=INT unit=TimeUnit) + // ; + return sList("time", object.getInterval(), object.getUnit()); + } + + @Override + public SExpr casePort(Port object) { + // Port: + // Input | Output; + throw new RuntimeException("not implemented"); + } + + @Override + public SExpr caseType(Type object) { + // Type: + // time?='time' (arraySpec=ArraySpec)? + // | id=DottedName ('<' typeArgs+=Type (',' typeArgs+=Type)* '>')? (stars+='*')* + // (arraySpec=ArraySpec)? + // | code=Code + // ; + return sList( + "type", + object.getId(), + sList("stars", object.getStars().size()), + object.getCStyleArraySpec(), + object.getCode(), + sList("is-time", object.isTime())); + } + + @Override + public SExpr caseCStyleArraySpec(CStyleArraySpec object) { + // ArraySpec: + // '[' ( ofVariableLength?=']' | length=INT ']' ); + return sList( + "array-spec", + sList("length", object.getLength()), + sList("is-of-variable-length", object.isOfVariableLength())); + } + + @Override + public SExpr caseWidthSpec(WidthSpec object) { + // WidthSpec: + // '[' ( ofVariableLength?=']' | (terms+=WidthTerm) ('+' terms+=WidthTerm)* ']' ); + return sList( + "width-spec", + sList("terms", object.getTerms()), + sList("is-of-variable-length", object.isOfVariableLength())); + } + + @Override + public SExpr caseWidthTerm(WidthTerm object) { + // WidthTerm: + // width=INT + // | parameter=[Parameter] + // | 'widthof(' port=VarRef ')' + // | code=Code; + return sList( + "width-term", + sList("width", object.getWidth()), + object.getParameter(), + sList("width-of", object.getPort()), + object.getCode()); + } + + @Override + public SExpr caseIPV4Host(IPV4Host object) { + // IPV4Host: + // (user=Kebab '@')? addr=IPV4Addr (':' port=INT)? + // ; + return sList( + "ipv4-host", + sList("user", object.getUser()), + object.getAddr(), + sList("port", object.getPort())); + } + + @Override + public SExpr caseIPV6Host(IPV6Host object) { + // IPV6Host: + // ('[' (user=Kebab '@')? addr=IPV6Addr ']' (':' port=INT)?) + // ; + return sList( + "ipv6-host", + sList("user", object.getUser()), + object.getAddr(), + sList("port", object.getPort())); + } + + @Override + public SExpr caseNamedHost(NamedHost object) { + // NamedHost: + // (user=Kebab '@')? addr=HostName (':' port=INT)? + // ; + return sList( + "named-host", + sList("user", object.getUser()), + object.getAddr(), + sList("port", object.getPort())); + } + + @Override + public SExpr caseHost(Host object) { + // Host: + // IPV4Host | IPV6Host | NamedHost + // ; + // return super.caseHost(object); + throw new RuntimeException("not implemented"); + } + + @Override + public SExpr caseCode(Code object) { + // Code: + // // {Code} '{=' (tokens+=Token)* '=}' + // {Code} '{=' body=Body '=}' + // ; + return sList("code", object.getBody()); + } + + @Override + public SExpr caseLiteral(Literal object) { + // Literal: + // STRING | CHAR_LIT | SignedFloat | SignedInt | Boolean + // ; + return sList("literal", object.getLiteral()); + } + + @Override + public SExpr caseCodeExpr(CodeExpr object) { + return doSwitch(object.getCode()); + } + + @Override + public SExpr defaultCase(EObject object) { + throw new RuntimeException("this should be unreachable"); + } + + public abstract static class SExpr { + private Optional m = Optional.empty(); + + protected abstract String display(); + + public void setMetadata(Metadata m) { + // noinspection StatementWithEmptyBody + if (this.m.isPresent()) { + // It is actually possible for doSwitch to be + // invoked twice, so this can be called redundantly + // in non-error cases. Don't ask me why. It creates + // redundant work, but this hasn't been shown to a + // performance bottleneck, so it's fine. + // throw new IllegalStateException("tried to set the metadata twice for object " + + // display()); + } else { + this.m = Optional.of(m); + } + } + + @Override + public String toString() { + return m.map( + metadata -> String.format("(%s\n%s)", metadata, display().indent(1).stripTrailing())) + .orElseGet( + () -> { + var indented = display().indent(1); + return String.format("(()%s)", indented.stripTrailing()); + }); + } + } + + public class SList extends SExpr { + private final List parts; + + public SList(List parts) { + this.parts = parts; + } + + protected String display() { + var ret = new StringBuilder(); + ret.append('('); + var flat = parts.stream().allMatch(it -> it instanceof SAtom); + var first = true; + for (var part : parts) { + if (!first) { + if (!flat) { + ret.append('\n'); + } else { + ret.append(' '); + } + } + var s = part.toString(); + if (!flat && !first) { + s = s.indent(1).stripTrailing(); + } + ret.append(s); + first = false; + } + ret.append(')'); + return ret.toString(); + } + + public static class SAtom extends SExpr { + private final T x; + + public SAtom(T x) { + this.x = x; + } + + public String display() { + if (x instanceof String s) { + return "\"" + s.replaceAll("\"", "\uD83C\uDCA0") + "\""; + } else if (x instanceof Boolean b) { + return b ? "#t" : "#f"; + } else { + return String.valueOf(x); + } + } + } + + public record Sym(String s) { + @Override + public String toString() { + return s; + } + } + + public record Fingerprint(byte[] digest) { + @Override + public String toString() { + var ret = new StringBuilder(); + ret.append("#u8("); + var first = true; + for (byte c : digest) { + if (!first) { + ret.append(' '); + } + ret.append(Byte.toUnsignedInt(c)); + first = false; + } + ret.append(")"); + return ret.toString(); + } + } + + public record Metadata( + URI uri, int offset, int startLine, int startCol, int endLine, int endCol) { + @Override + public String toString() { + return String.format( + "(\"%s\" %d %d %d %d %d)", + uri.toFileString(), offset, startLine, startCol, endLine, endCol); + } + } + } +} diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtension.java b/core/src/main/java/org/lflang/federated/extensions/CExtension.java index 28010a90ac..87a19e29d4 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtension.java @@ -76,14 +76,14 @@ public class CExtension implements FedTargetExtension { @Override public void initializeTargetConfig( LFGeneratorContext context, - int numOfFederates, + List federateNames, FederateInstance federate, FederationFileConfig fileConfig, MessageReporter messageReporter, RtiConfig rtiConfig) throws IOException { - CExtensionUtils.handleCompileDefinitions(federate, numOfFederates, rtiConfig, messageReporter); + CExtensionUtils.handleCompileDefinitions(federate, federateNames, rtiConfig, messageReporter); generateCMakeInclude(federate, fileConfig); diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java b/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java index c5adf0a810..d65ff0658d 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java @@ -184,7 +184,7 @@ static boolean isSharedPtrType(InferredType type, CTypes types) { public static void handleCompileDefinitions( FederateInstance federate, - int numOfFederates, + List federateNames, RtiConfig rtiConfig, MessageReporter messageReporter) { @@ -198,9 +198,11 @@ public static void handleCompileDefinitions( if (federate.targetConfig.get(AuthProperty.INSTANCE)) { definitions.put("FEDERATED_AUTHENTICATED", ""); } - definitions.put("NUMBER_OF_FEDERATES", String.valueOf(numOfFederates)); + definitions.put("NUMBER_OF_FEDERATES", String.valueOf(federateNames.size())); definitions.put("EXECUTABLE_PREAMBLE", ""); definitions.put("FEDERATE_ID", String.valueOf(federate.id)); + definitions.put( + "_LF_FEDERATE_NAMES_COMMA_SEPARATED", "\"" + String.join(",", federateNames) + "\""); CompileDefinitionsProperty.INSTANCE.update(federate.targetConfig, definitions); diff --git a/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java b/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java index e708c117d7..07a20809ef 100644 --- a/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/FedTargetExtension.java @@ -1,6 +1,7 @@ package org.lflang.federated.extensions; import java.io.IOException; +import java.util.List; import org.lflang.InferredType; import org.lflang.MessageReporter; import org.lflang.federated.generator.FedConnectionInstance; @@ -21,14 +22,14 @@ public interface FedTargetExtension { * Perform necessary actions to initialize the target config. * * @param context The context of the original code generation process. - * @param numOfFederates The number of federates in the program. + * @param federateNames The names of all the federates in the program. * @param federate The federate instance. * @param fileConfig An instance of {@code FedFileConfig}. * @param messageReporter Used to report errors. */ void initializeTargetConfig( LFGeneratorContext context, - int numOfFederates, + List federateNames, FederateInstance federate, FederationFileConfig fileConfig, MessageReporter messageReporter, diff --git a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java index 86a220b5f1..92ec170404 100644 --- a/core/src/main/java/org/lflang/federated/extensions/TSExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/TSExtension.java @@ -33,7 +33,7 @@ public class TSExtension implements FedTargetExtension { @Override public void initializeTargetConfig( LFGeneratorContext context, - int numOfFederates, + List federateNames, FederateInstance federate, FederationFileConfig fileConfig, MessageReporter messageReporter, diff --git a/core/src/main/java/org/lflang/federated/generator/FedEmitter.java b/core/src/main/java/org/lflang/federated/generator/FedEmitter.java index 5d6452df77..a6cb4d232b 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedEmitter.java +++ b/core/src/main/java/org/lflang/federated/generator/FedEmitter.java @@ -4,6 +4,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.lflang.MessageReporter; import org.lflang.federated.launcher.RtiConfig; @@ -32,7 +33,7 @@ public FedEmitter( /** Generate a .lf file for federate {@code federate}. */ Map generateFederate( - LFGeneratorContext context, FederateInstance federate, int numOfFederates) + LFGeneratorContext context, FederateInstance federate, List federateNames) throws IOException { String fedName = federate.name; Files.createDirectories(fileConfig.getSrcPath()); @@ -49,7 +50,7 @@ Map generateFederate( "\n", new FedTargetEmitter() .generateTarget( - context, numOfFederates, federate, fileConfig, messageReporter, rtiConfig), + context, federateNames, federate, fileConfig, messageReporter, rtiConfig), new FedImportEmitter().generateImports(federate, fileConfig), new FedPreambleEmitter() .generatePreamble(federate, fileConfig, rtiConfig, messageReporter), diff --git a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java index 2d06f21436..2f4a4dfb0a 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java +++ b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java @@ -19,6 +19,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import java.util.stream.Collectors; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.xtext.generator.JavaIoFileSystemAccess; @@ -166,7 +167,11 @@ public boolean doGenerate(Resource resource, LFGeneratorContext context) throws // Generate LF code for each federate. Map lf2lfCodeMapMap = new HashMap<>(); for (FederateInstance federate : federates) { - lf2lfCodeMapMap.putAll(fedEmitter.generateFederate(context, federate, federates.size())); + lf2lfCodeMapMap.putAll( + fedEmitter.generateFederate( + context, + federate, + federates.stream().map(fed -> fed.name).collect(Collectors.toList()))); } // Do not invoke target code generators if --no-compile flag is used. diff --git a/core/src/main/java/org/lflang/federated/generator/FedTargetEmitter.java b/core/src/main/java/org/lflang/federated/generator/FedTargetEmitter.java index 53f3823f72..deea984ca6 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedTargetEmitter.java +++ b/core/src/main/java/org/lflang/federated/generator/FedTargetEmitter.java @@ -1,6 +1,7 @@ package org.lflang.federated.generator; import java.io.IOException; +import java.util.List; import org.lflang.MessageReporter; import org.lflang.ast.FormattingUtil; import org.lflang.federated.extensions.FedTargetExtensionFactory; @@ -11,7 +12,7 @@ public class FedTargetEmitter { String generateTarget( LFGeneratorContext context, - int numOfFederates, + List federateNames, FederateInstance federate, FederationFileConfig fileConfig, MessageReporter messageReporter, @@ -24,7 +25,7 @@ String generateTarget( // See https://issues.lf-lang.org/1667 FedTargetExtensionFactory.getExtension(federate.targetConfig.target) .initializeTargetConfig( - context, numOfFederates, federate, fileConfig, messageReporter, rtiConfig); + context, federateNames, federate, fileConfig, messageReporter, rtiConfig); return FormattingUtil.renderer(federate.targetConfig.target) .apply(federate.targetConfig.extractTargetDecl()); diff --git a/core/src/main/java/org/lflang/federated/validation/FedValidator.java b/core/src/main/java/org/lflang/federated/validation/FedValidator.java index fc82733e0d..634a6399c1 100644 --- a/core/src/main/java/org/lflang/federated/validation/FedValidator.java +++ b/core/src/main/java/org/lflang/federated/validation/FedValidator.java @@ -18,6 +18,10 @@ public class FedValidator { public static void validateFederatedReactor(Reactor reactor, MessageReporter messageReporter) { if (!reactor.isFederated()) return; + if (reactor.getInstantiations().size() == 0) { + messageReporter.at(reactor).error("The top-level reactor does not contain any federates"); + } + // Construct the set of excluded reactions for this federate. // If a reaction is a network reaction that belongs to this federate, we // don't need to perform this analysis. diff --git a/core/src/main/java/org/lflang/generator/IntegratedBuilder.java b/core/src/main/java/org/lflang/generator/IntegratedBuilder.java index 1e88a15817..6559c1221e 100644 --- a/core/src/main/java/org/lflang/generator/IntegratedBuilder.java +++ b/core/src/main/java/org/lflang/generator/IntegratedBuilder.java @@ -1,5 +1,6 @@ package org.lflang.generator; +import com.google.gson.JsonParser; import com.google.inject.Inject; import com.google.inject.Provider; import java.nio.file.Path; @@ -54,6 +55,7 @@ public interface ReportProgress { */ public GeneratorResult run( URI uri, + String json, boolean mustComplete, ReportProgress reportProgress, CancelIndicator cancelIndicator) { @@ -70,7 +72,7 @@ public GeneratorResult run( if (cancelIndicator.isCanceled()) return GeneratorResult.CANCELLED; if (messageReporter.getErrorsOccurred()) return GeneratorResult.FAILED; reportProgress.apply("Generating code...", VALIDATED_PERCENT_PROGRESS); - return doGenerate(uri, mustComplete, reportProgress, cancelIndicator); + return doGenerate(uri, json, mustComplete, reportProgress, cancelIndicator); } /* ------------------------- PRIVATE METHODS ------------------------- */ @@ -100,6 +102,7 @@ private void validate(URI uri, MessageReporter messageReporter) { */ private GeneratorResult doGenerate( URI uri, + String json, boolean mustComplete, ReportProgress reportProgress, CancelIndicator cancelIndicator) { @@ -109,7 +112,7 @@ private GeneratorResult doGenerate( mustComplete ? Mode.LSP_SLOW : LFGeneratorContext.Mode.LSP_MEDIUM, cancelIndicator, reportProgress, - getArgs(), + getArgs(json), resource, fileAccess, fileConfig -> new LanguageServerMessageReporter(resource.getContents().get(0))); @@ -123,7 +126,7 @@ private GeneratorResult doGenerate( * @param uri The URI of a Lingua Franca file. * @return The resource corresponding to {@code uri}. */ - private Resource getResource(URI uri) { + public Resource getResource(URI uri) { return resourceSetProvider.get().getResource(uri, true); } @@ -137,7 +140,11 @@ static DiagnosticSeverity convertSeverity(Severity severity) { } /** Return arguments to feed to the code generator. Currently, no arguments are being set. */ - protected GeneratorArguments getArgs() { - return GeneratorArguments.none(); + protected GeneratorArguments getArgs(String jsonString) { + if (jsonString == null || jsonString.isBlank()) { + return GeneratorArguments.none(); + } + var json = JsonParser.parseString(jsonString).getAsJsonObject(); + return new GeneratorArguments(false, null, false, json, false, false, null, List.of()); } } diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index 69ecc4b43c..9b984f5fc9 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -42,6 +42,7 @@ import org.lflang.target.property.PlatformProperty; import org.lflang.target.property.ProtobufsProperty; import org.lflang.target.property.SingleThreadedProperty; +import org.lflang.target.property.TracePluginProperty; import org.lflang.target.property.WorkersProperty; import org.lflang.target.property.type.PlatformType.Platform; import org.lflang.util.FileUtil; @@ -262,16 +263,18 @@ CodeBuilder generateCMakeCode( .get(CompileDefinitionsProperty.INSTANCE) .forEach( (key, value) -> { - cMakeCode.pr("if (NOT DEFINED " + key + ")\n"); - cMakeCode.indent(); var v = "TRUE"; if (value != null && !value.isEmpty()) { v = value; } - cMakeCode.pr("set(" + key + " " + v + ")\n"); - cMakeCode.unindent(); - cMakeCode.pr("endif()\n"); + cMakeCode.pr("set(" + key + " " + v + " CACHE STRING \"\")\n"); }); + // Add trace-plugin data + var tracePlugin = targetConfig.getOrDefault(TracePluginProperty.INSTANCE); + System.out.println(tracePlugin); + if (tracePlugin != null) { + cMakeCode.pr("set(LF_TRACE_PLUGIN " + tracePlugin + " CACHE STRING \"\")\n"); + } // Setup main target for different platforms switch (platformOptions.platform()) { diff --git a/core/src/main/java/org/lflang/generator/c/CCompiler.java b/core/src/main/java/org/lflang/generator/c/CCompiler.java index c0401b5684..98ca19aacf 100644 --- a/core/src/main/java/org/lflang/generator/c/CCompiler.java +++ b/core/src/main/java/org/lflang/generator/c/CCompiler.java @@ -42,7 +42,6 @@ import org.lflang.target.property.CompilerProperty; import org.lflang.target.property.PlatformProperty; import org.lflang.target.property.PlatformProperty.PlatformOptions; -import org.lflang.target.property.TracePluginProperty; import org.lflang.target.property.type.BuildTypeType.BuildType; import org.lflang.target.property.type.PlatformType.Platform; import org.lflang.util.FileUtil; @@ -238,14 +237,6 @@ private static List cmakeOptions(TargetConfig targetConfig, FileConfig f "-DCMAKE_INSTALL_BINDIR=" + FileUtil.toUnixString(fileConfig.getOutPath().relativize(fileConfig.binPath)), "-DLF_FILE_SEPARATOR=\"" + maybeQuote + separator + maybeQuote + "\"")); - var tracePlugin = targetConfig.getOrDefault(TracePluginProperty.INSTANCE); - if (tracePlugin != null) { - arguments.add( - "-DLF_TRACE_PLUGIN=" - + targetConfig - .getOrDefault(TracePluginProperty.INSTANCE) - .getImplementationArchiveFile()); - } // Add #define for source file directory. // Do not do this for federated programs because for those, the definition is put // into the cmake file (and fileConfig.srcPath is the wrong directory anyway). diff --git a/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java b/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java index ba054d6e49..603e7d7928 100644 --- a/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CMainFunctionGenerator.java @@ -98,8 +98,8 @@ private String generateSetDefaultCliOption() { ? String.join( "\n", "const char* _lf_default_argv[] = { " - + StringUtil.addDoubleQuotes( - StringUtil.joinObjects(runCommand, StringUtil.addDoubleQuotes(", "))) + + StringUtil.joinObjects( + runCommand.stream().map(StringUtil::addDoubleQuotes).toList(), ", ") + " };", "void lf_set_default_command_line_options() {", " default_argc = " + runCommand.size() + ";", diff --git a/core/src/main/java/org/lflang/generator/docker/CDockerGenerator.java b/core/src/main/java/org/lflang/generator/docker/CDockerGenerator.java index ab04e6cfd0..3292485c57 100644 --- a/core/src/main/java/org/lflang/generator/docker/CDockerGenerator.java +++ b/core/src/main/java/org/lflang/generator/docker/CDockerGenerator.java @@ -2,6 +2,7 @@ import org.eclipse.xtext.xbase.lib.IterableExtensions; import org.lflang.generator.LFGeneratorContext; +import org.lflang.generator.c.CCompiler; import org.lflang.target.Target; import org.lflang.target.property.BuildCommandsProperty; import org.lflang.util.StringUtil; @@ -71,24 +72,36 @@ protected String generateRunForBuildDependencies() { return """ # Install build dependencies RUN set -ex && apk add --no-cache %s musl-dev cmake make + # Optional user specified run command + %s """ - .formatted(compiler); + .formatted(compiler, userRunCommand()); } else { return """ + # Optional user specified run command + %s # Check for build dependencies RUN which make && which cmake && which %s """ - .formatted(compiler); + .formatted(userRunCommand(), compiler); } } /** Return the default compile command for the C docker container. */ protected String generateCompileCommand() { + var ccompile = + new CCompiler( + context.getTargetConfig(), + context.getFileConfig(), + context.getErrorReporter(), + context.getTargetConfig().target == Target.C); return String.join( "\n", "RUN set -ex && \\", "mkdir bin && \\", - "cmake -DCMAKE_INSTALL_BINDIR=./bin -S src-gen -B bin && \\", + String.format( + "%s -DCMAKE_INSTALL_BINDIR=./bin -S src-gen -B bin && \\", + ccompile.compileCmakeCommand()), "cd bin && \\", "make all"); } diff --git a/core/src/main/java/org/lflang/generator/docker/DockerGenerator.java b/core/src/main/java/org/lflang/generator/docker/DockerGenerator.java index 203c5f94d7..2fc47e9f0e 100644 --- a/core/src/main/java/org/lflang/generator/docker/DockerGenerator.java +++ b/core/src/main/java/org/lflang/generator/docker/DockerGenerator.java @@ -42,6 +42,15 @@ public String baseImage() { return defaultImage(); } + public String userRunCommand() { + var runCmd = context.getTargetConfig().get(DockerProperty.INSTANCE).run(); + if (runCmd != null && !runCmd.isEmpty()) { + return "RUN " + runCmd; + } else { + return ""; + } + } + /** * Produce a DockerData object, which bundles all information needed to output a Dockerfile. * diff --git a/core/src/main/java/org/lflang/target/Target.java b/core/src/main/java/org/lflang/target/Target.java index 0156866890..811220be53 100644 --- a/core/src/main/java/org/lflang/target/Target.java +++ b/core/src/main/java/org/lflang/target/Target.java @@ -643,6 +643,7 @@ public void initialize(TargetConfig config) { SchedulerProperty.INSTANCE, SingleThreadedProperty.INSTANCE, TracingProperty.INSTANCE, + TracePluginProperty.INSTANCE, WorkersProperty.INSTANCE); case Rust -> config.register( BuildTypeProperty.INSTANCE, diff --git a/core/src/main/java/org/lflang/target/property/DockerProperty.java b/core/src/main/java/org/lflang/target/property/DockerProperty.java index 1dc47724fe..c58e2bebf9 100644 --- a/core/src/main/java/org/lflang/target/property/DockerProperty.java +++ b/core/src/main/java/org/lflang/target/property/DockerProperty.java @@ -35,6 +35,7 @@ public DockerOptions initialValue() { public DockerOptions fromAst(Element node, MessageReporter reporter) { var enabled = false; var from = ""; + var run = ""; var rti = DockerOptions.DOCKERHUB_RTI_IMAGE; if (node.getLiteral() != null) { @@ -45,20 +46,26 @@ public DockerOptions fromAst(Element node, MessageReporter reporter) { enabled = true; for (KeyValuePair entry : node.getKeyvalue().getPairs()) { DockerOption option = (DockerOption) DictionaryType.DOCKER_DICT.forName(entry.getName()); - if (option != null && option.equals(DockerOption.FROM)) { - from = ASTUtils.elementToSingleString(entry.getValue()); - } - if (option != null && option.equals(DockerOption.RTI_IMAGE)) { - rti = ASTUtils.elementToSingleString(entry.getValue()); + switch (option) { + case FROM -> from = ASTUtils.elementToSingleString(entry.getValue()); + case RUN -> run = ASTUtils.elementToSingleString(entry.getValue()); + case RTI_IMAGE -> rti = ASTUtils.elementToSingleString(entry.getValue()); } } } - return new DockerOptions(enabled, from, rti); + return new DockerOptions(enabled, from, run, rti); } @Override protected DockerOptions fromString(String string, MessageReporter reporter) { - throw new UnsupportedOperationException("Not supported yet."); + if (string.equalsIgnoreCase("true")) { + return new DockerOptions(true); + } else if (string.equalsIgnoreCase("false")) { + return new DockerOptions(false); + } else { + throw new UnsupportedOperationException( + "Docker options other than \"true\" and \"false\" are not supported."); + } } @Override @@ -74,17 +81,10 @@ public Element toAstElement(DockerOptions value) { for (DockerOption opt : DockerOption.values()) { KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); pair.setName(opt.toString()); - if (opt == DockerOption.FROM) { - if (value.from == null) { - continue; - } - pair.setValue(ASTUtils.toElement(value.from)); - } - if (opt == DockerOption.RTI_IMAGE) { - if (value.rti.equals(DockerOptions.DOCKERHUB_RTI_IMAGE)) { - continue; - } - pair.setValue(ASTUtils.toElement(value.rti)); + switch (opt) { + case FROM -> pair.setValue(ASTUtils.toElement(value.from)); + case RUN -> pair.setValue(ASTUtils.toElement(value.run)); + case RTI_IMAGE -> pair.setValue(ASTUtils.toElement(value.rti)); } kvp.getPairs().add(pair); } @@ -102,7 +102,7 @@ public String name() { } /** Settings related to Docker options. */ - public record DockerOptions(boolean enabled, String from, String rti) { + public record DockerOptions(boolean enabled, String from, String run, String rti) { /** Default location to pull the rti from. */ public static final String DOCKERHUB_RTI_IMAGE = "lflang/rti:rti"; @@ -111,7 +111,7 @@ public record DockerOptions(boolean enabled, String from, String rti) { public static final String LOCAL_RTI_IMAGE = "rti:local"; public DockerOptions(boolean enabled) { - this(enabled, "", DOCKERHUB_RTI_IMAGE); + this(enabled, "", "", DOCKERHUB_RTI_IMAGE); } } @@ -122,6 +122,7 @@ public DockerOptions(boolean enabled) { */ public enum DockerOption implements DictionaryElement { FROM("FROM", PrimitiveType.STRING), + RUN("RUN", PrimitiveType.STRING), RTI_IMAGE("rti-image", PrimitiveType.STRING); public final PrimitiveType type; diff --git a/core/src/main/java/org/lflang/target/property/TracePluginProperty.java b/core/src/main/java/org/lflang/target/property/TracePluginProperty.java index 2d150b9fab..49bc833892 100644 --- a/core/src/main/java/org/lflang/target/property/TracePluginProperty.java +++ b/core/src/main/java/org/lflang/target/property/TracePluginProperty.java @@ -1,56 +1,18 @@ package org.lflang.target.property; -import org.lflang.MessageReporter; -import org.lflang.lf.Element; -import org.lflang.target.TargetConfig; -import org.lflang.target.property.TracePluginProperty.TracePluginOptions; -import org.lflang.target.property.type.PrimitiveType; - /** Property that provides an alternative tracing implementation. */ -public class TracePluginProperty extends TargetProperty { +/** The compiler to invoke, unless a build command has been specified. */ +public final class TracePluginProperty extends StringProperty { /** Singleton target property instance. */ public static final TracePluginProperty INSTANCE = new TracePluginProperty(); private TracePluginProperty() { - super(PrimitiveType.STRING); - } - - @Override - public TracePluginOptions initialValue() { - return null; - } - - @Override - protected TracePluginOptions fromAst(Element node, MessageReporter reporter) { - reporter.at(node).error(TargetConfig.NOT_IN_LF_SYNTAX_MESSAGE); - return null; - } - - @Override - protected TracePluginOptions fromString(String s, MessageReporter reporter) { - return new TracePluginOptions(s); + super(); } @Override public String name() { return "trace-plugin"; } - - @Override - public Element toAstElement(TracePluginOptions value) { - throw new UnsupportedOperationException(TargetConfig.NOT_IN_LF_SYNTAX_MESSAGE); - } - - public static class TracePluginOptions { - private final String implementationArchiveFile; - - public TracePluginOptions(String implementationArchiveFile) { - this.implementationArchiveFile = implementationArchiveFile; - } - - public String getImplementationArchiveFile() { - return implementationArchiveFile; - } - } } diff --git a/core/src/main/java/org/lflang/target/property/TracingProperty.java b/core/src/main/java/org/lflang/target/property/TracingProperty.java index b47793c717..11533fc2e2 100644 --- a/core/src/main/java/org/lflang/target/property/TracingProperty.java +++ b/core/src/main/java/org/lflang/target/property/TracingProperty.java @@ -52,7 +52,7 @@ public TracingOptions fromAst(Element node, MessageReporter reporter) { @Override protected TracingOptions fromString(String string, MessageReporter reporter) { - throw new UnsupportedOperationException("Not supported yet."); + return new TracingOptions(Boolean.parseBoolean(string)); } @Override @@ -152,6 +152,11 @@ public boolean equals(Object o) { public boolean isEnabled() { return enabled; } + + @Override + public String toString() { + return Boolean.toString(this.enabled); + } } /** diff --git a/core/src/main/java/org/lflang/util/LFCommand.java b/core/src/main/java/org/lflang/util/LFCommand.java index 8bed14916e..97325f8e9e 100644 --- a/core/src/main/java/org/lflang/util/LFCommand.java +++ b/core/src/main/java/org/lflang/util/LFCommand.java @@ -95,7 +95,11 @@ public File directory() { /** Get a String representation of the stored command */ public String toString() { - return String.join(" ", processBuilder.command()); + return String.join( + " ", + (Iterable) + () -> + processBuilder.command().stream().map(it -> it.replace("'", "'\"'\"'")).iterator()); } /** diff --git a/core/src/main/java/org/lflang/util/StringUtil.java b/core/src/main/java/org/lflang/util/StringUtil.java index 516f340c06..9e30c19ea5 100644 --- a/core/src/main/java/org/lflang/util/StringUtil.java +++ b/core/src/main/java/org/lflang/util/StringUtil.java @@ -154,7 +154,7 @@ private static int getWhitespacePrefix(String code, int firstLineToConsider) { } public static String addDoubleQuotes(String str) { - return "\"" + str + "\""; + return "\"" + str.replace("\"", "\\\"") + "\""; } public static String joinObjects(List things, String delimiter) { diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 329528f0d3..1bd513091c 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 329528f0d37071d7d84501232042c48fa80334cf +Subproject commit 1bd513091c57e65f7e4d3eae972b65c01f529e28 diff --git a/core/src/test/java/org/lflang/tests/compiler/LinguaFrancaValidationTest.java b/core/src/test/java/org/lflang/tests/compiler/LinguaFrancaValidationTest.java index a400ad69d3..5454f44fa3 100644 --- a/core/src/test/java/org/lflang/tests/compiler/LinguaFrancaValidationTest.java +++ b/core/src/test/java/org/lflang/tests/compiler/LinguaFrancaValidationTest.java @@ -1086,7 +1086,10 @@ public void testFederationSupport() throws Exception { parseWithoutError( """ target %s - federated reactor {} + reactor Foo {} + federated reactor { + foo = new Foo() + } """ .formatted(target)); diff --git a/lsp/src/main/java/org/lflang/diagram/lsp/BuildArgs.java b/lsp/src/main/java/org/lflang/diagram/lsp/BuildArgs.java new file mode 100644 index 0000000000..80a78214f4 --- /dev/null +++ b/lsp/src/main/java/org/lflang/diagram/lsp/BuildArgs.java @@ -0,0 +1,43 @@ +package org.lflang.diagram.lsp; + +import java.util.Objects; + +public class BuildArgs { + + private String uri; + + private String json; + + public String getUri() { + return uri; + } + + public void setUri(String uri) { + this.uri = uri; + } + + public String getJson() { + return json; + } + + public void setJson(String json) { + this.json = json; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BuildArgs buildArgs = (BuildArgs) o; + return Objects.equals(uri, buildArgs.uri) && Objects.equals(json, buildArgs.json); + } + + @Override + public int hashCode() { + return Objects.hash(uri, json); + } +} diff --git a/lsp/src/main/java/org/lflang/diagram/lsp/LFLanguageServerExtension.java b/lsp/src/main/java/org/lflang/diagram/lsp/LFLanguageServerExtension.java index 18de51f482..4b75417126 100644 --- a/lsp/src/main/java/org/lflang/diagram/lsp/LFLanguageServerExtension.java +++ b/lsp/src/main/java/org/lflang/diagram/lsp/LFLanguageServerExtension.java @@ -10,6 +10,7 @@ import org.eclipse.xtext.ide.server.ILanguageServerExtension; import org.lflang.LFRuntimeModule; import org.lflang.LFStandaloneSetup; +import org.lflang.ast.ToSExpr; import org.lflang.generator.GeneratorResult; import org.lflang.generator.GeneratorResult.Status; import org.lflang.generator.IntegratedBuilder; @@ -40,21 +41,43 @@ public void setClient(LanguageClient client) { this.client = client; } + @JsonRequest("parser/ast") + public CompletableFuture getAst(String uri) { + return CompletableFuture.supplyAsync( + () -> { + URI parsedUri; + try { + parsedUri = URI.createFileURI(new java.net.URI(uri).getPath()); + } catch (java.net.URISyntaxException e) { + System.err.println(e); + return "LF language server failed to get AST because the URI was invalid"; + } + var model = + builder + .getResource(parsedUri) + .getContents() + .get(0); // FIXME: if the resource has syntax errors this should fail + var toSExpr = new ToSExpr(); + var sExpr = toSExpr.doSwitch(model); + return sExpr.toString(); + }); + } + /** * Handle a request for a complete build of the Lingua Franca file specified by {@code uri}. * - * @param uri the URI of the LF file of interest + * @param args the URI of the LF file of interest * @return A message describing the outcome of the build process. */ @JsonRequest("generator/build") - public CompletableFuture build(String uri) { + public CompletableFuture build(BuildArgs args) { if (client == null) return CompletableFuture.completedFuture( "Please wait for the Lingua Franca language server to be fully initialized."); return CompletableFuture.supplyAsync( () -> { try { - return buildWithProgress(client, uri, true).getUserMessage(); + return buildWithProgress(client, args, true).getUserMessage(); } catch (Exception e) { return "An internal error occurred:\n" + e; } @@ -65,27 +88,27 @@ public CompletableFuture build(String uri) { * Handles a request for the most complete build of the specified Lingua Franca file that can be * done in a limited amount of time. * - * @param uri the URI of the LF file of interest + * @param args the URI of the LF file of interest */ @JsonNotification("generator/partialBuild") - public void partialBuild(String uri) { + public void partialBuild(BuildArgs args) { if (client == null) return; - buildWithProgress(client, uri, false); + buildWithProgress(client, args, false); } /** * Completely build the specified LF program and provide information that is sufficient to run it. * - * @param uri The URI of the LF program to be built. + * @param args The URI of the LF program to be built. * @return An array consisting of the directory in which the execute command should be executed, * the program of the execute command, and the arguments of the execute command. */ @JsonNotification("generator/buildAndRun") - public CompletableFuture buildAndRun(String uri) { + public CompletableFuture buildAndRun(BuildArgs args) { return new CompletableFuture() .completeAsync( () -> { - var result = buildWithProgress(client, uri, true); + var result = buildWithProgress(client, args, true); if (!result.getStatus().equals(Status.COMPILED)) return null; LFCommand cmd = result.getContext().getFileConfig().getCommand(); ArrayList ret = new ArrayList<>(); @@ -97,10 +120,10 @@ public CompletableFuture buildAndRun(String uri) { /** Describes a build process that has a progress. */ private GeneratorResult buildWithProgress( - LanguageClient client, String uri, boolean mustComplete) { + LanguageClient client, BuildArgs args, boolean mustComplete) { URI parsedUri; try { - parsedUri = URI.createFileURI(new java.net.URI(uri).getPath()); + parsedUri = URI.createFileURI(new java.net.URI(args.getUri()).getPath()); } catch (java.net.URISyntaxException e) { // This error will appear as a silent failure to most users, but that is acceptable because // this error @@ -116,7 +139,12 @@ private GeneratorResult buildWithProgress( GeneratorResult result = null; try { result = - builder.run(parsedUri, mustComplete, progress::report, progress.getCancelIndicator()); + builder.run( + parsedUri, + args.getJson(), + mustComplete, + progress::report, + progress.getCancelIndicator()); } finally { progress.end(result == null ? "An internal error occurred." : result.getUserMessage()); }