From 2db62e8c0e95d942528ef9ff2daababe17644872 Mon Sep 17 00:00:00 2001 From: Silje Susort Date: Fri, 28 Apr 2023 17:26:13 +0200 Subject: [PATCH 01/23] Make USER_THREADS possible to specify as a target property in platform options --- org.lflang/src/org/lflang/TargetConfig.java | 5 +++++ org.lflang/src/org/lflang/TargetProperty.java | 9 ++++++++- org.lflang/src/org/lflang/generator/c/CGenerator.java | 8 ++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/TargetConfig.java b/org.lflang/src/org/lflang/TargetConfig.java index 23e99541da..9a34529124 100644 --- a/org.lflang/src/org/lflang/TargetConfig.java +++ b/org.lflang/src/org/lflang/TargetConfig.java @@ -478,6 +478,11 @@ public static class PlatformOptions { * port values depending on the infrastructure you use to flash the boards. */ public boolean flash = false; + + /** + * The int value is used to determine the number of needed threads for the user application in Zephyr. + */ + public int userThreads = 0; } /** diff --git a/org.lflang/src/org/lflang/TargetProperty.java b/org.lflang/src/org/lflang/TargetProperty.java index 1bacb1f75e..d8058009bb 100644 --- a/org.lflang/src/org/lflang/TargetProperty.java +++ b/org.lflang/src/org/lflang/TargetProperty.java @@ -432,6 +432,9 @@ public enum TargetProperty { case PORT: pair.setValue(ASTUtils.toElement(config.platformOptions.port)); break; + case USERTHREADS: + pair.setValue(ASTUtils.toElement(config.platformOptions.userThreads)); + break; } kvp.getPairs().add(pair); } @@ -472,6 +475,9 @@ public enum TargetProperty { case PORT: config.platformOptions.port = ASTUtils.elementToSingleString(entry.getValue()); break; + case USERTHREADS: + config.platformOptions.userThreads = ASTUtils.toInteger(entry.getValue()); + break; default: break; } @@ -1659,7 +1665,8 @@ public enum PlatformOption implements DictionaryElement { BAUDRATE("baud-rate", PrimitiveType.NON_NEGATIVE_INTEGER), BOARD("board", PrimitiveType.STRING), FLASH("flash", PrimitiveType.BOOLEAN), - PORT("port", PrimitiveType.STRING); + PORT("port", PrimitiveType.STRING), + USERTHREADS("user_threads", PrimitiveType.NON_NEGATIVE_INTEGER); public final PrimitiveType type; diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index f598219ddd..2bec8857bf 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -1962,6 +1962,14 @@ protected void setUpGeneralParameters() { System.out.println("To enable compilation for the Arduino platform, you must specify the fully-qualified board name (FQBN) in the target property. For example, platform: {name: arduino, board: arduino:avr:leonardo}. Entering \"no-compile\" mode and generating target code only."); targetConfig.noCompile = true; } + + if (targetConfig.platformOptions.platform == Platform.ZEPHYR && targetConfig.platformOptions.userThreads >= 0) { + targetConfig.compileDefinitions.put( + "USER_THREADS", + String.valueOf(targetConfig.platformOptions.userThreads) + ); + } + if (targetConfig.threading) { // FIXME: This logic is duplicated in CMake pickScheduler(); // FIXME: this and pickScheduler should be combined. From d311497fb1444041ee263d82daa47a76945f2855 Mon Sep 17 00:00:00 2001 From: Silje Susort Date: Fri, 12 May 2023 22:04:02 +0200 Subject: [PATCH 02/23] Fix enum name after naming convention --- org.lflang/src/org/lflang/TargetProperty.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/TargetProperty.java b/org.lflang/src/org/lflang/TargetProperty.java index d8058009bb..6dcb81ebe1 100644 --- a/org.lflang/src/org/lflang/TargetProperty.java +++ b/org.lflang/src/org/lflang/TargetProperty.java @@ -432,7 +432,7 @@ public enum TargetProperty { case PORT: pair.setValue(ASTUtils.toElement(config.platformOptions.port)); break; - case USERTHREADS: + case USER_THREADS: pair.setValue(ASTUtils.toElement(config.platformOptions.userThreads)); break; } @@ -475,7 +475,7 @@ public enum TargetProperty { case PORT: config.platformOptions.port = ASTUtils.elementToSingleString(entry.getValue()); break; - case USERTHREADS: + case USER_THREADS: config.platformOptions.userThreads = ASTUtils.toInteger(entry.getValue()); break; default: @@ -1666,7 +1666,7 @@ public enum PlatformOption implements DictionaryElement { BOARD("board", PrimitiveType.STRING), FLASH("flash", PrimitiveType.BOOLEAN), PORT("port", PrimitiveType.STRING), - USERTHREADS("user_threads", PrimitiveType.NON_NEGATIVE_INTEGER); + USER_THREADS("user-threads", PrimitiveType.NON_NEGATIVE_INTEGER); public final PrimitiveType type; From 62c39cd609accef0b1629623ef8731455a3d1385 Mon Sep 17 00:00:00 2001 From: Silje Susort Date: Fri, 12 May 2023 22:04:36 +0200 Subject: [PATCH 03/23] Use enum for setting compile definition --- org.lflang/src/org/lflang/generator/c/CGenerator.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index 2bec8857bf..d53b39a6fa 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -58,6 +58,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.TargetConfig; import org.lflang.TargetProperty; import org.lflang.TargetProperty.Platform; +import org.lflang.TargetProperty.PlatformOption; import org.lflang.federated.extensions.CExtensionUtils; @@ -1965,11 +1966,15 @@ protected void setUpGeneralParameters() { if (targetConfig.platformOptions.platform == Platform.ZEPHYR && targetConfig.platformOptions.userThreads >= 0) { targetConfig.compileDefinitions.put( - "USER_THREADS", + PlatformOption.USER_THREADS.name(), String.valueOf(targetConfig.platformOptions.userThreads) ); } + if (targetConfig.platformOptions.platform != Platform.ZEPHYR && targetConfig.platformOptions.userThreads >= 0) { + System.out.println("Specifying user threads is only for the Zephyr platform. This option will be ignored."); + } + if (targetConfig.threading) { // FIXME: This logic is duplicated in CMake pickScheduler(); // FIXME: this and pickScheduler should be combined. From 166227a453dfb763b24a2cf7403b93525e192fd9 Mon Sep 17 00:00:00 2001 From: Silje Susort Date: Fri, 12 May 2023 22:05:01 +0200 Subject: [PATCH 04/23] Add user thread test for Zephyr --- test/C/src/zephyr/UserThreads.lf | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 test/C/src/zephyr/UserThreads.lf diff --git a/test/C/src/zephyr/UserThreads.lf b/test/C/src/zephyr/UserThreads.lf new file mode 100644 index 0000000000..dc54c9f3f6 --- /dev/null +++ b/test/C/src/zephyr/UserThreads.lf @@ -0,0 +1,36 @@ +// Test user threads platform option for Zephyr. The application should be able +// to create exactly three threads. +target C { + platform: { + name: Zephyr, + user-threads: 3 + }, + threading: true, + workers: 2, +} + +main reactor { + preamble {= + void func() {} + =} + reaction(startup) {= + int res; + int thread_id; + + for (int i = 0; i < 3; i++) { + res = lf_thread_create(&thread_id, &func, NULL); + if (res != 0) { + printf("FAILURE: Expected to be able to create thread.\n"); + exit(1); + } + } + + res = lf_thread_create(&thread_id, &func, NULL); + if (res == 0) { + printf("FAILURE: Expected to not be able to create thread.\n"); + exit(2); + } else { + printf("SUCCESS: Created exactly three user threads.\n"); + } + =} +} \ No newline at end of file From 5bf8a003ba918426901a7bcf1561fdd1c42021f0 Mon Sep 17 00:00:00 2001 From: Silje Susort Date: Mon, 15 May 2023 10:04:30 +0200 Subject: [PATCH 05/23] Fix error and warning reports --- org.lflang/src/org/lflang/generator/c/CGenerator.java | 3 +-- test/C/src/zephyr/UserThreads.lf | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index dfcdf1f445..401b380687 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -1943,8 +1943,7 @@ protected void setUpGeneralParameters() { } if (targetConfig.platformOptions.platform != Platform.ZEPHYR && targetConfig.platformOptions.userThreads >= 0) { - System.out.println("Specifying user threads is only for the Zephyr platform. This option will be ignored."); - } + errorReporter.reportWarning("Specifying user threads is only for the Zephyr platform. This option will be ignored."); } if (targetConfig.threading) { // FIXME: This logic is duplicated in CMake pickScheduler(); diff --git a/test/C/src/zephyr/UserThreads.lf b/test/C/src/zephyr/UserThreads.lf index dc54c9f3f6..105da11d01 100644 --- a/test/C/src/zephyr/UserThreads.lf +++ b/test/C/src/zephyr/UserThreads.lf @@ -20,15 +20,13 @@ main reactor { for (int i = 0; i < 3; i++) { res = lf_thread_create(&thread_id, &func, NULL); if (res != 0) { - printf("FAILURE: Expected to be able to create thread.\n"); - exit(1); + lf_print_error_and_exit("Could not create thread"); } } res = lf_thread_create(&thread_id, &func, NULL); if (res == 0) { - printf("FAILURE: Expected to not be able to create thread.\n"); - exit(2); + lf_print_error_and_exit("Could create more threads than specified."); } else { printf("SUCCESS: Created exactly three user threads.\n"); } From def5022b94e2f145eda84c9ba40acc3612fc9907 Mon Sep 17 00:00:00 2001 From: Silje Susort Date: Thu, 18 May 2023 10:00:07 +0200 Subject: [PATCH 06/23] Format test --- test/C/src/zephyr/UserThreads.lf | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/test/C/src/zephyr/UserThreads.lf b/test/C/src/zephyr/UserThreads.lf index 105da11d01..c16be556fc 100644 --- a/test/C/src/zephyr/UserThreads.lf +++ b/test/C/src/zephyr/UserThreads.lf @@ -1,18 +1,17 @@ -// Test user threads platform option for Zephyr. The application should be able +// Test user threads platform option for Zephyr. The application should be able // to create exactly three threads. target C { platform: { name: Zephyr, user-threads: 3 - }, - threading: true, - workers: 2, + }, + threading: true, + workers: 2 } main reactor { - preamble {= - void func() {} - =} + preamble {= void func() {} =} + reaction(startup) {= int res; int thread_id; @@ -31,4 +30,4 @@ main reactor { printf("SUCCESS: Created exactly three user threads.\n"); } =} -} \ No newline at end of file +} From 917ea837ea3be135c132c53f5453797807fcfb81 Mon Sep 17 00:00:00 2001 From: Silje Susort Date: Thu, 18 May 2023 10:57:18 +0200 Subject: [PATCH 07/23] Fix always getting user thread warning --- org.lflang/src/org/lflang/generator/c/CGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index 4215f015fa..a5dc78c2b0 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -1942,7 +1942,7 @@ protected void setUpGeneralParameters() { ); } - if (targetConfig.platformOptions.platform != Platform.ZEPHYR && targetConfig.platformOptions.userThreads >= 0) { + if (targetConfig.platformOptions.platform != Platform.ZEPHYR && targetConfig.platformOptions.userThreads > 0) { errorReporter.reportWarning("Specifying user threads is only for the Zephyr platform. This option will be ignored."); } if (targetConfig.threading) { // FIXME: This logic is duplicated in CMake From f0174827cc2cf92f322487b096f07a497ff23b5a Mon Sep 17 00:00:00 2001 From: Silje Susort Date: Fri, 19 May 2023 09:33:30 +0200 Subject: [PATCH 08/23] Add threading check to validation of property --- org.lflang/src/org/lflang/generator/c/CGenerator.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index a5dc78c2b0..371581331b 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -1935,16 +1935,16 @@ protected void setUpGeneralParameters() { targetConfig.noCompile = true; } - if (targetConfig.platformOptions.platform == Platform.ZEPHYR && targetConfig.platformOptions.userThreads >= 0) { + if (targetConfig.platformOptions.platform == Platform.ZEPHYR && targetConfig.threading + && targetConfig.platformOptions.userThreads >= 0) { targetConfig.compileDefinitions.put( PlatformOption.USER_THREADS.name(), String.valueOf(targetConfig.platformOptions.userThreads) ); + } else if (targetConfig.platformOptions.userThreads > 0) { + errorReporter.reportWarning("Specifying user threads is only for threaded Lingua Franca on the Zephyr platform. This option will be ignored."); } - if (targetConfig.platformOptions.platform != Platform.ZEPHYR && targetConfig.platformOptions.userThreads > 0) { - errorReporter.reportWarning("Specifying user threads is only for the Zephyr platform. This option will be ignored."); } - if (targetConfig.threading) { // FIXME: This logic is duplicated in CMake pickScheduler(); // FIXME: this and pickScheduler should be combined. From 69258c4aada5669e1205d74a22bbc339c3b1c7fc Mon Sep 17 00:00:00 2001 From: Silje Susort Date: Fri, 19 May 2023 09:35:28 +0200 Subject: [PATCH 09/23] Add threaded Zephyr test functionality --- .../src/org/lflang/tests/Configurators.java | 11 ++++++++++- .../src/org/lflang/tests/runtime/CZephyrTest.java | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/Configurators.java b/org.lflang.tests/src/org/lflang/tests/Configurators.java index e3fe6c5508..eaf7001f8c 100644 --- a/org.lflang.tests/src/org/lflang/tests/Configurators.java +++ b/org.lflang.tests/src/org/lflang/tests/Configurators.java @@ -65,7 +65,7 @@ public static boolean disableThreading(LFTest test) { return true; } - public static boolean makeZephyrCompatible(LFTest test) { + public static boolean makeZephyrCompatibleUnthreaded(LFTest test) { test.getContext().getArgs().setProperty("tracing", "false"); test.getContext().getTargetConfig().threading = false; test.getContext().getTargetConfig().setByUser.add(TargetProperty.THREADING); @@ -74,6 +74,15 @@ public static boolean makeZephyrCompatible(LFTest test) { test.getContext().getTargetConfig().platformOptions.board = "qemu_cortex_a53"; return true; } + + public static boolean makeZephyrCompatible(LFTest test) { + test.getContext().getArgs().setProperty("tracing", "false"); + test.getContext().getTargetConfig().platformOptions.platform = Platform.ZEPHYR; + test.getContext().getTargetConfig().platformOptions.flash = false; + test.getContext().getTargetConfig().platformOptions.board = "qemu_cortex_a53"; + return true; + } + /** * Make no changes to the configuration. * diff --git a/org.lflang.tests/src/org/lflang/tests/runtime/CZephyrTest.java b/org.lflang.tests/src/org/lflang/tests/runtime/CZephyrTest.java index afe1646829..c1e2e5e3f5 100644 --- a/org.lflang.tests/src/org/lflang/tests/runtime/CZephyrTest.java +++ b/org.lflang.tests/src/org/lflang/tests/runtime/CZephyrTest.java @@ -62,7 +62,7 @@ public void buildGenericTests() { super.runTestsFor(List.of(Target.C), Message.DESC_GENERIC, TestCategory.GENERIC::equals, - Configurators::makeZephyrCompatible, + Configurators::makeZephyrCompatibleUnthreaded, TestLevel.BUILD, false); } From 242ce41ad872d7f509524e8e44a57285f851d5a9 Mon Sep 17 00:00:00 2001 From: erlingrj Date: Tue, 23 May 2023 13:57:24 -0700 Subject: [PATCH 10/23] Fix UserThreads test --- test/C/src/zephyr/UserThreads.lf | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/test/C/src/zephyr/UserThreads.lf b/test/C/src/zephyr/UserThreads.lf index c16be556fc..744abb5748 100644 --- a/test/C/src/zephyr/UserThreads.lf +++ b/test/C/src/zephyr/UserThreads.lf @@ -10,20 +10,26 @@ target C { } main reactor { - preamble {= void func() {} =} + preamble {= + void func(void* arg) { + lf_print("Hello from user thread"); + } + + lf_thread_t thread_ids[3]; + =} + reaction(startup) {= int res; - int thread_id; for (int i = 0; i < 3; i++) { - res = lf_thread_create(&thread_id, &func, NULL); + res = lf_thread_create(&thread_ids[i], &func, NULL); if (res != 0) { lf_print_error_and_exit("Could not create thread"); } } - res = lf_thread_create(&thread_id, &func, NULL); + res = lf_thread_create(&thread_ids[i], &func, NULL); if (res == 0) { lf_print_error_and_exit("Could create more threads than specified."); } else { From 5f181ffacb7f78d0b37f890f38c8a54a6d26ea01 Mon Sep 17 00:00:00 2001 From: erlingrj Date: Tue, 23 May 2023 13:57:50 -0700 Subject: [PATCH 11/23] Bump reactorc --- org.lflang/src/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index dc6b138746..3440598118 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit dc6b13874697315859cd75e26ddd82f9c0d83643 +Subproject commit 3440598118f1e4b7ef3f59b0f24046216a03c3db From f6faf09cf362dbd7046b190a57690bb551e31e6f Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Tue, 23 May 2023 13:58:30 -0700 Subject: [PATCH 12/23] Apply formatter (ratched only) --- .../src/org/lflang/tests/Configurators.java | 121 +- .../org/lflang/tests/runtime/CZephyrTest.java | 99 +- org.lflang/src/org/lflang/TargetConfig.java | 725 ++-- org.lflang/src/org/lflang/TargetProperty.java | 3341 +++++++------- .../org/lflang/generator/c/CGenerator.java | 3841 +++++++++-------- 5 files changed, 3996 insertions(+), 4131 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/Configurators.java b/org.lflang.tests/src/org/lflang/tests/Configurators.java index eaf7001f8c..1870d2196c 100644 --- a/org.lflang.tests/src/org/lflang/tests/Configurators.java +++ b/org.lflang.tests/src/org/lflang/tests/Configurators.java @@ -26,7 +26,6 @@ import org.lflang.TargetProperty; import org.lflang.TargetProperty.Platform; -import org.lflang.generator.LFGeneratorContext.BuildParm; import org.lflang.tests.TestRegistry.TestCategory; /** @@ -37,71 +36,69 @@ */ public class Configurators { - /** Test configuration function. */ - @FunctionalInterface - public interface Configurator { - - /** - * Apply a side effect to the given test case to change its default configuration. - * Return true if configuration succeeded, false otherwise. - */ - boolean configure(LFTest test); - } + /** Test configuration function. */ + @FunctionalInterface + public interface Configurator { /** - * Configure the given test to use single-threaded execution. - * - * For targets that provide a threaded and an unthreaded runtime, - * this configures using the unthreaded runtime. For targets that - * do not distinguish threaded and unthreaded runtime, the number - * of workers is set to 1. - * - * @param test The test to configure. - * @return True if successful, false otherwise. + * Apply a side effect to the given test case to change its default configuration. Return true + * if configuration succeeded, false otherwise. */ - public static boolean disableThreading(LFTest test) { - test.getContext().getArgs().setProperty("threading", "false"); - test.getContext().getArgs().setProperty("workers", "1"); - return true; - } + boolean configure(LFTest test); + } - public static boolean makeZephyrCompatibleUnthreaded(LFTest test) { - test.getContext().getArgs().setProperty("tracing", "false"); - test.getContext().getTargetConfig().threading = false; - test.getContext().getTargetConfig().setByUser.add(TargetProperty.THREADING); - test.getContext().getTargetConfig().platformOptions.platform = Platform.ZEPHYR; - test.getContext().getTargetConfig().platformOptions.flash = false; - test.getContext().getTargetConfig().platformOptions.board = "qemu_cortex_a53"; - return true; - } + /** + * Configure the given test to use single-threaded execution. + * + *

For targets that provide a threaded and an unthreaded runtime, this configures using the + * unthreaded runtime. For targets that do not distinguish threaded and unthreaded runtime, the + * number of workers is set to 1. + * + * @param test The test to configure. + * @return True if successful, false otherwise. + */ + public static boolean disableThreading(LFTest test) { + test.getContext().getArgs().setProperty("threading", "false"); + test.getContext().getArgs().setProperty("workers", "1"); + return true; + } - public static boolean makeZephyrCompatible(LFTest test) { - test.getContext().getArgs().setProperty("tracing", "false"); - test.getContext().getTargetConfig().platformOptions.platform = Platform.ZEPHYR; - test.getContext().getTargetConfig().platformOptions.flash = false; - test.getContext().getTargetConfig().platformOptions.board = "qemu_cortex_a53"; - return true; - } + public static boolean makeZephyrCompatibleUnthreaded(LFTest test) { + test.getContext().getArgs().setProperty("tracing", "false"); + test.getContext().getTargetConfig().threading = false; + test.getContext().getTargetConfig().setByUser.add(TargetProperty.THREADING); + test.getContext().getTargetConfig().platformOptions.platform = Platform.ZEPHYR; + test.getContext().getTargetConfig().platformOptions.flash = false; + test.getContext().getTargetConfig().platformOptions.board = "qemu_cortex_a53"; + return true; + } - /** - * Make no changes to the configuration. - * - * @param ignoredTest The test to configure. - * @return True - */ - public static boolean noChanges(LFTest ignoredTest) { - return true; - } + public static boolean makeZephyrCompatible(LFTest test) { + test.getContext().getArgs().setProperty("tracing", "false"); + test.getContext().getTargetConfig().platformOptions.platform = Platform.ZEPHYR; + test.getContext().getTargetConfig().platformOptions.flash = false; + test.getContext().getTargetConfig().platformOptions.board = "qemu_cortex_a53"; + return true; + } - /** - * Given a test category, return true if it is compatible with single-threaded execution. - */ - public static boolean compatibleWithThreadingOff(TestCategory category) { + /** + * Make no changes to the configuration. + * + * @param ignoredTest The test to configure. + * @return True + */ + public static boolean noChanges(LFTest ignoredTest) { + return true; + } + + /** Given a test category, return true if it is compatible with single-threaded execution. */ + public static boolean compatibleWithThreadingOff(TestCategory category) { - // CONCURRENT, FEDERATED, DOCKER_FEDERATED, DOCKER - // are not compatible with single-threaded execution. - // ARDUINO and ZEPHYR have their own test suites, so we don't need to rerun. - boolean excluded = category == TestCategory.CONCURRENT + // CONCURRENT, FEDERATED, DOCKER_FEDERATED, DOCKER + // are not compatible with single-threaded execution. + // ARDUINO and ZEPHYR have their own test suites, so we don't need to rerun. + boolean excluded = + category == TestCategory.CONCURRENT || category == TestCategory.SERIALIZATION || category == TestCategory.FEDERATED || category == TestCategory.DOCKER_FEDERATED @@ -109,8 +106,8 @@ public static boolean compatibleWithThreadingOff(TestCategory category) { || category == TestCategory.ARDUINO || category == TestCategory.ZEPHYR; - // SERIALIZATION and TARGET tests are excluded on Windows. - excluded |= TestBase.isWindows() && (category == TestCategory.TARGET); - return !excluded; - } + // SERIALIZATION and TARGET tests are excluded on Windows. + excluded |= TestBase.isWindows() && (category == TestCategory.TARGET); + return !excluded; + } } diff --git a/org.lflang.tests/src/org/lflang/tests/runtime/CZephyrTest.java b/org.lflang.tests/src/org/lflang/tests/runtime/CZephyrTest.java index c1e2e5e3f5..db5eb46fc3 100644 --- a/org.lflang.tests/src/org/lflang/tests/runtime/CZephyrTest.java +++ b/org.lflang.tests/src/org/lflang/tests/runtime/CZephyrTest.java @@ -1,35 +1,32 @@ /************* -Copyright (c) 2023, 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. -***************/ + * Copyright (c) 2023, 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.tests.runtime; import java.util.List; - import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; - import org.lflang.Target; import org.lflang.tests.Configurators; import org.lflang.tests.RuntimeTest; @@ -42,29 +39,31 @@ */ public class CZephyrTest extends RuntimeTest { - public CZephyrTest() { - super(Target.C); - } + public CZephyrTest() { + super(Target.C); + } - @Test - public void buildZephyrTests() { - Assumptions.assumeTrue(isLinux(), "Zephyr tests only run on Linux"); - super.runTestsFor(List.of(Target.C), - Message.DESC_ZEPHYR, - TestCategory.ZEPHYR::equals, - Configurators::makeZephyrCompatible, - TestLevel.BUILD, - false); - } - @Test - public void buildGenericTests() { - Assumptions.assumeTrue(isLinux(), "Zephyr tests only run on Linux"); - super.runTestsFor(List.of(Target.C), - Message.DESC_GENERIC, - TestCategory.GENERIC::equals, - Configurators::makeZephyrCompatibleUnthreaded, - TestLevel.BUILD, - false); - } -} + @Test + public void buildZephyrTests() { + Assumptions.assumeTrue(isLinux(), "Zephyr tests only run on Linux"); + super.runTestsFor( + List.of(Target.C), + Message.DESC_ZEPHYR, + TestCategory.ZEPHYR::equals, + Configurators::makeZephyrCompatible, + TestLevel.BUILD, + false); + } + @Test + public void buildGenericTests() { + Assumptions.assumeTrue(isLinux(), "Zephyr tests only run on Linux"); + super.runTestsFor( + List.of(Target.C), + Message.DESC_GENERIC, + TestCategory.GENERIC::equals, + Configurators::makeZephyrCompatibleUnthreaded, + TestLevel.BUILD, + false); + } +} diff --git a/org.lflang/src/org/lflang/TargetConfig.java b/org.lflang/src/org/lflang/TargetConfig.java index 3d2b77dd4c..af805491da 100644 --- a/org.lflang/src/org/lflang/TargetConfig.java +++ b/org.lflang/src/org/lflang/TargetConfig.java @@ -1,27 +1,27 @@ /************* -Copyright (c) 2019, The University of California at Berkeley. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -***************/ + * Copyright (c) 2019, The University of California at Berkeley. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ***************/ package org.lflang; import java.util.ArrayList; @@ -32,7 +32,6 @@ import java.util.Objects; import java.util.Properties; import java.util.Set; - import org.lflang.TargetProperty.BuildType; import org.lflang.TargetProperty.ClockSyncMode; import org.lflang.TargetProperty.CoordinationType; @@ -48,458 +47,382 @@ /** * A class for keeping the current target configuration. * - * Class members of type String are initialized as empty strings, - * unless otherwise stated. + *

Class members of type String are initialized as empty strings, unless otherwise stated. + * * @author Marten Lohstroh */ public class TargetConfig { - /** - * The target of this configuration (e.g., C, TypeScript, Python). - */ - public final Target target; - - /** - * Create a new target configuration based on the given target declaration AST node only. - * @param target AST node of a target declaration. - */ - public TargetConfig(TargetDecl target) { // FIXME: eliminate this constructor if we can - this.target = Target.fromDecl(target); + /** The target of this configuration (e.g., C, TypeScript, Python). */ + public final Target target; + + /** + * Create a new target configuration based on the given target declaration AST node only. + * + * @param target AST node of a target declaration. + */ + public TargetConfig(TargetDecl target) { // FIXME: eliminate this constructor if we can + this.target = Target.fromDecl(target); + } + + /** + * Create a new target configuration based on the given commandline arguments and target + * declaration AST node. + * + * @param cliArgs Arguments passed on the commandline. + * @param target AST node of a target declaration. + * @param errorReporter An error reporter to report problems. + */ + public TargetConfig(Properties cliArgs, TargetDecl target, ErrorReporter errorReporter) { + this(target); + if (target.getConfig() != null) { + List pairs = target.getConfig().getPairs(); + TargetProperty.set(this, pairs != null ? pairs : List.of(), errorReporter); } - - /** - * Create a new target configuration based on the given commandline arguments and target - * declaration AST node. - * @param cliArgs Arguments passed on the commandline. - * @param target AST node of a target declaration. - * @param errorReporter An error reporter to report problems. - */ - public TargetConfig( - Properties cliArgs, - TargetDecl target, - ErrorReporter errorReporter - ) { - this(target); - if (target.getConfig() != null) { - List pairs = target.getConfig().getPairs(); - TargetProperty.set(this, pairs != null ? pairs : List.of(), errorReporter); - } - if (cliArgs.containsKey("no-compile")) { - this.noCompile = true; - } - if (cliArgs.containsKey("docker")) { - var arg = cliArgs.getProperty("docker"); - if (Boolean.parseBoolean(arg)) { - this.dockerOptions = new DockerOptions(); - } else { - this.dockerOptions = null; - } - // FIXME: this is pretty ad-hoc and does not account for more complex overrides yet. - } - if (cliArgs.containsKey("build-type")) { - this.cmakeBuildType = (BuildType) UnionType.BUILD_TYPE_UNION.forName(cliArgs.getProperty("build-type")); - } - if (cliArgs.containsKey("logging")) { - this.logLevel = LogLevel.valueOf(cliArgs.getProperty("logging").toUpperCase()); - } - if (cliArgs.containsKey("workers")) { - this.workers = Integer.parseInt(cliArgs.getProperty("workers")); - } - if (cliArgs.containsKey("threading")) { - this.threading = Boolean.parseBoolean(cliArgs.getProperty("threading")); - } - if (cliArgs.containsKey("target-compiler")) { - this.compiler = cliArgs.getProperty("target-compiler"); - } - if (cliArgs.containsKey("tracing")) { - this.tracing = new TracingOptions(); - } - if (cliArgs.containsKey("scheduler")) { - this.schedulerType = SchedulerOption.valueOf( - cliArgs.getProperty("scheduler") - ); - this.setByUser.add(TargetProperty.SCHEDULER); - } - if (cliArgs.containsKey("target-flags")) { - this.compilerFlags.clear(); - if (!cliArgs.getProperty("target-flags").isEmpty()) { - this.compilerFlags.addAll(List.of( - cliArgs.getProperty("target-flags").split(" ") - )); - } - } - if (cliArgs.containsKey("runtime-version")) { - this.runtimeVersion = cliArgs.getProperty("runtime-version"); - } - if (cliArgs.containsKey("external-runtime-path")) { - this.externalRuntimePath = cliArgs.getProperty("external-runtime-path"); - } - if (cliArgs.containsKey(TargetProperty.KEEPALIVE.description)) { - this.keepalive = Boolean.parseBoolean( - cliArgs.getProperty(TargetProperty.KEEPALIVE.description)); - } - if (cliArgs.containsKey(BuildParm.PRINT_STATISTICS.getKey())) { - this.printStatistics = true; - } + if (cliArgs.containsKey("no-compile")) { + this.noCompile = true; + } + if (cliArgs.containsKey("docker")) { + var arg = cliArgs.getProperty("docker"); + if (Boolean.parseBoolean(arg)) { + this.dockerOptions = new DockerOptions(); + } else { + this.dockerOptions = null; + } + // FIXME: this is pretty ad-hoc and does not account for more complex overrides yet. + } + if (cliArgs.containsKey("build-type")) { + this.cmakeBuildType = + (BuildType) UnionType.BUILD_TYPE_UNION.forName(cliArgs.getProperty("build-type")); + } + if (cliArgs.containsKey("logging")) { + this.logLevel = LogLevel.valueOf(cliArgs.getProperty("logging").toUpperCase()); + } + if (cliArgs.containsKey("workers")) { + this.workers = Integer.parseInt(cliArgs.getProperty("workers")); + } + if (cliArgs.containsKey("threading")) { + this.threading = Boolean.parseBoolean(cliArgs.getProperty("threading")); + } + if (cliArgs.containsKey("target-compiler")) { + this.compiler = cliArgs.getProperty("target-compiler"); + } + if (cliArgs.containsKey("tracing")) { + this.tracing = new TracingOptions(); + } + if (cliArgs.containsKey("scheduler")) { + this.schedulerType = SchedulerOption.valueOf(cliArgs.getProperty("scheduler")); + this.setByUser.add(TargetProperty.SCHEDULER); + } + if (cliArgs.containsKey("target-flags")) { + this.compilerFlags.clear(); + if (!cliArgs.getProperty("target-flags").isEmpty()) { + this.compilerFlags.addAll(List.of(cliArgs.getProperty("target-flags").split(" "))); + } + } + if (cliArgs.containsKey("runtime-version")) { + this.runtimeVersion = cliArgs.getProperty("runtime-version"); + } + if (cliArgs.containsKey("external-runtime-path")) { + this.externalRuntimePath = cliArgs.getProperty("external-runtime-path"); + } + if (cliArgs.containsKey(TargetProperty.KEEPALIVE.description)) { + this.keepalive = + Boolean.parseBoolean(cliArgs.getProperty(TargetProperty.KEEPALIVE.description)); } + if (cliArgs.containsKey(BuildParm.PRINT_STATISTICS.getKey())) { + this.printStatistics = true; + } + } - /** - * Keep track of every target property that is explicitly set by the user. - */ - public Set setByUser = new HashSet<>(); + /** Keep track of every target property that is explicitly set by the user. */ + public Set setByUser = new HashSet<>(); - /** - * A list of custom build commands that replace the default build process of - * directly invoking a designated compiler. A common usage of this target - * property is to set the command to build on the basis of a Makefile. - */ - public List buildCommands = new ArrayList<>(); + /** + * A list of custom build commands that replace the default build process of directly invoking a + * designated compiler. A common usage of this target property is to set the command to build on + * the basis of a Makefile. + */ + public List buildCommands = new ArrayList<>(); - /** - * The mode of clock synchronization to be used in federated programs. - * The default is 'initial'. - */ - public ClockSyncMode clockSync = ClockSyncMode.INIT; + /** + * The mode of clock synchronization to be used in federated programs. The default is 'initial'. + */ + public ClockSyncMode clockSync = ClockSyncMode.INIT; - /** - * Clock sync options. - */ - public ClockSyncOptions clockSyncOptions = new ClockSyncOptions(); + /** Clock sync options. */ + public ClockSyncOptions clockSyncOptions = new ClockSyncOptions(); - /** - * Parameter passed to cmake. The default is 'Release'. - */ - public BuildType cmakeBuildType = BuildType.RELEASE; + /** Parameter passed to cmake. The default is 'Release'. */ + public BuildType cmakeBuildType = BuildType.RELEASE; - /** - * Optional additional extensions to include in the generated CMakeLists.txt. - */ - public List cmakeIncludes = new ArrayList<>(); + /** Optional additional extensions to include in the generated CMakeLists.txt. */ + public List cmakeIncludes = new ArrayList<>(); - /** - * The compiler to invoke, unless a build command has been specified. - */ - public String compiler = ""; + /** The compiler to invoke, unless a build command has been specified. */ + public String compiler = ""; - /** - * Additional sources to add to the compile command if appropriate. - */ - public List compileAdditionalSources = new ArrayList<>(); + /** Additional sources to add to the compile command if appropriate. */ + public List compileAdditionalSources = new ArrayList<>(); - /** - * Additional (preprocessor) definitions to add to the compile command if appropriate. - * - * The first string is the definition itself, and the second string is the value to attribute to that definition, if any. - * The second value could be left empty. - */ - public Map compileDefinitions = new HashMap<>(); + /** + * Additional (preprocessor) definitions to add to the compile command if appropriate. + * + *

The first string is the definition itself, and the second string is the value to attribute + * to that definition, if any. The second value could be left empty. + */ + public Map compileDefinitions = new HashMap<>(); - /** - * Additional libraries to add to the compile command using the "-l" command-line option. - */ - public List compileLibraries = new ArrayList<>(); + /** Additional libraries to add to the compile command using the "-l" command-line option. */ + public List compileLibraries = new ArrayList<>(); - /** - * Flags to pass to the compiler, unless a build command has been specified. - */ - public List compilerFlags = new ArrayList<>(); + /** Flags to pass to the compiler, unless a build command has been specified. */ + public List compilerFlags = new ArrayList<>(); - /** - * The type of coordination used during the execution of a federated program. - * The default is 'centralized'. - */ - public CoordinationType coordination = CoordinationType.CENTRALIZED; + /** + * The type of coordination used during the execution of a federated program. The default is + * 'centralized'. + */ + public CoordinationType coordination = CoordinationType.CENTRALIZED; + + /** Docker options. */ + public DockerOptions dockerOptions = null; - /** - * Docker options. - */ - public DockerOptions dockerOptions = null; + /** Coordination options. */ + public CoordinationOptions coordinationOptions = new CoordinationOptions(); - /** - * Coordination options. - */ - public CoordinationOptions coordinationOptions = new CoordinationOptions(); + /** Link to an external runtime library instead of the default one. */ + public String externalRuntimePath = null; - /** - * Link to an external runtime library instead of the default one. - */ - public String externalRuntimePath = null; + /** + * If true, configure the execution environment such that it does not wait for physical time to + * match logical time. The default is false. + */ + public boolean fastMode = false; - /** - * If true, configure the execution environment such that it does not - * wait for physical time to match logical time. The default is false. - */ - public boolean fastMode = false; + /** List of files to be copied to src-gen. */ + public List files = new ArrayList<>(); - /** - * List of files to be copied to src-gen. - */ - public List files = new ArrayList<>(); + /** + * If true, configure the execution environment to keep executing if there are no more events on + * the event queue. The default is false. + */ + public boolean keepalive = false; - /** - * If true, configure the execution environment to keep executing if there - * are no more events on the event queue. The default is false. - */ - public boolean keepalive = false; + /** The level of logging during execution. The default is INFO. */ + public LogLevel logLevel = LogLevel.INFO; - /** - * The level of logging during execution. The default is INFO. - */ - public LogLevel logLevel = LogLevel.INFO; + /** Flags to pass to the linker, unless a build command has been specified. */ + public String linkerFlags = ""; - /** - * Flags to pass to the linker, unless a build command has been specified. - */ - public String linkerFlags = ""; + /** If true, do not invoke the target compiler or build command. The default is false. */ + public boolean noCompile = false; - /** - * If true, do not invoke the target compiler or build command. - * The default is false. - */ - public boolean noCompile = false; + /** If true, do not perform runtime validation. The default is false. */ + public boolean noRuntimeValidation = false; - /** - * If true, do not perform runtime validation. The default is false. - */ - public boolean noRuntimeValidation = false; + /** + * Set the target platform config. This tells the build system what platform-specific support + * files it needs to incorporate at compile time. + * + *

This is now a wrapped class to account for overloaded definitions of defining platform + * (either a string or dictionary of values) + */ + public PlatformOptions platformOptions = new PlatformOptions(); - /** - * Set the target platform config. - * This tells the build system what platform-specific support - * files it needs to incorporate at compile time. - * - * This is now a wrapped class to account for overloaded definitions - * of defining platform (either a string or dictionary of values) - */ - public PlatformOptions platformOptions = new PlatformOptions(); + /** If true, instruct the runtime to collect and print execution statistics. */ + public boolean printStatistics = false; - /** - * If true, instruct the runtime to collect and print execution statistics. - */ - public boolean printStatistics = false; + /** List of proto files to be processed by the code generator. */ + public List protoFiles = new ArrayList<>(); - /** - * List of proto files to be processed by the code generator. - */ - public List protoFiles = new ArrayList<>(); + /** If true, generate ROS2 specific code. */ + public boolean ros2 = false; - /** - * If true, generate ROS2 specific code. - */ - public boolean ros2 = false; + /** Additional ROS2 packages that the LF program depends on. */ + public List ros2Dependencies = null; + + /** The version of the runtime library to be used in the generated target. */ + public String runtimeVersion = null; + + /** Whether all reactors are to be generated into a single target language file. */ + public boolean singleFileProject = false; + + /** What runtime scheduler to use. */ + public SchedulerOption schedulerType = SchedulerOption.getDefault(); + + /** + * The number of worker threads to deploy. The default is zero, which indicates that the runtime + * is allowed to freely choose the number of workers. + */ + public int workers = 0; + + /** Indicate whether HMAC authentication is used. */ + public boolean auth = false; + + /** Indicate whether the runtime should use multithreaded execution. */ + public boolean threading = true; + + /** The timeout to be observed during execution of the program. */ + public TimeValue timeout; + + /** If non-null, configure the runtime environment to perform tracing. The default is null. */ + public TracingOptions tracing = null; + + /** + * If true, the resulting binary will output a graph visualizing all reaction dependencies. + * + *

This option is currently only used for C++ and Rust. This export function is a valuable tool + * for debugging LF programs and helps to understand the dependencies inferred by the runtime. + */ + public boolean exportDependencyGraph = false; + + /** + * If true, the resulting binary will output a yaml file describing the whole reactor structure of + * the program. + * + *

This option is currently only used for C++. This export function is a valuable tool for + * debugging LF programs and performing external analysis. + */ + public boolean exportToYaml = false; + + /** Rust-specific configuration. */ + public final RustTargetConfig rust = + new RustTargetConfig(); // FIXME: https://issue.lf-lang.org/1558 + + /** Path to a C file used by the Python target to setup federated execution. */ + public String fedSetupPreamble = null; // FIXME: https://issue.lf-lang.org/1558 + + /** Settings related to clock synchronization. */ + public static class ClockSyncOptions { /** - * Additional ROS2 packages that the LF program depends on. + * Dampen the adjustments to the clock synchronization offset by this rate. The default is 10. */ - public List ros2Dependencies = null; + public int attenuation = 10; /** - * The version of the runtime library to be used in the generated target. + * Whether to collect statistics while performing clock synchronization. This setting is only + * considered when clock synchronization has been activated. The default is true. */ - public String runtimeVersion = null; - - /** Whether all reactors are to be generated into a single target language file. */ - public boolean singleFileProject = false; + public boolean collectStats = true; - /** What runtime scheduler to use. */ - public SchedulerOption schedulerType = SchedulerOption.getDefault(); + /** Enable clock synchronization for federates on the same machine. Default is false. */ + public boolean localFederatesOn = false; /** - * The number of worker threads to deploy. The default is zero, which indicates that - * the runtime is allowed to freely choose the number of workers. + * Interval at which clock synchronization is initiated by the RTI (will be passed to it as an + * argument on the command-line). The default is 5 milliseconds. */ - public int workers = 0; + public TimeValue period = new TimeValue(5, TimeUnit.MILLI); /** - * Indicate whether HMAC authentication is used. + * Indicate the number of exchanges to be had per each clock synchronization round. See + * /lib/core/federated/clock-sync.h for more details. The default is 10. */ - public boolean auth = false; + public int trials = 10; /** - * Indicate whether the runtime should use multithreaded execution. + * Used to create an artificial clock synchronization error for the purpose of testing. The + * default is null. */ - public boolean threading = true; + public TimeValue testOffset; + } + + /** Settings related to coordination of federated execution. */ + public static class CoordinationOptions { /** - * The timeout to be observed during execution of the program. + * For centralized coordination, if a federate has a physical action that can trigger an output, + * directly or indirectly, then it will send NET (next event tag) messages to the RTI + * periodically as its physical clock advances. This option sets the amount of time to wait + * between sending such messages. Increasing this value results in downstream federates that lag + * further behind physical time (if the "after" delays are insufficient). The default is null, + * which means it is up the implementation to choose an interval. */ - public TimeValue timeout; + public TimeValue advance_message_interval = null; + } + /** Settings related to Docker options. */ + public static class DockerOptions { /** - * If non-null, configure the runtime environment to perform tracing. - * The default is null. + * The base image and tag from which to build the Docker image. The default is "alpine:latest". */ - public TracingOptions tracing = null; + public String from = "alpine:latest"; + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DockerOptions that = (DockerOptions) o; + return from.equals(that.from); + } + } + + /** Settings related to Platform Options. */ + public static class PlatformOptions { /** - * If true, the resulting binary will output a graph visualizing all reaction dependencies. - * - * This option is currently only used for C++ and Rust. This export function is a valuable tool - * for debugging LF programs and helps to understand the dependencies inferred by the runtime. + * The base platform we build our LF Files on. Should be set to AUTO by default unless + * developing for specific OS/Embedded Platform */ - public boolean exportDependencyGraph = false; - + public Platform platform = Platform.AUTO; /** - * If true, the resulting binary will output a yaml file describing the whole reactor structure - * of the program. - * - * This option is currently only used for C++. This export function is a valuable tool for debugging - * LF programs and performing external analysis. + * The string value used to determine what type of embedded board we work with and can be used + * to simplify the build process. For example, when we want to flash to an Arduino Nano 33 BLE + * board, we can use the string arduino:mbed_nano:nano33ble */ - public boolean exportToYaml = false; - - /** Rust-specific configuration. */ - public final RustTargetConfig rust = new RustTargetConfig(); // FIXME: https://issue.lf-lang.org/1558 - - /** Path to a C file used by the Python target to setup federated execution. */ - public String fedSetupPreamble = null; // FIXME: https://issue.lf-lang.org/1558 + public String board = null; /** - * Settings related to clock synchronization. + * The string value used to determine the port on which to flash the compiled program (i.e. + * /dev/cu.usbmodem21301) */ - public static class ClockSyncOptions { - - /** - * Dampen the adjustments to the clock synchronization offset by this rate. - * The default is 10. - */ - public int attenuation = 10; - - /** - * Whether to collect statistics while performing clock synchronization. - * This setting is only considered when clock synchronization has been activated. - * The default is true. - */ - public boolean collectStats = true; - - /** - * Enable clock synchronization for federates on the same machine. - * Default is false. - */ - public boolean localFederatesOn = false; - - - /** - * Interval at which clock synchronization is initiated by the RTI (will be passed - * to it as an argument on the command-line). - * The default is 5 milliseconds. - */ - public TimeValue period = new TimeValue(5, TimeUnit.MILLI); - - /** - * Indicate the number of exchanges to be had per each clock synchronization round. - * See /lib/core/federated/clock-sync.h for more details. - * The default is 10. - */ - public int trials = 10; - - /** - * Used to create an artificial clock synchronization error for the purpose of testing. - * The default is null. - */ - public TimeValue testOffset; - } + public String port = null; /** - * Settings related to coordination of federated execution. + * The baud rate used as a parameter to certain embedded platforms. 9600 is a standard rate + * amongst systems like Arduino, so it's the default value. */ - public static class CoordinationOptions { - - /** - * For centralized coordination, if a federate has a physical action that can trigger - * an output, directly or indirectly, then it will send NET (next event tag) messages - * to the RTI periodically as its physical clock advances. This option sets the amount - * of time to wait between sending such messages. Increasing this value results in - * downstream federates that lag further behind physical time (if the "after" delays - * are insufficient). - * The default is null, which means it is up the implementation to choose an interval. - */ - public TimeValue advance_message_interval = null; - } + public int baudRate = 9600; /** - * Settings related to Docker options. + * The boolean statement used to determine whether we should automatically attempt to flash once + * we compile. This may require the use of board and port values depending on the infrastructure + * you use to flash the boards. */ - public static class DockerOptions { - /** - * The base image and tag from which to build the Docker image. The default is "alpine:latest". - */ - public String from = "alpine:latest"; - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - DockerOptions that = (DockerOptions) o; - return from.equals(that.from); - } - } + public boolean flash = false; /** - * Settings related to Platform Options. + * The int value is used to determine the number of needed threads for the user application in + * Zephyr. */ - public static class PlatformOptions { - - /** - * The base platform we build our LF Files on. Should be set to AUTO by default unless developing for specific OS/Embedded Platform - */ - public Platform platform = Platform.AUTO; - - /** - * The string value used to determine what type of embedded board we work with and can be used to simplify the build process. For example, - * when we want to flash to an Arduino Nano 33 BLE board, we can use the string arduino:mbed_nano:nano33ble - */ - public String board = null; - - - /** - * The string value used to determine the port on which to flash the compiled program (i.e. /dev/cu.usbmodem21301) - */ - public String port = null; - - /** - * The baud rate used as a parameter to certain embedded platforms. 9600 is a standard rate amongst systems like Arduino, so it's the default value. - */ - public int baudRate = 9600; - - /** - * The boolean statement used to determine whether we should automatically attempt to flash once we compile. This may require the use of board and - * port values depending on the infrastructure you use to flash the boards. - */ - public boolean flash = false; - - /** - * The int value is used to determine the number of needed threads for the user application in Zephyr. - */ - public int userThreads = 0; - } + public int userThreads = 0; + } + /** Settings related to tracing options. */ + public static class TracingOptions { /** - * Settings related to tracing options. + * The name to use as the root of the trace file produced. This defaults to the name of the .lf + * file. */ - public static class TracingOptions { - /** - * The name to use as the root of the trace file produced. - * This defaults to the name of the .lf file. - */ - public String traceFileName = null; - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - TracingOptions that = (TracingOptions) o; - return Objects.equals(traceFileName, that.traceFileName); // traceFileName may be null - } + public String traceFileName = null; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TracingOptions that = (TracingOptions) o; + return Objects.equals(traceFileName, that.traceFileName); // traceFileName may be null } + } } diff --git a/org.lflang/src/org/lflang/TargetProperty.java b/org.lflang/src/org/lflang/TargetProperty.java index d874a97ee1..916b29b115 100644 --- a/org.lflang/src/org/lflang/TargetProperty.java +++ b/org.lflang/src/org/lflang/TargetProperty.java @@ -1,30 +1,31 @@ /************* -Copyright (c) 2019, The University of California at Berkeley. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -***************/ + * Copyright (c) 2019, The University of California at Berkeley. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ***************/ package org.lflang; +import com.google.common.collect.ImmutableList; import java.io.IOException; import java.nio.file.Path; import java.util.Arrays; @@ -33,7 +34,6 @@ import java.util.Optional; import java.util.function.Predicate; import java.util.stream.Collectors; - import org.eclipse.xtext.util.RuntimeIOException; import org.lflang.TargetConfig.DockerOptions; import org.lflang.TargetConfig.PlatformOptions; @@ -51,1824 +51,1765 @@ import org.lflang.util.StringUtil; import org.lflang.validation.LFValidator; -import com.google.common.collect.ImmutableList; - /** - * A target properties along with a type and a list of supporting targets - * that supports it, as well as a function for configuration updates. + * A target properties along with a type and a list of supporting targets that supports it, as well + * as a function for configuration updates. * * @author Marten Lohstroh */ public enum TargetProperty { - /** - * Directive to allow including OpenSSL libraries and process HMAC authentication. - */ - AUTH("auth", PrimitiveType.BOOLEAN, - Arrays.asList(Target.C, Target.CCPP), - (config) -> ASTUtils.toElement(config.auth), - (config, value, err) -> { - config.auth = ASTUtils.toBoolean(value); - }), - /** - * Directive to let the generator use the custom build command. - */ - BUILD("build", UnionType.STRING_OR_STRING_ARRAY, - Arrays.asList(Target.C, Target.CCPP), - (config) -> ASTUtils.toElement(config.buildCommands), - (config, value, err) -> { - config.buildCommands = ASTUtils.elementToListOfStrings(value); - }), - - /** - * Directive to specify the target build type such as 'Release' or 'Debug'. - * This is also used in the Rust target to select a Cargo profile. - */ - BUILD_TYPE("build-type", UnionType.BUILD_TYPE_UNION, - Arrays.asList(Target.C, Target.CCPP, Target.CPP, Target.Rust), - (config) -> ASTUtils.toElement(config.cmakeBuildType.toString()), - (config, value, err) -> { - config.cmakeBuildType = (BuildType) UnionType.BUILD_TYPE_UNION - .forName(ASTUtils.elementToSingleString(value)); - // set it there too, because the default is different. - config.rust.setBuildType(config.cmakeBuildType); - }), - - /** - * Directive to let the federate execution handle clock synchronization in software. - */ - CLOCK_SYNC("clock-sync", UnionType.CLOCK_SYNC_UNION, - Arrays.asList(Target.C, Target.CCPP, Target.Python), - (config) -> ASTUtils.toElement(config.clockSync.toString()), - (config, value, err) -> { - config.clockSync = (ClockSyncMode) UnionType.CLOCK_SYNC_UNION - .forName(ASTUtils.elementToSingleString(value)); - }), - /** - * Key-value pairs giving options for clock synchronization. - */ - CLOCK_SYNC_OPTIONS("clock-sync-options", - DictionaryType.CLOCK_SYNC_OPTION_DICT, Arrays.asList(Target.C, Target.CCPP, Target.Python), - (config) -> { - Element e = LfFactory.eINSTANCE.createElement(); - KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); - for (ClockSyncOption opt : ClockSyncOption.values()) { - KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); - pair.setName(opt.toString()); - switch (opt) { - case ATTENUATION: - pair.setValue(ASTUtils.toElement(config.clockSyncOptions.attenuation)); - break; - case COLLECT_STATS: - pair.setValue(ASTUtils.toElement(config.clockSyncOptions.collectStats)); - break; - case LOCAL_FEDERATES_ON: - pair.setValue(ASTUtils.toElement(config.clockSyncOptions.localFederatesOn)); - break; - case PERIOD: - if (config.clockSyncOptions.period == null) continue; // don't set if null - pair.setValue(ASTUtils.toElement(config.clockSyncOptions.period)); - break; - case TEST_OFFSET: - if (config.clockSyncOptions.testOffset == null) continue; // don't set if null - pair.setValue(ASTUtils.toElement(config.clockSyncOptions.testOffset)); - break; - case TRIALS: - pair.setValue(ASTUtils.toElement(config.clockSyncOptions.trials)); - break; - } - kvp.getPairs().add(pair); - } - e.setKeyvalue(kvp); - // kvp will never be empty - return e; - }, - (config, value, err) -> { - for (KeyValuePair entry : value.getKeyvalue().getPairs()) { - ClockSyncOption option = (ClockSyncOption) DictionaryType.CLOCK_SYNC_OPTION_DICT - .forName(entry.getName()); - switch (option) { - case ATTENUATION: - config.clockSyncOptions.attenuation = ASTUtils - .toInteger(entry.getValue()); - break; - case COLLECT_STATS: - config.clockSyncOptions.collectStats = ASTUtils - .toBoolean(entry.getValue()); - break; - case LOCAL_FEDERATES_ON: - config.clockSyncOptions.localFederatesOn = ASTUtils - .toBoolean(entry.getValue()); - break; - case PERIOD: - config.clockSyncOptions.period = ASTUtils - .toTimeValue(entry.getValue()); - break; - case TEST_OFFSET: - config.clockSyncOptions.testOffset = ASTUtils - .toTimeValue(entry.getValue()); - break; - case TRIALS: - config.clockSyncOptions.trials = ASTUtils - .toInteger(entry.getValue()); - break; - default: - break; - } - } - }), - - /** - * Directive to specify a cmake to be included by the generated build - * systems. - * - * This gives full control over the C/C++ build as any cmake parameters - * can be adjusted in the included file. - */ - CMAKE_INCLUDE("cmake-include", UnionType.FILE_OR_FILE_ARRAY, - Arrays.asList(Target.CPP, Target.C, Target.CCPP), - (config) -> ASTUtils.toElement(config.cmakeIncludes), - (config, value, err) -> { - config.cmakeIncludes = ASTUtils.elementToListOfStrings(value); - }, - // FIXME: This merging of lists is potentially dangerous since - // the incoming list of cmake-includes can belong to a .lf file that is - // located in a different location, and keeping just filename - // strings like this without absolute paths is incorrect. - (config, value, err) -> { - config.cmakeIncludes.addAll(ASTUtils.elementToListOfStrings(value)); - }), - /** - * Directive to specify the target compiler. - */ - COMPILER("compiler", PrimitiveType.STRING, Target.ALL, - (config) -> ASTUtils.toElement(config.compiler), - (config, value, err) -> { - config.compiler = ASTUtils.elementToSingleString(value); - }), - - /** - * Directive to specify compile-time definitions. - */ - COMPILE_DEFINITIONS("compile-definitions", StringDictionaryType.COMPILE_DEFINITION, - Arrays.asList(Target.C, Target.CCPP, Target.Python), - (config) -> ASTUtils.toElement(config.compileDefinitions), - (config, value, err) -> { - config.compileDefinitions = ASTUtils.elementToStringMaps(value); - }), - - /** - * Directive to generate a Dockerfile. This is either a boolean, - * true or false, or a dictionary of options. - */ - DOCKER("docker", UnionType.DOCKER_UNION, - Arrays.asList(Target.C, Target.CCPP, Target.Python, Target.TS), - (config) -> { - if (config.dockerOptions == null) { - return null; - } else if(config.dockerOptions.equals(new DockerOptions())) { - // default configuration - return ASTUtils.toElement(true); - } else { - Element e = LfFactory.eINSTANCE.createElement(); - KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); - for (DockerOption opt : DockerOption.values()) { - KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); - pair.setName(opt.toString()); - switch (opt) { - case FROM: - if (config.dockerOptions.from == null) continue; - pair.setValue(ASTUtils.toElement(config.dockerOptions.from)); - break; - } - kvp.getPairs().add(pair); - } - e.setKeyvalue(kvp); - if (kvp.getPairs().isEmpty()) return null; - return e; - } - }, - (config, value, err) -> setDockerProperty(config, value), - (config, value, err) -> setDockerProperty(config, value)), - - /** - * Directive for specifying a path to an external runtime to be used for the - * compiled binary. - */ - EXTERNAL_RUNTIME_PATH("external-runtime-path", PrimitiveType.STRING, - List.of(Target.CPP), - (config) -> ASTUtils.toElement(config.externalRuntimePath), - (config, value, err) -> { - config.externalRuntimePath = ASTUtils.elementToSingleString(value); - }), - - /** - * Directive to let the execution engine allow logical time to elapse - * faster than physical time. - */ - FAST("fast", PrimitiveType.BOOLEAN, Target.ALL, - (config) -> ASTUtils.toElement(config.fastMode), - (config, value, err) -> { - config.fastMode = ASTUtils.toBoolean(value); - }), - - /** - * Directive to stage particular files on the class path to be - * processed by the code generator. - */ - FILES("files", UnionType.FILE_OR_FILE_ARRAY, List.of(Target.C, Target.CCPP, Target.Python), - (config) -> ASTUtils.toElement(config.files), - (config, value, err) -> { - config.files = ASTUtils.elementToListOfStrings(value); - }, - // FIXME: This merging of lists is potentially dangerous since - // the incoming list of files can belong to a .lf file that is - // located in a different location, and keeping just filename - // strings like this without absolute paths is incorrect. - (config, value, err) -> { - config.files.addAll(ASTUtils.elementToListOfStrings(value)); - }), - - /** - * Flags to be passed on to the target compiler. - */ - FLAGS("flags", UnionType.STRING_OR_STRING_ARRAY, - Arrays.asList(Target.C, Target.CCPP), - (config) -> ASTUtils.toElement(config.compilerFlags), - (config, value, err) -> { - config.compilerFlags = ASTUtils.elementToListOfStrings(value); - }), - - /** - * Directive to specify the coordination mode - */ - COORDINATION("coordination", UnionType.COORDINATION_UNION, - Arrays.asList(Target.C, Target.CCPP, Target.Python), - (config) -> ASTUtils.toElement(config.coordination.toString()), - (config, value, err) -> { - config.coordination = (CoordinationType) UnionType.COORDINATION_UNION - .forName(ASTUtils.elementToSingleString(value)); - }, - (config, value, err) -> { - config.coordination = (CoordinationType) UnionType.COORDINATION_UNION - .forName(ASTUtils.elementToSingleString(value)); - }), - - /** - * Key-value pairs giving options for clock synchronization. - */ - COORDINATION_OPTIONS("coordination-options", - DictionaryType.COORDINATION_OPTION_DICT, Arrays.asList(Target.C, Target.CCPP, Target.Python, Target.TS), - (config) -> { - Element e = LfFactory.eINSTANCE.createElement(); - KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); - for (CoordinationOption opt : CoordinationOption.values()) { - KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); - pair.setName(opt.toString()); - switch (opt) { - case ADVANCE_MESSAGE_INTERVAL: - if (config.coordinationOptions.advance_message_interval == null) continue; - pair.setValue(ASTUtils.toElement(config.coordinationOptions.advance_message_interval)); - break; - } - kvp.getPairs().add(pair); - } - e.setKeyvalue(kvp); - if (kvp.getPairs().isEmpty()) return null; - return e; - }, - (config, value, err) -> { - for (KeyValuePair entry : value.getKeyvalue().getPairs()) { - CoordinationOption option = (CoordinationOption) DictionaryType.COORDINATION_OPTION_DICT - .forName(entry.getName()); - switch (option) { - case ADVANCE_MESSAGE_INTERVAL: - config.coordinationOptions.advance_message_interval = ASTUtils - .toTimeValue(entry.getValue()); - break; - default: - break; - } - } - }, - (config, value, err) -> { - for (KeyValuePair entry : value.getKeyvalue().getPairs()) { - CoordinationOption option = (CoordinationOption) DictionaryType.COORDINATION_OPTION_DICT - .forName(entry.getName()); - switch (option) { - case ADVANCE_MESSAGE_INTERVAL: - config.coordinationOptions.advance_message_interval = ASTUtils - .toTimeValue(entry.getValue()); - break; - default: - break; - } - } - }), - - /** - * Directive to let the execution engine remain active also if there - * are no more events in the event queue. - */ - KEEPALIVE("keepalive", PrimitiveType.BOOLEAN, Target.ALL, - (config) -> ASTUtils.toElement(config.keepalive), - (config, value, err) -> { - config.keepalive = ASTUtils.toBoolean(value); - }), - - /** - * Directive to specify the grain at which to report log messages during execution. - */ - LOGGING("logging", UnionType.LOGGING_UNION, Target.ALL, - (config) -> ASTUtils.toElement(config.logLevel.toString()), - (config, value, err) -> { - config.logLevel = (LogLevel) UnionType.LOGGING_UNION - .forName(ASTUtils.elementToSingleString(value)); - }), - - /** - * Directive to not invoke the target compiler. - */ - NO_COMPILE("no-compile", PrimitiveType.BOOLEAN, - Arrays.asList(Target.C, Target.CPP, Target.CCPP, Target.Python), - (config) -> ASTUtils.toElement(config.noCompile), - (config, value, err) -> { - config.noCompile = ASTUtils.toBoolean(value); - }), - - /** - * Directive to disable validation of reactor rules at runtime. - */ - NO_RUNTIME_VALIDATION("no-runtime-validation", PrimitiveType.BOOLEAN, - Arrays.asList(Target.CPP), - (config) -> ASTUtils.toElement(config.noRuntimeValidation), - (config, value, err) -> { - config.noRuntimeValidation = ASTUtils.toBoolean(value); - }), - - /** - * Directive to specify the platform for cross code generation. This is either a string of the platform - * or a dictionary of options that includes the string name. - */ - PLATFORM("platform", UnionType.PLATFORM_STRING_OR_DICTIONARY, Target.ALL, - (config) -> { - Element e = LfFactory.eINSTANCE.createElement(); - KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); - for (PlatformOption opt : PlatformOption.values()) { - KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); - pair.setName(opt.toString()); - switch (opt) { - case NAME: - pair.setValue(ASTUtils.toElement(config.platformOptions.platform.toString())); - break; - case BAUDRATE: - pair.setValue(ASTUtils.toElement(config.platformOptions.baudRate)); - break; - case BOARD: - pair.setValue(ASTUtils.toElement(config.platformOptions.board)); - break; - case FLASH: - pair.setValue(ASTUtils.toElement(config.platformOptions.flash)); - break; - case PORT: - pair.setValue(ASTUtils.toElement(config.platformOptions.port)); - break; - case USER_THREADS: - pair.setValue(ASTUtils.toElement(config.platformOptions.userThreads)); - break; - } - kvp.getPairs().add(pair); - } - e.setKeyvalue(kvp); - if (kvp.getPairs().isEmpty()) return null; - return e; - }, - (config, value, err) -> { - if (value.getLiteral() != null) { - config.platformOptions = new PlatformOptions(); - config.platformOptions.platform = (Platform) UnionType.PLATFORM_UNION - .forName(ASTUtils.elementToSingleString(value)); - } else { - config.platformOptions= new PlatformOptions(); - for (KeyValuePair entry : value.getKeyvalue().getPairs()) { - PlatformOption option = (PlatformOption) DictionaryType.PLATFORM_DICT - .forName(entry.getName()); - switch (option) { - case NAME: - Platform p = (Platform) UnionType.PLATFORM_UNION - .forName(ASTUtils.elementToSingleString(entry.getValue())); - if(p == null) { - String s = "Unidentified Platform Type, LF supports the following platform types: " + Arrays.asList(Platform.values()).toString(); - err.reportError(s); - throw new AssertionError(s); - } - config.platformOptions.platform = p; - break; - case BAUDRATE: - config.platformOptions.baudRate = ASTUtils.toInteger(entry.getValue()); - break; - case BOARD: - config.platformOptions.board = ASTUtils.elementToSingleString(entry.getValue()); - break; - case FLASH: - config.platformOptions.flash = ASTUtils.toBoolean(entry.getValue()); - break; - case PORT: - config.platformOptions.port = ASTUtils.elementToSingleString(entry.getValue()); - break; - case USER_THREADS: - config.platformOptions.userThreads = ASTUtils.toInteger(entry.getValue()); - break; - default: - break; - } - } - } - }), - - /** - * Directive to instruct the runtime to collect and print execution statistics. - */ - PRINT_STATISTICS("print-statistics", PrimitiveType.BOOLEAN, - Arrays.asList(Target.CPP), - (config) -> ASTUtils.toElement(config.printStatistics), - (config, value, err) -> { - config.printStatistics = ASTUtils.toBoolean(value); - }), - - /** - * Directive for specifying .proto files that need to be compiled and their - * code included in the sources. - */ - PROTOBUFS("protobufs", UnionType.FILE_OR_FILE_ARRAY, - Arrays.asList(Target.C, Target.CCPP, Target.TS, Target.Python), - (config) -> ASTUtils.toElement(config.protoFiles), - (config, value, err) -> { - config.protoFiles = ASTUtils.elementToListOfStrings(value); - }), - - - /** - * Directive to specify that ROS2 specific code is generated, - */ - ROS2("ros2", PrimitiveType.BOOLEAN, - List.of(Target.CPP), - (config) -> ASTUtils.toElement(config.ros2), - (config, value, err) -> { - config.ros2 = ASTUtils.toBoolean(value); - }), - - /** - * Directive to specify additional ROS2 packages that this LF program depends on. - */ - ROS2_DEPENDENCIES("ros2-dependencies", ArrayType.STRING_ARRAY, - List.of(Target.CPP), - (config) -> ASTUtils.toElement(config.ros2Dependencies), - (config, value, err) -> { - config.ros2Dependencies = ASTUtils.elementToListOfStrings(value); - }), - - /** - * Directive for specifying a specific version of the reactor runtime library. - */ - RUNTIME_VERSION("runtime-version", PrimitiveType.STRING, - Arrays.asList(Target.CPP), - (config) -> ASTUtils.toElement(config.runtimeVersion), - (config, value, err) -> { - config.runtimeVersion = ASTUtils.elementToSingleString(value); - }), - - - /** - * Directive for specifying a specific runtime scheduler, if supported. - */ - SCHEDULER("scheduler", UnionType.SCHEDULER_UNION, - Arrays.asList(Target.C, Target.CCPP, Target.Python), - (config) -> ASTUtils.toElement(config.schedulerType.toString()), - (config, value, err) -> { - config.schedulerType = (SchedulerOption) UnionType.SCHEDULER_UNION - .forName(ASTUtils.elementToSingleString(value)); - }), - - /** - * Directive to specify that all code is generated in a single file. - */ - SINGLE_FILE_PROJECT("single-file-project", PrimitiveType.BOOLEAN, - List.of(Target.Rust), - (config) -> ASTUtils.toElement(config.singleFileProject), - (config, value, err) -> { - config.singleFileProject = ASTUtils.toBoolean(value); - }), - - /** - * Directive to indicate whether the runtime should use multi-threading. - */ - THREADING("threading", PrimitiveType.BOOLEAN, - List.of(Target.C, Target.CCPP, Target.Python), - (config) -> ASTUtils.toElement(config.threading), - (config, value, err) -> { - config.threading = ASTUtils.toBoolean(value); - }), - - /** - * Directive to specify the number of worker threads used by the runtime. - */ - WORKERS("workers", PrimitiveType.NON_NEGATIVE_INTEGER, - List.of(Target.C, Target.CCPP, Target.Python, Target.CPP, Target.Rust), - (config) -> ASTUtils.toElement(config.workers), - (config, value, err) -> { - config.workers = ASTUtils.toInteger(value); - }), - - /** - * Directive to specify the execution timeout. - */ - TIMEOUT("timeout", PrimitiveType.TIME_VALUE, Target.ALL, - (config) -> ASTUtils.toElement(config.timeout), - (config, value, err) -> { - config.timeout = ASTUtils.toTimeValue(value); - }, - (config, value, err) -> { - config.timeout = ASTUtils.toTimeValue(value); - }), - - /** - * Directive to enable tracing. - */ - TRACING("tracing", UnionType.TRACING_UNION, - Arrays.asList(Target.C, Target.CCPP, Target.CPP, Target.Python), - (config) -> { - if (config.tracing == null) { - return null; - } else if (config.tracing.equals(new TracingOptions())) { - // default values - return ASTUtils.toElement(true); - } else { - Element e = LfFactory.eINSTANCE.createElement(); - KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); - for (TracingOption opt : TracingOption.values()) { - KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); - pair.setName(opt.toString()); - switch (opt) { - case TRACE_FILE_NAME: - if (config.tracing.traceFileName == null) continue; - pair.setValue(ASTUtils.toElement(config.tracing.traceFileName)); - } - kvp.getPairs().add(pair); - } - e.setKeyvalue(kvp); - if (kvp.getPairs().isEmpty()) return null; - return e; - } - }, - (config, value, err) -> { - if (value.getLiteral() != null) { - if (ASTUtils.toBoolean(value)) { - config.tracing = new TracingOptions(); - } else { - config.tracing = null; - } - } else { - config.tracing = new TracingOptions(); - for (KeyValuePair entry : value.getKeyvalue().getPairs()) { - TracingOption option = (TracingOption) DictionaryType.TRACING_DICT - .forName(entry.getName()); - switch (option) { - case TRACE_FILE_NAME: - config.tracing.traceFileName = ASTUtils.elementToSingleString(entry.getValue()); - break; - default: - break; - } - } - } - }), - - /** - * Directive to let the runtime export its internal dependency graph. - * - * This is a debugging feature and currently only used for C++ and Rust programs. - */ - EXPORT_DEPENDENCY_GRAPH("export-dependency-graph", PrimitiveType.BOOLEAN, - List.of(Target.CPP, Target.Rust), - (config) -> ASTUtils.toElement(config.exportDependencyGraph), - (config, value, err) -> { - config.exportDependencyGraph = ASTUtils.toBoolean(value); - }), - - /** - * Directive to let the runtime export the program structure to a yaml file. - * - * This is a debugging feature and currently only used for C++ programs. - */ - EXPORT_TO_YAML("export-to-yaml", PrimitiveType.BOOLEAN, - List.of(Target.CPP), - (config) -> ASTUtils.toElement(config.exportToYaml), - (config, value, err) -> { - config.exportToYaml = ASTUtils.toBoolean(value); - }), - - /** - * List of module files to link into the crate as top-level. - * For instance, a {@code target Rust { rust-modules: [ "foo.rs" ] }} - * will cause the file to be copied into the generated project, - * and the generated `main.rs` will include it with a `mod foo;`. - * If one of the paths is a directory, it must contain a `mod.rs` - * file, and all its contents are copied. - */ - RUST_INCLUDE("rust-include", - UnionType.FILE_OR_FILE_ARRAY, - List.of(Target.Rust), - (config) -> { - // do not check paths here, and simply copy the absolute path over - List paths = config.rust.getRustTopLevelModules(); - if (paths.isEmpty()) return null; - else if (paths.size() == 1) { - return ASTUtils.toElement(paths.get(0).toString()); - } else { - Element e = LfFactory.eINSTANCE.createElement(); - Array arr = LfFactory.eINSTANCE.createArray(); - for (Path p : paths) { - arr.getElements().add(ASTUtils.toElement(p.toString())); - } - e.setArray(arr); - return e; - } - }, - (config, value, err) -> { - Path referencePath; - try { - referencePath = FileUtil.toPath(value.eResource().getURI()).toAbsolutePath(); - } catch (IOException e) { - err.reportError(value, "Invalid path? " + e.getMessage()); - throw new RuntimeIOException(e); - } - - // we'll resolve relative paths to check that the files - // are as expected. - - if (value.getLiteral() != null) { - Path resolved = referencePath.resolveSibling(StringUtil.removeQuotes(value.getLiteral())); - - config.rust.addAndCheckTopLevelModule(resolved, value, err); - } else if (value.getArray() != null) { - for (Element element : value.getArray().getElements()) { - String literal = StringUtil.removeQuotes(element.getLiteral()); - Path resolved = referencePath.resolveSibling(literal); - config.rust.addAndCheckTopLevelModule(resolved, element, err); - } - } - }), - - /** - * Directive for specifying Cargo features of the generated - * program to enable. - */ - CARGO_FEATURES("cargo-features", ArrayType.STRING_ARRAY, - List.of(Target.Rust), - (config) -> ASTUtils.toElement(config.rust.getCargoFeatures()), - (config, value, err) -> { - config.rust.setCargoFeatures(ASTUtils.elementToListOfStrings(value)); - }), - - /** - * Dependency specifications for Cargo. This property looks like this: - *

{@code
-     * cargo-dependencies: {
-     *    // Name-of-the-crate: "version"
-     *    rand: "0.8",
-     *    // Equivalent to using an explicit map:
-     *    rand: {
-     *      version: "0.8"
-     *    },
-     *    // The map allows specifying more details
-     *    rand: {
-     *      // A path to a local unpublished crate.
-     *      // Note 'path' is mutually exclusive with 'version'.
-     *      path: "/home/me/Git/local-rand-clone"
-     *    },
-     *    rand: {
-     *      version: "0.8",
-     *      // you can specify cargo features
-     *      features: ["some-cargo-feature",]
-     *    }
-     * }
-     * }
- */ - CARGO_DEPENDENCIES("cargo-dependencies", - CargoDependenciesPropertyType.INSTANCE, - List.of(Target.Rust), - (config) -> { - var deps = config.rust.getCargoDependencies(); - if (deps.size() == 0) return null; - else { - Element e = LfFactory.eINSTANCE.createElement(); - KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); - for (var ent : deps.entrySet()) { - KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); - pair.setName(ent.getKey()); - pair.setValue(CargoDependencySpec.extractSpec(ent.getValue())); - kvp.getPairs().add(pair); - } - e.setKeyvalue(kvp); - return e; - } - }, - (config, value, err) -> { - config.rust.setCargoDependencies(CargoDependencySpec.parseAll(value)); - }), - - /** - * Directs the C or Python target to include the associated C file used for - * setting up federated execution before processing the first tag. - */ - FED_SETUP("_fed_setup", PrimitiveType.FILE, - Arrays.asList(Target.C, Target.CCPP, Target.Python), - (config) -> ASTUtils.toElement(config.fedSetupPreamble), - (config, value, err) -> - config.fedSetupPreamble = StringUtil.removeQuotes(ASTUtils.elementToSingleString(value)) - ) - ; - - /** - * Update {@code config}.dockerOptions based on value. - */ - private static void setDockerProperty(TargetConfig config, Element value) { - if (value.getLiteral() != null) { - if (ASTUtils.toBoolean(value)) { - config.dockerOptions = new DockerOptions(); - } else { - config.dockerOptions = null; + /** Directive to allow including OpenSSL libraries and process HMAC authentication. */ + AUTH( + "auth", + PrimitiveType.BOOLEAN, + Arrays.asList(Target.C, Target.CCPP), + (config) -> ASTUtils.toElement(config.auth), + (config, value, err) -> { + config.auth = ASTUtils.toBoolean(value); + }), + /** Directive to let the generator use the custom build command. */ + BUILD( + "build", + UnionType.STRING_OR_STRING_ARRAY, + Arrays.asList(Target.C, Target.CCPP), + (config) -> ASTUtils.toElement(config.buildCommands), + (config, value, err) -> { + config.buildCommands = ASTUtils.elementToListOfStrings(value); + }), + + /** + * Directive to specify the target build type such as 'Release' or 'Debug'. This is also used in + * the Rust target to select a Cargo profile. + */ + BUILD_TYPE( + "build-type", + UnionType.BUILD_TYPE_UNION, + Arrays.asList(Target.C, Target.CCPP, Target.CPP, Target.Rust), + (config) -> ASTUtils.toElement(config.cmakeBuildType.toString()), + (config, value, err) -> { + config.cmakeBuildType = + (BuildType) UnionType.BUILD_TYPE_UNION.forName(ASTUtils.elementToSingleString(value)); + // set it there too, because the default is different. + config.rust.setBuildType(config.cmakeBuildType); + }), + + /** Directive to let the federate execution handle clock synchronization in software. */ + CLOCK_SYNC( + "clock-sync", + UnionType.CLOCK_SYNC_UNION, + Arrays.asList(Target.C, Target.CCPP, Target.Python), + (config) -> ASTUtils.toElement(config.clockSync.toString()), + (config, value, err) -> { + config.clockSync = + (ClockSyncMode) + UnionType.CLOCK_SYNC_UNION.forName(ASTUtils.elementToSingleString(value)); + }), + /** Key-value pairs giving options for clock synchronization. */ + CLOCK_SYNC_OPTIONS( + "clock-sync-options", + DictionaryType.CLOCK_SYNC_OPTION_DICT, + Arrays.asList(Target.C, Target.CCPP, Target.Python), + (config) -> { + Element e = LfFactory.eINSTANCE.createElement(); + KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); + for (ClockSyncOption opt : ClockSyncOption.values()) { + KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); + pair.setName(opt.toString()); + switch (opt) { + case ATTENUATION: + pair.setValue(ASTUtils.toElement(config.clockSyncOptions.attenuation)); + break; + case COLLECT_STATS: + pair.setValue(ASTUtils.toElement(config.clockSyncOptions.collectStats)); + break; + case LOCAL_FEDERATES_ON: + pair.setValue(ASTUtils.toElement(config.clockSyncOptions.localFederatesOn)); + break; + case PERIOD: + if (config.clockSyncOptions.period == null) continue; // don't set if null + pair.setValue(ASTUtils.toElement(config.clockSyncOptions.period)); + break; + case TEST_OFFSET: + if (config.clockSyncOptions.testOffset == null) continue; // don't set if null + pair.setValue(ASTUtils.toElement(config.clockSyncOptions.testOffset)); + break; + case TRIALS: + pair.setValue(ASTUtils.toElement(config.clockSyncOptions.trials)); + break; + } + kvp.getPairs().add(pair); + } + e.setKeyvalue(kvp); + // kvp will never be empty + return e; + }, + (config, value, err) -> { + for (KeyValuePair entry : value.getKeyvalue().getPairs()) { + ClockSyncOption option = + (ClockSyncOption) DictionaryType.CLOCK_SYNC_OPTION_DICT.forName(entry.getName()); + switch (option) { + case ATTENUATION: + config.clockSyncOptions.attenuation = ASTUtils.toInteger(entry.getValue()); + break; + case COLLECT_STATS: + config.clockSyncOptions.collectStats = ASTUtils.toBoolean(entry.getValue()); + break; + case LOCAL_FEDERATES_ON: + config.clockSyncOptions.localFederatesOn = ASTUtils.toBoolean(entry.getValue()); + break; + case PERIOD: + config.clockSyncOptions.period = ASTUtils.toTimeValue(entry.getValue()); + break; + case TEST_OFFSET: + config.clockSyncOptions.testOffset = ASTUtils.toTimeValue(entry.getValue()); + break; + case TRIALS: + config.clockSyncOptions.trials = ASTUtils.toInteger(entry.getValue()); + break; + default: + break; + } + } + }), + + /** + * Directive to specify a cmake to be included by the generated build systems. + * + *

This gives full control over the C/C++ build as any cmake parameters can be adjusted in the + * included file. + */ + CMAKE_INCLUDE( + "cmake-include", + UnionType.FILE_OR_FILE_ARRAY, + Arrays.asList(Target.CPP, Target.C, Target.CCPP), + (config) -> ASTUtils.toElement(config.cmakeIncludes), + (config, value, err) -> { + config.cmakeIncludes = ASTUtils.elementToListOfStrings(value); + }, + // FIXME: This merging of lists is potentially dangerous since + // the incoming list of cmake-includes can belong to a .lf file that is + // located in a different location, and keeping just filename + // strings like this without absolute paths is incorrect. + (config, value, err) -> { + config.cmakeIncludes.addAll(ASTUtils.elementToListOfStrings(value)); + }), + /** Directive to specify the target compiler. */ + COMPILER( + "compiler", + PrimitiveType.STRING, + Target.ALL, + (config) -> ASTUtils.toElement(config.compiler), + (config, value, err) -> { + config.compiler = ASTUtils.elementToSingleString(value); + }), + + /** Directive to specify compile-time definitions. */ + COMPILE_DEFINITIONS( + "compile-definitions", + StringDictionaryType.COMPILE_DEFINITION, + Arrays.asList(Target.C, Target.CCPP, Target.Python), + (config) -> ASTUtils.toElement(config.compileDefinitions), + (config, value, err) -> { + config.compileDefinitions = ASTUtils.elementToStringMaps(value); + }), + + /** + * Directive to generate a Dockerfile. This is either a boolean, true or false, or a dictionary of + * options. + */ + DOCKER( + "docker", + UnionType.DOCKER_UNION, + Arrays.asList(Target.C, Target.CCPP, Target.Python, Target.TS), + (config) -> { + if (config.dockerOptions == null) { + return null; + } else if (config.dockerOptions.equals(new DockerOptions())) { + // default configuration + return ASTUtils.toElement(true); + } else { + Element e = LfFactory.eINSTANCE.createElement(); + KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); + for (DockerOption opt : DockerOption.values()) { + KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); + pair.setName(opt.toString()); + switch (opt) { + case FROM: + if (config.dockerOptions.from == null) continue; + pair.setValue(ASTUtils.toElement(config.dockerOptions.from)); + break; } + kvp.getPairs().add(pair); + } + e.setKeyvalue(kvp); + if (kvp.getPairs().isEmpty()) return null; + return e; + } + }, + (config, value, err) -> setDockerProperty(config, value), + (config, value, err) -> setDockerProperty(config, value)), + + /** Directive for specifying a path to an external runtime to be used for the compiled binary. */ + EXTERNAL_RUNTIME_PATH( + "external-runtime-path", + PrimitiveType.STRING, + List.of(Target.CPP), + (config) -> ASTUtils.toElement(config.externalRuntimePath), + (config, value, err) -> { + config.externalRuntimePath = ASTUtils.elementToSingleString(value); + }), + + /** + * Directive to let the execution engine allow logical time to elapse faster than physical time. + */ + FAST( + "fast", + PrimitiveType.BOOLEAN, + Target.ALL, + (config) -> ASTUtils.toElement(config.fastMode), + (config, value, err) -> { + config.fastMode = ASTUtils.toBoolean(value); + }), + + /** + * Directive to stage particular files on the class path to be processed by the code generator. + */ + FILES( + "files", + UnionType.FILE_OR_FILE_ARRAY, + List.of(Target.C, Target.CCPP, Target.Python), + (config) -> ASTUtils.toElement(config.files), + (config, value, err) -> { + config.files = ASTUtils.elementToListOfStrings(value); + }, + // FIXME: This merging of lists is potentially dangerous since + // the incoming list of files can belong to a .lf file that is + // located in a different location, and keeping just filename + // strings like this without absolute paths is incorrect. + (config, value, err) -> { + config.files.addAll(ASTUtils.elementToListOfStrings(value)); + }), + + /** Flags to be passed on to the target compiler. */ + FLAGS( + "flags", + UnionType.STRING_OR_STRING_ARRAY, + Arrays.asList(Target.C, Target.CCPP), + (config) -> ASTUtils.toElement(config.compilerFlags), + (config, value, err) -> { + config.compilerFlags = ASTUtils.elementToListOfStrings(value); + }), + + /** Directive to specify the coordination mode */ + COORDINATION( + "coordination", + UnionType.COORDINATION_UNION, + Arrays.asList(Target.C, Target.CCPP, Target.Python), + (config) -> ASTUtils.toElement(config.coordination.toString()), + (config, value, err) -> { + config.coordination = + (CoordinationType) + UnionType.COORDINATION_UNION.forName(ASTUtils.elementToSingleString(value)); + }, + (config, value, err) -> { + config.coordination = + (CoordinationType) + UnionType.COORDINATION_UNION.forName(ASTUtils.elementToSingleString(value)); + }), + + /** Key-value pairs giving options for clock synchronization. */ + COORDINATION_OPTIONS( + "coordination-options", + DictionaryType.COORDINATION_OPTION_DICT, + Arrays.asList(Target.C, Target.CCPP, Target.Python, Target.TS), + (config) -> { + Element e = LfFactory.eINSTANCE.createElement(); + KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); + for (CoordinationOption opt : CoordinationOption.values()) { + KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); + pair.setName(opt.toString()); + switch (opt) { + case ADVANCE_MESSAGE_INTERVAL: + if (config.coordinationOptions.advance_message_interval == null) continue; + pair.setValue( + ASTUtils.toElement(config.coordinationOptions.advance_message_interval)); + break; + } + kvp.getPairs().add(pair); + } + e.setKeyvalue(kvp); + if (kvp.getPairs().isEmpty()) return null; + return e; + }, + (config, value, err) -> { + for (KeyValuePair entry : value.getKeyvalue().getPairs()) { + CoordinationOption option = + (CoordinationOption) DictionaryType.COORDINATION_OPTION_DICT.forName(entry.getName()); + switch (option) { + case ADVANCE_MESSAGE_INTERVAL: + config.coordinationOptions.advance_message_interval = + ASTUtils.toTimeValue(entry.getValue()); + break; + default: + break; + } + } + }, + (config, value, err) -> { + for (KeyValuePair entry : value.getKeyvalue().getPairs()) { + CoordinationOption option = + (CoordinationOption) DictionaryType.COORDINATION_OPTION_DICT.forName(entry.getName()); + switch (option) { + case ADVANCE_MESSAGE_INTERVAL: + config.coordinationOptions.advance_message_interval = + ASTUtils.toTimeValue(entry.getValue()); + break; + default: + break; + } + } + }), + + /** + * Directive to let the execution engine remain active also if there are no more events in the + * event queue. + */ + KEEPALIVE( + "keepalive", + PrimitiveType.BOOLEAN, + Target.ALL, + (config) -> ASTUtils.toElement(config.keepalive), + (config, value, err) -> { + config.keepalive = ASTUtils.toBoolean(value); + }), + + /** Directive to specify the grain at which to report log messages during execution. */ + LOGGING( + "logging", + UnionType.LOGGING_UNION, + Target.ALL, + (config) -> ASTUtils.toElement(config.logLevel.toString()), + (config, value, err) -> { + config.logLevel = + (LogLevel) UnionType.LOGGING_UNION.forName(ASTUtils.elementToSingleString(value)); + }), + + /** Directive to not invoke the target compiler. */ + NO_COMPILE( + "no-compile", + PrimitiveType.BOOLEAN, + Arrays.asList(Target.C, Target.CPP, Target.CCPP, Target.Python), + (config) -> ASTUtils.toElement(config.noCompile), + (config, value, err) -> { + config.noCompile = ASTUtils.toBoolean(value); + }), + + /** Directive to disable validation of reactor rules at runtime. */ + NO_RUNTIME_VALIDATION( + "no-runtime-validation", + PrimitiveType.BOOLEAN, + Arrays.asList(Target.CPP), + (config) -> ASTUtils.toElement(config.noRuntimeValidation), + (config, value, err) -> { + config.noRuntimeValidation = ASTUtils.toBoolean(value); + }), + + /** + * Directive to specify the platform for cross code generation. This is either a string of the + * platform or a dictionary of options that includes the string name. + */ + PLATFORM( + "platform", + UnionType.PLATFORM_STRING_OR_DICTIONARY, + Target.ALL, + (config) -> { + Element e = LfFactory.eINSTANCE.createElement(); + KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); + for (PlatformOption opt : PlatformOption.values()) { + KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); + pair.setName(opt.toString()); + switch (opt) { + case NAME: + pair.setValue(ASTUtils.toElement(config.platformOptions.platform.toString())); + break; + case BAUDRATE: + pair.setValue(ASTUtils.toElement(config.platformOptions.baudRate)); + break; + case BOARD: + pair.setValue(ASTUtils.toElement(config.platformOptions.board)); + break; + case FLASH: + pair.setValue(ASTUtils.toElement(config.platformOptions.flash)); + break; + case PORT: + pair.setValue(ASTUtils.toElement(config.platformOptions.port)); + break; + case USER_THREADS: + pair.setValue(ASTUtils.toElement(config.platformOptions.userThreads)); + break; + } + kvp.getPairs().add(pair); + } + e.setKeyvalue(kvp); + if (kvp.getPairs().isEmpty()) return null; + return e; + }, + (config, value, err) -> { + if (value.getLiteral() != null) { + config.platformOptions = new PlatformOptions(); + config.platformOptions.platform = + (Platform) UnionType.PLATFORM_UNION.forName(ASTUtils.elementToSingleString(value)); } else { - config.dockerOptions = new DockerOptions(); - for (KeyValuePair entry : value.getKeyvalue().getPairs()) { - DockerOption option = (DockerOption) DictionaryType.DOCKER_DICT - .forName(entry.getName()); - switch (option) { - case FROM: - config.dockerOptions.from = ASTUtils.elementToSingleString(entry.getValue()); - break; - default: - break; + config.platformOptions = new PlatformOptions(); + for (KeyValuePair entry : value.getKeyvalue().getPairs()) { + PlatformOption option = + (PlatformOption) DictionaryType.PLATFORM_DICT.forName(entry.getName()); + switch (option) { + case NAME: + Platform p = + (Platform) + UnionType.PLATFORM_UNION.forName( + ASTUtils.elementToSingleString(entry.getValue())); + if (p == null) { + String s = + "Unidentified Platform Type, LF supports the following platform types: " + + Arrays.asList(Platform.values()).toString(); + err.reportError(s); + throw new AssertionError(s); } + config.platformOptions.platform = p; + break; + case BAUDRATE: + config.platformOptions.baudRate = ASTUtils.toInteger(entry.getValue()); + break; + case BOARD: + config.platformOptions.board = ASTUtils.elementToSingleString(entry.getValue()); + break; + case FLASH: + config.platformOptions.flash = ASTUtils.toBoolean(entry.getValue()); + break; + case PORT: + config.platformOptions.port = ASTUtils.elementToSingleString(entry.getValue()); + break; + case USER_THREADS: + config.platformOptions.userThreads = ASTUtils.toInteger(entry.getValue()); + break; + default: + break; } + } + } + }), + + /** Directive to instruct the runtime to collect and print execution statistics. */ + PRINT_STATISTICS( + "print-statistics", + PrimitiveType.BOOLEAN, + Arrays.asList(Target.CPP), + (config) -> ASTUtils.toElement(config.printStatistics), + (config, value, err) -> { + config.printStatistics = ASTUtils.toBoolean(value); + }), + + /** + * Directive for specifying .proto files that need to be compiled and their code included in the + * sources. + */ + PROTOBUFS( + "protobufs", + UnionType.FILE_OR_FILE_ARRAY, + Arrays.asList(Target.C, Target.CCPP, Target.TS, Target.Python), + (config) -> ASTUtils.toElement(config.protoFiles), + (config, value, err) -> { + config.protoFiles = ASTUtils.elementToListOfStrings(value); + }), + + /** Directive to specify that ROS2 specific code is generated, */ + ROS2( + "ros2", + PrimitiveType.BOOLEAN, + List.of(Target.CPP), + (config) -> ASTUtils.toElement(config.ros2), + (config, value, err) -> { + config.ros2 = ASTUtils.toBoolean(value); + }), + + /** Directive to specify additional ROS2 packages that this LF program depends on. */ + ROS2_DEPENDENCIES( + "ros2-dependencies", + ArrayType.STRING_ARRAY, + List.of(Target.CPP), + (config) -> ASTUtils.toElement(config.ros2Dependencies), + (config, value, err) -> { + config.ros2Dependencies = ASTUtils.elementToListOfStrings(value); + }), + + /** Directive for specifying a specific version of the reactor runtime library. */ + RUNTIME_VERSION( + "runtime-version", + PrimitiveType.STRING, + Arrays.asList(Target.CPP), + (config) -> ASTUtils.toElement(config.runtimeVersion), + (config, value, err) -> { + config.runtimeVersion = ASTUtils.elementToSingleString(value); + }), + + /** Directive for specifying a specific runtime scheduler, if supported. */ + SCHEDULER( + "scheduler", + UnionType.SCHEDULER_UNION, + Arrays.asList(Target.C, Target.CCPP, Target.Python), + (config) -> ASTUtils.toElement(config.schedulerType.toString()), + (config, value, err) -> { + config.schedulerType = + (SchedulerOption) + UnionType.SCHEDULER_UNION.forName(ASTUtils.elementToSingleString(value)); + }), + + /** Directive to specify that all code is generated in a single file. */ + SINGLE_FILE_PROJECT( + "single-file-project", + PrimitiveType.BOOLEAN, + List.of(Target.Rust), + (config) -> ASTUtils.toElement(config.singleFileProject), + (config, value, err) -> { + config.singleFileProject = ASTUtils.toBoolean(value); + }), + + /** Directive to indicate whether the runtime should use multi-threading. */ + THREADING( + "threading", + PrimitiveType.BOOLEAN, + List.of(Target.C, Target.CCPP, Target.Python), + (config) -> ASTUtils.toElement(config.threading), + (config, value, err) -> { + config.threading = ASTUtils.toBoolean(value); + }), + + /** Directive to specify the number of worker threads used by the runtime. */ + WORKERS( + "workers", + PrimitiveType.NON_NEGATIVE_INTEGER, + List.of(Target.C, Target.CCPP, Target.Python, Target.CPP, Target.Rust), + (config) -> ASTUtils.toElement(config.workers), + (config, value, err) -> { + config.workers = ASTUtils.toInteger(value); + }), + + /** Directive to specify the execution timeout. */ + TIMEOUT( + "timeout", + PrimitiveType.TIME_VALUE, + Target.ALL, + (config) -> ASTUtils.toElement(config.timeout), + (config, value, err) -> { + config.timeout = ASTUtils.toTimeValue(value); + }, + (config, value, err) -> { + config.timeout = ASTUtils.toTimeValue(value); + }), + + /** Directive to enable tracing. */ + TRACING( + "tracing", + UnionType.TRACING_UNION, + Arrays.asList(Target.C, Target.CCPP, Target.CPP, Target.Python), + (config) -> { + if (config.tracing == null) { + return null; + } else if (config.tracing.equals(new TracingOptions())) { + // default values + return ASTUtils.toElement(true); + } else { + Element e = LfFactory.eINSTANCE.createElement(); + KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); + for (TracingOption opt : TracingOption.values()) { + KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); + pair.setName(opt.toString()); + switch (opt) { + case TRACE_FILE_NAME: + if (config.tracing.traceFileName == null) continue; + pair.setValue(ASTUtils.toElement(config.tracing.traceFileName)); + } + kvp.getPairs().add(pair); + } + e.setKeyvalue(kvp); + if (kvp.getPairs().isEmpty()) return null; + return e; + } + }, + (config, value, err) -> { + if (value.getLiteral() != null) { + if (ASTUtils.toBoolean(value)) { + config.tracing = new TracingOptions(); + } else { + config.tracing = null; + } + } else { + config.tracing = new TracingOptions(); + for (KeyValuePair entry : value.getKeyvalue().getPairs()) { + TracingOption option = + (TracingOption) DictionaryType.TRACING_DICT.forName(entry.getName()); + switch (option) { + case TRACE_FILE_NAME: + config.tracing.traceFileName = ASTUtils.elementToSingleString(entry.getValue()); + break; + default: + break; + } + } + } + }), + + /** + * Directive to let the runtime export its internal dependency graph. + * + *

This is a debugging feature and currently only used for C++ and Rust programs. + */ + EXPORT_DEPENDENCY_GRAPH( + "export-dependency-graph", + PrimitiveType.BOOLEAN, + List.of(Target.CPP, Target.Rust), + (config) -> ASTUtils.toElement(config.exportDependencyGraph), + (config, value, err) -> { + config.exportDependencyGraph = ASTUtils.toBoolean(value); + }), + + /** + * Directive to let the runtime export the program structure to a yaml file. + * + *

This is a debugging feature and currently only used for C++ programs. + */ + EXPORT_TO_YAML( + "export-to-yaml", + PrimitiveType.BOOLEAN, + List.of(Target.CPP), + (config) -> ASTUtils.toElement(config.exportToYaml), + (config, value, err) -> { + config.exportToYaml = ASTUtils.toBoolean(value); + }), + + /** + * List of module files to link into the crate as top-level. For instance, a {@code target Rust { + * rust-modules: [ "foo.rs" ] }} will cause the file to be copied into the generated project, and + * the generated `main.rs` will include it with a `mod foo;`. If one of the paths is a directory, + * it must contain a `mod.rs` file, and all its contents are copied. + */ + RUST_INCLUDE( + "rust-include", + UnionType.FILE_OR_FILE_ARRAY, + List.of(Target.Rust), + (config) -> { + // do not check paths here, and simply copy the absolute path over + List paths = config.rust.getRustTopLevelModules(); + if (paths.isEmpty()) return null; + else if (paths.size() == 1) { + return ASTUtils.toElement(paths.get(0).toString()); + } else { + Element e = LfFactory.eINSTANCE.createElement(); + Array arr = LfFactory.eINSTANCE.createArray(); + for (Path p : paths) { + arr.getElements().add(ASTUtils.toElement(p.toString())); + } + e.setArray(arr); + return e; + } + }, + (config, value, err) -> { + Path referencePath; + try { + referencePath = FileUtil.toPath(value.eResource().getURI()).toAbsolutePath(); + } catch (IOException e) { + err.reportError(value, "Invalid path? " + e.getMessage()); + throw new RuntimeIOException(e); } - } - - /** - * String representation of this target property. - */ - public final String description; - - /** - * List of targets that support this property. If a property is used for - * a target that does not support it, a warning reported during - * validation. - */ - public final List supportedBy; - - /** - * The type of values that can be assigned to this property. - */ - public final TargetPropertyType type; - /** - * Function that given a configuration object and an Element AST node - * sets the configuration. It is assumed that validation already - * occurred, so this code should be straightforward. - */ - public final PropertyParser setter; + // we'll resolve relative paths to check that the files + // are as expected. - /** - * Function that given a configuration object and an Element AST node - * sets the configuration. It is assumed that validation already - * occurred, so this code should be straightforward. - */ - public final PropertyParser updater; + if (value.getLiteral() != null) { + Path resolved = referencePath.resolveSibling(StringUtil.removeQuotes(value.getLiteral())); + + config.rust.addAndCheckTopLevelModule(resolved, value, err); + } else if (value.getArray() != null) { + for (Element element : value.getArray().getElements()) { + String literal = StringUtil.removeQuotes(element.getLiteral()); + Path resolved = referencePath.resolveSibling(literal); + config.rust.addAndCheckTopLevelModule(resolved, element, err); + } + } + }), + + /** Directive for specifying Cargo features of the generated program to enable. */ + CARGO_FEATURES( + "cargo-features", + ArrayType.STRING_ARRAY, + List.of(Target.Rust), + (config) -> ASTUtils.toElement(config.rust.getCargoFeatures()), + (config, value, err) -> { + config.rust.setCargoFeatures(ASTUtils.elementToListOfStrings(value)); + }), + + /** + * Dependency specifications for Cargo. This property looks like this: + * + *

{@code
+   * cargo-dependencies: {
+   *    // Name-of-the-crate: "version"
+   *    rand: "0.8",
+   *    // Equivalent to using an explicit map:
+   *    rand: {
+   *      version: "0.8"
+   *    },
+   *    // The map allows specifying more details
+   *    rand: {
+   *      // A path to a local unpublished crate.
+   *      // Note 'path' is mutually exclusive with 'version'.
+   *      path: "/home/me/Git/local-rand-clone"
+   *    },
+   *    rand: {
+   *      version: "0.8",
+   *      // you can specify cargo features
+   *      features: ["some-cargo-feature",]
+   *    }
+   * }
+   * }
+ */ + CARGO_DEPENDENCIES( + "cargo-dependencies", + CargoDependenciesPropertyType.INSTANCE, + List.of(Target.Rust), + (config) -> { + var deps = config.rust.getCargoDependencies(); + if (deps.size() == 0) return null; + else { + Element e = LfFactory.eINSTANCE.createElement(); + KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); + for (var ent : deps.entrySet()) { + KeyValuePair pair = LfFactory.eINSTANCE.createKeyValuePair(); + pair.setName(ent.getKey()); + pair.setValue(CargoDependencySpec.extractSpec(ent.getValue())); + kvp.getPairs().add(pair); + } + e.setKeyvalue(kvp); + return e; + } + }, + (config, value, err) -> { + config.rust.setCargoDependencies(CargoDependencySpec.parseAll(value)); + }), + + /** + * Directs the C or Python target to include the associated C file used for setting up federated + * execution before processing the first tag. + */ + FED_SETUP( + "_fed_setup", + PrimitiveType.FILE, + Arrays.asList(Target.C, Target.CCPP, Target.Python), + (config) -> ASTUtils.toElement(config.fedSetupPreamble), + (config, value, err) -> + config.fedSetupPreamble = StringUtil.removeQuotes(ASTUtils.elementToSingleString(value))); + + /** Update {@code config}.dockerOptions based on value. */ + private static void setDockerProperty(TargetConfig config, Element value) { + if (value.getLiteral() != null) { + if (ASTUtils.toBoolean(value)) { + config.dockerOptions = new DockerOptions(); + } else { + config.dockerOptions = null; + } + } else { + config.dockerOptions = new DockerOptions(); + for (KeyValuePair entry : value.getKeyvalue().getPairs()) { + DockerOption option = (DockerOption) DictionaryType.DOCKER_DICT.forName(entry.getName()); + switch (option) { + case FROM: + config.dockerOptions.from = ASTUtils.elementToSingleString(entry.getValue()); + break; + default: + break; + } + } + } + } + + /** String representation of this target property. */ + public final String description; + + /** + * List of targets that support this property. If a property is used for a target that does not + * support it, a warning reported during validation. + */ + public final List supportedBy; + + /** The type of values that can be assigned to this property. */ + public final TargetPropertyType type; + + /** + * Function that given a configuration object and an Element AST node sets the configuration. It + * is assumed that validation already occurred, so this code should be straightforward. + */ + public final PropertyParser setter; + + /** + * Function that given a configuration object and an Element AST node sets the configuration. It + * is assumed that validation already occurred, so this code should be straightforward. + */ + public final PropertyParser updater; + + @FunctionalInterface + private interface PropertyParser { + + /** + * Parse the given element into the given target config. May use the error reporter to report + * format errors. + */ + void parseIntoTargetConfig(TargetConfig config, Element element, ErrorReporter err); + } + + public final PropertyGetter getter; + + @FunctionalInterface + private interface PropertyGetter { + + /** + * Read this property from the target config and build an element which represents it for the + * AST. May return null if the given value of this property is the same as the default. + */ + Element getPropertyElement(TargetConfig config); + } + + /** + * Private constructor for target properties. + * + * @param description String representation of this property. + * @param type The type that values assigned to this property should conform to. + * @param supportedBy List of targets that support this property. + * @param setter Function for configuration updates. + */ + TargetProperty( + String description, + TargetPropertyType type, + List supportedBy, + PropertyGetter getter, + PropertyParser setter) { + this.description = description; + this.type = type; + this.supportedBy = supportedBy; + this.getter = getter; + this.setter = setter; + this.updater = setter; // (Re)set by default + } + + /** + * Private constructor for target properties. This will take an additional `updater`, which will + * be used to merge target properties from imported resources. + * + * @param description String representation of this property. + * @param type The type that values assigned to this property should conform to. + * @param supportedBy List of targets that support this property. + * @param setter Function for setting configuration values. + * @param updater Function for updating configuration values. + */ + TargetProperty( + String description, + TargetPropertyType type, + List supportedBy, + PropertyGetter getter, + PropertyParser setter, + PropertyParser updater) { + this.description = description; + this.type = type; + this.supportedBy = supportedBy; + this.getter = getter; + this.setter = setter; + this.updater = updater; + } + + /** + * Return the name of the property in lingua franca. This is suitable for use as a key in a target + * properties block. It may be an invalid identifier in other languages (may contains dashes + * {@code -}). + */ + public String getDisplayName() { + return description; + } + + /** + * Set the given configuration using the given target properties. + * + * @param config The configuration object to update. + * @param properties AST node that holds all the target properties. + * @param err Error reporter on which property format errors will be reported + */ + public static void set(TargetConfig config, List properties, ErrorReporter err) { + properties.forEach( + property -> { + TargetProperty p = forName(property.getName()); + if (p != null) { + // Mark the specified target property as set by the user + config.setByUser.add(p); + try { + p.setter.parseIntoTargetConfig(config, property.getValue(), err); + } catch (InvalidLfSourceException e) { + err.reportError(e.getNode(), e.getProblem()); + } + } + }); + } + + /** + * Extracts all properties as a list of key-value pairs from a TargetConfig. Only extracts + * properties explicitly set by user. + * + * @param config The TargetConfig to extract from. + * @return The extracted properties. + */ + public static List extractProperties(TargetConfig config) { + var res = new LinkedList(); + for (TargetProperty p : config.setByUser) { + KeyValuePair kv = LfFactory.eINSTANCE.createKeyValuePair(); + kv.setName(p.toString()); + kv.setValue(p.getter.getPropertyElement(config)); + if (kv.getValue() != null) res.add(kv); + } + return res; + } + + /** + * Constructs a TargetDecl by extracting the fields of the given TargetConfig. + * + * @param target The target to generate for. + * @param config The TargetConfig to extract from. + * @return A generated TargetDecl. + */ + public static TargetDecl extractTargetDecl(Target target, TargetConfig config) { + TargetDecl decl = LfFactory.eINSTANCE.createTargetDecl(); + KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); + for (KeyValuePair p : extractProperties(config)) { + kvp.getPairs().add(p); + } + decl.setName(target.toString()); + decl.setConfig(kvp); + return decl; + } + + /** + * Update the given configuration using the given target properties. + * + * @param config The configuration object to update. + * @param properties AST node that holds all the target properties. + */ + public static void update(TargetConfig config, List properties, ErrorReporter err) { + properties.forEach( + property -> { + TargetProperty p = forName(property.getName()); + if (p != null) { + // Mark the specified target property as set by the user + config.setByUser.add(p); + p.updater.parseIntoTargetConfig(config, property.getValue(), err); + } + }); + } + + /** + * Update one of the target properties, given by 'propertyName'. For convenience, a list of target + * properties (e.g., taken from a file or resource) can be passed without any filtering. This + * function will do nothing if the list of target properties doesn't include the property given by + * 'propertyName'. + * + * @param config The target config to apply the update to. + * @param property The target property. + * @param properties AST node that holds all the target properties. + * @param err Error reporter on which property format errors will be reported + */ + public static void updateOne( + TargetConfig config, + TargetProperty property, + List properties, + ErrorReporter err) { + properties.stream() + .filter(p -> p.getName().equals(property.getDisplayName())) + .findFirst() + .map(KeyValuePair::getValue) + .ifPresent(value -> property.updater.parseIntoTargetConfig(config, value, err)); + } + + /** + * Return the entry that matches the given string. + * + * @param name The string to match against. + */ + public static TargetProperty forName(String name) { + return Target.match(name, TargetProperty.values()); + } + + /** + * Return a list with all target properties. + * + * @return All existing target properties. + */ + public static List getOptions() { + return Arrays.asList(TargetProperty.values()); + } + + /** Return the description. */ + @Override + public String toString() { + return this.description; + } + + // Inner classes for the various supported types. + + /** Dictionary type that allows for keys that will be interpreted as strings and string values. */ + public enum StringDictionaryType implements TargetPropertyType { + COMPILE_DEFINITION(); - @FunctionalInterface - private interface PropertyParser { + @Override + public boolean validate(Element e) { + if (e.getKeyvalue() != null) { + return true; + } + return false; + } - /** - * Parse the given element into the given target config. - * May use the error reporter to report format errors. - */ - void parseIntoTargetConfig(TargetConfig config, Element element, ErrorReporter err); + @Override + public void check(Element e, String name, LFValidator v) { + KeyValuePairs kv = e.getKeyvalue(); + if (kv == null) { + TargetPropertyType.produceError(name, this.toString(), v); + } else { + for (KeyValuePair pair : kv.getPairs()) { + String key = pair.getName(); + Element val = pair.getValue(); + + // Make sure the type is string + PrimitiveType.STRING.check(val, name + "." + key, v); + } + } } + } - public final PropertyGetter getter; + /** Interface for dictionary elements. It associates an entry with a type. */ + public interface DictionaryElement { - @FunctionalInterface - private interface PropertyGetter { + TargetPropertyType getType(); + } - /** - * Read this property from the target config and build an element which represents it for the AST. - * May return null if the given value of this property is the same as the default. - */ - Element getPropertyElement(TargetConfig config); - } + /** + * A dictionary type with a predefined set of possible keys and assignable types. + * + * @author Marten Lohstroh + */ + public enum DictionaryType implements TargetPropertyType { + CLOCK_SYNC_OPTION_DICT(Arrays.asList(ClockSyncOption.values())), + DOCKER_DICT(Arrays.asList(DockerOption.values())), + PLATFORM_DICT(Arrays.asList(PlatformOption.values())), + COORDINATION_OPTION_DICT(Arrays.asList(CoordinationOption.values())), + TRACING_DICT(Arrays.asList(TracingOption.values())); - /** - * Private constructor for target properties. - * - * @param description String representation of this property. - * @param type The type that values assigned to this property - * should conform to. - * @param supportedBy List of targets that support this property. - * @param setter Function for configuration updates. - */ - TargetProperty(String description, TargetPropertyType type, - List supportedBy, - PropertyGetter getter, - PropertyParser setter) { - this.description = description; - this.type = type; - this.supportedBy = supportedBy; - this.getter = getter; - this.setter = setter; - this.updater = setter; // (Re)set by default - } + /** The keys and assignable types that are allowed in this dictionary. */ + public List options; /** - * Private constructor for target properties. This will take an additional - * `updater`, which will be used to merge target properties from imported resources. + * A dictionary type restricted to sets of predefined keys and types of values. * - * @param description String representation of this property. - * @param type The type that values assigned to this property - * should conform to. - * @param supportedBy List of targets that support this property. - * @param setter Function for setting configuration values. - * @param updater Function for updating configuration values. - */ - TargetProperty(String description, TargetPropertyType type, - List supportedBy, - PropertyGetter getter, - PropertyParser setter, - PropertyParser updater) { - this.description = description; - this.type = type; - this.supportedBy = supportedBy; - this.getter = getter; - this.setter = setter; - this.updater = updater; - } - - /** - * Return the name of the property in lingua franca. This - * is suitable for use as a key in a target properties block. - * It may be an invalid identifier in other languages (may - * contains dashes {@code -}). + * @param options The dictionary elements allowed by this type. */ - public String getDisplayName() { - return description; + private DictionaryType(List options) { + this.options = options; } /** - * Set the given configuration using the given target properties. + * Return the dictionary element of which the key matches the given string. * - * @param config The configuration object to update. - * @param properties AST node that holds all the target properties. - * @param err Error reporter on which property format errors will be reported + * @param name The string to match against. + * @return The matching dictionary element (or null if there is none). */ - public static void set(TargetConfig config, List properties, ErrorReporter err) { - properties.forEach(property -> { - TargetProperty p = forName(property.getName()); - if (p != null) { - // Mark the specified target property as set by the user - config.setByUser.add(p); - try { - p.setter.parseIntoTargetConfig(config, property.getValue(), err); - } catch (InvalidLfSourceException e) { - err.reportError(e.getNode(), e.getProblem()); - } - } - }); + public DictionaryElement forName(String name) { + return Target.match(name, options); } - /** - * Extracts all properties as a list of key-value pairs from a TargetConfig. Only extracts properties explicitly set by user. - * @param config The TargetConfig to extract from. - * @return The extracted properties. - */ - public static List extractProperties(TargetConfig config) { - var res = new LinkedList(); - for (TargetProperty p : config.setByUser) { - KeyValuePair kv = LfFactory.eINSTANCE.createKeyValuePair(); - kv.setName(p.toString()); - kv.setValue(p.getter.getPropertyElement(config)); - if (kv.getValue() != null) - res.add(kv); + /** Recursively check that the passed in element conforms to the rules of this dictionary. */ + @Override + public void check(Element e, String name, LFValidator v) { + KeyValuePairs kv = e.getKeyvalue(); + if (kv == null) { + TargetPropertyType.produceError(name, this.toString(), v); + } else { + for (KeyValuePair pair : kv.getPairs()) { + String key = pair.getName(); + Element val = pair.getValue(); + Optional match = + this.options.stream() + .filter(element -> key.equalsIgnoreCase(element.toString())) + .findAny(); + if (match.isPresent()) { + // Make sure the type is correct, too. + TargetPropertyType type = match.get().getType(); + type.check(val, name + "." + key, v); + } else { + // No match found; report error. + TargetPropertyType.produceError(name, this.toString(), v); + } } - return res; + } } - /** - * Constructs a TargetDecl by extracting the fields of the given TargetConfig. - * @param target The target to generate for. - * @param config The TargetConfig to extract from. - * @return A generated TargetDecl. - */ - public static TargetDecl extractTargetDecl(Target target, TargetConfig config) { - TargetDecl decl = LfFactory.eINSTANCE.createTargetDecl(); - KeyValuePairs kvp = LfFactory.eINSTANCE.createKeyValuePairs(); - for (KeyValuePair p : extractProperties(config)) { - kvp.getPairs().add(p); - } - decl.setName(target.toString()); - decl.setConfig(kvp); - return decl; + /** Return true if the given element represents a dictionary, false otherwise. */ + @Override + public boolean validate(Element e) { + if (e.getKeyvalue() != null) { + return true; + } + return false; } - /** - * Update the given configuration using the given target properties. - * - * @param config The configuration object to update. - * @param properties AST node that holds all the target properties. - */ - public static void update(TargetConfig config, List properties, ErrorReporter err) { - properties.forEach(property -> { - TargetProperty p = forName(property.getName()); - if (p != null) { - // Mark the specified target property as set by the user - config.setByUser.add(p); - p.updater.parseIntoTargetConfig(config, property.getValue(), err); - } - }); + /** Return a human-readable description of this type. */ + @Override + public String toString() { + return "a dictionary with one or more of the following keys: " + + options.stream().map(option -> option.toString()).collect(Collectors.joining(", ")); } - - /** - * Update one of the target properties, given by 'propertyName'. - * For convenience, a list of target properties (e.g., taken from - * a file or resource) can be passed without any filtering. This - * function will do nothing if the list of target properties doesn't - * include the property given by 'propertyName'. + } + /** + * A type that can assume one of several types. + * + * @author Marten Lohstroh + */ + public enum UnionType implements TargetPropertyType { + STRING_OR_STRING_ARRAY(Arrays.asList(PrimitiveType.STRING, ArrayType.STRING_ARRAY), null), + PLATFORM_STRING_OR_DICTIONARY( + Arrays.asList(PrimitiveType.STRING, DictionaryType.PLATFORM_DICT), null), + FILE_OR_FILE_ARRAY(Arrays.asList(PrimitiveType.FILE, ArrayType.FILE_ARRAY), null), + BUILD_TYPE_UNION(Arrays.asList(BuildType.values()), null), + COORDINATION_UNION(Arrays.asList(CoordinationType.values()), CoordinationType.CENTRALIZED), + SCHEDULER_UNION(Arrays.asList(SchedulerOption.values()), SchedulerOption.getDefault()), + LOGGING_UNION(Arrays.asList(LogLevel.values()), LogLevel.INFO), + PLATFORM_UNION(Arrays.asList(Platform.values()), Platform.AUTO), + CLOCK_SYNC_UNION(Arrays.asList(ClockSyncMode.values()), ClockSyncMode.INIT), + DOCKER_UNION(Arrays.asList(PrimitiveType.BOOLEAN, DictionaryType.DOCKER_DICT), null), + TRACING_UNION(Arrays.asList(PrimitiveType.BOOLEAN, DictionaryType.TRACING_DICT), null); + + /** The constituents of this type union. */ + public final List> options; + + /** The default type, if there is one. */ + private final Enum defaultOption; + + /** + * Private constructor for creating unions types. * - * @param config The target config to apply the update to. - * @param property The target property. - * @param properties AST node that holds all the target properties. - * @param err Error reporter on which property format errors will be reported + * @param options The types that that are part of the union. + * @param defaultOption The default type. */ - public static void updateOne(TargetConfig config, TargetProperty property, List properties, ErrorReporter err) { - properties.stream() - .filter(p -> p.getName().equals(property.getDisplayName())) - .findFirst() - .map(KeyValuePair::getValue) - .ifPresent(value -> property.updater.parseIntoTargetConfig( - config, - value, - err - )); + private UnionType(List> options, Enum defaultOption) { + this.options = options; + this.defaultOption = defaultOption; } /** - * Return the entry that matches the given string. - * @param name The string to match against. - */ - public static TargetProperty forName(String name) { - return Target.match(name, TargetProperty.values()); - } - - /** - * Return a list with all target properties. + * Return the type among those in this type union that matches the given name. * - * @return All existing target properties. + * @param name The string to match against. + * @return The matching dictionary element (or null if there is none). */ - public static List getOptions() { - return Arrays.asList(TargetProperty.values()); + public Enum forName(String name) { + return Target.match(name, options); } - /** - * Return the description. - */ + /** Recursively check that the passed in element conforms to the rules of this union. */ @Override - public String toString() { - return this.description; + public void check(Element e, String name, LFValidator v) { + Optional> match = this.match(e); + if (match.isPresent()) { + // Go deeper if the element is an array or dictionary. + Enum type = match.get(); + if (type instanceof DictionaryType) { + ((DictionaryType) type).check(e, name, v); + } else if (type instanceof ArrayType) { + ((ArrayType) type).check(e, name, v); + } else if (type instanceof PrimitiveType) { + ((PrimitiveType) type).check(e, name, v); + } else if (!(type instanceof Enum)) { + throw new RuntimeException("Encountered an unknown type."); + } + } else { + // No match found; report error. + TargetPropertyType.produceError(name, this.toString(), v); + } } - // Inner classes for the various supported types. - /** - * Dictionary type that allows for keys that will be interpreted as strings - * and string values. + * Internal method for matching a given element against the allowable types. + * + * @param e AST node that represents the value of a target property. + * @return The matching type wrapped in an Optional object. */ - public enum StringDictionaryType implements TargetPropertyType { - COMPILE_DEFINITION(); - @Override - public boolean validate(Element e) { - if (e.getKeyvalue() != null) { - return true; - } - return false; - } - - @Override - public void check(Element e, String name, LFValidator v) { - KeyValuePairs kv = e.getKeyvalue(); - if (kv == null) { - TargetPropertyType.produceError(name, this.toString(), v); - } else { - for (KeyValuePair pair : kv.getPairs()) { - String key = pair.getName(); - Element val = pair.getValue(); - - // Make sure the type is string - PrimitiveType.STRING.check(val, name + "." + key, v); + private Optional> match(Element e) { + return this.options.stream() + .filter( + option -> { + if (option instanceof TargetPropertyType) { + return ((TargetPropertyType) option).validate(e); + } else { + return ASTUtils.elementToSingleString(e).equalsIgnoreCase(option.toString()); } - } - - } + }) + .findAny(); } /** - * Interface for dictionary elements. It associates an entry with a type. + * Return true if this union has an option that matches the given element. + * + * @param e The element to match against this type. */ - public interface DictionaryElement { - - TargetPropertyType getType(); + @Override + public boolean validate(Element e) { + if (this.match(e).isPresent()) { + return true; + } + return false; } /** - * A dictionary type with a predefined set of possible keys and assignable - * types. - * - * @author Marten Lohstroh - * + * Return a human-readable description of this type. If three is a default option, then indicate + * it. */ - public enum DictionaryType implements TargetPropertyType { - CLOCK_SYNC_OPTION_DICT(Arrays.asList(ClockSyncOption.values())), - DOCKER_DICT(Arrays.asList(DockerOption.values())), - PLATFORM_DICT(Arrays.asList(PlatformOption.values())), - COORDINATION_OPTION_DICT(Arrays.asList(CoordinationOption.values())), - TRACING_DICT(Arrays.asList(TracingOption.values())); - - /** - * The keys and assignable types that are allowed in this dictionary. - */ - public List options; - - /** - * A dictionary type restricted to sets of predefined keys and types of - * values. - * - * @param options The dictionary elements allowed by this type. - */ - private DictionaryType(List options) { - this.options = options; - } - - /** - * Return the dictionary element of which the key matches the given - * string. - * - * @param name The string to match against. - * @return The matching dictionary element (or null if there is none). - */ - public DictionaryElement forName(String name) { - return Target.match(name, options); - } - - /** - * Recursively check that the passed in element conforms to the rules of - * this dictionary. - */ - @Override - public void check(Element e, String name, LFValidator v) { - KeyValuePairs kv = e.getKeyvalue(); - if (kv == null) { - TargetPropertyType.produceError(name, this.toString(), v); - } else { - for (KeyValuePair pair : kv.getPairs()) { - String key = pair.getName(); - Element val = pair.getValue(); - Optional match = this.options.stream() - .filter(element -> key.equalsIgnoreCase(element.toString())).findAny(); - if (match.isPresent()) { - // Make sure the type is correct, too. - TargetPropertyType type = match.get().getType(); - type.check(val, name + "." + key, v); + @Override + public String toString() { + return "one of the following: " + + options.stream() + .map( + option -> { + if (option == this.defaultOption) { + return option.toString() + " (default)"; } else { - // No match found; report error. - TargetPropertyType.produceError(name, - this.toString(), v); + return option.toString(); } - } - } - } - - /** - * Return true if the given element represents a dictionary, false - * otherwise. - */ - @Override - public boolean validate(Element e) { - if (e.getKeyvalue() != null) { - return true; - } - return false; - } - - /** - * Return a human-readable description of this type. - */ - @Override - public String toString() { - return "a dictionary with one or more of the following keys: " - + options.stream().map(option -> option.toString()) - .collect(Collectors.joining(", ")); - } + }) + .collect(Collectors.joining(", ")); } - /** - * A type that can assume one of several types. - * - * @author Marten Lohstroh - * - */ - public enum UnionType implements TargetPropertyType { - STRING_OR_STRING_ARRAY( - Arrays.asList(PrimitiveType.STRING, ArrayType.STRING_ARRAY), - null), - PLATFORM_STRING_OR_DICTIONARY( - Arrays.asList(PrimitiveType.STRING, DictionaryType.PLATFORM_DICT), - null), - FILE_OR_FILE_ARRAY( - Arrays.asList(PrimitiveType.FILE, ArrayType.FILE_ARRAY), null), - BUILD_TYPE_UNION(Arrays.asList(BuildType.values()), null), - COORDINATION_UNION(Arrays.asList(CoordinationType.values()), - CoordinationType.CENTRALIZED), - SCHEDULER_UNION(Arrays.asList(SchedulerOption.values()), SchedulerOption.getDefault()), - LOGGING_UNION(Arrays.asList(LogLevel.values()), LogLevel.INFO), - PLATFORM_UNION(Arrays.asList(Platform.values()), Platform.AUTO), - CLOCK_SYNC_UNION(Arrays.asList(ClockSyncMode.values()), - ClockSyncMode.INIT), - DOCKER_UNION(Arrays.asList(PrimitiveType.BOOLEAN, DictionaryType.DOCKER_DICT), - null), - TRACING_UNION(Arrays.asList(PrimitiveType.BOOLEAN, DictionaryType.TRACING_DICT), - null); - - /** - * The constituents of this type union. - */ - public final List> options; - - /** - * The default type, if there is one. - */ - private final Enum defaultOption; - - /** - * Private constructor for creating unions types. - * - * @param options The types that that are part of the union. - * @param defaultOption The default type. - */ - private UnionType(List> options, Enum defaultOption) { - this.options = options; - this.defaultOption = defaultOption; - } - - /** - * Return the type among those in this type union that matches the given - * name. - * - * @param name The string to match against. - * @return The matching dictionary element (or null if there is none). - */ - public Enum forName(String name) { - return Target.match(name, options); - } - - /** - * Recursively check that the passed in element conforms to the rules of - * this union. - */ - @Override - public void check(Element e, String name, LFValidator v) { - Optional> match = this.match(e); - if (match.isPresent()) { - // Go deeper if the element is an array or dictionary. - Enum type = match.get(); - if (type instanceof DictionaryType) { - ((DictionaryType) type).check(e, name, v); - } else if (type instanceof ArrayType) { - ((ArrayType) type).check(e, name, v); - } else if (type instanceof PrimitiveType) { - ((PrimitiveType) type).check(e, name, v); - } else if (!(type instanceof Enum)) { - throw new RuntimeException("Encountered an unknown type."); - } - } else { - // No match found; report error. - TargetPropertyType.produceError(name, this.toString(), v); - } - } - - /** - * Internal method for matching a given element against the allowable - * types. - * - * @param e AST node that represents the value of a target property. - * @return The matching type wrapped in an Optional object. - */ - private Optional> match(Element e) { - return this.options.stream().filter(option -> { - if (option instanceof TargetPropertyType) { - return ((TargetPropertyType) option).validate(e); - } else { - return ASTUtils.elementToSingleString(e) - .equalsIgnoreCase(option.toString()); - } - }).findAny(); - } + } - /** - * Return true if this union has an option that matches the given - * element. - * @param e The element to match against this type. - */ - @Override - public boolean validate(Element e) { - if (this.match(e).isPresent()) { - return true; - } - return false; - } - - /** - * Return a human-readable description of this type. If three is a - * default option, then indicate it. - */ - @Override - public String toString() { - return "one of the following: " + options.stream().map(option -> { - if (option == this.defaultOption) { - return option.toString() + " (default)"; - } else { - return option.toString(); - } - }).collect(Collectors.joining(", ")); - } + /** + * An array type of which the elements confirm to a given type. + * + * @author Marten Lohstroh + */ + public enum ArrayType implements TargetPropertyType { + STRING_ARRAY(PrimitiveType.STRING), + FILE_ARRAY(PrimitiveType.FILE); - } + /** Type parameter of this array type. */ + public TargetPropertyType type; /** - * An array type of which the elements confirm to a given type. - * - * @author Marten Lohstroh + * Private constructor to create a new array type. * + * @param type The type of elements in the array. */ - public enum ArrayType implements TargetPropertyType { - STRING_ARRAY(PrimitiveType.STRING), - FILE_ARRAY(PrimitiveType.FILE); - - /** - * Type parameter of this array type. - */ - public TargetPropertyType type; - - /** - * Private constructor to create a new array type. - * - * @param type The type of elements in the array. - */ - private ArrayType(TargetPropertyType type) { - this.type = type; - } - - /** - * Check that the passed in element represents an array and ensure that - * its elements are all of the correct type. - */ - @Override - public void check(Element e, String name, LFValidator v) { - Array array = e.getArray(); - if (array == null) { - TargetPropertyType.produceError(name, this.toString(), v); - } else { - List elements = array.getElements(); - for (int i = 0; i < elements.size(); i++) { - this.type.check(elements.get(i), name + "[" + i + "]", v); - } - } - } - - /** - * Return true of the given element is an array. - */ - @Override - public boolean validate(Element e) { - if (e.getArray() != null) { - return true; - } - return false; - } - - /** - * Return a human-readable description of this type. - */ - @Override - public String toString() { - return "an array of which each element is " + this.type.toString(); - } + private ArrayType(TargetPropertyType type) { + this.type = type; } /** - * Enumeration of Cmake build types. These are also mapped - * to Cargo profiles for the Rust target (see {@link org.lflang.generator.rust.RustTargetConfig}) - * - * @author Christian Menard + * Check that the passed in element represents an array and ensure that its elements are all of + * the correct type. */ - public enum BuildType { - RELEASE("Release"), - DEBUG("Debug"), - TEST("Test"), - REL_WITH_DEB_INFO("RelWithDebInfo"), - MIN_SIZE_REL("MinSizeRel"); - - /** - * Alias used in toString method. - */ - private final String alias; - - /** - * Private constructor for Cmake build types. - */ - BuildType(String alias) { - this.alias = alias; + @Override + public void check(Element e, String name, LFValidator v) { + Array array = e.getArray(); + if (array == null) { + TargetPropertyType.produceError(name, this.toString(), v); + } else { + List elements = array.getElements(); + for (int i = 0; i < elements.size(); i++) { + this.type.check(elements.get(i), name + "[" + i + "]", v); } + } + } - /** - * Return the alias. - */ - @Override - public String toString() { - return this.alias; - } + /** Return true of the given element is an array. */ + @Override + public boolean validate(Element e) { + if (e.getArray() != null) { + return true; + } + return false; } - /** - * Enumeration of coordination types. - * - * @author Marten Lohstroh - */ - public enum CoordinationType { - CENTRALIZED, DECENTRALIZED; - - /** - * Return the name in lower case. - */ - @Override - public String toString() { - return this.name().toLowerCase(); - } + /** Return a human-readable description of this type. */ + @Override + public String toString() { + return "an array of which each element is " + this.type.toString(); + } + } + + /** + * Enumeration of Cmake build types. These are also mapped to Cargo profiles for the Rust target + * (see {@link org.lflang.generator.rust.RustTargetConfig}) + * + * @author Christian Menard + */ + public enum BuildType { + RELEASE("Release"), + DEBUG("Debug"), + TEST("Test"), + REL_WITH_DEB_INFO("RelWithDebInfo"), + MIN_SIZE_REL("MinSizeRel"); + + /** Alias used in toString method. */ + private final String alias; + + /** Private constructor for Cmake build types. */ + BuildType(String alias) { + this.alias = alias; + } + + /** Return the alias. */ + @Override + public String toString() { + return this.alias; + } + } + + /** + * Enumeration of coordination types. + * + * @author Marten Lohstroh + */ + public enum CoordinationType { + CENTRALIZED, + DECENTRALIZED; + + /** Return the name in lower case. */ + @Override + public String toString() { + return this.name().toLowerCase(); + } + } + + /** + * Enumeration of clock synchronization modes. + * + *

- OFF: The clock synchronization is universally off. - STARTUP: Clock synchronization occurs + * at startup only. - ON: Clock synchronization occurs at startup and at runtime. + * + * @author Edward A. Lee + */ + public enum ClockSyncMode { + OFF, + INIT, + ON; // TODO Discuss initial in now a mode keyword (same as startup) and cannot be used as target + // property value, thus changed it to init + // FIXME I could not test if this change breaks anything + + /** Return the name in lower case. */ + @Override + public String toString() { + return this.name().toLowerCase(); } + } + /** + * An interface for types associated with target properties. + * + * @author Marten Lohstroh + */ + public interface TargetPropertyType { /** - * Enumeration of clock synchronization modes. + * Return true if the the given Element is a valid instance of this type. * - * - OFF: The clock synchronization is universally off. - * - STARTUP: Clock synchronization occurs at startup only. - * - ON: Clock synchronization occurs at startup and at runtime. - * - * @author Edward A. Lee + * @param e The Element to validate. + * @return True if the element conforms to this type, false otherwise. */ - public enum ClockSyncMode { - OFF, INIT, ON; // TODO Discuss initial in now a mode keyword (same as startup) and cannot be used as target property value, thus changed it to init - // FIXME I could not test if this change breaks anything - - /** - * Return the name in lower case. - */ - @Override - public String toString() { - return this.name().toLowerCase(); - } - } + public boolean validate(Element e); /** - * An interface for types associated with target properties. + * Check (recursively) the given Element against its associated type(s) and add found problems + * to the given list of errors. * - * @author Marten Lohstroh + * @param e The Element to type check. + * @param name The name of the target property. + * @param v A reference to the validator to report errors to. */ - public interface TargetPropertyType { - - /** - * Return true if the the given Element is a valid instance of this - * type. - * - * @param e The Element to validate. - * @return True if the element conforms to this type, false otherwise. - */ - public boolean validate(Element e); - - /** - * Check (recursively) the given Element against its associated type(s) - * and add found problems to the given list of errors. - * - * @param e The Element to type check. - * @param name The name of the target property. - * @param v A reference to the validator to report errors to. - */ - public void check(Element e, String name, LFValidator v); - - /** - * Helper function to produce an error during type checking. - * - * @param name The description of the target property. - * @param description The description of the type. - * @param v A reference to the validator to report errors to. - */ - public static void produceError(String name, String description, - LFValidator v) { - v.getTargetPropertyErrors().add("Target property '" + name - + "' is required to be " + description + "."); - } - } + public void check(Element e, String name, LFValidator v); /** - * Primitive types for target properties, each with a description used in - * error messages and predicate used for validating values. + * Helper function to produce an error during type checking. * - * @author Marten Lohstroh + * @param name The description of the target property. + * @param description The description of the type. + * @param v A reference to the validator to report errors to. */ - public enum PrimitiveType implements TargetPropertyType { - BOOLEAN("'true' or 'false'", - v -> ASTUtils.elementToSingleString(v).equalsIgnoreCase("true") - || ASTUtils.elementToSingleString(v).equalsIgnoreCase("false")), - INTEGER("an integer", v -> { - try { - Integer.parseInt(ASTUtils.elementToSingleString(v)); - } catch (NumberFormatException e) { - return false; - } - return true; + public static void produceError(String name, String description, LFValidator v) { + v.getTargetPropertyErrors() + .add("Target property '" + name + "' is required to be " + description + "."); + } + } + + /** + * Primitive types for target properties, each with a description used in error messages and + * predicate used for validating values. + * + * @author Marten Lohstroh + */ + public enum PrimitiveType implements TargetPropertyType { + BOOLEAN( + "'true' or 'false'", + v -> + ASTUtils.elementToSingleString(v).equalsIgnoreCase("true") + || ASTUtils.elementToSingleString(v).equalsIgnoreCase("false")), + INTEGER( + "an integer", + v -> { + try { + Integer.parseInt(ASTUtils.elementToSingleString(v)); + } catch (NumberFormatException e) { + return false; + } + return true; }), - NON_NEGATIVE_INTEGER("a non-negative integer", v -> { - try { - int result = Integer.parseInt(ASTUtils.elementToSingleString(v)); - if (result < 0) - return false; - } catch (NumberFormatException e) { - return false; - } - return true; + NON_NEGATIVE_INTEGER( + "a non-negative integer", + v -> { + try { + int result = Integer.parseInt(ASTUtils.elementToSingleString(v)); + if (result < 0) return false; + } catch (NumberFormatException e) { + return false; + } + return true; }), - TIME_VALUE("a time value with units", v -> - v.getKeyvalue() == null && v.getArray() == null - && v.getLiteral() == null && v.getId() == null + TIME_VALUE( + "a time value with units", + v -> + v.getKeyvalue() == null + && v.getArray() == null + && v.getLiteral() == null + && v.getId() == null && (v.getTime() == 0 || v.getUnit() != null)), - STRING("a string", v -> v.getLiteral() != null && !isCharLiteral(v.getLiteral()) || v.getId() != null), - FILE("a path to a file", STRING.validator); - - /** - * A description of this type, featured in error messages. - */ - private final String description; - - /** - * A predicate for determining whether a given Element conforms to this - * type. - */ - public final Predicate validator; - - /** - * Private constructor to create a new primitive type. - * @param description A textual description of the type that should - * start with "a/an". - * @param validator A predicate that returns true if a given Element - * conforms to this type. - */ - private PrimitiveType(String description, - Predicate validator) { - this.description = description; - this.validator = validator; - } + STRING( + "a string", + v -> v.getLiteral() != null && !isCharLiteral(v.getLiteral()) || v.getId() != null), + FILE("a path to a file", STRING.validator); - /** - * Return true if the the given Element is a valid instance of this type. - */ - public boolean validate(Element e) { - return this.validator.test(e); - } + /** A description of this type, featured in error messages. */ + private final String description; - /** - * Check (recursively) the given Element against its associated type(s) - * and add found problems to the given list of errors. - * - * @param e The element to type check. - * @param name The name of the target property. - * @param v The LFValidator to append errors to. - */ - public void check(Element e, String name, LFValidator v) { - if (!this.validate(e)) { - TargetPropertyType.produceError(name, this.description, v); - } - // If this is a file, perform an additional check to make sure - // the file actually exists. - // FIXME: premature because we first need a mechanism for looking up files! - // Looking in the same directory is too restrictive. Disabling this check for now. - /* - if (this == FILE) { - String file = ASTUtils.toSingleString(e); - - if (!FileConfig.fileExists(file, FileConfig.toPath(e.eResource().getURI()).toFile().getParent())) { - v.targetPropertyWarnings - .add("Could not find file: '" + file + "'."); - } - } - */ - } - - /** - * Return a textual description of this type. - */ - @Override - public String toString() { - return this.description; - } - - - private static boolean isCharLiteral(String s) { - return s.length() > 2 - && '\'' == s.charAt(0) - && '\'' == s.charAt(s.length() - 1); - } - } + /** A predicate for determining whether a given Element conforms to this type. */ + public final Predicate validator; /** - * Clock synchronization options. - * @author Marten Lohstroh + * Private constructor to create a new primitive type. + * + * @param description A textual description of the type that should start with "a/an". + * @param validator A predicate that returns true if a given Element conforms to this type. */ - public enum ClockSyncOption implements DictionaryElement { - ATTENUATION("attenuation", PrimitiveType.NON_NEGATIVE_INTEGER), - LOCAL_FEDERATES_ON("local-federates-on", PrimitiveType.BOOLEAN), - PERIOD("period", PrimitiveType.TIME_VALUE), - TEST_OFFSET("test-offset", PrimitiveType.TIME_VALUE), - TRIALS("trials", PrimitiveType.NON_NEGATIVE_INTEGER), - COLLECT_STATS("collect-stats", PrimitiveType.BOOLEAN); - - public final PrimitiveType type; - - private final String description; - - private ClockSyncOption(String alias, PrimitiveType type) { - this.description = alias; - this.type = type; - } - - - /** - * Return the description of this dictionary element. - */ - @Override - public String toString() { - return this.description; - } + private PrimitiveType(String description, Predicate validator) { + this.description = description; + this.validator = validator; + } - /** - * Return the type associated with this dictionary element. - */ - public TargetPropertyType getType() { - return this.type; - } + /** Return true if the the given Element is a valid instance of this type. */ + public boolean validate(Element e) { + return this.validator.test(e); } /** - * Docker options. - * @author Edward A. Lee - */ - public enum DockerOption implements DictionaryElement { - FROM("FROM", PrimitiveType.STRING); - - public final PrimitiveType type; - - private final String description; - - private DockerOption(String alias, PrimitiveType type) { - this.description = alias; - this.type = type; - } - - /** - * Return the description of this dictionary element. - */ - @Override - public String toString() { - return this.description; - } + * Check (recursively) the given Element against its associated type(s) and add found problems + * to the given list of errors. + * + * @param e The element to type check. + * @param name The name of the target property. + * @param v The LFValidator to append errors to. + */ + public void check(Element e, String name, LFValidator v) { + if (!this.validate(e)) { + TargetPropertyType.produceError(name, this.description, v); + } + // If this is a file, perform an additional check to make sure + // the file actually exists. + // FIXME: premature because we first need a mechanism for looking up files! + // Looking in the same directory is too restrictive. Disabling this check for now. + /* + if (this == FILE) { + String file = ASTUtils.toSingleString(e); + + if (!FileConfig.fileExists(file, FileConfig.toPath(e.eResource().getURI()).toFile().getParent())) { + v.targetPropertyWarnings + .add("Could not find file: '" + file + "'."); + } + } + */ + } - /** - * Return the type associated with this dictionary element. - */ - public TargetPropertyType getType() { - return this.type; - } + /** Return a textual description of this type. */ + @Override + public String toString() { + return this.description; } + private static boolean isCharLiteral(String s) { + return s.length() > 2 && '\'' == s.charAt(0) && '\'' == s.charAt(s.length() - 1); + } + } + + /** + * Clock synchronization options. + * + * @author Marten Lohstroh + */ + public enum ClockSyncOption implements DictionaryElement { + ATTENUATION("attenuation", PrimitiveType.NON_NEGATIVE_INTEGER), + LOCAL_FEDERATES_ON("local-federates-on", PrimitiveType.BOOLEAN), + PERIOD("period", PrimitiveType.TIME_VALUE), + TEST_OFFSET("test-offset", PrimitiveType.TIME_VALUE), + TRIALS("trials", PrimitiveType.NON_NEGATIVE_INTEGER), + COLLECT_STATS("collect-stats", PrimitiveType.BOOLEAN); + + public final PrimitiveType type; + + private final String description; + + private ClockSyncOption(String alias, PrimitiveType type) { + this.description = alias; + this.type = type; + } - /** - * Platform options. - * @author Anirudh Rengarajan - */ - public enum PlatformOption implements DictionaryElement { - NAME("name", PrimitiveType.STRING), - BAUDRATE("baud-rate", PrimitiveType.NON_NEGATIVE_INTEGER), - BOARD("board", PrimitiveType.STRING), - FLASH("flash", PrimitiveType.BOOLEAN), - PORT("port", PrimitiveType.STRING), - USER_THREADS("user-threads", PrimitiveType.NON_NEGATIVE_INTEGER); + /** Return the description of this dictionary element. */ + @Override + public String toString() { + return this.description; + } - public final PrimitiveType type; + /** Return the type associated with this dictionary element. */ + public TargetPropertyType getType() { + return this.type; + } + } - private final String description; + /** + * Docker options. + * + * @author Edward A. Lee + */ + public enum DockerOption implements DictionaryElement { + FROM("FROM", PrimitiveType.STRING); - private PlatformOption(String alias, PrimitiveType type) { - this.description = alias; - this.type = type; - } + public final PrimitiveType type; + private final String description; - /** - * Return the description of this dictionary element. - */ - @Override - public String toString() { - return this.description; - } + private DockerOption(String alias, PrimitiveType type) { + this.description = alias; + this.type = type; + } - /** - * Return the type associated with this dictionary element. - */ - public TargetPropertyType getType() { - return this.type; - } + /** Return the description of this dictionary element. */ + @Override + public String toString() { + return this.description; } - /** - * Coordination options. - * @author Edward A. Lee - */ - public enum CoordinationOption implements DictionaryElement { - ADVANCE_MESSAGE_INTERVAL("advance-message-interval", PrimitiveType.TIME_VALUE); + /** Return the type associated with this dictionary element. */ + public TargetPropertyType getType() { + return this.type; + } + } + + /** + * Platform options. + * + * @author Anirudh Rengarajan + */ + public enum PlatformOption implements DictionaryElement { + NAME("name", PrimitiveType.STRING), + BAUDRATE("baud-rate", PrimitiveType.NON_NEGATIVE_INTEGER), + BOARD("board", PrimitiveType.STRING), + FLASH("flash", PrimitiveType.BOOLEAN), + PORT("port", PrimitiveType.STRING), + USER_THREADS("user-threads", PrimitiveType.NON_NEGATIVE_INTEGER); + + public final PrimitiveType type; + + private final String description; + + private PlatformOption(String alias, PrimitiveType type) { + this.description = alias; + this.type = type; + } - public final PrimitiveType type; + /** Return the description of this dictionary element. */ + @Override + public String toString() { + return this.description; + } - private final String description; + /** Return the type associated with this dictionary element. */ + public TargetPropertyType getType() { + return this.type; + } + } - private CoordinationOption(String alias, PrimitiveType type) { - this.description = alias; - this.type = type; - } + /** + * Coordination options. + * + * @author Edward A. Lee + */ + public enum CoordinationOption implements DictionaryElement { + ADVANCE_MESSAGE_INTERVAL("advance-message-interval", PrimitiveType.TIME_VALUE); + public final PrimitiveType type; - /** - * Return the description of this dictionary element. - */ - @Override - public String toString() { - return this.description; - } + private final String description; - /** - * Return the type associated with this dictionary element. - */ - public TargetPropertyType getType() { - return this.type; - } + private CoordinationOption(String alias, PrimitiveType type) { + this.description = alias; + this.type = type; } - /** - * Log levels in descending order of severity. - * @author Marten Lohstroh - */ - public enum LogLevel { - ERROR, WARN, INFO, LOG, DEBUG; - - /** - * Return the name in lower case. - */ - @Override - public String toString() { - return this.name().toLowerCase(); - } + /** Return the description of this dictionary element. */ + @Override + public String toString() { + return this.description; } - /** - * Enumeration of supported platforms - */ - public enum Platform { - AUTO, - ARDUINO, - NRF52("Nrf52"), - LINUX("Linux"), - MAC("Darwin"), - ZEPHYR("Zephyr"), - WINDOWS("Windows"); - - String cMakeName; - Platform() { - this.cMakeName = this.toString(); - } - Platform(String cMakeName) { - this.cMakeName = cMakeName; - } + /** Return the type associated with this dictionary element. */ + public TargetPropertyType getType() { + return this.type; + } + } + + /** + * Log levels in descending order of severity. + * + * @author Marten Lohstroh + */ + public enum LogLevel { + ERROR, + WARN, + INFO, + LOG, + DEBUG; + + /** Return the name in lower case. */ + @Override + public String toString() { + return this.name().toLowerCase(); + } + } + + /** Enumeration of supported platforms */ + public enum Platform { + AUTO, + ARDUINO, + NRF52("Nrf52"), + LINUX("Linux"), + MAC("Darwin"), + ZEPHYR("Zephyr"), + WINDOWS("Windows"); + + String cMakeName; + + Platform() { + this.cMakeName = this.toString(); + } - /** - * Return the name in lower case. - */ - @Override - public String toString() { - return this.name().toLowerCase(); - } + Platform(String cMakeName) { + this.cMakeName = cMakeName; + } - /** - * Get the CMake name for the platform. - */ - public String getcMakeName() { - return this.cMakeName; - } + /** Return the name in lower case. */ + @Override + public String toString() { + return this.name().toLowerCase(); } - /** - * Supported schedulers. - * @author Soroush Bateni - */ - public enum SchedulerOption { - NP(false), // Non-preemptive - ADAPTIVE(false, List.of( + /** Get the CMake name for the platform. */ + public String getcMakeName() { + return this.cMakeName; + } + } + + /** + * Supported schedulers. + * + * @author Soroush Bateni + */ + public enum SchedulerOption { + NP(false), // Non-preemptive + ADAPTIVE( + false, + List.of( Path.of("scheduler_adaptive.c"), Path.of("worker_assignments.h"), Path.of("worker_states.h"), - Path.of("data_collection.h") - )), - GEDF_NP(true), // Global EDF non-preemptive - GEDF_NP_CI(true); // Global EDF non-preemptive with chain ID - // PEDF_NP(true); // Partitioned EDF non-preemptive (FIXME: To be re-added in a future PR) - - /** - * Indicate whether or not the scheduler prioritizes reactions by deadline. - */ - private final boolean prioritizesDeadline; - - /** Relative paths to files required by this scheduler. */ - private final List relativePaths; - - SchedulerOption(boolean prioritizesDeadline) { - this(prioritizesDeadline, null); - } + Path.of("data_collection.h"))), + GEDF_NP(true), // Global EDF non-preemptive + GEDF_NP_CI(true); // Global EDF non-preemptive with chain ID + // PEDF_NP(true); // Partitioned EDF non-preemptive (FIXME: To be re-added in a future PR) - SchedulerOption(boolean prioritizesDeadline, List relativePaths) { - this.prioritizesDeadline = prioritizesDeadline; - this.relativePaths = relativePaths; - } + /** Indicate whether or not the scheduler prioritizes reactions by deadline. */ + private final boolean prioritizesDeadline; - /** - * Return true if the scheduler prioritizes reactions by deadline. - */ - public boolean prioritizesDeadline() { - return this.prioritizesDeadline; - } + /** Relative paths to files required by this scheduler. */ + private final List relativePaths; - public List getRelativePaths() { - return relativePaths != null ? ImmutableList.copyOf(relativePaths) : - List.of(Path.of("scheduler_" + this + ".c")); - } + SchedulerOption(boolean prioritizesDeadline) { + this(prioritizesDeadline, null); + } - public static SchedulerOption getDefault() { - return NP; - } + SchedulerOption(boolean prioritizesDeadline, List relativePaths) { + this.prioritizesDeadline = prioritizesDeadline; + this.relativePaths = relativePaths; } - /** - * Tracing options. - * @author Edward A. Lee - */ - public enum TracingOption implements DictionaryElement { - TRACE_FILE_NAME("trace-file-name", PrimitiveType.STRING); + /** Return true if the scheduler prioritizes reactions by deadline. */ + public boolean prioritizesDeadline() { + return this.prioritizesDeadline; + } - public final PrimitiveType type; + public List getRelativePaths() { + return relativePaths != null + ? ImmutableList.copyOf(relativePaths) + : List.of(Path.of("scheduler_" + this + ".c")); + } - private final String description; + public static SchedulerOption getDefault() { + return NP; + } + } - private TracingOption(String alias, PrimitiveType type) { - this.description = alias; - this.type = type; - } + /** + * Tracing options. + * + * @author Edward A. Lee + */ + public enum TracingOption implements DictionaryElement { + TRACE_FILE_NAME("trace-file-name", PrimitiveType.STRING); - /** - * Return the description of this dictionary element. - */ - @Override - public String toString() { - return this.description; - } + public final PrimitiveType type; - /** - * Return the type associated with this dictionary element. - */ - public TargetPropertyType getType() { - return this.type; - } + private final String description; + + private TracingOption(String alias, PrimitiveType type) { + this.description = alias; + this.type = type; } + /** Return the description of this dictionary element. */ + @Override + public String toString() { + return this.description; + } + + /** Return the type associated with this dictionary element. */ + public TargetPropertyType getType() { + return this.type; + } + } } diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index 01adccc8ca..0f9c25952c 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -1,26 +1,26 @@ /************* -Copyright (c) 2019-2021, 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. -***************/ + * Copyright (c) 2019-2021, 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.generator.c; @@ -34,6 +34,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import static org.lflang.ASTUtils.toText; import static org.lflang.util.StringUtil.addDoubleQuotes; +import com.google.common.collect.Iterables; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -44,35 +45,28 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; - import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.xbase.lib.Exceptions; import org.eclipse.xtext.xbase.lib.IterableExtensions; import org.eclipse.xtext.xbase.lib.StringExtensions; - import org.lflang.ASTUtils; -import org.lflang.generator.CodeMap; -import org.lflang.generator.DockerComposeGenerator; import org.lflang.FileConfig; import org.lflang.Target; import org.lflang.TargetConfig; import org.lflang.TargetProperty; import org.lflang.TargetProperty.Platform; import org.lflang.TargetProperty.PlatformOption; - -import org.lflang.federated.extensions.CExtensionUtils; - import org.lflang.ast.DelayedConnectionTransformation; - +import org.lflang.federated.extensions.CExtensionUtils; import org.lflang.generator.ActionInstance; import org.lflang.generator.CodeBuilder; +import org.lflang.generator.CodeMap; +import org.lflang.generator.DelayBodyGenerator; +import org.lflang.generator.DockerComposeGenerator; import org.lflang.generator.DockerGenerator; import org.lflang.generator.GeneratorBase; import org.lflang.generator.GeneratorResult; import org.lflang.generator.GeneratorUtils; - -import org.lflang.generator.DelayBodyGenerator; - import org.lflang.generator.LFGeneratorContext; import org.lflang.generator.LFResource; import org.lflang.generator.ParameterInstance; @@ -87,7 +81,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.Input; import org.lflang.lf.Instantiation; import org.lflang.lf.Mode; -import org.lflang.lf.Model; import org.lflang.lf.Port; import org.lflang.lf.Preamble; import org.lflang.lf.Reaction; @@ -98,201 +91,168 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.util.ArduinoUtil; import org.lflang.util.FileUtil; -import com.google.common.collect.Iterables; - /** - * Generator for C target. This class generates C code defining each reactor - * class given in the input .lf file and imported .lf files. The generated code - * has the following components: + * Generator for C target. This class generates C code defining each reactor class given in the + * input .lf file and imported .lf files. The generated code has the following components: * - * * A typedef for inputs, outputs, and actions of each reactor class. These - * define the types of the variables that reactions use to access inputs and - * action values and to set output values. + *

* A typedef for inputs, outputs, and actions of each reactor class. These define the types of + * the variables that reactions use to access inputs and action values and to set output values. * - * * A typedef for a "self" struct for each reactor class. One instance of this - * struct will be created for each reactor instance. See below for details. + *

* A typedef for a "self" struct for each reactor class. One instance of this struct will be + * created for each reactor instance. See below for details. * - * * A function definition for each reaction in each reactor class. These - * functions take an instance of the self struct as an argument. + *

* A function definition for each reaction in each reactor class. These functions take an + * instance of the self struct as an argument. * - * * A constructor function for each reactor class. This is used to create - * a new instance of the reactor. + *

* A constructor function for each reactor class. This is used to create a new instance of the + * reactor. * - * After these, the main generated function is `_lf_initialize_trigger_objects()`. - * This function creates the instances of reactors (using their constructors) - * and makes connections between them. + *

After these, the main generated function is `_lf_initialize_trigger_objects()`. This function + * creates the instances of reactors (using their constructors) and makes connections between them. * - * A few other smaller functions are also generated. + *

A few other smaller functions are also generated. * - * ## Self Struct + *

## Self Struct * - * The "self" struct has fields for each of the following: + *

The "self" struct has fields for each of the following: * - * * parameter: the field name and type match the parameter. - * * state: the field name and type match the state. - * * action: the field name prepends the action name with "_lf_". - * A second field for the action is also created to house the trigger_t object. - * That second field prepends the action name with "_lf__". - * * output: the field name prepends the output name with "_lf_". - * * input: the field name prepends the output name with "_lf_". - * A second field for the input is also created to house the trigger_t object. - * That second field prepends the input name with "_lf__". + *

* parameter: the field name and type match the parameter. * state: the field name and type + * match the state. * action: the field name prepends the action name with "_lf_". A second field + * for the action is also created to house the trigger_t object. That second field prepends the + * action name with "_lf__". * output: the field name prepends the output name with "_lf_". * input: + * the field name prepends the output name with "_lf_". A second field for the input is also created + * to house the trigger_t object. That second field prepends the input name with "_lf__". * - * If, in addition, the reactor contains other reactors and reacts to their outputs, - * then there will be a struct within the self struct for each such contained reactor. - * The name of that self struct will be the name of the contained reactor prepended with "_lf_". - * That inside struct will contain pointers the outputs of the contained reactors - * that are read together with pointers to booleans indicating whether those outputs are present. + *

If, in addition, the reactor contains other reactors and reacts to their outputs, then there + * will be a struct within the self struct for each such contained reactor. The name of that self + * struct will be the name of the contained reactor prepended with "_lf_". That inside struct will + * contain pointers the outputs of the contained reactors that are read together with pointers to + * booleans indicating whether those outputs are present. * - * If, in addition, the reactor has a reaction to shutdown, then there will be a pointer to - * trigger_t object (see reactor.h) for the shutdown event and an action struct named - * _lf_shutdown on the self struct. + *

If, in addition, the reactor has a reaction to shutdown, then there will be a pointer to + * trigger_t object (see reactor.h) for the shutdown event and an action struct named _lf_shutdown + * on the self struct. * - * ## Reaction Functions + *

## Reaction Functions * - * For each reaction in a reactor class, this generator will produce a C function - * that expects a pointer to an instance of the "self" struct as an argument. - * This function will contain verbatim the C code specified in the reaction, but - * before that C code, the generator inserts a few lines of code that extract from the - * self struct the variables that that code has declared it will use. For example, if - * the reaction declares that it is triggered by or uses an input named "x" of type - * int, the function will contain a line like this: - * ``` - * r_x_t* x = self->_lf_x; - * ``` - * where `r` is the full name of the reactor class and the struct type `r_x_t` - * has fields `is_present` and `value`, where the type of `value` matches the port type. - * If the programmer fails to declare that it uses x, then the absence of the - * above code will trigger a compile error when the verbatim code attempts to read `x`. + *

For each reaction in a reactor class, this generator will produce a C function that expects a + * pointer to an instance of the "self" struct as an argument. This function will contain verbatim + * the C code specified in the reaction, but before that C code, the generator inserts a few lines + * of code that extract from the self struct the variables that that code has declared it will use. + * For example, if the reaction declares that it is triggered by or uses an input named "x" of type + * int, the function will contain a line like this: ``` r_x_t* x = self->_lf_x; ``` where `r` is the + * full name of the reactor class and the struct type `r_x_t` has fields `is_present` and `value`, + * where the type of `value` matches the port type. If the programmer fails to declare that it uses + * x, then the absence of the above code will trigger a compile error when the verbatim code + * attempts to read `x`. * - * ## Constructor + *

## Constructor * - * For each reactor class, this generator will create a constructor function named - * `new_r`, where `r` is the reactor class name. This function will malloc and return - * a pointer to an instance of the "self" struct. This struct initially represents - * an unconnected reactor. To establish connections between reactors, additional - * information needs to be inserted (see below). The self struct is made visible - * to the body of a reaction as a variable named "self". The self struct contains the - * following: + *

For each reactor class, this generator will create a constructor function named `new_r`, where + * `r` is the reactor class name. This function will malloc and return a pointer to an instance of + * the "self" struct. This struct initially represents an unconnected reactor. To establish + * connections between reactors, additional information needs to be inserted (see below). The self + * struct is made visible to the body of a reaction as a variable named "self". The self struct + * contains the following: * - * * Parameters: For each parameter `p` of the reactor, there will be a field `p` - * with the type and value of the parameter. So C code in the body of a reaction - * can access parameter values as `self->p`. + *

* Parameters: For each parameter `p` of the reactor, there will be a field `p` with the type + * and value of the parameter. So C code in the body of a reaction can access parameter values as + * `self->p`. * - * * State variables: For each state variable `s` of the reactor, there will be a field `s` - * with the type and value of the state variable. So C code in the body of a reaction - * can access state variables as `self->s`. + *

* State variables: For each state variable `s` of the reactor, there will be a field `s` with + * the type and value of the state variable. So C code in the body of a reaction can access state + * variables as `self->s`. * - * The self struct also contains various fields that the user is not intended to - * use. The names of these fields begin with at least two underscores. They are: + *

The self struct also contains various fields that the user is not intended to use. The names + * of these fields begin with at least two underscores. They are: * - * * Outputs: For each output named `out`, there will be a field `_lf_out` that is - * a struct containing a value field whose type matches that of the output. - * The output value is stored here. That struct also has a field `is_present` - * that is a boolean indicating whether the output has been set. - * This field is reset to false at the start of every time - * step. There is also a field `num_destinations` whose value matches the - * number of downstream reactors that use this variable. This field must be - * set when connections are made or changed. It is used to determine for - * a mutable input destination whether a copy needs to be made. + *

* Outputs: For each output named `out`, there will be a field `_lf_out` that is a struct + * containing a value field whose type matches that of the output. The output value is stored here. + * That struct also has a field `is_present` that is a boolean indicating whether the output has + * been set. This field is reset to false at the start of every time step. There is also a field + * `num_destinations` whose value matches the number of downstream reactors that use this variable. + * This field must be set when connections are made or changed. It is used to determine for a + * mutable input destination whether a copy needs to be made. * - * * Inputs: For each input named `in` of type T, there is a field named `_lf_in` - * that is a pointer struct with a value field of type T. The struct pointed - * to also has an `is_present` field of type bool that indicates whether the - * input is present. + *

* Inputs: For each input named `in` of type T, there is a field named `_lf_in` that is a + * pointer struct with a value field of type T. The struct pointed to also has an `is_present` field + * of type bool that indicates whether the input is present. * - * * Outputs of contained reactors: If a reactor reacts to outputs of a - * contained reactor `r`, then the self struct will contain a nested struct - * named `_lf_r` that has fields pointing to those outputs. For example, - * if `r` has an output `out` of type T, then there will be field in `_lf_r` - * named `out` that points to a struct containing a value field - * of type T and a field named `is_present` of type bool. + *

* Outputs of contained reactors: If a reactor reacts to outputs of a contained reactor `r`, + * then the self struct will contain a nested struct named `_lf_r` that has fields pointing to those + * outputs. For example, if `r` has an output `out` of type T, then there will be field in `_lf_r` + * named `out` that points to a struct containing a value field of type T and a field named + * `is_present` of type bool. * - * * Inputs of contained reactors: If a reactor sends to inputs of a - * contained reactor `r`, then the self struct will contain a nested struct - * named `_lf_r` that has fields for storing the values provided to those - * inputs. For example, if R has an input `in` of type T, then there will - * be field in _lf_R named `in` that is a struct with a value field - * of type T and a field named `is_present` of type bool. + *

* Inputs of contained reactors: If a reactor sends to inputs of a contained reactor `r`, then + * the self struct will contain a nested struct named `_lf_r` that has fields for storing the values + * provided to those inputs. For example, if R has an input `in` of type T, then there will be field + * in _lf_R named `in` that is a struct with a value field of type T and a field named `is_present` + * of type bool. * - * * Actions: If the reactor has an action a (logical or physical), then there - * will be a field in the self struct named `_lf_a` and another named `_lf__a`. - * The type of the first is specific to the action and contains a `value` - * field with the type and value of the action (if it has a value). That - * struct also has a `has_value` field, an `is_present` field, and a - * `token` field (which is NULL if the action carries no value). - * The `_lf__a` field is of type trigger_t. - * That struct contains various things, including an array of reactions - * sensitive to this trigger and a lf_token_t struct containing the value of - * the action, if it has a value. See reactor.h in the C library for - * details. + *

* Actions: If the reactor has an action a (logical or physical), then there will be a field in + * the self struct named `_lf_a` and another named `_lf__a`. The type of the first is specific to + * the action and contains a `value` field with the type and value of the action (if it has a + * value). That struct also has a `has_value` field, an `is_present` field, and a `token` field + * (which is NULL if the action carries no value). The `_lf__a` field is of type trigger_t. That + * struct contains various things, including an array of reactions sensitive to this trigger and a + * lf_token_t struct containing the value of the action, if it has a value. See reactor.h in the C + * library for details. * - * * Reactions: Each reaction will have several fields in the self struct. - * Each of these has a name that begins with `_lf__reaction_i`, where i is - * the number of the reaction, starting with 0. The fields are: - * * _lf__reaction_i: The struct that is put onto the reaction queue to - * execute the reaction (see reactor.h in the C library). + *

* Reactions: Each reaction will have several fields in the self struct. Each of these has a + * name that begins with `_lf__reaction_i`, where i is the number of the reaction, starting with 0. + * The fields are: * _lf__reaction_i: The struct that is put onto the reaction queue to execute the + * reaction (see reactor.h in the C library). * - * * Timers: For each timer t, there is are two fields in the self struct: - * * _lf__t: The trigger_t struct for this timer (see reactor.h). - * * _lf__t_reactions: An array of reactions (pointers to the - * reaction_t structs on this self struct) sensitive to this timer. + *

* Timers: For each timer t, there is are two fields in the self struct: * _lf__t: The + * trigger_t struct for this timer (see reactor.h). * _lf__t_reactions: An array of reactions + * (pointers to the reaction_t structs on this self struct) sensitive to this timer. * - * * Triggers: For each Timer, Action, Input, and Output of a contained - * reactor that triggers reactions, there will be a trigger_t struct - * on the self struct with name `_lf__t`, where t is the name of the trigger. + *

* Triggers: For each Timer, Action, Input, and Output of a contained reactor that triggers + * reactions, there will be a trigger_t struct on the self struct with name `_lf__t`, where t is the + * name of the trigger. * - * ## Connections Between Reactors + *

## Connections Between Reactors * - * Establishing connections between reactors involves two steps. - * First, each destination (e.g. an input port) must have pointers to - * the source (the output port). As explained above, for an input named - * `in`, the field `_lf_in->value` is a pointer to the output data being read. - * In addition, `_lf_in->is_present` is a pointer to the corresponding - * `out->is_present` field of the output reactor's self struct. + *

Establishing connections between reactors involves two steps. First, each destination (e.g. an + * input port) must have pointers to the source (the output port). As explained above, for an input + * named `in`, the field `_lf_in->value` is a pointer to the output data being read. In addition, + * `_lf_in->is_present` is a pointer to the corresponding `out->is_present` field of the output + * reactor's self struct. * - * In addition, the `reaction_i` struct on the self struct has a `triggers` - * field that records all the trigger_t structs for ports and actions - * that are triggered by the i-th reaction. The triggers field is - * an array of arrays of pointers to trigger_t structs. - * The length of the outer array is the number of output channels - * (single ports plus multiport widths) that the reaction effects - * plus the number of input port channels of contained - * reactors that it effects. Each inner array has a length equal to the - * number of final destinations of that output channel or input channel. - * The reaction_i struct has an array triggered_sizes that indicates - * the sizes of these inner arrays. The num_outputs field of the - * reaction_i struct gives the length of the triggered_sizes and - * (outer) triggers arrays. The num_outputs field is equal to the - * total number of single ports and multiport channels that the reaction - * writes to. + *

In addition, the `reaction_i` struct on the self struct has a `triggers` field that records + * all the trigger_t structs for ports and actions that are triggered by the i-th reaction. The + * triggers field is an array of arrays of pointers to trigger_t structs. The length of the outer + * array is the number of output channels (single ports plus multiport widths) that the reaction + * effects plus the number of input port channels of contained reactors that it effects. Each inner + * array has a length equal to the number of final destinations of that output channel or input + * channel. The reaction_i struct has an array triggered_sizes that indicates the sizes of these + * inner arrays. The num_outputs field of the reaction_i struct gives the length of the + * triggered_sizes and (outer) triggers arrays. The num_outputs field is equal to the total number + * of single ports and multiport channels that the reaction writes to. * - * ## Runtime Tables + *

## Runtime Tables * - * This generator creates an populates the following tables used at run time. - * These tables may have to be resized and adjusted when mutations occur. + *

This generator creates an populates the following tables used at run time. These tables may + * have to be resized and adjusted when mutations occur. * - * * _lf_is_present_fields: An array of pointers to booleans indicating whether an - * event is present. The _lf_start_time_step() function in reactor_common.c uses - * this to mark every event absent at the start of a time step. The size of this - * table is contained in the variable _lf_is_present_fields_size. - * * This table is accompanied by another list, _lf_is_present_fields_abbreviated, - * which only contains the is_present fields that have been set to true in the - * current tag. This list can allow a performance improvement if most ports are - * seldom present because only fields that have been set to true need to be - * reset to false. + *

* _lf_is_present_fields: An array of pointers to booleans indicating whether an event is + * present. The _lf_start_time_step() function in reactor_common.c uses this to mark every event + * absent at the start of a time step. The size of this table is contained in the variable + * _lf_is_present_fields_size. * This table is accompanied by another list, + * _lf_is_present_fields_abbreviated, which only contains the is_present fields that have been set + * to true in the current tag. This list can allow a performance improvement if most ports are + * seldom present because only fields that have been set to true need to be reset to false. * - * * _lf_shutdown_triggers: An array of pointers to trigger_t structs for shutdown - * reactions. The length of this table is in the _lf_shutdown_triggers_size - * variable. + *

* _lf_shutdown_triggers: An array of pointers to trigger_t structs for shutdown reactions. The + * length of this table is in the _lf_shutdown_triggers_size variable. * - * * _lf_timer_triggers: An array of pointers to trigger_t structs for timers that - * need to be started when the program runs. The length of this table is in the - * _lf_timer_triggers_size variable. + *

* _lf_timer_triggers: An array of pointers to trigger_t structs for timers that need to be + * started when the program runs. The length of this table is in the _lf_timer_triggers_size + * variable. * - * * _lf_action_table: For a federated execution, each federate will have this table - * that maps port IDs to the corresponding action struct, which can be cast to - * action_base_t. + *

* _lf_action_table: For a federated execution, each federate will have this table that maps + * port IDs to the corresponding action struct, which can be cast to action_base_t. * * @author Edward A. Lee * @author Marten Lohstroh @@ -307,1129 +267,1135 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY @SuppressWarnings("StaticPseudoFunctionalStyleMethod") public class CGenerator extends GeneratorBase { - // Regular expression pattern for compiler error messages with resource - // and line number information. The first match will a resource URI in the - // form of "file:/path/file.lf". The second match will be a line number. - // The third match is a character position within the line. - // The fourth match will be the error message. - static final Pattern compileErrorPattern = Pattern.compile( - "^(?.*):(?\\d+):(?\\d+):(?.*)$" - ); - - public static int UNDEFINED_MIN_SPACING = -1; - - //////////////////////////////////////////// - //// Protected fields - - /** The main place to put generated code. */ - protected CodeBuilder code = new CodeBuilder(); - - /** Place to collect code to initialize the trigger objects for all reactor instances. */ - protected CodeBuilder initializeTriggerObjects = new CodeBuilder(); - - protected final CFileConfig fileConfig; - - /** - * Count of the number of is_present fields of the self struct that - * need to be reinitialized in _lf_start_time_step(). - */ - protected int startTimeStepIsPresentCount = 0; - - //////////////////////////////////////////// - //// Private fields - /** - * Extra lines that need to go into the generated CMakeLists.txt. - */ - private String cMakeExtras = ""; - - /** Place to collect code to execute at the start of a time step. */ - private CodeBuilder startTimeStep = new CodeBuilder(); - - /** Count of the number of token pointers that need to have their - * reference count decremented in _lf_start_time_step(). - */ - private int timerCount = 0; - private int startupReactionCount = 0; - private int shutdownReactionCount = 0; - private int resetReactionCount = 0; - private int modalReactorCount = 0; - private int modalStateResetCount = 0; - private int watchdogCount = 0; - - // Indicate whether the generator is in Cpp mode or not - private final boolean CCppMode; - - private final CTypes types; - - private final CCmakeGenerator cmakeGenerator; - - protected CGenerator( - LFGeneratorContext context, - boolean CCppMode, - CTypes types, - CCmakeGenerator cmakeGenerator, - DelayBodyGenerator delayBodyGenerator - ) { - super(context); - this.fileConfig = (CFileConfig) context.getFileConfig(); - this.CCppMode = CCppMode; - this.types = types; - this.cmakeGenerator = cmakeGenerator; - - // Register the delayed connection transformation to be applied by GeneratorBase. - // transform both after delays and physical connections - registerTransformation(new DelayedConnectionTransformation(delayBodyGenerator, types, fileConfig.resource, true, true)); + // Regular expression pattern for compiler error messages with resource + // and line number information. The first match will a resource URI in the + // form of "file:/path/file.lf". The second match will be a line number. + // The third match is a character position within the line. + // The fourth match will be the error message. + static final Pattern compileErrorPattern = + Pattern.compile("^(?.*):(?\\d+):(?\\d+):(?.*)$"); + + public static int UNDEFINED_MIN_SPACING = -1; + + //////////////////////////////////////////// + //// Protected fields + + /** The main place to put generated code. */ + protected CodeBuilder code = new CodeBuilder(); + + /** Place to collect code to initialize the trigger objects for all reactor instances. */ + protected CodeBuilder initializeTriggerObjects = new CodeBuilder(); + + protected final CFileConfig fileConfig; + + /** + * Count of the number of is_present fields of the self struct that need to be reinitialized in + * _lf_start_time_step(). + */ + protected int startTimeStepIsPresentCount = 0; + + //////////////////////////////////////////// + //// Private fields + /** Extra lines that need to go into the generated CMakeLists.txt. */ + private String cMakeExtras = ""; + + /** Place to collect code to execute at the start of a time step. */ + private CodeBuilder startTimeStep = new CodeBuilder(); + + /** + * Count of the number of token pointers that need to have their reference count decremented in + * _lf_start_time_step(). + */ + private int timerCount = 0; + + private int startupReactionCount = 0; + private int shutdownReactionCount = 0; + private int resetReactionCount = 0; + private int modalReactorCount = 0; + private int modalStateResetCount = 0; + private int watchdogCount = 0; + + // Indicate whether the generator is in Cpp mode or not + private final boolean CCppMode; + + private final CTypes types; + + private final CCmakeGenerator cmakeGenerator; + + protected CGenerator( + LFGeneratorContext context, + boolean CCppMode, + CTypes types, + CCmakeGenerator cmakeGenerator, + DelayBodyGenerator delayBodyGenerator) { + super(context); + this.fileConfig = (CFileConfig) context.getFileConfig(); + this.CCppMode = CCppMode; + this.types = types; + this.cmakeGenerator = cmakeGenerator; + + // Register the delayed connection transformation to be applied by GeneratorBase. + // transform both after delays and physical connections + registerTransformation( + new DelayedConnectionTransformation( + delayBodyGenerator, types, fileConfig.resource, true, true)); + } + + public CGenerator(LFGeneratorContext context, boolean ccppMode) { + this( + context, + ccppMode, + new CTypes(), + new CCmakeGenerator(context.getFileConfig(), List.of()), + new CDelayBodyGenerator(new CTypes())); + } + + /** + * Look for physical actions in all resources. If found, set threads to be at least one to allow + * asynchronous schedule calls. + */ + public void accommodatePhysicalActionsIfPresent() { + // If there are any physical actions, ensure the threaded engine is used and that + // keepalive is set to true, unless the user has explicitly set it to false. + for (Resource resource : GeneratorUtils.getResources(reactors)) { + for (Action action : ASTUtils.allElementsOfClass(resource, Action.class)) { + if (ActionOrigin.PHYSICAL.equals(action.getOrigin())) { + // If the unthreaded runtime is not requested by the user, use the threaded runtime + // instead + // because it is the only one currently capable of handling asynchronous events. + if (!targetConfig.threading + && !targetConfig.setByUser.contains(TargetProperty.THREADING)) { + targetConfig.threading = true; + errorReporter.reportWarning( + action, + "Using the threaded C runtime to allow for asynchronous handling of physical action" + + " " + + action.getName()); + return; + } + } + } } - - public CGenerator(LFGeneratorContext context, boolean ccppMode) { - this( - context, - ccppMode, - new CTypes(), - new CCmakeGenerator(context.getFileConfig(), List.of()), - new CDelayBodyGenerator(new CTypes()) - ); + } + + /** + * Return true if the host operating system is compatible and otherwise report an error and return + * false. + */ + protected boolean isOSCompatible() { + if (GeneratorUtils.isHostWindows()) { + if (CCppMode) { + errorReporter.reportError( + "LF programs with a CCpp target are currently not supported on Windows. " + + "Exiting code generation."); + return false; + } } + return true; + } + + /** + * Generate C code from the Lingua Franca model contained by the specified resource. This is the + * main entry point for code generation. + * + * @param resource The resource containing the source code. + * @param context The context in which the generator is invoked, including whether it is cancelled + * and whether it is a standalone context + */ + @Override + public void doGenerate(Resource resource, LFGeneratorContext context) { + super.doGenerate(resource, context); + if (!GeneratorUtils.canGenerate(errorsOccurred(), mainDef, errorReporter, context)) return; + if (!isOSCompatible()) return; // Incompatible OS and configuration - /** - * Look for physical actions in all resources. - * If found, set threads to be at least one to allow asynchronous schedule calls. - */ - public void accommodatePhysicalActionsIfPresent() { - // If there are any physical actions, ensure the threaded engine is used and that - // keepalive is set to true, unless the user has explicitly set it to false. - for (Resource resource : GeneratorUtils.getResources(reactors)) { - for (Action action : ASTUtils.allElementsOfClass(resource, Action.class)) { - if (ActionOrigin.PHYSICAL.equals(action.getOrigin())) { - // If the unthreaded runtime is not requested by the user, use the threaded runtime instead - // because it is the only one currently capable of handling asynchronous events. - if (!targetConfig.threading && !targetConfig.setByUser.contains(TargetProperty.THREADING)) { - targetConfig.threading = true; - errorReporter.reportWarning( - action, - "Using the threaded C runtime to allow for asynchronous handling of physical action " + - action.getName() - ); - return; - } - } - } - } + // Perform set up that does not generate code + setUpGeneralParameters(); + + FileUtil.createDirectoryIfDoesNotExist(fileConfig.getSrcGenPath().toFile()); + FileUtil.createDirectoryIfDoesNotExist(fileConfig.binPath.toFile()); + FileUtil.createDirectoryIfDoesNotExist(fileConfig.getIncludePath().toFile()); + handleProtoFiles(); + + // Derive target filename from the .lf filename. + var lfModuleName = fileConfig.name; + var cFilename = CCompiler.getTargetFileName(lfModuleName, this.CCppMode, targetConfig); + var targetFile = fileConfig.getSrcGenPath() + File.separator + cFilename; + try { + generateCodeFor(lfModuleName); + copyTargetFiles(); + generateHeaders(); + code.writeToFile(targetFile); + } catch (IOException e) { + //noinspection ThrowableNotThrown,ResultOfMethodCallIgnored + Exceptions.sneakyThrow(e); } - /** - * Return true if the host operating system is compatible and - * otherwise report an error and return false. - */ - protected boolean isOSCompatible() { - if (GeneratorUtils.isHostWindows()) { - if (CCppMode) { - errorReporter.reportError( - "LF programs with a CCpp target are currently not supported on Windows. " + - "Exiting code generation." - ); - return false; - } - } - return true; + // Create docker file. + if (targetConfig.dockerOptions != null && mainDef != null) { + try { + var dockerData = getDockerGenerator(context).generateDockerData(); + dockerData.writeDockerFile(); + (new DockerComposeGenerator(context)).writeDockerComposeFile(List.of(dockerData)); + } catch (IOException e) { + throw new RuntimeException("Error while writing Docker files", e); + } } - /** - * Generate C code from the Lingua Franca model contained by the - * specified resource. This is the main entry point for code - * generation. - * @param resource The resource containing the source code. - * @param context The context in which the generator is - * invoked, including whether it is cancelled and - * whether it is a standalone context - */ - @Override - public void doGenerate(Resource resource, LFGeneratorContext context) { - super.doGenerate(resource, context); - if (!GeneratorUtils.canGenerate(errorsOccurred(), mainDef, errorReporter, context)) return; - if (!isOSCompatible()) return; // Incompatible OS and configuration - - // Perform set up that does not generate code - setUpGeneralParameters(); - - FileUtil.createDirectoryIfDoesNotExist(fileConfig.getSrcGenPath().toFile()); - FileUtil.createDirectoryIfDoesNotExist(fileConfig.binPath.toFile()); - FileUtil.createDirectoryIfDoesNotExist(fileConfig.getIncludePath().toFile()); - handleProtoFiles(); - - // Derive target filename from the .lf filename. - var lfModuleName = fileConfig.name; - var cFilename = CCompiler.getTargetFileName(lfModuleName, this.CCppMode, targetConfig); - var targetFile = fileConfig.getSrcGenPath() + File.separator + cFilename; - try { - generateCodeFor(lfModuleName); - copyTargetFiles(); - generateHeaders(); - code.writeToFile(targetFile); - } catch (IOException e) { - //noinspection ThrowableNotThrown,ResultOfMethodCallIgnored - Exceptions.sneakyThrow(e); - } - - // Create docker file. - if (targetConfig.dockerOptions != null && mainDef != null) { - try { - var dockerData = getDockerGenerator(context).generateDockerData(); - dockerData.writeDockerFile(); - (new DockerComposeGenerator(context)).writeDockerComposeFile(List.of(dockerData)); - } catch (IOException e) { - throw new RuntimeException("Error while writing Docker files", e); - } - } - - // If cmake is requested, generate the CMakeLists.txt - if (targetConfig.platformOptions.platform != Platform.ARDUINO) { - var cmakeFile = fileConfig.getSrcGenPath() + File.separator + "CMakeLists.txt"; - var sources = new HashSet<>(ASTUtils.recursiveChildren(main)).stream() - .map(CUtil::getName).map(it -> it + (CCppMode ? ".cpp" : ".c")) - .collect(Collectors.toList()); - sources.add(cFilename); - var cmakeCode = cmakeGenerator.generateCMakeCode( - sources, - lfModuleName, - errorReporter, - CCppMode, - mainDef != null, - cMakeExtras, - targetConfig - ); - try { - cmakeCode.writeToFile(cmakeFile); - } catch (IOException e) { - //noinspection ThrowableNotThrown,ResultOfMethodCallIgnored - Exceptions.sneakyThrow(e); - } - } else { - try { - Path include = fileConfig.getSrcGenPath().resolve("include/"); - Path src = fileConfig.getSrcGenPath().resolve("src/"); - FileUtil.arduinoDeleteHelper(src, targetConfig.threading); - FileUtil.relativeIncludeHelper(src, include); - FileUtil.relativeIncludeHelper(include, include); - } catch (IOException e) { - //noinspection ThrowableNotThrown,ResultOfMethodCallIgnored - Exceptions.sneakyThrow(e); - } - - if (!targetConfig.noCompile) { - ArduinoUtil arduinoUtil = new ArduinoUtil(context, commandFactory, errorReporter); - arduinoUtil.buildArduino(fileConfig, targetConfig); - context.finish( - GeneratorResult.Status.COMPILED, null - ); - } else { - System.out.println("********"); - System.out.println("To compile your program, run the following command to see information about the board you plugged in:\n\n\tarduino-cli board list\n\nGrab the FQBN and PORT from the command and run the following command in the generated sources directory:\n\n\tarduino-cli compile -b --build-property compiler.c.extra_flags='-DLF_UNTHREADED -DPLATFORM_ARDUINO -DINITIAL_EVENT_QUEUE_SIZE=10 -DINITIAL_REACT_QUEUE_SIZE=10' --build-property compiler.cpp.extra_flags='-DLF_UNTHREADED -DPLATFORM_ARDUINO -DINITIAL_EVENT_QUEUE_SIZE=10 -DINITIAL_REACT_QUEUE_SIZE=10' .\n\nTo flash/upload your generated sketch to the board, run the following command in the generated sources directory:\n\n\tarduino-cli upload -b -p \n"); - // System.out.println("For a list of all boards installed on your computer, you can use the following command:\n\n\tarduino-cli board listall\n"); - context.finish( - GeneratorResult.GENERATED_NO_EXECUTABLE.apply(context, null) - ); - } - GeneratorUtils.refreshProject(resource, context.getMode()); - return; - } + // If cmake is requested, generate the CMakeLists.txt + if (targetConfig.platformOptions.platform != Platform.ARDUINO) { + var cmakeFile = fileConfig.getSrcGenPath() + File.separator + "CMakeLists.txt"; + var sources = + new HashSet<>(ASTUtils.recursiveChildren(main)) + .stream() + .map(CUtil::getName) + .map(it -> it + (CCppMode ? ".cpp" : ".c")) + .collect(Collectors.toList()); + sources.add(cFilename); + var cmakeCode = + cmakeGenerator.generateCMakeCode( + sources, + lfModuleName, + errorReporter, + CCppMode, + mainDef != null, + cMakeExtras, + targetConfig); + try { + cmakeCode.writeToFile(cmakeFile); + } catch (IOException e) { + //noinspection ThrowableNotThrown,ResultOfMethodCallIgnored + Exceptions.sneakyThrow(e); + } + } else { + try { + Path include = fileConfig.getSrcGenPath().resolve("include/"); + Path src = fileConfig.getSrcGenPath().resolve("src/"); + FileUtil.arduinoDeleteHelper(src, targetConfig.threading); + FileUtil.relativeIncludeHelper(src, include); + FileUtil.relativeIncludeHelper(include, include); + } catch (IOException e) { + //noinspection ThrowableNotThrown,ResultOfMethodCallIgnored + Exceptions.sneakyThrow(e); + } - // Dump the additional compile definitions to a file to keep the generated project - // self-contained. In this way, third-party build tools like PlatformIO, west, arduino-cli can - // take over and do the rest of compilation. - try { - String compileDefs = targetConfig.compileDefinitions.keySet().stream() - .map(key -> key + "=" + targetConfig.compileDefinitions.get(key)) - .collect(Collectors.joining("\n")) + "\n"; - FileUtil.writeToFile( - compileDefs, - Path.of(fileConfig.getSrcGenPath() + File.separator + "CompileDefinitions.txt") - ); - } catch (IOException e) { - Exceptions.sneakyThrow(e); - } + if (!targetConfig.noCompile) { + ArduinoUtil arduinoUtil = new ArduinoUtil(context, commandFactory, errorReporter); + arduinoUtil.buildArduino(fileConfig, targetConfig); + context.finish(GeneratorResult.Status.COMPILED, null); + } else { + System.out.println("********"); + System.out.println( + "To compile your program, run the following command to see information about the board" + + " you plugged in:\n\n" + + "\tarduino-cli board list\n\n" + + "Grab the FQBN and PORT from the command and run the following command in the" + + " generated sources directory:\n\n" + + "\tarduino-cli compile -b --build-property" + + " compiler.c.extra_flags='-DLF_UNTHREADED -DPLATFORM_ARDUINO" + + " -DINITIAL_EVENT_QUEUE_SIZE=10 -DINITIAL_REACT_QUEUE_SIZE=10' --build-property" + + " compiler.cpp.extra_flags='-DLF_UNTHREADED -DPLATFORM_ARDUINO" + + " -DINITIAL_EVENT_QUEUE_SIZE=10 -DINITIAL_REACT_QUEUE_SIZE=10' .\n\n" + + "To flash/upload your generated sketch to the board, run the following command in" + + " the generated sources directory:\n\n" + + "\tarduino-cli upload -b -p \n"); + // System.out.println("For a list of all boards installed on your computer, you can use the + // following command:\n\n\tarduino-cli board listall\n"); + context.finish(GeneratorResult.GENERATED_NO_EXECUTABLE.apply(context, null)); + } + GeneratorUtils.refreshProject(resource, context.getMode()); + return; + } - // Create a .vscode/settings.json file in the target directory so that VSCode can - // immediately compile the generated code. - try { - String compileDefs = targetConfig.compileDefinitions.keySet().stream() - .map(key -> "\"-D" + key + "=" + targetConfig.compileDefinitions.get(key) + "\"") - .collect(Collectors.joining(",\n")); - String settings = "{\n" - + "\"cmake.configureArgs\": [\n" - + compileDefs - + "\n]\n}\n"; - Path vscodePath = Path.of(fileConfig.getSrcGenPath() + File.separator + ".vscode"); - if (!Files.exists(vscodePath)) - Files.createDirectory(vscodePath); - FileUtil.writeToFile( - settings, - Path.of(fileConfig.getSrcGenPath() - + File.separator + ".vscode" - + File.separator + "settings.json") - ); - } catch (IOException e) { - Exceptions.sneakyThrow(e); - } + // Dump the additional compile definitions to a file to keep the generated project + // self-contained. In this way, third-party build tools like PlatformIO, west, arduino-cli can + // take over and do the rest of compilation. + try { + String compileDefs = + targetConfig.compileDefinitions.keySet().stream() + .map(key -> key + "=" + targetConfig.compileDefinitions.get(key)) + .collect(Collectors.joining("\n")) + + "\n"; + FileUtil.writeToFile( + compileDefs, + Path.of(fileConfig.getSrcGenPath() + File.separator + "CompileDefinitions.txt")); + } catch (IOException e) { + Exceptions.sneakyThrow(e); + } - // If this code generator is directly compiling the code, compile it now so that we - // clean it up after, removing the #line directives after errors have been reported. - if ( - !targetConfig.noCompile && targetConfig.dockerOptions == null - && IterableExtensions.isNullOrEmpty(targetConfig.buildCommands) - // This code is unreachable in LSP_FAST mode, so that check is omitted. - && context.getMode() != LFGeneratorContext.Mode.LSP_MEDIUM - ) { - // FIXME: Currently, a lack of main is treated as a request to not produce - // a binary and produce a .o file instead. There should be a way to control - // this. - // Create an anonymous Runnable class and add it to the compileThreadPool - // so that compilation can happen in parallel. - var cleanCode = code.removeLines("#line"); - - var execName = lfModuleName; - var threadFileConfig = fileConfig; - var generator = this; // FIXME: currently only passed to report errors with line numbers in the Eclipse IDE - var CppMode = CCppMode; - // generatingContext.reportProgress( - // String.format("Generated code for %d/%d executables. Compiling...", federateCount, federates.size()), - // 100 * federateCount / federates.size() - // ); // FIXME: Move to FedGenerator - // Create the compiler to be used later - - var cCompiler = new CCompiler(targetConfig, threadFileConfig, errorReporter, CppMode); - try { - if (!cCompiler.runCCompiler(generator, context)) { - // If compilation failed, remove any bin files that may have been created. - CUtil.deleteBinFiles(threadFileConfig); - // If finish has already been called, it is illegal and makes no sense. However, - // if finish has already been called, then this must be a federated execution. - context.unsuccessfulFinish(); - } else { - context.finish( - GeneratorResult.Status.COMPILED, null - ); - } - cleanCode.writeToFile(targetFile); - } catch (IOException e) { - Exceptions.sneakyThrow(e); - } - } + // Create a .vscode/settings.json file in the target directory so that VSCode can + // immediately compile the generated code. + try { + String compileDefs = + targetConfig.compileDefinitions.keySet().stream() + .map(key -> "\"-D" + key + "=" + targetConfig.compileDefinitions.get(key) + "\"") + .collect(Collectors.joining(",\n")); + String settings = "{\n" + "\"cmake.configureArgs\": [\n" + compileDefs + "\n]\n}\n"; + Path vscodePath = Path.of(fileConfig.getSrcGenPath() + File.separator + ".vscode"); + if (!Files.exists(vscodePath)) Files.createDirectory(vscodePath); + FileUtil.writeToFile( + settings, + Path.of( + fileConfig.getSrcGenPath() + + File.separator + + ".vscode" + + File.separator + + "settings.json")); + } catch (IOException e) { + Exceptions.sneakyThrow(e); + } - // If a build directive has been given, invoke it now. - // Note that the code does not get cleaned in this case. - if (!targetConfig.noCompile) { - if (!IterableExtensions.isNullOrEmpty(targetConfig.buildCommands)) { - CUtil.runBuildCommand( - fileConfig, - targetConfig, - commandFactory, - errorReporter, - this::reportCommandErrors, - context.getMode() - ); - context.finish( - GeneratorResult.Status.COMPILED, null - ); - } - if (!errorsOccurred()){ - System.out.println("Compiled binary is in " + fileConfig.binPath); - } + // If this code generator is directly compiling the code, compile it now so that we + // clean it up after, removing the #line directives after errors have been reported. + if (!targetConfig.noCompile + && targetConfig.dockerOptions == null + && IterableExtensions.isNullOrEmpty(targetConfig.buildCommands) + // This code is unreachable in LSP_FAST mode, so that check is omitted. + && context.getMode() != LFGeneratorContext.Mode.LSP_MEDIUM) { + // FIXME: Currently, a lack of main is treated as a request to not produce + // a binary and produce a .o file instead. There should be a way to control + // this. + // Create an anonymous Runnable class and add it to the compileThreadPool + // so that compilation can happen in parallel. + var cleanCode = code.removeLines("#line"); + + var execName = lfModuleName; + var threadFileConfig = fileConfig; + var generator = + this; // FIXME: currently only passed to report errors with line numbers in the Eclipse + // IDE + var CppMode = CCppMode; + // generatingContext.reportProgress( + // String.format("Generated code for %d/%d executables. Compiling...", federateCount, + // federates.size()), + // 100 * federateCount / federates.size() + // ); // FIXME: Move to FedGenerator + // Create the compiler to be used later + + var cCompiler = new CCompiler(targetConfig, threadFileConfig, errorReporter, CppMode); + try { + if (!cCompiler.runCCompiler(generator, context)) { + // If compilation failed, remove any bin files that may have been created. + CUtil.deleteBinFiles(threadFileConfig); + // If finish has already been called, it is illegal and makes no sense. However, + // if finish has already been called, then this must be a federated execution. + context.unsuccessfulFinish(); } else { - context.finish(GeneratorResult.GENERATED_NO_EXECUTABLE.apply(context, null)); + context.finish(GeneratorResult.Status.COMPILED, null); } + cleanCode.writeToFile(targetFile); + } catch (IOException e) { + Exceptions.sneakyThrow(e); + } + } - // In case we are in Eclipse, make sure the generated code is visible. - GeneratorUtils.refreshProject(resource, context.getMode()); + // If a build directive has been given, invoke it now. + // Note that the code does not get cleaned in this case. + if (!targetConfig.noCompile) { + if (!IterableExtensions.isNullOrEmpty(targetConfig.buildCommands)) { + CUtil.runBuildCommand( + fileConfig, + targetConfig, + commandFactory, + errorReporter, + this::reportCommandErrors, + context.getMode()); + context.finish(GeneratorResult.Status.COMPILED, null); + } + if (!errorsOccurred()) { + System.out.println("Compiled binary is in " + fileConfig.binPath); + } + } else { + context.finish(GeneratorResult.GENERATED_NO_EXECUTABLE.apply(context, null)); } - private void generateCodeFor( - String lfModuleName - ) throws IOException { - startTimeStepIsPresentCount = 0; - code.pr(generateDirectives()); - code.pr(new CMainFunctionGenerator(targetConfig).generateCode()); - // Generate code for each reactor. - generateReactorDefinitions(); - - // Generate main instance, if there is one. - // Note that any main reactors in imported files are ignored. - // Skip generation if there are cycles. - if (main != null) { - initializeTriggerObjects.pr(String.join("\n", - "int _lf_startup_reactions_count = 0;", - "SUPPRESS_UNUSED_WARNING(_lf_startup_reactions_count);", - "int _lf_shutdown_reactions_count = 0;", - "SUPPRESS_UNUSED_WARNING(_lf_shutdown_reactions_count);", - "int _lf_reset_reactions_count = 0;", - "SUPPRESS_UNUSED_WARNING(_lf_reset_reactions_count);", - "int _lf_timer_triggers_count = 0;", - "SUPPRESS_UNUSED_WARNING(_lf_timer_triggers_count);", - "int bank_index;", - "SUPPRESS_UNUSED_WARNING(bank_index);", - "int watchdog_number = 0;", - "SUPPRESS_UNUSED_WARNING(watchdog_number);")); - // Add counters for modal initialization - initializeTriggerObjects.pr(CModesGenerator.generateModalInitalizationCounters(hasModalReactors)); - - // Create an array of arrays to store all self structs. - // This is needed because connections cannot be established until - // all reactor instances have self structs because ports that - // receive data reference the self structs of the originating - // reactors, which are arbitarily far away in the program graph. - generateSelfStructs(main); - generateReactorInstance(main); - - if (targetConfig.fedSetupPreamble != null) { - if (targetLanguageIsCpp()) code.pr("extern \"C\" {"); - code.pr("#include \"" + targetConfig.fedSetupPreamble + "\""); - if (targetLanguageIsCpp()) code.pr("}"); - } + // In case we are in Eclipse, make sure the generated code is visible. + GeneratorUtils.refreshProject(resource, context.getMode()); + } + + private void generateCodeFor(String lfModuleName) throws IOException { + startTimeStepIsPresentCount = 0; + code.pr(generateDirectives()); + code.pr(new CMainFunctionGenerator(targetConfig).generateCode()); + // Generate code for each reactor. + generateReactorDefinitions(); + + // Generate main instance, if there is one. + // Note that any main reactors in imported files are ignored. + // Skip generation if there are cycles. + if (main != null) { + initializeTriggerObjects.pr( + String.join( + "\n", + "int _lf_startup_reactions_count = 0;", + "SUPPRESS_UNUSED_WARNING(_lf_startup_reactions_count);", + "int _lf_shutdown_reactions_count = 0;", + "SUPPRESS_UNUSED_WARNING(_lf_shutdown_reactions_count);", + "int _lf_reset_reactions_count = 0;", + "SUPPRESS_UNUSED_WARNING(_lf_reset_reactions_count);", + "int _lf_timer_triggers_count = 0;", + "SUPPRESS_UNUSED_WARNING(_lf_timer_triggers_count);", + "int bank_index;", + "SUPPRESS_UNUSED_WARNING(bank_index);", + "int watchdog_number = 0;", + "SUPPRESS_UNUSED_WARNING(watchdog_number);")); + // Add counters for modal initialization + initializeTriggerObjects.pr( + CModesGenerator.generateModalInitalizationCounters(hasModalReactors)); + + // Create an array of arrays to store all self structs. + // This is needed because connections cannot be established until + // all reactor instances have self structs because ports that + // receive data reference the self structs of the originating + // reactors, which are arbitarily far away in the program graph. + generateSelfStructs(main); + generateReactorInstance(main); + + if (targetConfig.fedSetupPreamble != null) { + if (targetLanguageIsCpp()) code.pr("extern \"C\" {"); + code.pr("#include \"" + targetConfig.fedSetupPreamble + "\""); + if (targetLanguageIsCpp()) code.pr("}"); + } - // If there are timers, create a table of timers to be initialized. - code.pr(CTimerGenerator.generateDeclarations(timerCount)); - - // If there are startup reactions, create a table of triggers. - code.pr(CReactionGenerator.generateBuiltinTriggersTable(startupReactionCount, "startup")); - - // If there are shutdown reactions, create a table of triggers. - code.pr(CReactionGenerator.generateBuiltinTriggersTable(shutdownReactionCount, "shutdown")); - - // If there are reset reactions, create a table of triggers. - code.pr(CReactionGenerator.generateBuiltinTriggersTable(resetReactionCount, "reset")); - - // If there are watchdogs, create a table of triggers. - code.pr(CWatchdogGenerator.generateWatchdogTable(watchdogCount)); - - // If there are modes, create a table of mode state to be checked for transitions. - code.pr(CModesGenerator.generateModeStatesTable( - hasModalReactors, - modalReactorCount, - modalStateResetCount - )); - - // Generate function to initialize the trigger objects for all reactors. - code.pr(CTriggerObjectsGenerator.generateInitializeTriggerObjects( - main, - targetConfig, - initializeTriggerObjects, - startTimeStep, - types, - lfModuleName, - startTimeStepIsPresentCount - )); - - // Generate function to trigger startup reactions for all reactors. - code.pr(CReactionGenerator.generateLfTriggerStartupReactions(startupReactionCount, hasModalReactors)); - - // Generate function to schedule timers for all reactors. - code.pr(CTimerGenerator.generateLfInitializeTimer(timerCount)); - - // Generate a function that will either do nothing - // (if there is only one federate or the coordination - // is set to decentralized) or, if there are - // downstream federates, will notify the RTI - // that the specified logical time is complete. - if (CCppMode || targetConfig.platformOptions.platform == Platform.ARDUINO) code.pr("extern \"C\""); - code.pr(String.join("\n", - "void logical_tag_complete(tag_t tag_to_send) {", - CExtensionUtils.surroundWithIfFederatedCentralized( - " _lf_logical_tag_complete(tag_to_send);" - ), - "}" - )); - - // Generate function to schedule shutdown reactions if any - // reactors have reactions to shutdown. - code.pr(CReactionGenerator.generateLfTriggerShutdownReactions(shutdownReactionCount, hasModalReactors)); - - // Generate an empty termination function for non-federated - // execution. For federated execution, an implementation is - // provided in federate.c. That implementation will resign - // from the federation and close any open sockets. - code.pr(""" + // If there are timers, create a table of timers to be initialized. + code.pr(CTimerGenerator.generateDeclarations(timerCount)); + + // If there are startup reactions, create a table of triggers. + code.pr(CReactionGenerator.generateBuiltinTriggersTable(startupReactionCount, "startup")); + + // If there are shutdown reactions, create a table of triggers. + code.pr(CReactionGenerator.generateBuiltinTriggersTable(shutdownReactionCount, "shutdown")); + + // If there are reset reactions, create a table of triggers. + code.pr(CReactionGenerator.generateBuiltinTriggersTable(resetReactionCount, "reset")); + + // If there are watchdogs, create a table of triggers. + code.pr(CWatchdogGenerator.generateWatchdogTable(watchdogCount)); + + // If there are modes, create a table of mode state to be checked for transitions. + code.pr( + CModesGenerator.generateModeStatesTable( + hasModalReactors, modalReactorCount, modalStateResetCount)); + + // Generate function to initialize the trigger objects for all reactors. + code.pr( + CTriggerObjectsGenerator.generateInitializeTriggerObjects( + main, + targetConfig, + initializeTriggerObjects, + startTimeStep, + types, + lfModuleName, + startTimeStepIsPresentCount)); + + // Generate function to trigger startup reactions for all reactors. + code.pr( + CReactionGenerator.generateLfTriggerStartupReactions( + startupReactionCount, hasModalReactors)); + + // Generate function to schedule timers for all reactors. + code.pr(CTimerGenerator.generateLfInitializeTimer(timerCount)); + + // Generate a function that will either do nothing + // (if there is only one federate or the coordination + // is set to decentralized) or, if there are + // downstream federates, will notify the RTI + // that the specified logical time is complete. + if (CCppMode || targetConfig.platformOptions.platform == Platform.ARDUINO) + code.pr("extern \"C\""); + code.pr( + String.join( + "\n", + "void logical_tag_complete(tag_t tag_to_send) {", + CExtensionUtils.surroundWithIfFederatedCentralized( + " _lf_logical_tag_complete(tag_to_send);"), + "}")); + + // Generate function to schedule shutdown reactions if any + // reactors have reactions to shutdown. + code.pr( + CReactionGenerator.generateLfTriggerShutdownReactions( + shutdownReactionCount, hasModalReactors)); + + // Generate an empty termination function for non-federated + // execution. For federated execution, an implementation is + // provided in federate.c. That implementation will resign + // from the federation and close any open sockets. + code.pr( + """ #ifndef FEDERATED void terminate_execution() {} - #endif""" - ); - - - // Generate functions for modes - code.pr(CModesGenerator.generateLfInitializeModes( - hasModalReactors - )); - code.pr(CModesGenerator.generateLfHandleModeChanges( - hasModalReactors, - modalStateResetCount - )); - code.pr(CReactionGenerator.generateLfModeTriggeredReactions( - startupReactionCount, - resetReactionCount, - hasModalReactors - )); - } - } - - @Override - public void checkModalReactorSupport(boolean __) { - // Modal reactors are currently only supported for non federated applications - super.checkModalReactorSupport(true); + #endif"""); + + // Generate functions for modes + code.pr(CModesGenerator.generateLfInitializeModes(hasModalReactors)); + code.pr(CModesGenerator.generateLfHandleModeChanges(hasModalReactors, modalStateResetCount)); + code.pr( + CReactionGenerator.generateLfModeTriggeredReactions( + startupReactionCount, resetReactionCount, hasModalReactors)); } - - @Override - protected String getConflictingConnectionsInModalReactorsBody(String source, String dest) { - return String.join("\n", - "// Generated forwarding reaction for connections with the same destination", - "// but located in mutually exclusive modes.", - "lf_set("+dest+", "+source+"->value);" - ); - } - - /** Set the scheduler type in the target config as needed. */ - private void pickScheduler() { - // Don't use a scheduler that does not prioritize reactions based on deadlines - // if the program contains a deadline (handler). Use the GEDF_NP scheduler instead. - if (!targetConfig.schedulerType.prioritizesDeadline()) { - // Check if a deadline is assigned to any reaction - if (hasDeadlines(reactors)) { - if (!targetConfig.setByUser.contains(TargetProperty.SCHEDULER)) { - targetConfig.schedulerType = TargetProperty.SchedulerOption.GEDF_NP; - } - } + } + + @Override + public void checkModalReactorSupport(boolean __) { + // Modal reactors are currently only supported for non federated applications + super.checkModalReactorSupport(true); + } + + @Override + protected String getConflictingConnectionsInModalReactorsBody(String source, String dest) { + return String.join( + "\n", + "// Generated forwarding reaction for connections with the same destination", + "// but located in mutually exclusive modes.", + "lf_set(" + dest + ", " + source + "->value);"); + } + + /** Set the scheduler type in the target config as needed. */ + private void pickScheduler() { + // Don't use a scheduler that does not prioritize reactions based on deadlines + // if the program contains a deadline (handler). Use the GEDF_NP scheduler instead. + if (!targetConfig.schedulerType.prioritizesDeadline()) { + // Check if a deadline is assigned to any reaction + if (hasDeadlines(reactors)) { + if (!targetConfig.setByUser.contains(TargetProperty.SCHEDULER)) { + targetConfig.schedulerType = TargetProperty.SchedulerOption.GEDF_NP; } + } } + } - private boolean hasDeadlines(List reactors) { - for (Reactor reactor : reactors) { - for (Reaction reaction : allReactions(reactor)) { - if (reaction.getDeadline() != null) { - return true; - } - } + private boolean hasDeadlines(List reactors) { + for (Reactor reactor : reactors) { + for (Reaction reaction : allReactions(reactor)) { + if (reaction.getDeadline() != null) { + return true; } - return false; + } } - - /** - * Look at the 'reactor' eResource. - * If it is an imported .lf file, incorporate it into the current - * program in the following manner: - * - Merge its target property with `targetConfig` - * - If there are any preambles, add them to the preambles of the reactor. - */ - private void inspectReactorEResource(ReactorDecl reactor) { - // If the reactor is imported, look at the - // target definition of the .lf file in which the reactor is imported from and - // append any cmake-include. - // Check if the reactor definition is imported - if (reactor.eResource() != mainDef.getReactorClass().eResource()) { - // Find the LFResource corresponding to this eResource - LFResource lfResource = null; - for (var resource : resources) { - if (resource.getEResource() == reactor.eResource()) { - lfResource = resource; - break; - } - } - if (lfResource != null) { - // Copy the user files and cmake-includes to the src-gen path of the main .lf file - copyUserFiles(lfResource.getTargetConfig(), lfResource.getFileConfig()); - // Merge the CMake includes from the imported file into the target config - lfResource.getTargetConfig().cmakeIncludes.forEach(incl -> { - if (!this.targetConfig.cmakeIncludes.contains(incl)) { - this.targetConfig.cmakeIncludes.add(incl); - } - }); - } + return false; + } + + /** + * Look at the 'reactor' eResource. If it is an imported .lf file, incorporate it into the current + * program in the following manner: - Merge its target property with `targetConfig` - If there are + * any preambles, add them to the preambles of the reactor. + */ + private void inspectReactorEResource(ReactorDecl reactor) { + // If the reactor is imported, look at the + // target definition of the .lf file in which the reactor is imported from and + // append any cmake-include. + // Check if the reactor definition is imported + if (reactor.eResource() != mainDef.getReactorClass().eResource()) { + // Find the LFResource corresponding to this eResource + LFResource lfResource = null; + for (var resource : resources) { + if (resource.getEResource() == reactor.eResource()) { + lfResource = resource; + break; } + } + if (lfResource != null) { + // Copy the user files and cmake-includes to the src-gen path of the main .lf file + copyUserFiles(lfResource.getTargetConfig(), lfResource.getFileConfig()); + // Merge the CMake includes from the imported file into the target config + lfResource + .getTargetConfig() + .cmakeIncludes + .forEach( + incl -> { + if (!this.targetConfig.cmakeIncludes.contains(incl)) { + this.targetConfig.cmakeIncludes.add(incl); + } + }); + } } - - /** - * Copy all files or directories listed in the target property `files`, `cmake-include`, - * and `_fed_setup` into the src-gen folder of the main .lf file - * - * @param targetConfig The targetConfig to read the target properties from. - * @param fileConfig The fileConfig used to make the copy and resolve paths. - */ - @Override - protected void copyUserFiles(TargetConfig targetConfig, FileConfig fileConfig) { - super.copyUserFiles(targetConfig, fileConfig); - // Must use class variable to determine destination! - var destination = this.fileConfig.getSrcGenPath(); - - FileUtil.copyFilesOrDirectories(targetConfig.cmakeIncludes, destination, fileConfig, errorReporter, true); - - // FIXME: Unclear what the following does, but it does not appear to belong here. - if (!StringExtensions.isNullOrEmpty(targetConfig.fedSetupPreamble)) { - try { - FileUtil.copyFile(fileConfig.srcFile.getParent().resolve(targetConfig.fedSetupPreamble), - destination.resolve(targetConfig.fedSetupPreamble)); - } catch (IOException e) { - errorReporter.reportError("Failed to find _fed_setup file " + targetConfig.fedSetupPreamble); - } - } + } + + /** + * Copy all files or directories listed in the target property `files`, `cmake-include`, and + * `_fed_setup` into the src-gen folder of the main .lf file + * + * @param targetConfig The targetConfig to read the target properties from. + * @param fileConfig The fileConfig used to make the copy and resolve paths. + */ + @Override + protected void copyUserFiles(TargetConfig targetConfig, FileConfig fileConfig) { + super.copyUserFiles(targetConfig, fileConfig); + // Must use class variable to determine destination! + var destination = this.fileConfig.getSrcGenPath(); + + FileUtil.copyFilesOrDirectories( + targetConfig.cmakeIncludes, destination, fileConfig, errorReporter, true); + + // FIXME: Unclear what the following does, but it does not appear to belong here. + if (!StringExtensions.isNullOrEmpty(targetConfig.fedSetupPreamble)) { + try { + FileUtil.copyFile( + fileConfig.srcFile.getParent().resolve(targetConfig.fedSetupPreamble), + destination.resolve(targetConfig.fedSetupPreamble)); + } catch (IOException e) { + errorReporter.reportError( + "Failed to find _fed_setup file " + targetConfig.fedSetupPreamble); + } } - - /** - * Generate code for defining all reactors that belong to the federate, - * including all the child reactors down the hierarchy. Duplicate - * Duplicates are avoided. - * - * Imported reactors' original .lf file is - * incorporated in the following manner: - * - If there are any cmake-include files, add them to the current list - * of cmake-include files. - * - If there are any preambles, add them to the preambles of the reactor. - */ - private void generateReactorDefinitions() throws IOException { - var generatedReactors = new LinkedHashSet(); - if (this.main != null) { - generateReactorChildren(this.main, generatedReactors); - } - - if (this.mainDef != null) { - generateReactorClass(ASTUtils.toDefinition(this.mainDef.getReactorClass())); - } - - if (mainDef == null) { - // Generate code for each reactor that was not instantiated in main or its children. - for (Reactor r : reactors) { - // Get the declarations for reactors that are instantiated somewhere. - // A declaration is either a reactor definition or an import statement.; - var declarations = this.instantiationGraph.getDeclarations(r); - // If the reactor has no instantiations and there is no main reactor, then - // generate code for it anyway (at a minimum, this means that the compiler is invoked - // so that reaction bodies are checked). - if (declarations.isEmpty()) { - generateReactorClass(r); - } - } - } + } + + /** + * Generate code for defining all reactors that belong to the federate, including all the child + * reactors down the hierarchy. Duplicate Duplicates are avoided. + * + *

Imported reactors' original .lf file is incorporated in the following manner: - If there are + * any cmake-include files, add them to the current list of cmake-include files. - If there are + * any preambles, add them to the preambles of the reactor. + */ + private void generateReactorDefinitions() throws IOException { + var generatedReactors = new LinkedHashSet(); + if (this.main != null) { + generateReactorChildren(this.main, generatedReactors); } - /** Generate user-visible header files for all reactors instantiated. */ - private void generateHeaders() throws IOException { - FileUtil.deleteDirectory(fileConfig.getIncludePath()); - FileUtil.copyFromClassPath( - fileConfig.getRuntimeIncludePath(), - fileConfig.getIncludePath(), - false, - true - ); - for (Reactor r : reactors) { - CReactorHeaderFileGenerator.doGenerate( - types, r, fileConfig, - (builder, rr, userFacing) -> { - generateAuxiliaryStructs(builder, rr, userFacing); - if (userFacing) { - ASTUtils.allInstantiations(r).stream().map(Instantiation::getReactorClass).collect(Collectors.toSet()).forEach(it -> { - ASTUtils.allPorts(ASTUtils.toDefinition(it)) - .forEach(p -> builder.pr(CPortGenerator.generateAuxiliaryStruct( - ASTUtils.toDefinition(it), p, getTarget(), errorReporter, types, new CodeBuilder(), true, it - ))); - }); - } - }, - this::generateTopLevelPreambles); - } - FileUtil.copyDirectoryContents(fileConfig.getIncludePath(), fileConfig.getSrcGenPath().resolve("include"), false); + if (this.mainDef != null) { + generateReactorClass(ASTUtils.toDefinition(this.mainDef.getReactorClass())); } - /** - * Generate code for the children of 'reactor' that belong to 'federate'. - * Duplicates are avoided. - * - * Imported reactors' original .lf file is - * incorporated in the following manner: - * - If there are any cmake-include files, add them to the current list - * of cmake-include files. - * - If there are any preambles, add them to the preambles of the reactor. - * - * @param reactor Used to extract children from - */ - private void generateReactorChildren( - ReactorInstance reactor, - LinkedHashSet generatedReactors - ) throws IOException { - for (ReactorInstance r : reactor.children) { - if (r.reactorDeclaration != null && - !generatedReactors.contains(r.reactorDefinition)) { - generatedReactors.add(r.reactorDefinition); - generateReactorChildren(r, generatedReactors); - inspectReactorEResource(r.reactorDeclaration); - generateReactorClass(r.reactorDefinition); - } + if (mainDef == null) { + // Generate code for each reactor that was not instantiated in main or its children. + for (Reactor r : reactors) { + // Get the declarations for reactors that are instantiated somewhere. + // A declaration is either a reactor definition or an import statement.; + var declarations = this.instantiationGraph.getDeclarations(r); + // If the reactor has no instantiations and there is no main reactor, then + // generate code for it anyway (at a minimum, this means that the compiler is invoked + // so that reaction bodies are checked). + if (declarations.isEmpty()) { + generateReactorClass(r); } + } } - - /** - * Choose which platform files to compile with according to the OS. - * If there is no main reactor, then compilation will produce a .o file requiring further linking. - * Also, if useCmake is set to true, we don't need to add platform files. The CMakeLists.txt file - * will detect and use the appropriate platform file based on the platform that cmake is invoked on. - */ - private void pickCompilePlatform() { - var osName = System.getProperty("os.name").toLowerCase(); - // if platform target was set, use given platform instead - if (targetConfig.platformOptions.platform != Platform.AUTO) { - osName = targetConfig.platformOptions.platform.toString(); - } else if (Stream.of("mac", "darwin", "win", "nux").noneMatch(osName::contains)) { - errorReporter.reportError("Platform " + osName + " is not supported"); - } + } + + /** Generate user-visible header files for all reactors instantiated. */ + private void generateHeaders() throws IOException { + FileUtil.deleteDirectory(fileConfig.getIncludePath()); + FileUtil.copyFromClassPath( + fileConfig.getRuntimeIncludePath(), fileConfig.getIncludePath(), false, true); + for (Reactor r : reactors) { + CReactorHeaderFileGenerator.doGenerate( + types, + r, + fileConfig, + (builder, rr, userFacing) -> { + generateAuxiliaryStructs(builder, rr, userFacing); + if (userFacing) { + ASTUtils.allInstantiations(r).stream() + .map(Instantiation::getReactorClass) + .collect(Collectors.toSet()) + .forEach( + it -> { + ASTUtils.allPorts(ASTUtils.toDefinition(it)) + .forEach( + p -> + builder.pr( + CPortGenerator.generateAuxiliaryStruct( + ASTUtils.toDefinition(it), + p, + getTarget(), + errorReporter, + types, + new CodeBuilder(), + true, + it))); + }); + } + }, + this::generateTopLevelPreambles); } - - /** - * Copy target-specific header file to the src-gen directory. - */ - protected void copyTargetFiles() throws IOException { - // Copy the core lib - String coreLib = LFGeneratorContext.BuildParm.EXTERNAL_RUNTIME_PATH.getValue(context); - Path dest = fileConfig.getSrcGenPath(); - if (targetConfig.platformOptions.platform == Platform.ARDUINO) { - dest = dest.resolve("src"); + FileUtil.copyDirectoryContents( + fileConfig.getIncludePath(), fileConfig.getSrcGenPath().resolve("include"), false); + } + + /** + * Generate code for the children of 'reactor' that belong to 'federate'. Duplicates are avoided. + * + *

Imported reactors' original .lf file is incorporated in the following manner: - If there are + * any cmake-include files, add them to the current list of cmake-include files. - If there are + * any preambles, add them to the preambles of the reactor. + * + * @param reactor Used to extract children from + */ + private void generateReactorChildren( + ReactorInstance reactor, LinkedHashSet generatedReactors) throws IOException { + for (ReactorInstance r : reactor.children) { + if (r.reactorDeclaration != null && !generatedReactors.contains(r.reactorDefinition)) { + generatedReactors.add(r.reactorDefinition); + generateReactorChildren(r, generatedReactors); + inspectReactorEResource(r.reactorDeclaration); + generateReactorClass(r.reactorDefinition); } - if (coreLib != null) { - FileUtil.copyDirectoryContents(Path.of(coreLib), dest, true); - } else { - FileUtil.copyFromClassPath( - "/lib/c/reactor-c/core", - dest, - true, - false - ); - FileUtil.copyFromClassPath( - "/lib/c/reactor-c/lib", - dest, - true, - false - ); - } - - // For the Zephyr target, copy default config and board files. - if (targetConfig.platformOptions.platform == Platform.ZEPHYR) { - FileUtil.copyFromClassPath( - "/lib/platform/zephyr/boards", - fileConfig.getSrcGenPath(), - false, - false - ); - FileUtil.copyFileFromClassPath( - "/lib/platform/zephyr/prj_lf.conf", - fileConfig.getSrcGenPath(), - true - ); - - FileUtil.copyFileFromClassPath( - "/lib/platform/zephyr/Kconfig", - fileConfig.getSrcGenPath(), - true - ); - } } - - //////////////////////////////////////////// - //// Code generators. - - /** - * Generate a reactor class definition for the specified federate. - * A class definition has four parts: - * - * * Preamble code, if any, specified in the Lingua Franca file. - * * A "self" struct type definition (see the class documentation above). - * * A function for each reaction. - * * A constructor for creating an instance. - * for deleting an instance. - * - * If the reactor is the main reactor, then - * the generated code may be customized. Specifically, - * if the main reactor has reactions, these reactions - * will not be generated if they are triggered by or send - * data to contained reactors that are not in the federate. - * @param reactor The parsed reactor data structure. - */ - private void generateReactorClass(Reactor reactor) throws IOException { - // FIXME: Currently we're not reusing definitions for declarations that point to the same definition. - CodeBuilder header = new CodeBuilder(); - CodeBuilder src = new CodeBuilder(); - final String headerName = CUtil.getName(reactor) + ".h"; - var guardMacro = headerName.toUpperCase().replace(".", "_"); - header.pr("#ifndef " + guardMacro); - header.pr("#define " + guardMacro); - generateReactorClassHeaders(reactor, headerName, header, src); - header.pr(generateTopLevelPreambles(reactor)); - generateUserPreamblesForReactor(reactor, src); - generateReactorClassBody(reactor, header, src); - header.pr("#endif // " + guardMacro); - FileUtil.writeToFile(CodeMap.fromGeneratedCode(header.toString()).getGeneratedCode(), fileConfig.getSrcGenPath().resolve(headerName), true); - var extension = targetConfig.platformOptions.platform == Platform.ARDUINO ? ".ino" : - CCppMode ? ".cpp" : ".c"; - FileUtil.writeToFile(CodeMap.fromGeneratedCode(src.toString()).getGeneratedCode(), fileConfig.getSrcGenPath().resolve(CUtil.getName(reactor) + extension), true); + } + + /** + * Choose which platform files to compile with according to the OS. If there is no main reactor, + * then compilation will produce a .o file requiring further linking. Also, if useCmake is set to + * true, we don't need to add platform files. The CMakeLists.txt file will detect and use the + * appropriate platform file based on the platform that cmake is invoked on. + */ + private void pickCompilePlatform() { + var osName = System.getProperty("os.name").toLowerCase(); + // if platform target was set, use given platform instead + if (targetConfig.platformOptions.platform != Platform.AUTO) { + osName = targetConfig.platformOptions.platform.toString(); + } else if (Stream.of("mac", "darwin", "win", "nux").noneMatch(osName::contains)) { + errorReporter.reportError("Platform " + osName + " is not supported"); } - - protected void generateReactorClassHeaders(Reactor reactor, String headerName, CodeBuilder header, CodeBuilder src) { - if (CCppMode) { - src.pr("extern \"C\" {"); - header.pr("extern \"C\" {"); - } - header.pr("#include \"include/core/reactor.h\""); - src.pr("#include \"include/api/api.h\""); - src.pr("#include \"include/api/set.h\""); - generateIncludes(reactor); - if (CCppMode) { - src.pr("}"); - header.pr("}"); - } - src.pr("#include \"include/" + CReactorHeaderFileGenerator.outputPath(reactor) + "\""); - src.pr("#include \"" + headerName + "\""); - ASTUtils.allNestedClasses(reactor).map(CUtil::getName) - .map(name -> "#include \"" + name + ".h\"") - .forEach(header::pr); + } + + /** Copy target-specific header file to the src-gen directory. */ + protected void copyTargetFiles() throws IOException { + // Copy the core lib + String coreLib = LFGeneratorContext.BuildParm.EXTERNAL_RUNTIME_PATH.getValue(context); + Path dest = fileConfig.getSrcGenPath(); + if (targetConfig.platformOptions.platform == Platform.ARDUINO) { + dest = dest.resolve("src"); } - - private void generateReactorClassBody(Reactor reactor, CodeBuilder header, CodeBuilder src) { - // Some of the following methods create lines of code that need to - // go into the constructor. Collect those lines of code here: - var constructorCode = new CodeBuilder(); - generateAuxiliaryStructs(header, reactor, false); - // The following must go before the self struct so the #include watchdog.h ends up in the header. - CWatchdogGenerator.generateWatchdogs(src, header, reactor, errorReporter); - generateSelfStruct(header, reactor, constructorCode); - generateMethods(src, reactor); - generateReactions(src, reactor); - generateConstructor(src, header, reactor, constructorCode); + if (coreLib != null) { + FileUtil.copyDirectoryContents(Path.of(coreLib), dest, true); + } else { + FileUtil.copyFromClassPath("/lib/c/reactor-c/core", dest, true, false); + FileUtil.copyFromClassPath("/lib/c/reactor-c/lib", dest, true, false); } - /** - * Generate methods for {@code reactor}. - */ - protected void generateMethods(CodeBuilder src, ReactorDecl reactor) { - CMethodGenerator.generateMethods(reactor, src, types); - } + // For the Zephyr target, copy default config and board files. + if (targetConfig.platformOptions.platform == Platform.ZEPHYR) { + FileUtil.copyFromClassPath( + "/lib/platform/zephyr/boards", fileConfig.getSrcGenPath(), false, false); + FileUtil.copyFileFromClassPath( + "/lib/platform/zephyr/prj_lf.conf", fileConfig.getSrcGenPath(), true); - /** - * Generates preambles defined by user for a given reactor - * @param reactor The given reactor - */ - protected void generateUserPreamblesForReactor(Reactor reactor, CodeBuilder src) { - for (Preamble p : ASTUtils.allPreambles(reactor)) { - src.pr("// *********** From the preamble, verbatim:"); - src.prSourceLineNumber(p.getCode()); - src.pr(toText(p.getCode())); - src.pr("\n// *********** End of preamble."); - } + FileUtil.copyFileFromClassPath( + "/lib/platform/zephyr/Kconfig", fileConfig.getSrcGenPath(), true); } - - /** - * Generate a constructor for the specified reactor in the specified federate. - * @param reactor The parsed reactor data structure. - * @param constructorCode Lines of code previously generated that need to - * go into the constructor. - */ - protected void generateConstructor( - CodeBuilder src, CodeBuilder header, Reactor reactor, CodeBuilder constructorCode - ) { - header.pr(CConstructorGenerator.generateConstructorPrototype(reactor)); - src.pr(CConstructorGenerator.generateConstructor( - reactor, - constructorCode.toString() - )); + } + + //////////////////////////////////////////// + //// Code generators. + + /** + * Generate a reactor class definition for the specified federate. A class definition has four + * parts: + * + *

* Preamble code, if any, specified in the Lingua Franca file. * A "self" struct type + * definition (see the class documentation above). * A function for each reaction. * A constructor + * for creating an instance. for deleting an instance. + * + *

If the reactor is the main reactor, then the generated code may be customized. Specifically, + * if the main reactor has reactions, these reactions will not be generated if they are triggered + * by or send data to contained reactors that are not in the federate. + * + * @param reactor The parsed reactor data structure. + */ + private void generateReactorClass(Reactor reactor) throws IOException { + // FIXME: Currently we're not reusing definitions for declarations that point to the same + // definition. + CodeBuilder header = new CodeBuilder(); + CodeBuilder src = new CodeBuilder(); + final String headerName = CUtil.getName(reactor) + ".h"; + var guardMacro = headerName.toUpperCase().replace(".", "_"); + header.pr("#ifndef " + guardMacro); + header.pr("#define " + guardMacro); + generateReactorClassHeaders(reactor, headerName, header, src); + header.pr(generateTopLevelPreambles(reactor)); + generateUserPreamblesForReactor(reactor, src); + generateReactorClassBody(reactor, header, src); + header.pr("#endif // " + guardMacro); + FileUtil.writeToFile( + CodeMap.fromGeneratedCode(header.toString()).getGeneratedCode(), + fileConfig.getSrcGenPath().resolve(headerName), + true); + var extension = + targetConfig.platformOptions.platform == Platform.ARDUINO + ? ".ino" + : CCppMode ? ".cpp" : ".c"; + FileUtil.writeToFile( + CodeMap.fromGeneratedCode(src.toString()).getGeneratedCode(), + fileConfig.getSrcGenPath().resolve(CUtil.getName(reactor) + extension), + true); + } + + protected void generateReactorClassHeaders( + Reactor reactor, String headerName, CodeBuilder header, CodeBuilder src) { + if (CCppMode) { + src.pr("extern \"C\" {"); + header.pr("extern \"C\" {"); } - - protected void generateIncludes(Reactor r) { - code.pr("#include \"" + CUtil.getName(r) + ".h\""); + header.pr("#include \"include/core/reactor.h\""); + src.pr("#include \"include/api/api.h\""); + src.pr("#include \"include/api/set.h\""); + generateIncludes(reactor); + if (CCppMode) { + src.pr("}"); + header.pr("}"); } - - /** - * Generate the struct type definitions for inputs, outputs, and - * actions of the specified reactor. - */ - protected void generateAuxiliaryStructs(CodeBuilder builder, Reactor r, boolean userFacing) { - // In the case where there are incoming - // p2p logical connections in decentralized - // federated execution, there will be an - // intended_tag field added to accommodate - // the case where a reaction triggered by a - // port or action is late due to network - // latency, etc.. - var federatedExtension = new CodeBuilder(); - federatedExtension.pr(""" + src.pr("#include \"include/" + CReactorHeaderFileGenerator.outputPath(reactor) + "\""); + src.pr("#include \"" + headerName + "\""); + ASTUtils.allNestedClasses(reactor) + .map(CUtil::getName) + .map(name -> "#include \"" + name + ".h\"") + .forEach(header::pr); + } + + private void generateReactorClassBody(Reactor reactor, CodeBuilder header, CodeBuilder src) { + // Some of the following methods create lines of code that need to + // go into the constructor. Collect those lines of code here: + var constructorCode = new CodeBuilder(); + generateAuxiliaryStructs(header, reactor, false); + // The following must go before the self struct so the #include watchdog.h ends up in the + // header. + CWatchdogGenerator.generateWatchdogs(src, header, reactor, errorReporter); + generateSelfStruct(header, reactor, constructorCode); + generateMethods(src, reactor); + generateReactions(src, reactor); + generateConstructor(src, header, reactor, constructorCode); + } + + /** Generate methods for {@code reactor}. */ + protected void generateMethods(CodeBuilder src, ReactorDecl reactor) { + CMethodGenerator.generateMethods(reactor, src, types); + } + + /** + * Generates preambles defined by user for a given reactor + * + * @param reactor The given reactor + */ + protected void generateUserPreamblesForReactor(Reactor reactor, CodeBuilder src) { + for (Preamble p : ASTUtils.allPreambles(reactor)) { + src.pr("// *********** From the preamble, verbatim:"); + src.prSourceLineNumber(p.getCode()); + src.pr(toText(p.getCode())); + src.pr("\n// *********** End of preamble."); + } + } + + /** + * Generate a constructor for the specified reactor in the specified federate. + * + * @param reactor The parsed reactor data structure. + * @param constructorCode Lines of code previously generated that need to go into the constructor. + */ + protected void generateConstructor( + CodeBuilder src, CodeBuilder header, Reactor reactor, CodeBuilder constructorCode) { + header.pr(CConstructorGenerator.generateConstructorPrototype(reactor)); + src.pr(CConstructorGenerator.generateConstructor(reactor, constructorCode.toString())); + } + + protected void generateIncludes(Reactor r) { + code.pr("#include \"" + CUtil.getName(r) + ".h\""); + } + + /** + * Generate the struct type definitions for inputs, outputs, and actions of the specified reactor. + */ + protected void generateAuxiliaryStructs(CodeBuilder builder, Reactor r, boolean userFacing) { + // In the case where there are incoming + // p2p logical connections in decentralized + // federated execution, there will be an + // intended_tag field added to accommodate + // the case where a reaction triggered by a + // port or action is late due to network + // latency, etc.. + var federatedExtension = new CodeBuilder(); + federatedExtension.pr( + """ #ifdef FEDERATED #ifdef FEDERATED_DECENTRALIZED %s intended_tag; #endif %s physical_time_of_arrival; #endif - """.formatted(types.getTargetTagType(), types.getTargetTimeType()) - ); - for (Port p : allPorts(r)) { - builder.pr(CPortGenerator.generateAuxiliaryStruct( - r, - p, - getTarget(), - errorReporter, - types, - federatedExtension, - userFacing, - null - )); - } - // The very first item on this struct needs to be - // a trigger_t* because the struct will be cast to (trigger_t*) - // by the lf_schedule() functions to get to the trigger. - for (Action action : allActions(r)) { - builder.pr(CActionGenerator.generateAuxiliaryStruct( - r, - action, - getTarget(), - types, - federatedExtension, - userFacing - )); - } + """ + .formatted(types.getTargetTagType(), types.getTargetTimeType())); + for (Port p : allPorts(r)) { + builder.pr( + CPortGenerator.generateAuxiliaryStruct( + r, p, getTarget(), errorReporter, types, federatedExtension, userFacing, null)); } - - /** - * Generate the self struct type definition for the specified reactor - * in the specified federate. - * @param decl The parsed reactor data structure. - * @param constructorCode Place to put lines of code that need to - * go into the constructor. - */ - private void generateSelfStruct(CodeBuilder builder, ReactorDecl decl, CodeBuilder constructorCode) { - var reactor = toDefinition(decl); - var selfType = CUtil.selfType(ASTUtils.toDefinition(decl)); - - // Construct the typedef for the "self" struct. - // Create a type name for the self struct. - var body = new CodeBuilder(); - - // Extensions can add functionality to the CGenerator - generateSelfStructExtension(body, decl, constructorCode); - - // Next handle parameters. - body.pr(CParameterGenerator.generateDeclarations(reactor, types)); - - // Next handle states. - body.pr(CStateGenerator.generateDeclarations(reactor, types)); - - // Next handle actions. - CActionGenerator.generateDeclarations(reactor, body, constructorCode); - - // Next handle inputs and outputs. - CPortGenerator.generateDeclarations(reactor, decl, body, constructorCode); - - // If there are contained reactors that either receive inputs - // from reactions of this reactor or produce outputs that trigger - // reactions of this reactor, then we need to create a struct - // inside the self struct for each contained reactor. That - // struct has a place to hold the data produced by this reactor's - // reactions and a place to put pointers to data produced by - // the contained reactors. - generateInteractingContainedReactors(reactor, body, constructorCode); - - // Next, generate the fields needed for each reaction. - CReactionGenerator.generateReactionAndTriggerStructs( - body, - reactor, - constructorCode, - types - ); - - // Generate the fields needed for each watchdog. - CWatchdogGenerator.generateWatchdogStruct(body, decl, constructorCode); - - // Next, generate fields for modes - CModesGenerator.generateDeclarations(reactor, body, constructorCode); - - // The first field has to always be a pointer to the list of - // of allocated memory that must be freed when the reactor is freed. - // This means that the struct can be safely cast to self_base_t. - builder.pr("typedef struct {"); - builder.indent(); - builder.pr("struct self_base_t base;"); - builder.pr(body.toString()); - builder.unindent(); - builder.pr("} " + selfType + ";"); + // The very first item on this struct needs to be + // a trigger_t* because the struct will be cast to (trigger_t*) + // by the lf_schedule() functions to get to the trigger. + for (Action action : allActions(r)) { + builder.pr( + CActionGenerator.generateAuxiliaryStruct( + r, action, getTarget(), types, federatedExtension, userFacing)); } - - /** - * Generate structs and associated code for contained reactors that - * send or receive data to or from the container's reactions. - * - * If there are contained reactors that either receive inputs - * from reactions of this reactor or produce outputs that trigger - * reactions of this reactor, then we need to create a struct - * inside the self struct of the container for each contained reactor. - * That struct has a place to hold the data produced by the container reactor's - * reactions and a place to put pointers to data produced by - * the contained reactors. - * - * @param reactor The reactor. - * @param body The place to put the struct definition for the contained reactors. - * @param constructorCode The place to put matching code that goes in the container's constructor. - */ - private void generateInteractingContainedReactors( - Reactor reactor, - CodeBuilder body, - CodeBuilder constructorCode - ) { - // The contents of the struct will be collected first so that - // we avoid duplicate entries and then the struct will be constructed. - var contained = new InteractingContainedReactors(reactor); - // Next generate the relevant code. - for (Instantiation containedReactor : contained.containedReactors()) { - Reactor containedReactorType = ASTUtils.toDefinition(containedReactor.getReactorClass()); - // First define an _width variable in case it is a bank. - var array = ""; - var width = -2; - // If the instantiation is a bank, find the maximum bank width - // to define an array. - if (containedReactor.getWidthSpec() != null) { - width = CReactionGenerator.maxContainedReactorBankWidth(containedReactor, null, 0, mainDef); - array = "[" + width + "]"; - } - // NOTE: The following needs to be done for each instance - // so that the width can be parameter, not in the constructor. - // Here, we conservatively use a width that is the largest of all isntances. - constructorCode.pr(String.join("\n", - "// Set the _width variable for all cases. This will be -2", - "// if the reactor is not a bank of reactors.", - "self->_lf_"+containedReactor.getName()+"_width = "+width+";" - )); - - // Generate one struct for each contained reactor that interacts. - body.pr("struct {"); - body.indent(); - for (Port port : contained.portsOfInstance(containedReactor)) { - if (port instanceof Input) { - // If the variable is a multiport, then the place to store the data has - // to be malloc'd at initialization. - if (!ASTUtils.isMultiport(port)) { - // Not a multiport. - body.pr(port, variableStructType(port, containedReactorType, false)+" "+port.getName()+";"); - } else { - // Is a multiport. - // Memory will be malloc'd in initialization. - body.pr(port, String.join("\n", - variableStructType(port, containedReactorType, false)+"** "+port.getName()+";", - "int "+port.getName()+"_width;" - )); - } - } else { - // Must be an output port. - // Outputs of contained reactors are pointers to the source of data on the - // self struct of the container. - if (!ASTUtils.isMultiport(port)) { - // Not a multiport. - body.pr(port, variableStructType(port, containedReactorType, false)+"* "+port.getName()+";"); - } else { - // Is a multiport. - // Here, we will use an array of pointers. - // Memory will be malloc'd in initialization. - body.pr(port, String.join("\n", - variableStructType(port, containedReactorType, false)+"** "+port.getName()+";", - "int "+port.getName()+"_width;" - )); - } - body.pr(port, "trigger_t "+port.getName()+"_trigger;"); - var reactorIndex = ""; - if (containedReactor.getWidthSpec() != null) { - reactorIndex = "[reactor_index]"; - constructorCode.pr("for (int reactor_index = 0; reactor_index < self->_lf_"+containedReactor.getName()+"_width; reactor_index++) {"); - constructorCode.indent(); - } - var portOnSelf = "self->_lf_"+containedReactor.getName()+reactorIndex+"."+port.getName(); - - constructorCode.pr( - port, - CExtensionUtils.surroundWithIfFederatedDecentralized( - portOnSelf+"_trigger.intended_tag = (tag_t) { .time = NEVER, .microstep = 0u};" - ) - ); - - var triggered = contained.reactionsTriggered(containedReactor, port); - //noinspection StatementWithEmptyBody - if (triggered.size() > 0) { - body.pr(port, "reaction_t* "+port.getName()+"_reactions["+triggered.size()+"];"); - var triggeredCount = 0; - for (Integer index : triggered) { - constructorCode.pr(port, portOnSelf+"_reactions["+triggeredCount+++"] = &self->_lf__reaction_"+index+";"); - } - constructorCode.pr(port, portOnSelf+"_trigger.reactions = "+portOnSelf+"_reactions;"); - } else { - // Since the self struct is created using calloc, there is no need to set - // self->_lf_"+containedReactor.getName()+"."+port.getName()+"_trigger.reactions = NULL - } - // Since the self struct is created using calloc, there is no need to set falsy fields. - constructorCode.pr(port, String.join("\n", - portOnSelf+"_trigger.last = NULL;", - portOnSelf+"_trigger.number_of_reactions = "+triggered.size()+";" - )); - - - // Set the physical_time_of_arrival - constructorCode.pr( - port, - CExtensionUtils.surroundWithIfFederated( - portOnSelf+"_trigger.physical_time_of_arrival = NEVER;" - ) - ); - - if (containedReactor.getWidthSpec() != null) { - constructorCode.unindent(); - constructorCode.pr("}"); - } - } + } + + /** + * Generate the self struct type definition for the specified reactor in the specified federate. + * + * @param decl The parsed reactor data structure. + * @param constructorCode Place to put lines of code that need to go into the constructor. + */ + private void generateSelfStruct( + CodeBuilder builder, ReactorDecl decl, CodeBuilder constructorCode) { + var reactor = toDefinition(decl); + var selfType = CUtil.selfType(ASTUtils.toDefinition(decl)); + + // Construct the typedef for the "self" struct. + // Create a type name for the self struct. + var body = new CodeBuilder(); + + // Extensions can add functionality to the CGenerator + generateSelfStructExtension(body, decl, constructorCode); + + // Next handle parameters. + body.pr(CParameterGenerator.generateDeclarations(reactor, types)); + + // Next handle states. + body.pr(CStateGenerator.generateDeclarations(reactor, types)); + + // Next handle actions. + CActionGenerator.generateDeclarations(reactor, body, constructorCode); + + // Next handle inputs and outputs. + CPortGenerator.generateDeclarations(reactor, decl, body, constructorCode); + + // If there are contained reactors that either receive inputs + // from reactions of this reactor or produce outputs that trigger + // reactions of this reactor, then we need to create a struct + // inside the self struct for each contained reactor. That + // struct has a place to hold the data produced by this reactor's + // reactions and a place to put pointers to data produced by + // the contained reactors. + generateInteractingContainedReactors(reactor, body, constructorCode); + + // Next, generate the fields needed for each reaction. + CReactionGenerator.generateReactionAndTriggerStructs(body, reactor, constructorCode, types); + + // Generate the fields needed for each watchdog. + CWatchdogGenerator.generateWatchdogStruct(body, decl, constructorCode); + + // Next, generate fields for modes + CModesGenerator.generateDeclarations(reactor, body, constructorCode); + + // The first field has to always be a pointer to the list of + // of allocated memory that must be freed when the reactor is freed. + // This means that the struct can be safely cast to self_base_t. + builder.pr("typedef struct {"); + builder.indent(); + builder.pr("struct self_base_t base;"); + builder.pr(body.toString()); + builder.unindent(); + builder.pr("} " + selfType + ";"); + } + + /** + * Generate structs and associated code for contained reactors that send or receive data to or + * from the container's reactions. + * + *

If there are contained reactors that either receive inputs from reactions of this reactor or + * produce outputs that trigger reactions of this reactor, then we need to create a struct inside + * the self struct of the container for each contained reactor. That struct has a place to hold + * the data produced by the container reactor's reactions and a place to put pointers to data + * produced by the contained reactors. + * + * @param reactor The reactor. + * @param body The place to put the struct definition for the contained reactors. + * @param constructorCode The place to put matching code that goes in the container's constructor. + */ + private void generateInteractingContainedReactors( + Reactor reactor, CodeBuilder body, CodeBuilder constructorCode) { + // The contents of the struct will be collected first so that + // we avoid duplicate entries and then the struct will be constructed. + var contained = new InteractingContainedReactors(reactor); + // Next generate the relevant code. + for (Instantiation containedReactor : contained.containedReactors()) { + Reactor containedReactorType = ASTUtils.toDefinition(containedReactor.getReactorClass()); + // First define an _width variable in case it is a bank. + var array = ""; + var width = -2; + // If the instantiation is a bank, find the maximum bank width + // to define an array. + if (containedReactor.getWidthSpec() != null) { + width = CReactionGenerator.maxContainedReactorBankWidth(containedReactor, null, 0, mainDef); + array = "[" + width + "]"; + } + // NOTE: The following needs to be done for each instance + // so that the width can be parameter, not in the constructor. + // Here, we conservatively use a width that is the largest of all isntances. + constructorCode.pr( + String.join( + "\n", + "// Set the _width variable for all cases. This will be -2", + "// if the reactor is not a bank of reactors.", + "self->_lf_" + containedReactor.getName() + "_width = " + width + ";")); + + // Generate one struct for each contained reactor that interacts. + body.pr("struct {"); + body.indent(); + for (Port port : contained.portsOfInstance(containedReactor)) { + if (port instanceof Input) { + // If the variable is a multiport, then the place to store the data has + // to be malloc'd at initialization. + if (!ASTUtils.isMultiport(port)) { + // Not a multiport. + body.pr( + port, + variableStructType(port, containedReactorType, false) + " " + port.getName() + ";"); + } else { + // Is a multiport. + // Memory will be malloc'd in initialization. + body.pr( + port, + String.join( + "\n", + variableStructType(port, containedReactorType, false) + + "** " + + port.getName() + + ";", + "int " + port.getName() + "_width;")); + } + } else { + // Must be an output port. + // Outputs of contained reactors are pointers to the source of data on the + // self struct of the container. + if (!ASTUtils.isMultiport(port)) { + // Not a multiport. + body.pr( + port, + variableStructType(port, containedReactorType, false) + + "* " + + port.getName() + + ";"); + } else { + // Is a multiport. + // Here, we will use an array of pointers. + // Memory will be malloc'd in initialization. + body.pr( + port, + String.join( + "\n", + variableStructType(port, containedReactorType, false) + + "** " + + port.getName() + + ";", + "int " + port.getName() + "_width;")); + } + body.pr(port, "trigger_t " + port.getName() + "_trigger;"); + var reactorIndex = ""; + if (containedReactor.getWidthSpec() != null) { + reactorIndex = "[reactor_index]"; + constructorCode.pr( + "for (int reactor_index = 0; reactor_index < self->_lf_" + + containedReactor.getName() + + "_width; reactor_index++) {"); + constructorCode.indent(); + } + var portOnSelf = + "self->_lf_" + containedReactor.getName() + reactorIndex + "." + port.getName(); + + constructorCode.pr( + port, + CExtensionUtils.surroundWithIfFederatedDecentralized( + portOnSelf + + "_trigger.intended_tag = (tag_t) { .time = NEVER, .microstep = 0u};")); + + var triggered = contained.reactionsTriggered(containedReactor, port); + //noinspection StatementWithEmptyBody + if (triggered.size() > 0) { + body.pr( + port, "reaction_t* " + port.getName() + "_reactions[" + triggered.size() + "];"); + var triggeredCount = 0; + for (Integer index : triggered) { + constructorCode.pr( + port, + portOnSelf + + "_reactions[" + + triggeredCount++ + + "] = &self->_lf__reaction_" + + index + + ";"); } - body.unindent(); - body.pr(String.join("\n", - "} _lf_"+containedReactor.getName()+array+";", - "int _lf_"+containedReactor.getName()+"_width;" - )); + constructorCode.pr( + port, portOnSelf + "_trigger.reactions = " + portOnSelf + "_reactions;"); + } else { + // Since the self struct is created using calloc, there is no need to set + // self->_lf_"+containedReactor.getName()+"."+port.getName()+"_trigger.reactions = NULL + } + // Since the self struct is created using calloc, there is no need to set falsy fields. + constructorCode.pr( + port, + String.join( + "\n", + portOnSelf + "_trigger.last = NULL;", + portOnSelf + "_trigger.number_of_reactions = " + triggered.size() + ";")); + + // Set the physical_time_of_arrival + constructorCode.pr( + port, + CExtensionUtils.surroundWithIfFederated( + portOnSelf + "_trigger.physical_time_of_arrival = NEVER;")); + + if (containedReactor.getWidthSpec() != null) { + constructorCode.unindent(); + constructorCode.pr("}"); + } } + } + body.unindent(); + body.pr( + String.join( + "\n", + "} _lf_" + containedReactor.getName() + array + ";", + "int _lf_" + containedReactor.getName() + "_width;")); } - - /** - * This function is provided to allow extensions of the CGenerator to append the structure of the self struct - * @param body The body of the self struct - * @param decl The reactor declaration for the self struct - * @param constructorCode Code that is executed when the reactor is instantiated - */ - protected void generateSelfStructExtension( - CodeBuilder body, - ReactorDecl decl, - CodeBuilder constructorCode - ) { - // Do nothing - } - - /** Generate reaction functions definition for a reactor. - * These functions have a single argument that is a void* pointing to - * a struct that contains parameters, state variables, inputs (triggering or not), - * actions (triggering or produced), and outputs. - * @param r The reactor. - */ - public void generateReactions(CodeBuilder src, Reactor r) { - var reactionIndex = 0; - var reactor = ASTUtils.toDefinition(r); - for (Reaction reaction : allReactions(reactor)) { - generateReaction(src, reaction, r, reactionIndex); - // Increment reaction index even if the reaction is not in the federate - // so that across federates, the reaction indices are consistent. - reactionIndex++; - } + } + + /** + * This function is provided to allow extensions of the CGenerator to append the structure of the + * self struct + * + * @param body The body of the self struct + * @param decl The reactor declaration for the self struct + * @param constructorCode Code that is executed when the reactor is instantiated + */ + protected void generateSelfStructExtension( + CodeBuilder body, ReactorDecl decl, CodeBuilder constructorCode) { + // Do nothing + } + + /** + * Generate reaction functions definition for a reactor. These functions have a single argument + * that is a void* pointing to a struct that contains parameters, state variables, inputs + * (triggering or not), actions (triggering or produced), and outputs. + * + * @param r The reactor. + */ + public void generateReactions(CodeBuilder src, Reactor r) { + var reactionIndex = 0; + var reactor = ASTUtils.toDefinition(r); + for (Reaction reaction : allReactions(reactor)) { + generateReaction(src, reaction, r, reactionIndex); + // Increment reaction index even if the reaction is not in the federate + // so that across federates, the reaction indices are consistent. + reactionIndex++; } - - /** Generate a reaction function definition for a reactor. - * This function will have a single argument that is a void* pointing to - * a struct that contains parameters, state variables, inputs (triggering or not), - * actions (triggering or produced), and outputs. - * @param reaction The reaction. - * @param r The reactor. - * @param reactionIndex The position of the reaction within the reactor. - */ - protected void generateReaction(CodeBuilder src, Reaction reaction, Reactor r, int reactionIndex) { - src.pr(CReactionGenerator.generateReaction( + } + + /** + * Generate a reaction function definition for a reactor. This function will have a single + * argument that is a void* pointing to a struct that contains parameters, state variables, inputs + * (triggering or not), actions (triggering or produced), and outputs. + * + * @param reaction The reaction. + * @param r The reactor. + * @param reactionIndex The position of the reaction within the reactor. + */ + protected void generateReaction( + CodeBuilder src, Reaction reaction, Reactor r, int reactionIndex) { + src.pr( + CReactionGenerator.generateReaction( reaction, r, reactionIndex, @@ -1437,707 +1403,746 @@ protected void generateReaction(CodeBuilder src, Reaction reaction, Reactor r, i errorReporter, types, targetConfig, - getTarget().requiresTypes - )); - } - - /** - * Record startup, shutdown, and reset reactions. - * @param instance A reactor instance. - */ - private void recordBuiltinTriggers(ReactorInstance instance) { - // For each reaction instance, allocate the arrays that will be used to - // trigger downstream reactions. - for (ReactionInstance reaction : instance.reactions) { - var reactor = reaction.getParent(); - var temp = new CodeBuilder(); - var foundOne = false; - - var reactionRef = CUtil.reactionRef(reaction); - - // Next handle triggers of the reaction that come from a multiport output - // of a contained reactor. Also, handle startup and shutdown triggers. - for (TriggerInstance trigger : reaction.triggers) { - if (trigger.isStartup()) { - temp.pr("_lf_startup_reactions[_lf_startup_reactions_count++] = &"+reactionRef+";"); - startupReactionCount += reactor.getTotalWidth(); - foundOne = true; - } else if (trigger.isShutdown()) { - temp.pr("_lf_shutdown_reactions[_lf_shutdown_reactions_count++] = &"+reactionRef+";"); - foundOne = true; - shutdownReactionCount += reactor.getTotalWidth(); - - if (targetConfig.tracing != null) { - var description = CUtil.getShortenedName(reactor); - var reactorRef = CUtil.reactorRef(reactor); - temp.pr(String.join("\n", - "_lf_register_trace_event("+reactorRef+", &("+reactorRef+"->_lf__shutdown),", - "trace_trigger, "+addDoubleQuotes(description+".shutdown")+");" - )); - } - } else if (trigger.isReset()) { - temp.pr("_lf_reset_reactions[_lf_reset_reactions_count++] = &"+reactionRef+";"); - resetReactionCount += reactor.getTotalWidth(); - foundOne = true; - } - } - if (foundOne) initializeTriggerObjects.pr(temp.toString()); + getTarget().requiresTypes)); + } + + /** + * Record startup, shutdown, and reset reactions. + * + * @param instance A reactor instance. + */ + private void recordBuiltinTriggers(ReactorInstance instance) { + // For each reaction instance, allocate the arrays that will be used to + // trigger downstream reactions. + for (ReactionInstance reaction : instance.reactions) { + var reactor = reaction.getParent(); + var temp = new CodeBuilder(); + var foundOne = false; + + var reactionRef = CUtil.reactionRef(reaction); + + // Next handle triggers of the reaction that come from a multiport output + // of a contained reactor. Also, handle startup and shutdown triggers. + for (TriggerInstance trigger : reaction.triggers) { + if (trigger.isStartup()) { + temp.pr("_lf_startup_reactions[_lf_startup_reactions_count++] = &" + reactionRef + ";"); + startupReactionCount += reactor.getTotalWidth(); + foundOne = true; + } else if (trigger.isShutdown()) { + temp.pr("_lf_shutdown_reactions[_lf_shutdown_reactions_count++] = &" + reactionRef + ";"); + foundOne = true; + shutdownReactionCount += reactor.getTotalWidth(); + + if (targetConfig.tracing != null) { + var description = CUtil.getShortenedName(reactor); + var reactorRef = CUtil.reactorRef(reactor); + temp.pr( + String.join( + "\n", + "_lf_register_trace_event(" + + reactorRef + + ", &(" + + reactorRef + + "->_lf__shutdown),", + "trace_trigger, " + addDoubleQuotes(description + ".shutdown") + ");")); + } + } else if (trigger.isReset()) { + temp.pr("_lf_reset_reactions[_lf_reset_reactions_count++] = &" + reactionRef + ";"); + resetReactionCount += reactor.getTotalWidth(); + foundOne = true; } + } + if (foundOne) initializeTriggerObjects.pr(temp.toString()); } - - /** - * Generate code to set up the tables used in _lf_start_time_step to decrement reference - * counts and mark outputs absent between time steps. This function puts the code - * into startTimeStep. - */ - private void generateStartTimeStep(ReactorInstance instance) { - // Avoid generating dead code if nothing is relevant. - var foundOne = false; - var temp = new CodeBuilder(); - var containerSelfStructName = CUtil.reactorRef(instance); - - // Handle inputs that get sent data from a reaction rather than from - // another contained reactor and reactions that are triggered by an - // output of a contained reactor. - // Note that there may be more than one reaction reacting to the same - // port so we have to avoid listing the port more than once. - var portsSeen = new LinkedHashSet(); - for (ReactionInstance reaction : instance.reactions) { - for (PortInstance port : Iterables.filter(reaction.effects, PortInstance.class)) { - if (port.getDefinition() instanceof Input && !portsSeen.contains(port)) { - portsSeen.add(port); - // This reaction is sending to an input. Must be - // the input of a contained reactor in the federate. - // NOTE: If instance == main and the federate is within a bank, - // this assumes that the reaction writes only to the bank member in the federate. - foundOne = true; - - temp.pr("// Add port "+port.getFullName()+" to array of is_present fields."); - - if (!instance.equals(port.getParent())) { - // The port belongs to contained reactor, so we also have - // iterate over the instance bank members. - temp.startScopedBlock(); - temp.pr("int count = 0; SUPPRESS_UNUSED_WARNING(count);"); - temp.startScopedBlock(instance); - temp.startScopedBankChannelIteration(port, null); - } else { - temp.startScopedBankChannelIteration(port, "count"); - } - var portRef = CUtil.portRefNested(port); - var con = (port.isMultiport()) ? "->" : "."; - - temp.pr("_lf_is_present_fields["+startTimeStepIsPresentCount+" + count] = &"+portRef+con+"is_present;"); - // Intended_tag is only applicable to ports in federated execution. - temp.pr( - CExtensionUtils.surroundWithIfFederatedDecentralized( - "_lf_intended_tag_fields["+startTimeStepIsPresentCount+" + count] = &"+portRef+con+"intended_tag;" - ) - ); - - startTimeStepIsPresentCount += port.getWidth() * port.getParent().getTotalWidth(); - - if (!instance.equals(port.getParent())) { - temp.pr("count++;"); - temp.endScopedBlock(); - temp.endScopedBlock(); - temp.endScopedBankChannelIteration(port, null); - } else { - temp.endScopedBankChannelIteration(port, "count"); - } - } - } + } + + /** + * Generate code to set up the tables used in _lf_start_time_step to decrement reference counts + * and mark outputs absent between time steps. This function puts the code into startTimeStep. + */ + private void generateStartTimeStep(ReactorInstance instance) { + // Avoid generating dead code if nothing is relevant. + var foundOne = false; + var temp = new CodeBuilder(); + var containerSelfStructName = CUtil.reactorRef(instance); + + // Handle inputs that get sent data from a reaction rather than from + // another contained reactor and reactions that are triggered by an + // output of a contained reactor. + // Note that there may be more than one reaction reacting to the same + // port so we have to avoid listing the port more than once. + var portsSeen = new LinkedHashSet(); + for (ReactionInstance reaction : instance.reactions) { + for (PortInstance port : Iterables.filter(reaction.effects, PortInstance.class)) { + if (port.getDefinition() instanceof Input && !portsSeen.contains(port)) { + portsSeen.add(port); + // This reaction is sending to an input. Must be + // the input of a contained reactor in the federate. + // NOTE: If instance == main and the federate is within a bank, + // this assumes that the reaction writes only to the bank member in the federate. + foundOne = true; + + temp.pr("// Add port " + port.getFullName() + " to array of is_present fields."); + + if (!instance.equals(port.getParent())) { + // The port belongs to contained reactor, so we also have + // iterate over the instance bank members. + temp.startScopedBlock(); + temp.pr("int count = 0; SUPPRESS_UNUSED_WARNING(count);"); + temp.startScopedBlock(instance); + temp.startScopedBankChannelIteration(port, null); + } else { + temp.startScopedBankChannelIteration(port, "count"); + } + var portRef = CUtil.portRefNested(port); + var con = (port.isMultiport()) ? "->" : "."; + + temp.pr( + "_lf_is_present_fields[" + + startTimeStepIsPresentCount + + " + count] = &" + + portRef + + con + + "is_present;"); + // Intended_tag is only applicable to ports in federated execution. + temp.pr( + CExtensionUtils.surroundWithIfFederatedDecentralized( + "_lf_intended_tag_fields[" + + startTimeStepIsPresentCount + + " + count] = &" + + portRef + + con + + "intended_tag;")); + + startTimeStepIsPresentCount += port.getWidth() * port.getParent().getTotalWidth(); + + if (!instance.equals(port.getParent())) { + temp.pr("count++;"); + temp.endScopedBlock(); + temp.endScopedBlock(); + temp.endScopedBankChannelIteration(port, null); + } else { + temp.endScopedBankChannelIteration(port, "count"); + } } - if (foundOne) startTimeStep.pr(temp.toString()); - temp = new CodeBuilder(); - foundOne = false; + } + } + if (foundOne) startTimeStep.pr(temp.toString()); + temp = new CodeBuilder(); + foundOne = false; + + for (ActionInstance action : instance.actions) { + foundOne = true; + temp.startScopedBlock(instance); + + temp.pr( + String.join( + "\n", + "// Add action " + action.getFullName() + " to array of is_present fields.", + "_lf_is_present_fields[" + startTimeStepIsPresentCount + "] ", + " = &" + + containerSelfStructName + + "->_lf_" + + action.getName() + + ".is_present;")); + + // Intended_tag is only applicable to actions in federated execution with decentralized + // coordination. + temp.pr( + CExtensionUtils.surroundWithIfFederatedDecentralized( + String.join( + "\n", + "// Add action " + action.getFullName() + " to array of intended_tag fields.", + "_lf_intended_tag_fields[" + startTimeStepIsPresentCount + "] ", + " = &" + + containerSelfStructName + + "->_lf_" + + action.getName() + + ".intended_tag;"))); + + startTimeStepIsPresentCount += action.getParent().getTotalWidth(); + temp.endScopedBlock(); + } + if (foundOne) startTimeStep.pr(temp.toString()); + temp = new CodeBuilder(); + foundOne = false; - for (ActionInstance action : instance.actions) { - foundOne = true; - temp.startScopedBlock(instance); + // Next, set up the table to mark each output of each contained reactor absent. + for (ReactorInstance child : instance.children) { + if (child.outputs.size() > 0) { - temp.pr(String.join("\n", - "// Add action "+action.getFullName()+" to array of is_present fields.", - "_lf_is_present_fields["+startTimeStepIsPresentCount+"] ", - " = &"+containerSelfStructName+"->_lf_"+action.getName()+".is_present;" - )); + temp.startScopedBlock(); + temp.pr("int count = 0; SUPPRESS_UNUSED_WARNING(count);"); + temp.startScopedBlock(child); - // Intended_tag is only applicable to actions in federated execution with decentralized coordination. + var channelCount = 0; + for (PortInstance output : child.outputs) { + if (!output.getDependsOnReactions().isEmpty()) { + foundOne = true; + temp.pr("// Add port " + output.getFullName() + " to array of is_present fields."); + temp.startChannelIteration(output); + temp.pr( + "_lf_is_present_fields[" + + startTimeStepIsPresentCount + + " + count] = &" + + CUtil.portRef(output) + + ".is_present;"); + + // Intended_tag is only applicable to ports in federated execution with decentralized + // coordination. temp.pr( CExtensionUtils.surroundWithIfFederatedDecentralized( - String.join("\n", - "// Add action " + action.getFullName() - + " to array of intended_tag fields.", - "_lf_intended_tag_fields[" - + startTimeStepIsPresentCount + "] ", - " = &" + containerSelfStructName - + "->_lf_" + action.getName() - + ".intended_tag;" - ))); - - startTimeStepIsPresentCount += action.getParent().getTotalWidth(); - temp.endScopedBlock(); + String.join( + "\n", + "// Add port " + output.getFullName() + " to array of intended_tag fields.", + "_lf_intended_tag_fields[" + + startTimeStepIsPresentCount + + " + count] = &" + + CUtil.portRef(output) + + ".intended_tag;"))); + + temp.pr("count++;"); + channelCount += output.getWidth(); + temp.endChannelIteration(output); + } } - if (foundOne) startTimeStep.pr(temp.toString()); - temp = new CodeBuilder(); - foundOne = false; - - // Next, set up the table to mark each output of each contained reactor absent. - for (ReactorInstance child : instance.children) { - if (child.outputs.size() > 0) { - - temp.startScopedBlock(); - temp.pr("int count = 0; SUPPRESS_UNUSED_WARNING(count);"); - temp.startScopedBlock(child); - - var channelCount = 0; - for (PortInstance output : child.outputs) { - if (!output.getDependsOnReactions().isEmpty()){ - foundOne = true; - temp.pr("// Add port "+output.getFullName()+" to array of is_present fields."); - temp.startChannelIteration(output); - temp.pr("_lf_is_present_fields["+startTimeStepIsPresentCount+" + count] = &"+CUtil.portRef(output)+".is_present;"); - - // Intended_tag is only applicable to ports in federated execution with decentralized coordination. - temp.pr( - CExtensionUtils.surroundWithIfFederatedDecentralized( - String.join("\n", - "// Add port "+output.getFullName()+" to array of intended_tag fields.", - "_lf_intended_tag_fields["+startTimeStepIsPresentCount+" + count] = &"+CUtil.portRef(output)+".intended_tag;" - ))); - - temp.pr("count++;"); - channelCount += output.getWidth(); - temp.endChannelIteration(output); - } - } - startTimeStepIsPresentCount += channelCount * child.getTotalWidth(); - temp.endScopedBlock(); - temp.endScopedBlock(); - } - } - if (foundOne) startTimeStep.pr(temp.toString()); + startTimeStepIsPresentCount += channelCount * child.getTotalWidth(); + temp.endScopedBlock(); + temp.endScopedBlock(); + } } - - /** - * For each timer in the given reactor, generate initialization code for the offset - * and period fields. - * - * This method will also populate the global _lf_timer_triggers array, which is - * used to start all timers at the start of execution. - * - * @param instance A reactor instance. - */ - private void generateTimerInitializations(ReactorInstance instance) { - for (TimerInstance timer : instance.timers) { - if (!timer.isStartup()) { - initializeTriggerObjects.pr(CTimerGenerator.generateInitializer(timer)); - timerCount += timer.getParent().getTotalWidth(); - } - } + if (foundOne) startTimeStep.pr(temp.toString()); + } + + /** + * For each timer in the given reactor, generate initialization code for the offset and period + * fields. + * + *

This method will also populate the global _lf_timer_triggers array, which is used to start + * all timers at the start of execution. + * + * @param instance A reactor instance. + */ + private void generateTimerInitializations(ReactorInstance instance) { + for (TimerInstance timer : instance.timers) { + if (!timer.isStartup()) { + initializeTriggerObjects.pr(CTimerGenerator.generateInitializer(timer)); + timerCount += timer.getParent().getTotalWidth(); + } } - - /** - * Process a given .proto file. - * - * Run, if possible, the proto-c protocol buffer code generator to produce - * the required .h and .c files. - * @param filename Name of the file to process. - */ - public void processProtoFile(String filename) { - var protoc = commandFactory.createCommand( + } + + /** + * Process a given .proto file. + * + *

Run, if possible, the proto-c protocol buffer code generator to produce the required .h and + * .c files. + * + * @param filename Name of the file to process. + */ + public void processProtoFile(String filename) { + var protoc = + commandFactory.createCommand( "protoc-c", - List.of("--c_out="+this.fileConfig.getSrcGenPath(), filename), + List.of("--c_out=" + this.fileConfig.getSrcGenPath(), filename), fileConfig.srcPath); - if (protoc == null) { - errorReporter.reportError("Processing .proto files requires protoc-c >= 1.3.3."); - return; - } - var returnCode = protoc.run(); - if (returnCode == 0) { - var nameSansProto = filename.substring(0, filename.length() - 6); - targetConfig.compileAdditionalSources.add( - fileConfig.getSrcGenPath().resolve(nameSansProto + ".pb-c.c").toString() - ); - - targetConfig.compileLibraries.add("-l"); - targetConfig.compileLibraries.add("protobuf-c"); - targetConfig.compilerFlags.add("-lprotobuf-c"); - } else { - errorReporter.reportError("protoc-c returns error code " + returnCode); - } + if (protoc == null) { + errorReporter.reportError("Processing .proto files requires protoc-c >= 1.3.3."); + return; } - - /** - * Construct a unique type for the struct of the specified - * typed variable (port or action) of the specified reactor class. - * This is required to be the same as the type name returned by - * {@link #variableStructType(TriggerInstance)}. - */ - public static String variableStructType(Variable variable, Reactor reactor, boolean userFacing) { - return (userFacing ? reactor.getName().toLowerCase() : CUtil.getName(reactor)) +"_"+variable.getName()+"_t"; + var returnCode = protoc.run(); + if (returnCode == 0) { + var nameSansProto = filename.substring(0, filename.length() - 6); + targetConfig.compileAdditionalSources.add( + fileConfig.getSrcGenPath().resolve(nameSansProto + ".pb-c.c").toString()); + + targetConfig.compileLibraries.add("-l"); + targetConfig.compileLibraries.add("protobuf-c"); + targetConfig.compilerFlags.add("-lprotobuf-c"); + } else { + errorReporter.reportError("protoc-c returns error code " + returnCode); } - - /** - * Construct a unique type for the struct of the specified - * instance (port or action). - * This is required to be the same as the type name returned by - * {@link #variableStructType(Variable, Reactor, boolean)}. - * @param portOrAction The port or action instance. - * @return The name of the self struct. - */ - public static String variableStructType(TriggerInstance portOrAction) { - return CUtil.getName(portOrAction.getParent().reactorDefinition)+"_"+portOrAction.getName()+"_t"; + } + + /** + * Construct a unique type for the struct of the specified typed variable (port or action) of the + * specified reactor class. This is required to be the same as the type name returned by {@link + * #variableStructType(TriggerInstance)}. + */ + public static String variableStructType(Variable variable, Reactor reactor, boolean userFacing) { + return (userFacing ? reactor.getName().toLowerCase() : CUtil.getName(reactor)) + + "_" + + variable.getName() + + "_t"; + } + + /** + * Construct a unique type for the struct of the specified instance (port or action). This is + * required to be the same as the type name returned by {@link #variableStructType(Variable, + * Reactor, boolean)}. + * + * @param portOrAction The port or action instance. + * @return The name of the self struct. + */ + public static String variableStructType(TriggerInstance portOrAction) { + return CUtil.getName(portOrAction.getParent().reactorDefinition) + + "_" + + portOrAction.getName() + + "_t"; + } + + /** + * If tracing is turned on, then generate code that records the full name of the specified reactor + * instance in the trace table. If tracing is not turned on, do nothing. + * + * @param instance The reactor instance. + */ + private void generateTraceTableEntries(ReactorInstance instance) { + if (targetConfig.tracing != null) { + initializeTriggerObjects.pr(CTracingGenerator.generateTraceTableEntries(instance)); } - - /** - * If tracing is turned on, then generate code that records - * the full name of the specified reactor instance in the - * trace table. If tracing is not turned on, do nothing. - * @param instance The reactor instance. - */ - private void generateTraceTableEntries(ReactorInstance instance) { - if (targetConfig.tracing != null) { - initializeTriggerObjects.pr( - CTracingGenerator.generateTraceTableEntries(instance) - ); - } + } + + /** + * Generate code to instantiate the specified reactor instance and initialize it. + * + * @param instance A reactor instance. + */ + public void generateReactorInstance(ReactorInstance instance) { + var reactorClass = ASTUtils.toDefinition(instance.getDefinition().getReactorClass()); + var fullName = instance.getFullName(); + initializeTriggerObjects.pr( + "// ***** Start initializing " + fullName + " of class " + reactorClass.getName()); + // Generate the instance self struct containing parameters, state variables, + // and outputs (the "self" struct). + initializeTriggerObjects.pr( + CUtil.reactorRefName(instance) + + "[" + + CUtil.runtimeIndex(instance) + + "] = new_" + + CUtil.getName(reactorClass) + + "();"); + // Generate code to initialize the "self" struct in the + // _lf_initialize_trigger_objects function. + generateTraceTableEntries(instance); + generateReactorInstanceExtension(instance); + generateParameterInitialization(instance); + initializeOutputMultiports(instance); + initializeInputMultiports(instance); + recordBuiltinTriggers(instance); + watchdogCount += + CWatchdogGenerator.generateInitializeWatchdogs(initializeTriggerObjects, instance); + + // Next, initialize the "self" struct with state variables. + // These values may be expressions that refer to the parameter values defined above. + generateStateVariableInitializations(instance); + + // Generate trigger objects for the instance. + generateTimerInitializations(instance); + generateActionInitializations(instance); + generateInitializeActionToken(instance); + generateSetDeadline(instance); + generateModeStructure(instance); + + // Recursively generate code for the children. + for (ReactorInstance child : instance.children) { + // If this reactor is a placeholder for a bank of reactors, then generate + // an array of instances of reactors and create an enclosing for loop. + // Need to do this for each of the builders into which the code writes. + startTimeStep.startScopedBlock(child); + initializeTriggerObjects.startScopedBlock(child); + generateReactorInstance(child); + initializeTriggerObjects.endScopedBlock(); + startTimeStep.endScopedBlock(); } - /** - * Generate code to instantiate the specified reactor instance and - * initialize it. - * @param instance A reactor instance. - */ - public void generateReactorInstance(ReactorInstance instance) { - var reactorClass = ASTUtils.toDefinition(instance.getDefinition().getReactorClass()); - var fullName = instance.getFullName(); - initializeTriggerObjects.pr( - "// ***** Start initializing " + fullName + " of class " + reactorClass.getName()); - // Generate the instance self struct containing parameters, state variables, - // and outputs (the "self" struct). - initializeTriggerObjects.pr(CUtil.reactorRefName(instance)+"["+CUtil.runtimeIndex(instance)+"] = new_"+CUtil.getName(reactorClass)+"();"); - // Generate code to initialize the "self" struct in the - // _lf_initialize_trigger_objects function. - generateTraceTableEntries(instance); - generateReactorInstanceExtension(instance); - generateParameterInitialization(instance); - initializeOutputMultiports(instance); - initializeInputMultiports(instance); - recordBuiltinTriggers(instance); - watchdogCount += CWatchdogGenerator.generateInitializeWatchdogs(initializeTriggerObjects, instance); - - // Next, initialize the "self" struct with state variables. - // These values may be expressions that refer to the parameter values defined above. - generateStateVariableInitializations(instance); - - // Generate trigger objects for the instance. - generateTimerInitializations(instance); - generateActionInitializations(instance); - generateInitializeActionToken(instance); - generateSetDeadline(instance); - generateModeStructure(instance); - - // Recursively generate code for the children. - for (ReactorInstance child : instance.children) { - // If this reactor is a placeholder for a bank of reactors, then generate - // an array of instances of reactors and create an enclosing for loop. - // Need to do this for each of the builders into which the code writes. - startTimeStep.startScopedBlock(child); - initializeTriggerObjects.startScopedBlock(child); - generateReactorInstance(child); - initializeTriggerObjects.endScopedBlock(); - startTimeStep.endScopedBlock(); + // For this instance, define what must be done at the start of + // each time step. This sets up the tables that are used by the + // _lf_start_time_step() function in reactor_common.c. + // Note that this function is also run once at the end + // so that it can deallocate any memory. + generateStartTimeStep(instance); + initializeTriggerObjects.pr("//***** End initializing " + fullName); + } + + /** + * For each action of the specified reactor instance, generate initialization code for the offset + * and period fields. + * + * @param instance The reactor. + */ + private void generateActionInitializations(ReactorInstance instance) { + initializeTriggerObjects.pr(CActionGenerator.generateInitializers(instance)); + } + + /** + * Initialize actions by creating a lf_token_t in the self struct. This has the information + * required to allocate memory for the action payload. Skip any action that is not actually used + * as a trigger. + * + * @param reactor The reactor containing the actions. + */ + private void generateInitializeActionToken(ReactorInstance reactor) { + for (ActionInstance action : reactor.actions) { + // Skip this step if the action is not in use. + if (action.getParent().getTriggers().contains(action)) { + var type = getInferredType(action.getDefinition()); + var payloadSize = "0"; + if (!type.isUndefined()) { + var typeStr = types.getTargetType(type); + if (CUtil.isTokenType(type, types)) { + typeStr = CUtil.rootType(typeStr); + } + if (typeStr != null && !typeStr.equals("") && !typeStr.equals("void")) { + payloadSize = "sizeof(" + typeStr + ")"; + } } - // For this instance, define what must be done at the start of - // each time step. This sets up the tables that are used by the - // _lf_start_time_step() function in reactor_common.c. - // Note that this function is also run once at the end - // so that it can deallocate any memory. - generateStartTimeStep(instance); - initializeTriggerObjects.pr("//***** End initializing " + fullName); - } - - /** - * For each action of the specified reactor instance, generate initialization code - * for the offset and period fields. - * @param instance The reactor. - */ - private void generateActionInitializations(ReactorInstance instance) { - initializeTriggerObjects.pr(CActionGenerator.generateInitializers(instance)); + var selfStruct = CUtil.reactorRef(action.getParent()); + initializeTriggerObjects.pr( + CActionGenerator.generateTokenInitializer(selfStruct, action.getName(), payloadSize)); + } } - - /** - * Initialize actions by creating a lf_token_t in the self struct. - * This has the information required to allocate memory for the action payload. - * Skip any action that is not actually used as a trigger. - * @param reactor The reactor containing the actions. - */ - private void generateInitializeActionToken(ReactorInstance reactor) { - for (ActionInstance action : reactor.actions) { - // Skip this step if the action is not in use. - if (action.getParent().getTriggers().contains(action) - ) { - var type = getInferredType(action.getDefinition()); - var payloadSize = "0"; - if (!type.isUndefined()) { - var typeStr = types.getTargetType(type); - if (CUtil.isTokenType(type, types)) { - typeStr = CUtil.rootType(typeStr); - } - if (typeStr != null && !typeStr.equals("") && !typeStr.equals("void")) { - payloadSize = "sizeof("+typeStr+")"; - } - } - - var selfStruct = CUtil.reactorRef(action.getParent()); - initializeTriggerObjects.pr( - CActionGenerator.generateTokenInitializer( - selfStruct, action.getName(), payloadSize - ) - ); - } + } + + /** + * Generate code that is executed while the reactor instance is being initialized. This is + * provided as an extension point for subclasses. Normally, the reactions argument is the full + * list of reactions, but for the top-level of a federate, will be a subset of reactions that is + * relevant to the federate. + * + * @param instance The reactor instance. + */ + protected void generateReactorInstanceExtension(ReactorInstance instance) { + // Do nothing + } + + /** + * Generate code that initializes the state variables for a given instance. Unlike parameters, + * state variables are uniformly initialized for all instances of the same reactor. + * + * @param instance The reactor class instance + */ + protected void generateStateVariableInitializations(ReactorInstance instance) { + var reactorClass = instance.getDefinition().getReactorClass(); + var selfRef = CUtil.reactorRef(instance); + for (StateVar stateVar : allStateVars(toDefinition(reactorClass))) { + if (isInitialized(stateVar)) { + var mode = + stateVar.eContainer() instanceof Mode + ? instance.lookupModeInstance((Mode) stateVar.eContainer()) + : instance.getMode(false); + initializeTriggerObjects.pr( + CStateGenerator.generateInitializer(instance, selfRef, stateVar, mode, types)); + if (mode != null && stateVar.isReset()) { + modalStateResetCount += instance.getTotalWidth(); } + } } - - /** - * Generate code that is executed while the reactor instance is being initialized. - * This is provided as an extension point for subclasses. - * Normally, the reactions argument is the full list of reactions, - * but for the top-level of a federate, will be a subset of reactions that - * is relevant to the federate. - * @param instance The reactor instance. - */ - protected void generateReactorInstanceExtension(ReactorInstance instance) { - // Do nothing - } - - /** - * Generate code that initializes the state variables for a given instance. - * Unlike parameters, state variables are uniformly initialized for all instances - * of the same reactor. - * @param instance The reactor class instance - */ - protected void generateStateVariableInitializations(ReactorInstance instance) { - var reactorClass = instance.getDefinition().getReactorClass(); - var selfRef = CUtil.reactorRef(instance); - for (StateVar stateVar : allStateVars(toDefinition(reactorClass))) { - if (isInitialized(stateVar)) { - var mode = stateVar.eContainer() instanceof Mode ? - instance.lookupModeInstance((Mode) stateVar.eContainer()) : - instance.getMode(false); - initializeTriggerObjects.pr(CStateGenerator.generateInitializer( - instance, - selfRef, - stateVar, - mode, - types - )); - if (mode != null && stateVar.isReset()) { - modalStateResetCount += instance.getTotalWidth(); - } - } - } + } + + /** + * Generate code to set the deadline field of the reactions in the specified reactor instance. + * + * @param instance The reactor instance. + */ + private void generateSetDeadline(ReactorInstance instance) { + for (ReactionInstance reaction : instance.reactions) { + var selfRef = CUtil.reactorRef(reaction.getParent()) + "->_lf__reaction_" + reaction.index; + if (reaction.declaredDeadline != null) { + var deadline = reaction.declaredDeadline.maxDelay; + initializeTriggerObjects.pr( + selfRef + ".deadline = " + types.getTargetTimeExpr(deadline) + ";"); + } else { // No deadline. + initializeTriggerObjects.pr(selfRef + ".deadline = NEVER;"); + } } - - /** - * Generate code to set the deadline field of the reactions in the - * specified reactor instance. - * @param instance The reactor instance. - */ - private void generateSetDeadline(ReactorInstance instance) { - for (ReactionInstance reaction : instance.reactions) { - var selfRef = CUtil.reactorRef(reaction.getParent())+"->_lf__reaction_"+reaction.index; - if (reaction.declaredDeadline != null) { - var deadline = reaction.declaredDeadline.maxDelay; - initializeTriggerObjects.pr(selfRef+".deadline = "+types.getTargetTimeExpr(deadline)+";"); - } else { // No deadline. - initializeTriggerObjects.pr(selfRef+".deadline = NEVER;"); - } - } + } + + /** + * Generate code to initialize modes. + * + * @param instance The reactor instance. + */ + private void generateModeStructure(ReactorInstance instance) { + CModesGenerator.generateModeStructure(instance, initializeTriggerObjects); + if (!instance.modes.isEmpty()) { + modalReactorCount += instance.getTotalWidth(); } - - /** - * Generate code to initialize modes. - * @param instance The reactor instance. - */ - private void generateModeStructure(ReactorInstance instance) { - CModesGenerator.generateModeStructure(instance, initializeTriggerObjects); - if (!instance.modes.isEmpty()) { - modalReactorCount += instance.getTotalWidth(); - } + } + + /** + * Generate runtime initialization code for parameters of a given reactor instance + * + * @param instance The reactor instance. + */ + protected void generateParameterInitialization(ReactorInstance instance) { + var selfRef = CUtil.reactorRef(instance); + // Set the local bank_index variable so that initializers can use it. + initializeTriggerObjects.pr( + "bank_index = " + + CUtil.bankIndex(instance) + + ";" + + " SUPPRESS_UNUSED_WARNING(bank_index);"); + for (ParameterInstance parameter : instance.parameters) { + // NOTE: we now use the resolved literal value. For better efficiency, we could + // store constants in a global array and refer to its elements to avoid duplicate + // memory allocations. + // NOTE: If the parameter is initialized with a static initializer for an array + // or struct (the initialization expression is surrounded by { ... }), then we + // have to declare a static variable to ensure that the memory is put in data space + // and not on the stack. + // FIXME: Is there a better way to determine this than the string comparison? + var initializer = CParameterGenerator.getInitializer(parameter); + if (initializer.startsWith("{")) { + var temporaryVariableName = parameter.uniqueID(); + initializeTriggerObjects.pr( + String.join( + "\n", + "static " + + types.getVariableDeclaration(parameter.type, temporaryVariableName, true) + + " = " + + initializer + + ";", + selfRef + "->" + parameter.getName() + " = " + temporaryVariableName + ";")); + } else { + initializeTriggerObjects.pr( + selfRef + "->" + parameter.getName() + " = " + initializer + ";"); + } } - - /** - * Generate runtime initialization code for parameters of a given reactor instance - * @param instance The reactor instance. - */ - protected void generateParameterInitialization(ReactorInstance instance) { - var selfRef = CUtil.reactorRef(instance); - // Set the local bank_index variable so that initializers can use it. - initializeTriggerObjects.pr("bank_index = "+CUtil.bankIndex(instance)+";" - + " SUPPRESS_UNUSED_WARNING(bank_index);"); - for (ParameterInstance parameter : instance.parameters) { - // NOTE: we now use the resolved literal value. For better efficiency, we could - // store constants in a global array and refer to its elements to avoid duplicate - // memory allocations. - // NOTE: If the parameter is initialized with a static initializer for an array - // or struct (the initialization expression is surrounded by { ... }), then we - // have to declare a static variable to ensure that the memory is put in data space - // and not on the stack. - // FIXME: Is there a better way to determine this than the string comparison? - var initializer = CParameterGenerator.getInitializer(parameter); - if (initializer.startsWith("{")) { - var temporaryVariableName = parameter.uniqueID(); - initializeTriggerObjects.pr(String.join("\n", - "static "+types.getVariableDeclaration(parameter.type, temporaryVariableName, true)+" = "+initializer+";", - selfRef+"->"+parameter.getName()+" = "+temporaryVariableName+";" - )); - } else { - initializeTriggerObjects.pr(selfRef+"->"+parameter.getName()+" = "+initializer+";"); - } - } + } + + /** + * Generate code that mallocs memory for any output multiports. + * + * @param reactor The reactor instance. + */ + private void initializeOutputMultiports(ReactorInstance reactor) { + var reactorSelfStruct = CUtil.reactorRef(reactor); + for (PortInstance output : reactor.outputs) { + initializeTriggerObjects.pr( + CPortGenerator.initializeOutputMultiport(output, reactorSelfStruct)); } - - /** - * Generate code that mallocs memory for any output multiports. - * @param reactor The reactor instance. - */ - private void initializeOutputMultiports(ReactorInstance reactor) { - var reactorSelfStruct = CUtil.reactorRef(reactor); - for (PortInstance output : reactor.outputs) { - initializeTriggerObjects.pr(CPortGenerator.initializeOutputMultiport( - output, - reactorSelfStruct - )); - } + } + + /** + * Allocate memory for inputs. + * + * @param reactor The reactor. + */ + private void initializeInputMultiports(ReactorInstance reactor) { + var reactorSelfStruct = CUtil.reactorRef(reactor); + for (PortInstance input : reactor.inputs) { + initializeTriggerObjects.pr( + CPortGenerator.initializeInputMultiport(input, reactorSelfStruct)); } - - /** - * Allocate memory for inputs. - * @param reactor The reactor. - */ - private void initializeInputMultiports(ReactorInstance reactor) { - var reactorSelfStruct = CUtil.reactorRef(reactor); - for (PortInstance input : reactor.inputs) { - initializeTriggerObjects.pr(CPortGenerator.initializeInputMultiport( - input, - reactorSelfStruct - )); - } + } + + @Override + public TargetTypes getTargetTypes() { + return types; + } + + /** + * Get the Docker generator. + * + * @param context + * @return + */ + protected DockerGenerator getDockerGenerator(LFGeneratorContext context) { + return new CDockerGenerator(context); + } + + // ////////////////////////////////////////// + // // Protected methods. + + // Perform set up that does not generate code + protected void setUpGeneralParameters() { + accommodatePhysicalActionsIfPresent(); + targetConfig.compileDefinitions.put("LOG_LEVEL", targetConfig.logLevel.ordinal() + ""); + targetConfig.compileAdditionalSources.addAll(CCoreFilesUtils.getCTargetSrc()); + // Create the main reactor instance if there is a main reactor. + createMainReactorInstance(); + if (hasModalReactors) { + // So that each separate compile knows about modal reactors, do this: + targetConfig.compileDefinitions.put("MODAL_REACTORS", "TRUE"); } - - @Override - public TargetTypes getTargetTypes() { - return types; + if (targetConfig.threading + && targetConfig.platformOptions.platform == Platform.ARDUINO + && (targetConfig.platformOptions.board == null + || !targetConfig.platformOptions.board.contains("mbed"))) { + // non-MBED boards should not use threading + System.out.println( + "Threading is incompatible on your current Arduino flavor. Setting threading to false."); + targetConfig.threading = false; } - /** - * Get the Docker generator. - * @param context - * @return - */ - protected DockerGenerator getDockerGenerator(LFGeneratorContext context) { - return new CDockerGenerator(context); + if (targetConfig.platformOptions.platform == Platform.ARDUINO + && !targetConfig.noCompile + && targetConfig.platformOptions.board == null) { + System.out.println( + "To enable compilation for the Arduino platform, you must specify the fully-qualified" + + " board name (FQBN) in the target property. For example, platform: {name: arduino," + + " board: arduino:avr:leonardo}. Entering \"no-compile\" mode and generating target" + + " code only."); + targetConfig.noCompile = true; } - // ////////////////////////////////////////// - // // Protected methods. - - // Perform set up that does not generate code - protected void setUpGeneralParameters() { - accommodatePhysicalActionsIfPresent(); - targetConfig.compileDefinitions.put("LOG_LEVEL", targetConfig.logLevel.ordinal() + ""); - targetConfig.compileAdditionalSources.addAll(CCoreFilesUtils.getCTargetSrc()); - // Create the main reactor instance if there is a main reactor. - createMainReactorInstance(); - if (hasModalReactors) { - // So that each separate compile knows about modal reactors, do this: - targetConfig.compileDefinitions.put("MODAL_REACTORS", "TRUE"); - } - if (targetConfig.threading && targetConfig.platformOptions.platform == Platform.ARDUINO - && (targetConfig.platformOptions.board == null || !targetConfig.platformOptions.board.contains("mbed"))) { - //non-MBED boards should not use threading - System.out.println("Threading is incompatible on your current Arduino flavor. Setting threading to false."); - targetConfig.threading = false; - } - - if (targetConfig.platformOptions.platform == Platform.ARDUINO && !targetConfig.noCompile - && targetConfig.platformOptions.board == null) { - System.out.println("To enable compilation for the Arduino platform, you must specify the fully-qualified board name (FQBN) in the target property. For example, platform: {name: arduino, board: arduino:avr:leonardo}. Entering \"no-compile\" mode and generating target code only."); - targetConfig.noCompile = true; - } - - if (targetConfig.platformOptions.platform == Platform.ZEPHYR && targetConfig.threading - && targetConfig.platformOptions.userThreads >= 0) { - targetConfig.compileDefinitions.put( - PlatformOption.USER_THREADS.name(), - String.valueOf(targetConfig.platformOptions.userThreads) - ); - } else if (targetConfig.platformOptions.userThreads > 0) { - errorReporter.reportWarning("Specifying user threads is only for threaded Lingua Franca on the Zephyr platform. This option will be ignored."); - } - - if (targetConfig.threading) { // FIXME: This logic is duplicated in CMake - pickScheduler(); - // FIXME: this and pickScheduler should be combined. - targetConfig.compileDefinitions.put( - "SCHEDULER", - targetConfig.schedulerType.name() - ); - targetConfig.compileDefinitions.put( - "NUMBER_OF_WORKERS", - String.valueOf(targetConfig.workers) - ); - } - pickCompilePlatform(); + if (targetConfig.platformOptions.platform == Platform.ZEPHYR + && targetConfig.threading + && targetConfig.platformOptions.userThreads >= 0) { + targetConfig.compileDefinitions.put( + PlatformOption.USER_THREADS.name(), + String.valueOf(targetConfig.platformOptions.userThreads)); + } else if (targetConfig.platformOptions.userThreads > 0) { + errorReporter.reportWarning( + "Specifying user threads is only for threaded Lingua Franca on the Zephyr platform. This" + + " option will be ignored."); } - protected void handleProtoFiles() { - // Handle .proto files. - for (String file : targetConfig.protoFiles) { - this.processProtoFile(file); - } + if (targetConfig.threading) { // FIXME: This logic is duplicated in CMake + pickScheduler(); + // FIXME: this and pickScheduler should be combined. + targetConfig.compileDefinitions.put("SCHEDULER", targetConfig.schedulerType.name()); + targetConfig.compileDefinitions.put( + "NUMBER_OF_WORKERS", String.valueOf(targetConfig.workers)); } + pickCompilePlatform(); + } - /** - * Generate code that needs to appear at the top of the generated - * C file, such as #define and #include statements. - */ - public String generateDirectives() { - CodeBuilder code = new CodeBuilder(); - code.prComment("Code generated by the Lingua Franca compiler from:"); - code.prComment("file:/" + FileUtil.toUnixString(fileConfig.srcFile)); - code.pr(CPreambleGenerator.generateDefineDirectives( - targetConfig, - fileConfig.getSrcGenPath(), - hasModalReactors - )); - code.pr(CPreambleGenerator.generateIncludeStatements( - targetConfig, - CCppMode - )); - return code.toString(); + protected void handleProtoFiles() { + // Handle .proto files. + for (String file : targetConfig.protoFiles) { + this.processProtoFile(file); } - - /** - * Generate top-level preamble code. - */ - protected String generateTopLevelPreambles(Reactor reactor) { - CodeBuilder builder = new CodeBuilder(); - var guard = "TOP_LEVEL_PREAMBLE_" + reactor.eContainer().hashCode() + "_H"; - builder.pr("#ifndef " + guard); - builder.pr("#define " + guard); - // Reactors that are instantiated by the specified reactor need to have - // their file-level preambles included. This needs to also include file-level - // preambles of base classes of those reactors. - Stream.concat(Stream.of(reactor), ASTUtils.allNestedClasses(reactor)) - .flatMap(it -> ASTUtils.allFileLevelPreambles(it).stream()) - .collect(Collectors.toSet()) - .forEach(it -> builder.pr(toText(it.getCode()))); - for (String file : targetConfig.protoFiles) { - var dotIndex = file.lastIndexOf("."); - var rootFilename = file; - if (dotIndex > 0) { - rootFilename = file.substring(0, dotIndex); - } - code.pr("#include " + addDoubleQuotes(rootFilename + ".pb-c.h")); - builder.pr("#include " + addDoubleQuotes(rootFilename + ".pb-c.h")); - } - builder.pr("#endif"); - return builder.toString(); + } + + /** + * Generate code that needs to appear at the top of the generated C file, such as #define and + * #include statements. + */ + public String generateDirectives() { + CodeBuilder code = new CodeBuilder(); + code.prComment("Code generated by the Lingua Franca compiler from:"); + code.prComment("file:/" + FileUtil.toUnixString(fileConfig.srcFile)); + code.pr( + CPreambleGenerator.generateDefineDirectives( + targetConfig, fileConfig.getSrcGenPath(), hasModalReactors)); + code.pr(CPreambleGenerator.generateIncludeStatements(targetConfig, CCppMode)); + return code.toString(); + } + + /** Generate top-level preamble code. */ + protected String generateTopLevelPreambles(Reactor reactor) { + CodeBuilder builder = new CodeBuilder(); + var guard = "TOP_LEVEL_PREAMBLE_" + reactor.eContainer().hashCode() + "_H"; + builder.pr("#ifndef " + guard); + builder.pr("#define " + guard); + // Reactors that are instantiated by the specified reactor need to have + // their file-level preambles included. This needs to also include file-level + // preambles of base classes of those reactors. + Stream.concat(Stream.of(reactor), ASTUtils.allNestedClasses(reactor)) + .flatMap(it -> ASTUtils.allFileLevelPreambles(it).stream()) + .collect(Collectors.toSet()) + .forEach(it -> builder.pr(toText(it.getCode()))); + for (String file : targetConfig.protoFiles) { + var dotIndex = file.lastIndexOf("."); + var rootFilename = file; + if (dotIndex > 0) { + rootFilename = file.substring(0, dotIndex); + } + code.pr("#include " + addDoubleQuotes(rootFilename + ".pb-c.h")); + builder.pr("#include " + addDoubleQuotes(rootFilename + ".pb-c.h")); } - - protected boolean targetLanguageIsCpp() { - return CCppMode; + builder.pr("#endif"); + return builder.toString(); + } + + protected boolean targetLanguageIsCpp() { + return CCppMode; + } + + /** + * Given a line of text from the output of a compiler, return an instance of ErrorFileAndLine if + * the line is recognized as the first line of an error message. Otherwise, return null. + * + * @param line A line of output from a compiler or other external tool that might generate errors. + * @return If the line is recognized as the start of an error message, then return a class + * containing the path to the file on which the error occurred (or null if there is none), the + * line number (or the string "1" if there is none), the character position (or the string "0" + * if there is none), and the message (or an empty string if there is none). + */ + @Override + public GeneratorBase.ErrorFileAndLine parseCommandOutput(String line) { + var matcher = compileErrorPattern.matcher(line); + if (matcher.find()) { + var result = new ErrorFileAndLine(); + result.filepath = matcher.group("path"); + result.line = matcher.group("line"); + result.character = matcher.group("column"); + result.message = matcher.group("message"); + + if (!result.message.toLowerCase().contains("error:")) { + result.isError = false; + } + return result; } - - /** Given a line of text from the output of a compiler, return - * an instance of ErrorFileAndLine if the line is recognized as - * the first line of an error message. Otherwise, return null. - * @param line A line of output from a compiler or other external - * tool that might generate errors. - * @return If the line is recognized as the start of an error message, - * then return a class containing the path to the file on which the - * error occurred (or null if there is none), the line number (or the - * string "1" if there is none), the character position (or the string - * "0" if there is none), and the message (or an empty string if there - * is none). - */ - @Override - public GeneratorBase.ErrorFileAndLine parseCommandOutput(String line) { - var matcher = compileErrorPattern.matcher(line); - if (matcher.find()) { - var result = new ErrorFileAndLine(); - result.filepath = matcher.group("path"); - result.line = matcher.group("line"); - result.character = matcher.group("column"); - result.message = matcher.group("message"); - - if (!result.message.toLowerCase().contains("error:")) { - result.isError = false; - } - return result; + return null; + } + + //////////////////////////////////////////// + //// Private methods. + + /** Returns the Target enum for this generator */ + @Override + public Target getTarget() { + return Target.C; + } + + //////////////////////////////////////////////////////////// + //// Private methods + + /** + * If a main or federated reactor has been declared, create a ReactorInstance for this top level. + * This will also assign levels to reactions, then, if the program is federated, perform an AST + * transformation to disconnect connections between federates. + */ + private void createMainReactorInstance() { + if (this.mainDef != null) { + if (this.main == null) { + // Recursively build instances. + this.main = new ReactorInstance(toDefinition(mainDef.getReactorClass()), errorReporter); + var reactionInstanceGraph = this.main.assignLevels(); + if (reactionInstanceGraph.nodeCount() > 0) { + errorReporter.reportError("Main reactor has causality cycles. Skipping code generation."); + return; } - return null; - } - - //////////////////////////////////////////// - //// Private methods. - - /** Returns the Target enum for this generator */ - @Override - public Target getTarget() { - return Target.C; - } - - //////////////////////////////////////////////////////////// - //// Private methods - - /** - * If a main or federated reactor has been declared, create a ReactorInstance - * for this top level. This will also assign levels to reactions, then, - * if the program is federated, perform an AST transformation to disconnect - * connections between federates. - */ - private void createMainReactorInstance() { - if (this.mainDef != null) { - if (this.main == null) { - // Recursively build instances. - this.main = new ReactorInstance(toDefinition(mainDef.getReactorClass()), errorReporter); - var reactionInstanceGraph = this.main.assignLevels(); - if (reactionInstanceGraph.nodeCount() > 0) { - errorReporter.reportError("Main reactor has causality cycles. Skipping code generation."); - return; - } - if (hasDeadlines) { - this.main.assignDeadlines(); - } - // Inform the run-time of the breadth/parallelism of the reaction graph - var breadth = reactionInstanceGraph.getBreadth(); - if (breadth == 0) { - errorReporter.reportWarning("The program has no reactions"); - } else { - targetConfig.compileDefinitions.put( - "LF_REACTION_GRAPH_BREADTH", - String.valueOf(reactionInstanceGraph.getBreadth()) - ); - } - } + if (hasDeadlines) { + this.main.assignDeadlines(); } - } - - /** - * Generate an array of self structs for the reactor - * and one for each of its children. - * @param r The reactor instance. - */ - private void generateSelfStructs(ReactorInstance r) { - initializeTriggerObjects.pr(CUtil.selfType(r)+"* "+CUtil.reactorRefName(r)+"["+r.getTotalWidth()+"];"); - initializeTriggerObjects.pr("SUPPRESS_UNUSED_WARNING("+CUtil.reactorRefName(r)+");"); - for (ReactorInstance child : r.children) { - generateSelfStructs(child); + // Inform the run-time of the breadth/parallelism of the reaction graph + var breadth = reactionInstanceGraph.getBreadth(); + if (breadth == 0) { + errorReporter.reportWarning("The program has no reactions"); + } else { + targetConfig.compileDefinitions.put( + "LF_REACTION_GRAPH_BREADTH", String.valueOf(reactionInstanceGraph.getBreadth())); } + } + } + } + + /** + * Generate an array of self structs for the reactor and one for each of its children. + * + * @param r The reactor instance. + */ + private void generateSelfStructs(ReactorInstance r) { + initializeTriggerObjects.pr( + CUtil.selfType(r) + "* " + CUtil.reactorRefName(r) + "[" + r.getTotalWidth() + "];"); + initializeTriggerObjects.pr("SUPPRESS_UNUSED_WARNING(" + CUtil.reactorRefName(r) + ");"); + for (ReactorInstance child : r.children) { + generateSelfStructs(child); } + } } From a3a6d5b510556dcf69c4efc0599ab893501ce13d Mon Sep 17 00:00:00 2001 From: erlingrj Date: Tue, 23 May 2023 14:59:39 -0700 Subject: [PATCH 13/23] Add makeZephyrCompatible configurator --- org.lflang.tests/src/org/lflang/tests/Configurators.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/org.lflang.tests/src/org/lflang/tests/Configurators.java b/org.lflang.tests/src/org/lflang/tests/Configurators.java index 21cb85fb43..a5db8f2abf 100644 --- a/org.lflang.tests/src/org/lflang/tests/Configurators.java +++ b/org.lflang.tests/src/org/lflang/tests/Configurators.java @@ -72,6 +72,14 @@ public static boolean makeZephyrCompatibleUnthreaded(LFTest test) { test.getContext().getTargetConfig().platformOptions.board = "qemu_cortex_a53"; return true; } + + public static boolean makeZephyrCompatible(LFTest test) { + test.getContext().getArgs().setProperty("tracing", "false"); + test.getContext().getTargetConfig().platformOptions.platform = Platform.ZEPHYR; + test.getContext().getTargetConfig().platformOptions.flash = false; + test.getContext().getTargetConfig().platformOptions.board = "qemu_cortex_a53"; + return true; + } /** * Make no changes to the configuration. * From 24e581aeb66c3d3d3fc65905e908cc259389f5c4 Mon Sep 17 00:00:00 2001 From: erlingrj Date: Tue, 23 May 2023 16:20:56 -0700 Subject: [PATCH 14/23] Run formatter --- test/C/src/zephyr/Timer.lf | 1 - test/C/src/zephyr/UserThreads.lf | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/test/C/src/zephyr/Timer.lf b/test/C/src/zephyr/Timer.lf index d3cb3f96de..a3dd0e68cc 100644 --- a/test/C/src/zephyr/Timer.lf +++ b/test/C/src/zephyr/Timer.lf @@ -3,7 +3,6 @@ target C { name: Zephyr, board: qemu_cortex_m3 }, - threading: false, timeout: 10 sec } diff --git a/test/C/src/zephyr/UserThreads.lf b/test/C/src/zephyr/UserThreads.lf index 744abb5748..115d8114a7 100644 --- a/test/C/src/zephyr/UserThreads.lf +++ b/test/C/src/zephyr/UserThreads.lf @@ -10,15 +10,15 @@ target C { } main reactor { - preamble {= + preamble {= + #include "platform.h" void func(void* arg) { lf_print("Hello from user thread"); - } + } - lf_thread_t thread_ids[3]; + lf_thread_t thread_ids[4]; =} - reaction(startup) {= int res; @@ -29,7 +29,7 @@ main reactor { } } - res = lf_thread_create(&thread_ids[i], &func, NULL); + res = lf_thread_create(&thread_ids[3], &func, NULL); if (res == 0) { lf_print_error_and_exit("Could create more threads than specified."); } else { From bf4ac760e074e418e846d2550f232f1594be3cd2 Mon Sep 17 00:00:00 2001 From: erlingrj Date: Thu, 25 May 2023 13:41:19 -0700 Subject: [PATCH 15/23] Try qemu_riscv32 instead --- org.lflang.tests/src/org/lflang/tests/Configurators.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang.tests/src/org/lflang/tests/Configurators.java b/org.lflang.tests/src/org/lflang/tests/Configurators.java index a5db8f2abf..9df77e134a 100644 --- a/org.lflang.tests/src/org/lflang/tests/Configurators.java +++ b/org.lflang.tests/src/org/lflang/tests/Configurators.java @@ -77,7 +77,7 @@ public static boolean makeZephyrCompatible(LFTest test) { test.getContext().getArgs().setProperty("tracing", "false"); test.getContext().getTargetConfig().platformOptions.platform = Platform.ZEPHYR; test.getContext().getTargetConfig().platformOptions.flash = false; - test.getContext().getTargetConfig().platformOptions.board = "qemu_cortex_a53"; + test.getContext().getTargetConfig().platformOptions.board = "qemu_riscv32"; return true; } /** From 597cdd738a912c553379f3bbdd60339bf79f5726 Mon Sep 17 00:00:00 2001 From: erlingrj Date: Thu, 25 May 2023 13:41:19 -0700 Subject: [PATCH 16/23] Use qemu_riscv32 with debug logging turned of for Zephyr tests --- .../src/org/lflang/tests/Configurators.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/Configurators.java b/org.lflang.tests/src/org/lflang/tests/Configurators.java index a5db8f2abf..bbc9d315ee 100644 --- a/org.lflang.tests/src/org/lflang/tests/Configurators.java +++ b/org.lflang.tests/src/org/lflang/tests/Configurators.java @@ -26,6 +26,7 @@ import org.lflang.TargetProperty; import org.lflang.TargetProperty.Platform; +import org.lflang.TargetProperty.LogLevel; import org.lflang.tests.TestRegistry.TestCategory; /** @@ -69,7 +70,10 @@ public static boolean makeZephyrCompatibleUnthreaded(LFTest test) { test.getContext().getTargetConfig().setByUser.add(TargetProperty.THREADING); test.getContext().getTargetConfig().platformOptions.platform = Platform.ZEPHYR; test.getContext().getTargetConfig().platformOptions.flash = false; - test.getContext().getTargetConfig().platformOptions.board = "qemu_cortex_a53"; + test.getContext().getTargetConfig().platformOptions.board = "qemu_riscv32"; + // FIXME: Zephyr qemu emulations fails with debug log-levels. + test.getContext().getTargetConfig().logLevel = LogLevel.WARN; + test.getContext().getArgs().setProperty("logging", "warning"); return true; } @@ -77,7 +81,11 @@ public static boolean makeZephyrCompatible(LFTest test) { test.getContext().getArgs().setProperty("tracing", "false"); test.getContext().getTargetConfig().platformOptions.platform = Platform.ZEPHYR; test.getContext().getTargetConfig().platformOptions.flash = false; - test.getContext().getTargetConfig().platformOptions.board = "qemu_cortex_a53"; + test.getContext().getTargetConfig().platformOptions.board = "qemu_riscv32"; + // FIXME: Zephyr qemu emulations fails with debug log-levels. + test.getContext().getTargetConfig().logLevel = LogLevel.WARN; + test.getContext().getArgs().setProperty("logging", "warning"); + return true; } /** From 3128b20430bb6c97ed2f89f5ff8a76c6a6e8ddc5 Mon Sep 17 00:00:00 2001 From: erlingrj Date: Mon, 29 May 2023 06:05:44 +0200 Subject: [PATCH 17/23] Bump reactor-c --- org.lflang/src/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index 98f13080c4..d43e973780 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 98f13080c4346a76aec92603509b1c5ac62abb9e +Subproject commit d43e9737804f2d984d52a99cac20d8e57adad543 From fa0fa3efd30a6281e337fb6d1957c7ab6dc483ee Mon Sep 17 00:00:00 2001 From: erlingrj Date: Mon, 29 May 2023 06:08:24 +0200 Subject: [PATCH 18/23] Bump reactor-c again --- .west/config | 4 ++++ org.lflang/src/lib/c/reactor-c | 2 +- org.lflang/src/lib/cpp/reactor-cpp | 2 +- test/C/lib/libapp.a | Bin 0 -> 74484 bytes 4 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 .west/config create mode 100644 test/C/lib/libapp.a diff --git a/.west/config b/.west/config new file mode 100644 index 0000000000..ccd607713e --- /dev/null +++ b/.west/config @@ -0,0 +1,4 @@ +[manifest] +path = /home/erling/dev/lf-west-template/deps/zephyr +file = west.yml + diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index d43e973780..c5613560e1 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit d43e9737804f2d984d52a99cac20d8e57adad543 +Subproject commit c5613560e160349154785ff5328ab8f1f764e504 diff --git a/org.lflang/src/lib/cpp/reactor-cpp b/org.lflang/src/lib/cpp/reactor-cpp index b607f1f640..e80cd36ce5 160000 --- a/org.lflang/src/lib/cpp/reactor-cpp +++ b/org.lflang/src/lib/cpp/reactor-cpp @@ -1 +1 @@ -Subproject commit b607f1f64083ab531e7a676b29de75f076aa1cde +Subproject commit e80cd36ce5bd625a7b167e7dfd65d25f78b0dd01 diff --git a/test/C/lib/libapp.a b/test/C/lib/libapp.a new file mode 100644 index 0000000000000000000000000000000000000000..d710e87c0ec4ba481729cee7abcf33a864b98114 GIT binary patch literal 74484 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 Date: Mon, 29 May 2023 06:53:39 +0200 Subject: [PATCH 19/23] Add ConcurrentTests to Zephyr and exclude the tracing specific concurrent tests --- .../src/org/lflang/tests/runtime/CZephyrTest.java | 12 ++++++++++++ util/RunZephyrTests.sh | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/org.lflang.tests/src/org/lflang/tests/runtime/CZephyrTest.java b/org.lflang.tests/src/org/lflang/tests/runtime/CZephyrTest.java index db5eb46fc3..079c6c18b7 100644 --- a/org.lflang.tests/src/org/lflang/tests/runtime/CZephyrTest.java +++ b/org.lflang.tests/src/org/lflang/tests/runtime/CZephyrTest.java @@ -66,4 +66,16 @@ public void buildGenericTests() { TestLevel.BUILD, false); } + @Test + public void buildConcurrentTests() { + Assumptions.assumeTrue(isLinux(), "Zephyr tests only run on Linux"); + + super.runTestsFor( + List.of(Target.C), + Message.DESC_CONCURRENT, + TestCategory.CONCURRENT::equals, + Configurators::makeZephyrCompatible, + TestLevel.BUILD, + false); + } } diff --git a/util/RunZephyrTests.sh b/util/RunZephyrTests.sh index 742a23ef18..c1079221d5 100755 --- a/util/RunZephyrTests.sh +++ b/util/RunZephyrTests.sh @@ -9,7 +9,7 @@ num_failures=0 failed_tests="" # Skip -skip=("FileReader" "FilePkgReader") +skip=("FileReader" "FilePkgReader" "Tracing" "ThreadedThreaded") find_kconfig_folders() { if [ -f "$folder/CMakeLists.txt" ]; then From 05f404553622c4c43c7c733a786b5cf5d996ae64 Mon Sep 17 00:00:00 2001 From: erlingrj Date: Mon, 29 May 2023 06:59:03 +0200 Subject: [PATCH 20/23] Print all skipped tests in RunZephyrTests.sh --- util/RunZephyrTests.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/util/RunZephyrTests.sh b/util/RunZephyrTests.sh index c1079221d5..ffb15198a9 100755 --- a/util/RunZephyrTests.sh +++ b/util/RunZephyrTests.sh @@ -116,8 +116,7 @@ else fi echo "Number of passes: $num_successes" echo "Number of fails: $num_failures" -echo "Skipped tests: $skip" - +echo "Skipped tests: ${skip[@]}" if [ "$overall_success" = false ]; then echo "Failed tests: $failed_tests" From a4a9feecd535ff7c285541f834cf5d2a0e85869c Mon Sep 17 00:00:00 2001 From: erlingrj Date: Mon, 29 May 2023 07:28:44 +0200 Subject: [PATCH 21/23] spotless --- org.lflang.tests/src/org/lflang/tests/Configurators.java | 6 +++--- .../src/org/lflang/tests/runtime/CZephyrTest.java | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/Configurators.java b/org.lflang.tests/src/org/lflang/tests/Configurators.java index bbc9d315ee..85a8465c29 100644 --- a/org.lflang.tests/src/org/lflang/tests/Configurators.java +++ b/org.lflang.tests/src/org/lflang/tests/Configurators.java @@ -25,8 +25,8 @@ package org.lflang.tests; import org.lflang.TargetProperty; -import org.lflang.TargetProperty.Platform; import org.lflang.TargetProperty.LogLevel; +import org.lflang.TargetProperty.Platform; import org.lflang.tests.TestRegistry.TestCategory; /** @@ -71,12 +71,12 @@ public static boolean makeZephyrCompatibleUnthreaded(LFTest test) { test.getContext().getTargetConfig().platformOptions.platform = Platform.ZEPHYR; test.getContext().getTargetConfig().platformOptions.flash = false; test.getContext().getTargetConfig().platformOptions.board = "qemu_riscv32"; - // FIXME: Zephyr qemu emulations fails with debug log-levels. + // FIXME: Zephyr qemu emulations fails with debug log-levels. test.getContext().getTargetConfig().logLevel = LogLevel.WARN; test.getContext().getArgs().setProperty("logging", "warning"); return true; } - + public static boolean makeZephyrCompatible(LFTest test) { test.getContext().getArgs().setProperty("tracing", "false"); test.getContext().getTargetConfig().platformOptions.platform = Platform.ZEPHYR; diff --git a/org.lflang.tests/src/org/lflang/tests/runtime/CZephyrTest.java b/org.lflang.tests/src/org/lflang/tests/runtime/CZephyrTest.java index 079c6c18b7..b29c2755d1 100644 --- a/org.lflang.tests/src/org/lflang/tests/runtime/CZephyrTest.java +++ b/org.lflang.tests/src/org/lflang/tests/runtime/CZephyrTest.java @@ -66,6 +66,7 @@ public void buildGenericTests() { TestLevel.BUILD, false); } + @Test public void buildConcurrentTests() { Assumptions.assumeTrue(isLinux(), "Zephyr tests only run on Linux"); From 0d731fb926b03632cde508cedfd81656bede4de9 Mon Sep 17 00:00:00 2001 From: erlingrj Date: Mon, 29 May 2023 08:14:02 +0200 Subject: [PATCH 22/23] Bump reactor-cpp --- org.lflang/src/lib/cpp/reactor-cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/lib/cpp/reactor-cpp b/org.lflang/src/lib/cpp/reactor-cpp index e80cd36ce5..b607f1f640 160000 --- a/org.lflang/src/lib/cpp/reactor-cpp +++ b/org.lflang/src/lib/cpp/reactor-cpp @@ -1 +1 @@ -Subproject commit e80cd36ce5bd625a7b167e7dfd65d25f78b0dd01 +Subproject commit b607f1f64083ab531e7a676b29de75f076aa1cde From eec9cd87278782dfdc9ddc70d6d07416a688503c Mon Sep 17 00:00:00 2001 From: erlingrj Date: Tue, 30 May 2023 16:04:22 +0200 Subject: [PATCH 23/23] Remove mistaken commit --- .west/config | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 .west/config diff --git a/.west/config b/.west/config deleted file mode 100644 index ccd607713e..0000000000 --- a/.west/config +++ /dev/null @@ -1,4 +0,0 @@ -[manifest] -path = /home/erling/dev/lf-west-template/deps/zephyr -file = west.yml -