From 901eea7964f94676ff97574f2c7ca66bce27583e Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Fri, 25 Feb 2022 23:18:18 -0600 Subject: [PATCH] Revert "Attempt to resolve merge conflicts with master. Tests not passing" This reverts commit dd6498313137189297ac0b8a7be1695b39654313, reversing changes made to 3e9d836015f2e8c9221cb9da075743f865018d4d. --- .github/scripts/test-lfc.sh | 4 - .github/workflows/lsp-tests.yml | 8 + .github/workflows/py-tests.yml | 8 + benchmark/C/Savina/src/BenchmarkRunner.lf | 2 +- .../C/Savina/src/concurrency/SortedList.lf | 23 +- .../C/Savina/src/parallelism/FilterBank.lf | 50 +- .../C/Savina/src/parallelism/PiPrecision.lf | 1 - .../runner/conf/target/lf-c-unthreaded.yaml | 2 +- benchmark/runner/parser.py | 10 +- example/Cpp/src/AlarmClock/AlarmClock.cmake | 9 - example/Cpp/src/AlarmClock/AlarmClock.lf | 40 - example/Cpp/src/AlarmClock/Clock.lf | 206 -- example/Cpp/src/AlarmClock/Network.lf | 225 -- example/Cpp/src/AlarmClock/README.md | 171 -- example/Cpp/src/AlarmClock/derivation.nix | 110 - example/Cpp/src/AlarmClock/flake.nix | 15 - example/Cpp/src/AlarmClock/images/clock.png | Bin 18704 -> 0 bytes .../src/AlarmClock/images/entire_program.png | Bin 56985 -> 0 bytes example/Cpp/src/AlarmClock/images/trigger.png | Bin 4745 -> 0 bytes example/Cpp/src/AlarmClock/shared_header.hpp | 34 - experimental/C/src/AnytimePrime.lf | 4 +- gradle.properties | 2 +- org.lflang.lfc/src/org/lflang/lfc/Main.java | 41 +- .../compiler/LinguaFrancaValidationTest.java | 47 +- org.lflang/META-INF/MANIFEST.MF | 3 +- org.lflang/pom.xml | 5 - org.lflang/src/org/lflang/ASTUtils.java | 171 +- org.lflang/src/org/lflang/FileConfig.java | 100 +- org.lflang/src/org/lflang/ModelInfo.java | 15 - .../lflang/federated/CGeneratorExtension.java | 14 +- .../src/org/lflang/federated/FedASTUtils.java | 46 +- .../lflang/federated/FederateInstance.java | 59 +- .../federated/PythonGeneratorExtension.java | 18 +- .../org/lflang/generator/GeneratorBase.xtend | 42 +- .../lflang/generator/JavaGeneratorUtils.java | 12 +- .../org/lflang/generator/NamedInstance.java | 28 +- .../lflang/generator/c/CCmakeCompiler.java | 3 + .../src/org/lflang/generator/c/CCompiler.java | 3 + .../org/lflang/generator/c/CGenerator.xtend | 1520 +++++++++--- .../generator/c/CParameterGenerator.java | 64 - .../generator/c/CPreambleGenerator.java | 33 - .../generator/c/CReactionGenerator.java | 803 ------ .../src/org/lflang/generator/c/CUtil.java | 36 - .../org/lflang/generator/cpp/CppGenerator.kt | 18 +- .../org/lflang/generator/python/PyUtil.java | 111 +- .../python/PythonActionGenerator.java | 13 - .../python/PythonDockerGenerator.java | 18 - .../generator/python/PythonGenerator.java | 938 ------- .../generator/python/PythonGenerator.xtend | 2159 +++++++++++++++++ .../generator/python/PythonInfoGenerator.java | 56 - .../python/PythonNetworkGenerator.java | 122 - .../python/PythonParameterGenerator.java | 157 -- .../generator/python/PythonPortGenerator.java | 256 -- .../python/PythonPreambleGenerator.java | 22 - .../python/PythonReactionGenerator.java | 560 ----- .../python/PythonReactorGenerator.java | 181 -- .../python/PythonStateGenerator.java | 41 - .../org/lflang/generator/ts/TSGenerator.kt | 5 +- .../org/lflang/validation/LFValidator.java | 1026 ++++---- pom.xml | 3 +- test/C/src/RepeatedInheritance.lf | 37 - test/C/src/federated/TopLevelArtifacts.lf | 11 +- test/Cpp/src/Timeout_Test.lf | 2 +- 63 files changed, 4246 insertions(+), 5477 deletions(-) delete mode 100644 example/Cpp/src/AlarmClock/AlarmClock.cmake delete mode 100644 example/Cpp/src/AlarmClock/AlarmClock.lf delete mode 100644 example/Cpp/src/AlarmClock/Clock.lf delete mode 100644 example/Cpp/src/AlarmClock/Network.lf delete mode 100644 example/Cpp/src/AlarmClock/README.md delete mode 100644 example/Cpp/src/AlarmClock/derivation.nix delete mode 100644 example/Cpp/src/AlarmClock/flake.nix delete mode 100755 example/Cpp/src/AlarmClock/images/clock.png delete mode 100755 example/Cpp/src/AlarmClock/images/entire_program.png delete mode 100755 example/Cpp/src/AlarmClock/images/trigger.png delete mode 100644 example/Cpp/src/AlarmClock/shared_header.hpp delete mode 100644 org.lflang/src/org/lflang/generator/c/CParameterGenerator.java delete mode 100644 org.lflang/src/org/lflang/generator/c/CPreambleGenerator.java delete mode 100644 org.lflang/src/org/lflang/generator/c/CReactionGenerator.java delete mode 100644 org.lflang/src/org/lflang/generator/python/PythonActionGenerator.java delete mode 100644 org.lflang/src/org/lflang/generator/python/PythonDockerGenerator.java delete mode 100644 org.lflang/src/org/lflang/generator/python/PythonGenerator.java create mode 100644 org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend delete mode 100644 org.lflang/src/org/lflang/generator/python/PythonInfoGenerator.java delete mode 100644 org.lflang/src/org/lflang/generator/python/PythonNetworkGenerator.java delete mode 100644 org.lflang/src/org/lflang/generator/python/PythonParameterGenerator.java delete mode 100644 org.lflang/src/org/lflang/generator/python/PythonPortGenerator.java delete mode 100644 org.lflang/src/org/lflang/generator/python/PythonPreambleGenerator.java delete mode 100644 org.lflang/src/org/lflang/generator/python/PythonReactionGenerator.java delete mode 100644 org.lflang/src/org/lflang/generator/python/PythonReactorGenerator.java delete mode 100644 org.lflang/src/org/lflang/generator/python/PythonStateGenerator.java delete mode 100644 test/C/src/RepeatedInheritance.lf diff --git a/.github/scripts/test-lfc.sh b/.github/scripts/test-lfc.sh index 3d4638a09d..c5132e1cd0 100755 --- a/.github/scripts/test-lfc.sh +++ b/.github/scripts/test-lfc.sh @@ -45,7 +45,6 @@ bin/lfc -f --rti rti test/C/src/Minimal.lf bin/lfc --federated --rti rti test/C/src/Minimal.lf # -h,--help Display this information. -bin/lfc -h bin/lfc --help # -l, --lint Enable linting during build. @@ -73,8 +72,5 @@ bin/lfc -threads 2 test/C/src/Minimal.lf # (Added no-compile to avoid adding dependency.) bin/lfc --target-compiler gcc --no-compile test/C/src/Minimal.lf -# --version -bin/lfc --version - # Ensure that lfc is robust to symbolic links. test_with_links "lfc" diff --git a/.github/workflows/lsp-tests.yml b/.github/workflows/lsp-tests.yml index 5ac5d1b883..5d240e420e 100644 --- a/.github/workflows/lsp-tests.yml +++ b/.github/workflows/lsp-tests.yml @@ -47,6 +47,14 @@ jobs: brew install protobuf brew install protobuf-c if: ${{ runner.os == 'macOS' }} + - name: Install dependencies Windows + uses: lukka/run-vcpkg@v4 + with: + vcpkgArguments: protobuf + vcpkgGitCommitId: 6185aa76504a5025f36754324abf307cc776f3da + vcpkgDirectory: ${{ github.workspace }}/vcpkg/ + vcpkgTriplet: x64-windows-static + if: ${{ runner.os == 'Windows' }} - name: Check out lingua-franca repository uses: actions/checkout@v2 with: diff --git a/.github/workflows/py-tests.yml b/.github/workflows/py-tests.yml index 213061e589..d70292bd8f 100644 --- a/.github/workflows/py-tests.yml +++ b/.github/workflows/py-tests.yml @@ -34,6 +34,14 @@ jobs: brew install protobuf brew install coreutils if: ${{ runner.os == 'macOS' }} + - name: Install dependencies Windows + uses: lukka/run-vcpkg@v4 + with: + vcpkgArguments: protobuf + vcpkgGitCommitId: 6185aa76504a5025f36754324abf307cc776f3da + vcpkgDirectory: ${{ github.workspace }}/vcpkg/ + vcpkgTriplet: x64-windows-static + if: ${{ runner.os == 'Windows' }} - name: Install Google API Python Client run: pip3 install --upgrade google-api-python-client - name: Check out lingua-franca repository diff --git a/benchmark/C/Savina/src/BenchmarkRunner.lf b/benchmark/C/Savina/src/BenchmarkRunner.lf index 9280e28d73..18142b2358 100644 --- a/benchmark/C/Savina/src/BenchmarkRunner.lf +++ b/benchmark/C/Savina/src/BenchmarkRunner.lf @@ -80,7 +80,7 @@ reactor BenchmarkRunner(num_iterations:int(12)) { self->measuredTimes[self->count] = duration; self->count += 1; - printf("Iteration %d - %.3f ms\n", self->count, toMS(duration)); + printf("Iteration: %d\t Duration: %.3f msec\n", self->count, toMS(duration)); schedule(nextIteration, 0); diff --git a/benchmark/C/Savina/src/concurrency/SortedList.lf b/benchmark/C/Savina/src/concurrency/SortedList.lf index 9519de8c05..b1278a1dda 100644 --- a/benchmark/C/Savina/src/concurrency/SortedList.lf +++ b/benchmark/C/Savina/src/concurrency/SortedList.lf @@ -123,33 +123,30 @@ reactor Worker( self->requests_sent++; } else { SET(finished, true); - self->requests_sent = 0; } =} } reactor LinkedList(num_workers: int(20)) { - state data_list: {=SortedLinkedList*=}; + state data_list: SortedLinkedList; state responses_to_send: int[]; logical action send_responses; - input finished: bool; + input print_result: bool; input[num_workers] requests: message_t; output[num_workers] responses: int; reaction(startup) {= - self->data_list = new SortedLinkedList(); + self->data_list = SortedLinkedList(); self->responses_to_send = (int*) malloc(self->num_workers * sizeof(int)); =} - reaction(finished) {= + reaction(print_result) {= // check result - info_print("List Size = %d", self->data_list->size()); - // reset local state - delete self->data_list; - self->data_list = new SortedLinkedList(); + info_print("List Size = %d", self->data_list.size()); + // no need to reset local state =} reaction(send_responses) -> responses {= @@ -166,19 +163,19 @@ reactor LinkedList(num_workers: int(20)) { int value = requests[i]->value.value; switch (requests[i]->value.type) { case CONTAINS: - self->responses_to_send[i] = self->data_list->contains(value); + self->responses_to_send[i] = self->data_list.contains(value); LOG_PRINT( "Worker %ld checks if %d is contained in the list", i, value ); break; case WRITE: - self->data_list->add(value); + self->data_list.add(value); self->responses_to_send[i] = value; LOG_PRINT("Worker %ld writes %d", i, value); break; case SIZE: - self->responses_to_send[i] = self->data_list->size(); + self->responses_to_send[i] = self->data_list.size(); LOG_PRINT("Worker %ld reads the list size", i); break; } @@ -379,7 +376,7 @@ main reactor(numIterations:int(12), numWorkers:int(20), numMessagesPerWorker:int (runner.start)+ -> manager.start, workers.doWork; manager.finished -> runner.finish; - manager.finished -> sorted_list.finished; + manager.finished -> sorted_list.print_result; workers.request -> sorted_list.requests; sorted_list.responses -> workers.response; diff --git a/benchmark/C/Savina/src/parallelism/FilterBank.lf b/benchmark/C/Savina/src/parallelism/FilterBank.lf index 5b1e09f0dd..74cdf90e02 100644 --- a/benchmark/C/Savina/src/parallelism/FilterBank.lf +++ b/benchmark/C/Savina/src/parallelism/FilterBank.lf @@ -39,7 +39,12 @@ target C { ]]] */ threads: 0, /// [[[end]]] - fast: true + fast: true, + /* [[[cog + cog.outl(f'timeout: {time_steps} msec // Specifies number of samples.') + ]]] */ + //timeout: 34816 msec // Specifies number of samples. + /// [[[end]]] }; import BenchmarkRunner from "../BenchmarkRunner.lf"; @@ -55,7 +60,7 @@ reactor Producer { state cnt: int(0); reaction (start) -> send_next {= schedule(send_next, 0); - =} + =} reaction (send_next) -> next, finish, send_next {= if (self->cnt >= 34816) { // reset state variable @@ -74,15 +79,10 @@ reactor Producer { * then start over again at 0. */ reactor Source(maxValue:int(1000)) { - input in_finished: bool; input next: bool; output value: double; state current: size_t(0); - - reaction (in_finished) {= - self->current = 0; - =} reaction (next) -> value {= SET(value, self->current); @@ -94,8 +94,6 @@ reactor Source(maxValue:int(1000)) { reactor Bank(bank_index: size_t(0), columns: size_t(16384), channels: size_t(8)) { input in: double; output out: double; - - input in_finished: bool; delay0 = new Delay(length=columns); fir0 = new FirFilter(bank_index=bank_index, peek_length=columns, first = true); @@ -108,13 +106,7 @@ reactor Bank(bank_index: size_t(0), columns: size_t(16384), channels: size_t(8)) fir0.out -> sample.in; sample.out -> delay1.in; delay1.out -> fir1.in; - fir1.out -> out; - - in_finished -> delay0.in_finished; - in_finished -> fir0.in_finished; - in_finished -> sample.in_finished; - in_finished -> delay1.in_finished; - in_finished -> fir1.in_finished; + fir1.out -> out; } /** @@ -128,20 +120,11 @@ reactor Delay(length: size_t(16383), period:time(1 msec)) { input in: double; output out: double; - - input in_finished: bool; reaction(startup) {= self->buffer = calloc(self->length - 1, sizeof(double)); self->pointer = 0; =} - - reaction (in_finished) {= - for (size_t i = 0; i < self->length - 1; i++) { - self->buffer[i] = 0; - } - self->pointer = 0; - =} reaction(in) -> out {= // info_print("Delay %d output: %f", self->pointer, self->buffer[self->pointer]); @@ -177,8 +160,6 @@ reactor FirFilter(bank_index:size_t(0), peek_length:size_t(16384), first:bool(tr input in: double; output out: double; - - input in_finished: bool; reaction(startup) {= // Allocate local state. @@ -201,14 +182,6 @@ reactor FirFilter(bank_index:size_t(0), peek_length:size_t(16384), first:bool(tr } self->data_index = 0; =} - - reaction(in_finished) {= - for (size_t i = 0; i < self->peek_length; i++) { - self->data[i] = 0; - } - self->data_index = 0; - self->data_full = false; - =} reaction(in) -> out {= self->data[self->data_index++] = in->value; @@ -243,8 +216,6 @@ reactor SampleFilter(sample_rate: size_t(16384)) { input in: double; output out: double; - - input in_finished: bool; reaction(in) -> out {= if(self->samples_received == 0) { @@ -256,10 +227,6 @@ reactor SampleFilter(sample_rate: size_t(16384)) { } self->samples_received = (self->samples_received + 1) % self->sample_rate; =} - - reaction(in_finished) {= - in_finished = 0; - =} } /** @@ -311,7 +278,6 @@ main reactor (numIterations:int(12), columns: size_t(16384), channels: size_t(8) =} runner.start -> producer.start; producer.finish -> runner.finish; - (producer.finish)+ -> banks.in_finished; producer.next -> source.next; (source.value)+ -> banks.in; banks.out -> combine.inValues; diff --git a/benchmark/C/Savina/src/parallelism/PiPrecision.lf b/benchmark/C/Savina/src/parallelism/PiPrecision.lf index c0f0d79bb5..1530b35b36 100644 --- a/benchmark/C/Savina/src/parallelism/PiPrecision.lf +++ b/benchmark/C/Savina/src/parallelism/PiPrecision.lf @@ -115,7 +115,6 @@ reactor Manager(numWorkers:int(20), scale:uint32_t({=5000ul=})) { free(solution_pruned); mpf_clear(self->result); mpf_clear(self->tolerance); - self->termsRequested = 0; SET(finished, true); =} diff --git a/benchmark/runner/conf/target/lf-c-unthreaded.yaml b/benchmark/runner/conf/target/lf-c-unthreaded.yaml index e142d459a5..ffaa630aeb 100644 --- a/benchmark/runner/conf/target/lf-c-unthreaded.yaml +++ b/benchmark/runner/conf/target/lf-c-unthreaded.yaml @@ -8,6 +8,6 @@ gen: ["cog", "-r", "${args:benchmark.targets.lf-c.gen_args}", "-D", "threaded_runtime=False", "src/${benchmark.targets.lf-c.lf_file}"] compile: ["${lf_path}/bin/lfc", "src/${benchmark.targets.lf-c.lf_file}"] -run: ["bin/${benchmark.targets.lf-c.binary}"] +run: ["bash", "-c", "seq ${iterations} | xargs -I{} bin/${benchmark.targets.lf-c.binary}"] parser: _target_: "parser.parse_lfc_output" diff --git a/benchmark/runner/parser.py b/benchmark/runner/parser.py index e903c2ca61..01a44d6d41 100644 --- a/benchmark/runner/parser.py +++ b/benchmark/runner/parser.py @@ -35,8 +35,16 @@ def parse_lfcpp_output(lines): return times -parse_lfc_output = parse_lfcpp_output +def parse_lfc_output(lines): + times = [] + for line in lines: + prefix = "---- Elapsed physical time (in nsec): " + if line.startswith(prefix): + p = len(prefix) + ns = int(line[p:].replace(",", "")) + times.append(ns / 1000000.0) + return times def parse_lf_rust_output(lines): times = [] diff --git a/example/Cpp/src/AlarmClock/AlarmClock.cmake b/example/Cpp/src/AlarmClock/AlarmClock.cmake deleted file mode 100644 index d764890d3c..0000000000 --- a/example/Cpp/src/AlarmClock/AlarmClock.cmake +++ /dev/null @@ -1,9 +0,0 @@ -find_package (Threads) -find_package (Crow) - -set(CROW_HEADERS ../../Crow/include/) -set(CPP_SOURCES ${CMAKE_CURRENT_LIST_DIR}) - -target_link_libraries(${LF_MAIN_TARGET} ${CMAKE_THREAD_LIBS_INIT} Crow::Crow) -target_include_directories(${LF_MAIN_TARGET} PUBLIC ${CPP_SOURCES}) - diff --git a/example/Cpp/src/AlarmClock/AlarmClock.lf b/example/Cpp/src/AlarmClock/AlarmClock.lf deleted file mode 100644 index 338b219fdb..0000000000 --- a/example/Cpp/src/AlarmClock/AlarmClock.lf +++ /dev/null @@ -1,40 +0,0 @@ -/* -* This is a minimal example of an alarmclock implemeted using the -* features lingua franca supplies. -* -* This is just an extract and simplification from the main project -* which you can find here: https://github.com/revol-xut/lf-alarm-clock -* -* This file contains the networking implementation it is really just an -* simple socket application which parses simple http headers and respondes -* in text/plain -* -* @author Tassilo Tanneberer -*/ - -target Cpp{ - cmake-include: "AlarmClock.cmake", - keepalive: true -}; - -import Network from "./Network.lf"; -import Clock from "./Clock.lf"; - -#import Network.lf; -#import Clock.lf; - -main reactor AlarmClock { - clock = new Clock(); - network = new Network(); - - // additon of a new event - network.event -> clock.event; - network.delete_index -> clock.cancel_by_index; - clock.event_dump -> network.updated_events; - - reaction (startup) {= - std::cout << "Starting Lingua Franca AlarmClock" << std::endl; - =} -} - - diff --git a/example/Cpp/src/AlarmClock/Clock.lf b/example/Cpp/src/AlarmClock/Clock.lf deleted file mode 100644 index 30e3c64199..0000000000 --- a/example/Cpp/src/AlarmClock/Clock.lf +++ /dev/null @@ -1,206 +0,0 @@ -/* -* This is a minimal example of an alarmclock implemeted using the -* features lingua franca supplies. -* -* This is just an extract and simplification from the main project -* which you can find here: https://github.com/revol-xut/lf-alarm-clock -* -* Author: Tassilo Tanneberer -*/ - - -target Cpp{ - cmake-include: "AlarmClock.cmake", - keepalive: true -}; - -public preamble {= - #include "shared_header.hpp" -=} - -reactor Trigger { - private preamble {= - auto convert_to_relative = [](long time_stamp){ - const auto t = std::chrono::system_clock::now(); - std::chrono::seconds desired_time = std::chrono::seconds(time_stamp); - std::chrono::seconds current_time = - std::chrono::duration_cast(t.time_since_epoch()); - std::chrono::seconds delta_t = desired_time - current_time; - return delta_t; - }; - =} - - input input_event: {=Event=}; - input input_interrupt: long; - logical action interrupt; - logical action triggered_event: {=std::string=}; - state ignore_flag: bool; - - //the input_event will scheduled - reaction (input_event) -> triggered_event {= - if(input_event.is_present()) { - auto extracted = input_event.get().get(); - auto delta_t = convert_to_relative(extracted->time_stamp_); - triggered_event.schedule(extracted->message_, delta_t); - } - =} - - reaction (input_interrupt) -> interrupt {= - if(input_interrupt.is_present()){ - auto delta_t = convert_to_relative(*(input_interrupt.get().get())); - interrupt.schedule(delta_t); - } - =} - - // reaction which will be triggered when a event is due - reaction(triggered_event) {= - auto select_random_file = []{ - std::vector files; - for (const auto& file : std::filesystem::directory_iterator(kMusicDir)) { - files.push_back(file.path().filename()); - } - - return files[rand() % files.size()]; - }; - - // takes a random audio file and playes it with mpg321 - if(triggered_event.is_present() and not ignore_flag){ - std::cout << "Triggering Event: " << *(triggered_event.get().get()) << std::endl; - std::string command = std::string(kPlayerCommand) + " " + std::string(kMusicDir) + select_random_file(); - if( system(command.c_str()) != 0 ){ - std::runtime_error("mpc finished with non zero return value"); - } - } - ignore_flag = false; - =} - - reaction (interrupt) {= - ignore_flag = true; - =} -} - -reactor Clock { - // function which is used to check if a given event has already past - private preamble {= - auto time_over(const Event& event) noexcept -> bool { - const auto p1 = std::chrono::system_clock::now(); - auto current_time = std::chrono::duration_cast(p1.time_since_epoch()).count(); - return current_time > event.time_stamp_; - } - =} - - // trigger reactor which handles the execution of the scheduled reaction - trigger = new Trigger(); - // this event will be scheduled and added to persistent storage - input event: Event; - input cancel_by_index: std::size_t; - // list of events - output event_dump: {= std::vector =}; - // timer which triggers clear and save - timer maintance(10 sec, 30 sec); - - // persistant storage - state events: std::vector(); - - // reaction that appends new events which will be scheduled - // the newtwork reactor is updated - reaction (event) -> trigger.input_event, event_dump {= - if (event.is_present() and not time_over(*event.get())){ - trigger.input_event.set(*event.get()); - events.push_back(*event.get()); - event_dump.set(events); - } - =} - - // initiation ... reading file to create state - reaction (startup) -> trigger.input_event, event_dump {= - // if the calender file doesn't exists it's created - if (not std::filesystem::exists(kFile)){ - std::ofstream{kFile}; - } - - std::ifstream file; - file.open(kFile); - - std::string line; - if(not file.is_open()) { - throw std::runtime_error("Cannot open event file!"); - } - - // iterating through the file every line corresponds to one events (csv) - while (file) { - std::getline(file, line); - if (line.empty()) { - break; - } - - Event serialized_event {}; - - // an event has the shape message;time_stamp\newline - std::size_t colon_pos = line.find(";"); - serialized_event.message_ = line.substr(0, colon_pos); - serialized_event.time_stamp_ = static_cast( - std::stoi(line.substr(colon_pos + 1, line.size() - 2)) - ); - - // if the given event is not already in the past it gets schedules by lingua franca - if(not time_over(serialized_event)){ - trigger.input_event.set(serialized_event); - events.push_back(serialized_event); - } - } - - file.close(); - event_dump.set(events); - =} - - // state needs to be saved to file - reaction (shutdown, maintance) -> event_dump {= - remove_events(); - save(); - event_dump.set(events); - =} - - reaction (cancel_by_index) -> trigger.input_interrupt, event_dump {= - if(cancel_by_index.is_present()) { - std::size_t index = *(cancel_by_index.get().get()); - - if( index < events.size()){ - auto tag = events.at(index).time_stamp_; - trigger.input_interrupt.set(tag); - events.erase(events.begin() + index); - event_dump.set(events); - } - - } - =} - - method remove_events() {= - // list of element which can be removed in the next iteration - std::vector removed_indices; - std::size_t index = 0; - for(const Event& event: events) { - if (time_over(event)){ - removed_indices.push_back(index); - } - index++; - } - - std::size_t removed_counter = 0; - for (std::size_t i : removed_indices) { - events.erase(events.begin() + i - removed_counter); - removed_counter++; - } - =} - - method save() {= - std::ofstream file(kFile, std::ios::trunc); - - for (const Event& e : events ) { - file << e.message_ + ";" + std::to_string(e.time_stamp_) + "\n"; - } - - file.close(); - =} -} - diff --git a/example/Cpp/src/AlarmClock/Network.lf b/example/Cpp/src/AlarmClock/Network.lf deleted file mode 100644 index 630922078d..0000000000 --- a/example/Cpp/src/AlarmClock/Network.lf +++ /dev/null @@ -1,225 +0,0 @@ -/* -* This is a minimal example of an alarmclock implemeted using the -* features lingua franca supplies. -* -* This is just an extract and simplification from the main project -* which you can find here: https://github.com/revol-xut/lf-alarm-clock - -* This file contains the networking implementation it is really just an -* simple socket application which parses simple http headers and respondes -* in text/plain -* -* @author Tassilo Tanneberer -*/ - -target Cpp{ - cmake-include: "AlarmClock.cmake", - keepalive: true -}; - -public preamble {= - #include "shared_header.hpp" -=} - -reactor Network { - private preamble {= - #include - #include - #include - #include - #include - =} - - // physical event which is triggered by receiving a request - physical action new_event: Event; - physical action delete_request: std::size_t; - - // variables for the receive thread - state thread: std::thread; // receive thread - state events: std::vector; // copy - - input updated_events: std::vector; - output event: Event; // event which will be added to the clock - output delete_index: std::size_t; - - // this reaction transforms a physical action into a logical reaction - reaction (new_event) -> event {= - if(new_event.is_present()){ - event.set(new_event.get()); - } - =} - - reaction (delete_request) -> delete_index {= - if(delete_request.is_present()){ - delete_index.set(delete_request.get()); - } - =} - - // main starts receive thread - reaction (startup) -> delete_request, new_event{= - thread = std::thread([&] { - crow::SimpleApp app; - - // returns json of all the upcoming events - CROW_ROUTE(app, "/list") ([&]{ - // function converts unix timestamp to human readable datetime string - auto unix_to_human_readable = [](unsigned int time_stamp){ - using Clock = std::chrono::high_resolution_clock; - using TimePoint = std::chrono::time_point; - const Clock::duration duration_time_stamp = std::chrono::seconds(time_stamp); - const TimePoint chrono_time_point(duration_time_stamp); - std::time_t end_time = std::chrono::system_clock::to_time_t(chrono_time_point); - std::string return_string(std::ctime(&end_time)); - return return_string.substr(0, return_string.size() - 2); - }; - - crow::json::wvalue response; - for (const Event& event : events ){ - crow::json::wvalue json_event; - json_event["date"] = std::move(unix_to_human_readable(event.time_stamp_)); - json_event["message"] = event.message_; - - response[std::to_string(event.time_stamp_)] = std::move(json_event); - }; - return crow::response(response); - }); - - // adds new event by unix time stamp - CROW_ROUTE(app, "/add_event_timestamp").methods("POST"_method) - ([&new_event](const crow::request& req){ - auto json_body = crow::json::load(req.body); - if (!json_body) { - return crow::response(400); - } - - // maybe add extra input validation - Event serialized_event { - json_body["message"].s(), - static_cast(json_body["time_stamp"].u()) - }; - - // triggers physical action - new_event.schedule(serialized_event, 0ms); - crow::json::wvalue response; - response["success"] = true; - return crow::response(response); - }); - - // adds new event by relativ times - CROW_ROUTE(app, "/add_event_relative").methods("POST"_method) - ([&new_event](const crow::request& req){ - auto relativ_time = 0l; - auto json_body = crow::json::load(req.body); - if (!json_body) { - return crow::response(400); - } - - // calculates relative time in seconds - if(json_body.has("day")){ - relativ_time += 24 * 60 * 60 * json_body["day"].i(); - } - if(json_body.has("hour")){ - relativ_time += 60 * 60 * json_body["hour"].i(); - } - if(json_body.has("minute")){ - relativ_time += 60 * json_body["minute"].i(); - } - if(json_body.has("second")){ - relativ_time += json_body["second"].i(); - } - - const auto now = std::chrono::system_clock::now(); - auto current_time = std::chrono::duration_cast(now.time_since_epoch()).count(); - - std::cout << "current_time: " << current_time << " offset:" << relativ_time << std::endl; - Event serialized_event { - json_body["message"].s(), - current_time + relativ_time - }; - - // triggers physical action - new_event.schedule(serialized_event, 0ms); - crow::json::wvalue response; - response["success"] = true; - return crow::response(response); - }); - - // will set the timer in the text 24 hours - CROW_ROUTE(app, "/add_event_time").methods("POST"_method) - ([&new_event](const crow::request& req){ - // just % doesn't work because it is the remainder operator - // and does not behave like modulo for negative numbers - auto mod = [](int a, int b) { - int r = a % b; - return r < 0 ? r + b : r; - }; - - auto relativ_time = 0l; - auto json_body = crow::json::load(req.body); - if (!json_body) { - return crow::response(400); - } - - // use std::chrono::hh_mm_ss when C++20 is available - time_t time_struct = time(NULL); - struct tm *formatted_time = localtime(&time_struct); - - // calculating time differences and turning them into seconds for the time_stamp - if(json_body.has("hour")){ - relativ_time += 3600 * mod(json_body["hour"].i() - formatted_time->tm_hour - 1, 24); - } - if(json_body.has("minute")){ - relativ_time += 60 * mod(json_body["minute"].i() - formatted_time->tm_min - 1, 60); - } - if(json_body.has("second")){ - relativ_time += mod(json_body["second"].u() - formatted_time->tm_sec - 1, 60); - } - - Event serialized_event { - json_body["message"].s(), - relativ_time + time_struct - }; - - // triggers physical action - new_event.schedule(serialized_event, 0ms); - crow::json::wvalue response; - response["success"] = true; - return crow::response(response); - }); - - // request stopping playing music - // just used pidof to kill the process - CROW_ROUTE(app, "/stop") ([]{ - int status = system((std::string(kKillCommand) + " $(" + std::string(kPidofCommand) + " mpg321)").c_str()); - crow::json::wvalue response; - response["success"] = status; - return crow::response(response); - }); - - CROW_ROUTE(app, "/remove").methods("POST"_method) - ([&delete_request](const crow::request& req){ - auto json_body = crow::json::load(req.body); - if (!json_body) { - return crow::response(400); - } - - std::size_t index = json_body["index"].u(); - delete_request.schedule(index, 0s); - - crow::json::wvalue response; - response["success"] = true; - return crow::response(response); - }); - - // start the http server - app.port(kPort).multithreaded().run(); - }); - =} - reaction (updated_events) {= - events = std::move(*updated_events.get()); - =} - - reaction ( shutdown ) {= - thread.join(); - =} -} diff --git a/example/Cpp/src/AlarmClock/README.md b/example/Cpp/src/AlarmClock/README.md deleted file mode 100644 index 914f796dfd..0000000000 --- a/example/Cpp/src/AlarmClock/README.md +++ /dev/null @@ -1,171 +0,0 @@ -Lingua Franca Alarm Clock ----------------------------- - -**Contact:** - -**Main Repository:** [](https://github.com/revol-xut/lf-alarm-clock) - -A small and tiny alarmclock which is written using the scheduling and time features from lingua franca. - -## What you will learn - -- sharing state between reactors -- stopping scheduled events - -## Project - -![Programm Structure](./images/entire_program.png) - - -## Building - -**Dependencies:** jdk11, boost, mpg321, Crow - - -```bash - $ lfc ./AlarmClock.lf -``` - -**Building with nix** - -This cross compiles for aarch64. -``` - nix build .#packages.aarch64-linux.lf-alarm-clock -``` - -## Installation - -By default the AlarmClock expects the sound files to be placed in `~/music/AlarmClock/` you can change this -path by editing the `shared_header.cpp` file. Furthermore is it possible to configure paths to other binaries -in this file e.g. kill, mpg321 -commands. - -### Installing Crow from source - -On most distros, Crow needs to be build and installed from source: - -```bash - $ git clone git@github.com:CrowCpp/Crow.git - $ mkdir Crow/build - $ cd Crow/build - $ cmake -DCMAKE_INSTALL_PREFIX= - $ make install -``` -Note that you can adjust the preferred install location by replacing ``. - -To build the alarm clock using this manually installed version of Crow, simply run: -```bash - $ CMAKE_PREFIX_PATH= lfc ./AlarmClock.lf -``` - -## Endpoints & Usage - -### /list **GET** -Returns a list of upcoming events. - -```json - "timestamp": { - "date": "" - "message": "" - } -``` - -**Examples** - -``` -$ curl http://0.0.0.0:8680/list -``` - -### /stop **GET** -Stops the currently playing alarm sound. - -```json -{ - "success": "exit code" -} -``` - -**Examples** - -``` -$ curl http://0.0.0.0:8680/stop -``` - -### /add_event_timestamp **POST** -Will schedule your alarmclock for the given timestamp - -Request: -```json -{ - "message": "", - "time_stamp": 0 -} -``` -Response: -```json -{ - "success": true -} -``` - -**Examples** - -``` -$ curl http://0.0.0.0:8680/add_event_timestamp -X POST -H "Content-Type: text/json" -d '{"message": "test", "time_stamp": 1643400000}' -``` - -Schedules event for given timestamp. - -### /add_event_relative **POST** -Will schedule a event relative to the current time. - -Request -```json -{ - "days": 0, - "hours": 0, - "minutes": 0, - "seconds": 0 -} -``` - -Response: -``` -{ - "success": true -} -``` - -**Example** - -``` -$ curl http://0.0.0.0:8680/add_event_relative -X POST -H "Content-Type: text/json" -d '{"hour": 6, "minute":0, "second": 0, "message": "hello"}' -``` - -Schedules sets your alarmclock to activate in 6 hours. - -### /add_event_time **POST** -Schedule event for this time in the next 24 hours. If a parameter -is unspecified the current time is used. - -Request -```json -{ - "hour": 0, - "minute": 0, - "second": 0 -} -``` - -Response: -```json -{ - "success": true -} -``` -**Example** - -``` - $ curl http://0.0.0.0:8680/add_event_time -X POST -H "Content-Type: text/json" -d '{"message": "test", "hour": 6, "minute":0, "second": 0, "message": "hello"}' -``` - -Schedules the event for the next time the given time occures. diff --git a/example/Cpp/src/AlarmClock/derivation.nix b/example/Cpp/src/AlarmClock/derivation.nix deleted file mode 100644 index bd65751d5d..0000000000 --- a/example/Cpp/src/AlarmClock/derivation.nix +++ /dev/null @@ -1,110 +0,0 @@ -{stdenv, pkgs, lib, fetchFromGitHub,...}: -let - -lfc = stdenv.mkDerivation { - pname = "lfc"; - version = "0.1.0"; - - src = fetchFromGitHub { - owner = "revol-xut"; - repo = "lingua-franca-nix-releases"; - rev = "11c6d5297cd63bf0b365a68c5ca31ec80083bd05"; - sha256 = "DgxunzC8Ep0WdwChDHWgG5QJbJZ8UgQRXtP1HZqL9Jg="; - }; - - buildInputs = with pkgs; [ jdk11_headless ]; - - _JAVA_HOME = "${pkgs.jdk11_headless}/"; - - postPatch = '' - substituteInPlace bin/lfc \ - --replace 'base=`dirname $(dirname ''${abs_path})`' "base='$out'" \ - --replace "run_lfc_with_args" "${pkgs.jdk11_headless}/bin/java -jar $out/lib/jars/org.lflang.lfc-0.1.0-SNAPSHOT-all.jar" - ''; - - installPhase = '' - cp -r ./ $out/ - chmod +x $out/bin/lfc - ''; - - meta = with lib; { - description = "Polyglot coordination language"; - longDescription = '' - Lingua Franca (LF) is a polyglot coordination language for concurrent - and possibly time-sensitive applications ranging from low-level - embedded code to distributed cloud and edge applications. - ''; - homepage = "https://github.com/lf-lang/lingua-franca"; - license = licenses.bsd2; - platforms = platforms.linux; - maintainers = with maintainers; [ revol-xut ]; - }; -}; - -# downloading the cpp runtime -cpp-runtime = stdenv.mkDerivation { - name = "cpp-lingua-franca-runtime"; - - src = fetchFromGitHub { - owner = "lf-lang"; - repo = "reactor-cpp"; - rev = "007143225dbc198a5fee233ce125c3584a9541d8"; - sha256 = "sha256-wiBTJ4jSzoAu/Tg2cMqMWv7qZD29F+ysDOOF6F/DLJM="; - }; - - nativeBuildInputs = with pkgs; [ cmake gcc ]; - configurePhase = '' - echo "Configuration" - ''; - - buildPhase = '' - mkdir -p build - cd build - cmake .. -DCMAKE_INSTALL_PREFIX=./ - make install - ''; - - installPhase = '' - cp -r ./ $out/ - ''; - - fixupPhase = '' - echo "FIXUP PHASE SKIP" - ''; -}; - - - -in - stdenv.mkDerivation { - name = "alarm-clock"; - version = "0.0.1"; - - src = fetchFromGitHub { - owner = "revol-xut"; - repo = "lf-alarm-clock"; - rev = "8113a2c84db3d960d56455a91b288e8e7c584964"; - sha256 = "sha256-WHGSlqD5CUl4JhILZeiwB0/zXP+h6rsOuEvKzn5SqFA="; - fetchSubmodules = true; - }; - - buildInputs = with pkgs; [ lfc which gcc cmake git boost ]; - - configurePhase = '' - echo "Test"; - ''; - - buildPhase = '' - echo "Starting compiling" - mkdir -p include/reactor-cpp/ - ls -a ${cpp-runtime} - cp -r ${cpp-runtime}/include/reactor-cpp/* include/reactor-cpp/ - ${lfc}/bin/lfc --external-runtime-path ${cpp-runtime}/ src/AlarmClock.lf - ''; - - installPhase = '' - mkdir -p $out/bin - cp -r ./bin/* $out/bin - ''; - } - diff --git a/example/Cpp/src/AlarmClock/flake.nix b/example/Cpp/src/AlarmClock/flake.nix deleted file mode 100644 index a05f5f6cce..0000000000 --- a/example/Cpp/src/AlarmClock/flake.nix +++ /dev/null @@ -1,15 +0,0 @@ -{ - description = "build script for the lingua-franca alarm clock"; - - inputs = { - utils.url = "github:numtide/flake-utils"; - }; - - outputs = inputs@{self, utils, nixpkgs, ...}: - utils.lib.eachDefaultSystem (system: let - pkgs = nixpkgs.legacyPackages.${system}; - in rec { - packages.lf-alarm-clock = nixpkgs.legacyPackages.${system}.callPackage ./derivation.nix {}; - } - ); -} diff --git a/example/Cpp/src/AlarmClock/images/clock.png b/example/Cpp/src/AlarmClock/images/clock.png deleted file mode 100755 index 583f4dbd1c90f0a58b9e049f6b3585fb7a49352b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18704 zcmd74WmuKn_bt3lLKKjc5Rnj3O1c#Uq(iz9=~B7`6_DgwFf@dJ z*6!}+zCLUQ1_lWU32V|J!Jk73I~Sd7gCg5%=@ii?YL8epPj#Z^>P zg2Tgw=;FoFmEc8L8JS0c7qBM1UitY<9v&VQ_vVvi;C^#+?{LvQ{_`Q<2M->!HA>KB z6r*`y1>UB5DpE7wRUe0Ds*Fi+BwVzP)!dPA-W=_xjPEhVd>m;?S7h}iaBQ6UoklQz zH5p&6>Gk#~utfdV!@`v;xGN?(pa&m9#@z{sOHeGE< z`8ST1!ZE+~j*Ow=c(O^v9*`*3UNsJpJkr1W>~jCz@883QT0JB;JkSnZNYN~>ws6aT z*6P~lwoMRL`0)R=Z4l){#suXvBebO4^^N=|0V5oB<@e8~tJ)+(Pyf{FY6;z7AfhR? zV!rpLtlMV(8_Pb;8E%Wqd6;$N?D>g00{^=ZgS&lW>k%gTzC!4xb*`P;w9@9%r4feM zW>(YN+=mC~Iaa(o0j`yf;U;CWJ$T#ujfLAUB6Zu%3pP6aJC9T)?T?)U^o`~vQOO_T z{Tm;KD)UO{?6xZdp6JxHoxyd)HVv^6FEg&QDnk3%jkUhgI8MwBI71>&j#a zAA3}F+6I=YaMzNh?$MJzD0b3kYJ^LjK5L z{{Qo}ZBF}#DxF%1pS%0bU%!4?leTL>mWg82x&HU>-->%eFumv@GG4}m8E>DknT~ue zEoDdd5E!0KZ)|GHx7*Sb5EQf_^}!P0!KF;X8f+gPCO|!x>{*YPJ`^nj~~go zxW2cLxyEwYtS)mwB1=Dbf6pV`HYq8Il9Dn}x}%x!!GqbEnY!*c?*Hvd-CbRGrKP1q zLPC&_L_$hhZn=oQv$ON>k&JmoX}O0=7f7EpaU-Ph@k zF!26t|Q8~A@)${e%9R%Tv_oxW|t8-?=K$_(IOubuXImsOY`u8Whit+09cdrfV`wmb&9Q;FpM(A3iYZ=;&mYH2nU(eR!x?%n(W zkvtu@%#Ap1JIn2vR|?~BF?lK_`1JeU#8>YP40Mf*+SfBG%j+SR zpx+)|-`R-)q!|h9`9<&5IWyZ+}Y7&d$m%>M~q##Wl6irCWrw z2Jf}0iqjx|{P05F8biej0SNw>Q7L*nx|Wf4%pI(48O2$=4X!*wSEV zv7A=7X)YF*78V-ApD7{oii?~2`$J;bEw0ZjEZltg?%iWVe0)5zx<^FZewi%n@8#?J zwsPYHFsY zrt)Uf<1NVdb91W=wj60rQ%ipTurt^6->J8;u}Li}E1OtzG9Zhz`_$FhnXXmqQa*gt z&6l%$dU|s5abv9bf4$k#@^^2q%(LO6S67~ymY;@ysWjk7^Sm0)(U5J4W;OuiR|f;l zo^NtB7820W(-V@F#r{m255L#GJU?MEpHOb;^UkDs{``I44OZ`tHP(V?cLqS{QL<3yYx=*MfhwPhb>uWM>*5)u*h|59PDIr&Q*5*m8Va3Jk2 zjQD0<&Ic8Gsa3ZnYFK+Cs`@T{k%y zH{TKnIfuA9Gv3J5wA14b0aULa_|d#hY~Q|pgGwuN$HHMkAJ*q+b26Gq_qIO~YrpJ! z0n#j`Txxc90ud3B;(*)$wTFO(W4Y}DbQ-?R!peIvqmvu|uH32-han8cM?Ed&Df&6& zFB26N)tfAWd3vxWQtNVIO&XB?1Cqym92~cVgoGTya&bAi`%_a>ArgR~7Zw&|WMmT8 z7;hF+CbjnTv`kFIKIC65pYSFeq-z}@i@vxc@d(QGwRBaz{P)ekQyFVvWS zZ1coKKA%P8xMN@@M|(Ij;o;%s>viBmypNWBq`;w;(BAvom08YGsNeOj{_Cxjw6p^I zop&lp@~FJ;baax2be9FrU~~3W+F9_OEa1TXpd$Cy!Su9tb~d)Oh-A-O&pyzIeB?^506_= z@i{m+-(8_7`7YU*u9kb%vZ>1basAEP=;C?%Qe>4LSK2NC7aNJKNhq?*+m{lVr3Z0YX0o zrTQ;y%ghSy#gBG&E#2KYw;$u;^^LpFMKVw(*(;N9d_df}bEi7SDJUrDWvTh(!lemo zTW2RF48Pf*xZ^`XddPUwF{H-Ut}Z&e>Fwt-U_lyCp1&<`@{-iSww&v>#1vcG3 zNklLGy?LGbtH6Ezc$?;A5QGbd|SJHp(CRZauIk@w9-o6H=$+K-vu=|`dJyrc+uly!g3<)K8+ z5d{aE?W6K5wJwh~i+MrnH6^m7;b+sj>O+@z5?)@zvU{*r?-V`$Cu)R2aOL1Y_O#nr zw8!YJVf9~*ff5(LOI!o|)e(sD^73*@Dk_C4tGhmdfd&A76davdpFj7jb~;*}@U6xwgGilgTc0`s=hw22`!kp57$4eyo^k1MwFeLgg7tL_R6VkNiKgGe`dcr z?&qSbt6No7m1!;E=*U&PpOj7%85~T0RP3QRGBy?x94xD^H;GTD3cE$KLDm~3Lrx$` zo|Xy2Ax3g~e{rg^`v!{Y-hQTT)pEIyug+T|3)J$(bHiiMG zXm~!degx%BTwGkkll}ZZ^BZ=w20{h1klFvy@Y;>E0s}h zQOiD=zqakD|ER>IR(o|di-$Ll&IkRFp_bglboWK;^9(U-0r_0rmVh=a7pm~Eu+|B* zBs_p05b6=-7oKD;GTG+Ot+R~hdmr``It!r&Ckq%pz%t45}qZ2%w|eNl?1h2r6QMVw+fg4+Unmk6~VEv_+CoZ zUPU&&h}Lqoqb7SYj%elT>aqBS)Bl^mV)|tT&kJ*SfQ`No0ocAs|{;i8l&{kZT{PT>3W!OCjjzYhf%ts^<;hr}YE|IDh7 z$zRQPFFw2Cc>a!IO6@=|3oE=^H+ch7b!S4$y9fDqB z#LzG_V|M&;kS(l=fA@0c8h&-D2l0s*dMM`Zj)7~?+dekL(Ffrpp0*a8$Q?WNhg4xf z3636awOg}3n^VD49QNCfA{n%X{`*8qnhvSI!!bSV^G^L#^1qr78{mvJ#^8VAZ&+UW z!otE*-M9PBKDWb_@zQG<#zfb%GX_?T(|=AA%Bw6t2}e> zc^u-IlKO7RKN-5(dw7JO&R{W~oc4nYx38}c*%4I7d;Ur4efR+INXTu>htGQ|?-c8i zjCbceQwd9?ylU2}I@-Cdp5Ws17ta)Dk=uG1ODU(di|o@Q=1%u?N@?jH5!<<#UiFXX z^!kC$9_U0jaKXVSE85Lw-zz>{DY9xgY@6K^vMM z_yud6k+qK9*}H7y#&`Y^pnp^$Y#RIfy8zE_&3I)ec2t$Q8P(KF3Vm$TEeS(BjAvKq$CcArkHl*LM1eU3y1%aXXG4%Z(#@gla;|p>Z@DdAI z>?mIP+Z98?Z7acWHZVM_wDDNH0cB>!1B<~sd|YS z3IJtB3pG~KdUHZ$Xs`D=^sfOAd3~V0(v-CBYHOQ?2G)<1yF}O|00;u5j?(q!GKoS6x{` z!=dgiHc_GzqGBPZzy)GPvnmQW)|E+iudiRPYdUQd)8aKE8*tp-zQ=-sg0POluV3E; z>{xM6q-kP;1hB%rhdggQ?~!H}XOnW<&H$ww8j52w^h*aCepQQx)>bcijfy;jo`==z zOM;&FuzgyO4oI=<)3phHei+rNbi<>g(QYW1_i=IMvoD?-f1B9tr2zC~3e!v_9^ zNmgLJB6qkx`c_vkcQEOd+iea`&fxFgGl9pMg9}4nnTb2x7W(8VJ$+7d`a@o500Arm zR{F>EynFXGE{?R)ZY!sR&31j{+Dm|?hJ%@S&@u)Xur|P1jn;gs%n{5uxQ#(r_e03{kCbzDRaIVT=@Tj{p$)VpKUNrIe6h315tiy3h2an_b1MM)v zb1#u7jrDV=o(D8@0O&_5Y@QVw^!$ypo0?4yxM48l^1YyYhz7-4F-Dcnv)&J(j`TE|O zcHX;dQMGd$usa1CTgf+yLG?&_jW<$KIVBcat1>jW5SxW&<0a@vKw&IEUpx<;7%P^_ z#@yW8S0F}!CaJnO9EW)-bh$XCrNZy-?zVSw3XP5ZrHC;#TNurLW~R9^^fG(C1D@%n zgoNMFRnH_h*GDy*Ksg{tW)>Gy_4$XB#}}lPkK3%N13!5y8FXrEQ+yW4iREykp8pvT z5CD=+wW3Rj_thJLBnn`9c#bBloKF@b!(A4P0HsQNXYppzZI-vP9y8F@6_k+)nwor8 zT386&OpMi1cX+w9sw&a7YuBI)!yzH*9L!QOp|4YItgHJBi9t?IE{flk57-C=qWtCl z)H^_n7PGDV`UM#vQHExDFw1LJ@#2JPtJaK_Nvsbt3~Q*9Q&kK{60Km`^Te5HakZ8~ zJju*L8eDXBb@k)D9{wz6Q(t_#hu_=NisN_|lPIP?9g$|M7SFXkN%+Cd%)(A!!~1PW(EKattZfWw*lUspPzeq zdy_LU1p}AdTlP_xUbE_rm>BxKd-qsOM}i=nLqJu6fB-TM=w_ewbyG;*o*5ZWkuZO8 z5o*U5xeQ7+Hc;D;WHIB57jqB^QIN+IU)=zf#oOP%X>5$h)zuYwWuW!`LmXSJ{&}h} z4jf4=uTunYSTIE&V3W=@`9iZk0~ZC63jX2Z;mPJ|^5UZ3f**j?M*iUA>#JaPc6=NZ z74^p1`TUbK6(J#^jI#0@V`Ez6B>r|sE32&31rFkDt5zBkDrEax+CcX{S1s}a{C$nl za^Vf^zYjJx3Rho^qamGf+pYs0g#`?ufPetB*9Jh&BHTSa2Vkf2oo@>!?q3-?J6F*@ zwZFPJ8ij2Ree+8r+1&n5WaoD-V816R-r~ zZw@5u>EXXpU%QX~_U&6P>*YEizmuC$RIaz!lK%Z}M&W$jnMsPLi$Aivz+myogiKA( zuP1|AICW!V-+@{k09g|r{~OTMK+rs9$lcG<)zKjrOT7;Z4C}DB)YH<}C(A0RktBmf zMiy9J&I!CL-~rg$e&OMGii(O57Qqn_-(X$=bHH*2MMrxlCsPOu3#a`285$bu2H_-@ z8iJ%6_Vzy7*a&6FhXe(EhOBXTcnFJ(O-$?ybga$lpI7ioRFoHxh{${3b9X zqttd2DHH%r{7bF$9+V@{lJiSS^ntN`xzZB|5ohsHth%^TEDXNSn!YH4Y~yRJb*E&_X~$ougdjeX+c;#P}c9)L>2c*vt{BbhlxPB60FBrp^!)>1VaDiOsKl%}{Ex0*AcZvY&9 z9Iev6dZGSZ$X`Il8$ATsmlBsNC?w=J(B?*yc5CeI|Mdb?6sxuztq%J3C5t4>(;j4& z2@tuRY9@51-?zem*D9jT9VNcaVZLJxWun6rVWL(+A? zQ^V41JG ztixxN4vxn8*BGkz??VO4&FdO1G+;IzVQlt$;1(R*_lZC7=-$pWvA9Dnh==}_m6fVg z3wO*n^;Zn0KhnJVVsaK4wJ(55uc|sm&BqsSK3UP$o>UwKlZ1tXlPsI`1d`+W#)b-% zm{k%WI`o0&Ll6=Z50qOO3|_ne-U<{$dBHS@2H1%>czCzME(Kn&vHjg&8F;zE+XkS0 zZ~zmgU%l+Zs{>^%5S2KsmxD6HEP$?5u65y+IW>C<34Rdd3trgHBs}&qgSMg6l3re3 z>F;cKoQT+}=NCzjEX>q3`idw6U_(IIBVnFY9M@ziFC$}K^N?-28Ty?&P-{W8@p}?) z|8-^Hr>?fPOsd8A|IkXuH4Etl{Q)_82h&e!LN7m|XD;)oMbIxkqAw!D7iMO4eF-U= z2^1NXLjBdanS0}<77(PbbJ36PqNB@orS|F{bOOjWq7@M3VwX?GuTm#VoIi0y@`zvn1Hmw%JE(hH}hUyxWh}XixmY3%a ziPmopA+gC*5!zAXLZ-NR>sE&5+3D#!U0o}s@zSlyQj2NL>Xg;#v(j2E6v4=DKL_Ex zv(nGTE??8XFD$%AQv3v3&Pk_2HXz8*#_0mZHZnSzQ5|B^ec(D$c@$ux6kFF;jrN&A zv*%lq6xW9{+kv;zys3&3M<5hT-AGuc>44RJ03wKDmH{*g+7=eM`ut15c23ugbY2hy zPVc066+S69ZnmyWI6KStv z#Jjrs(D(rlI^?^}wL)J41|;?hS7>7Y0r2_+8p`dxy@9ORO$bFu+zsFE4jik)0vmud zlqvgLj`)H30bO4M5Uz} ztO6Rtp1x&K?U;vV@aD~%%ahq9V{d>Ez6SaQP`los(ybj+hy?Veh=!_0OQ=`c`Xhm> zixENNx_+M@=P%B1H{{;qGz(G;LHi7yA3zYwXc@P(PbIo7_WnfZ}aTSma+a5Uw(11a(c8SeRX+JZo5ImP;>nF;_QgQ6>a&v7y~n^UFYt* zr`)Y?e`yrlIgpc~8+iA;^jUZ&tEz%O;Ee5^9oZ{UFYYj2yB`ev?sfrvTD2+14r8!)lb81IRooUnN)n;Q4s!D zu@yea%FDloP`Gg$qqqHJ%5ja3S}Hc(cJv%FKa=qv8vnm?%O?CkQ&J2kdH|(?Fw?4= z?D!Ja;iHuma^*lg$OpVFmK{L=V`T2(X#m>Vc=G;OQ&ZFWNWP#O(T!}vBUgS#sVv#> zobB)7;Z0uH6q^+u)?4>`BxBh0fRk7}a;m4FA|`4&p0{#%K5uZFZ-<}@MLEQpD*jGe zyu|q_j$;Ml0Y7uzKmyCEiIAe)?csq52ZyP%eHu-Hp5PPbV%^7@2(^|z(=&urmq|x} zH2y~xs+iKVgvQLi4>gy^D=3}m4CJ;;o>i=_nBE>9GZSuhd`@myC>#f}BGhWDpmx|8HXfb_VDKp_0kCjb16wDzC#&hOhzGQ*dWQG6 z)F-t*gP-ZD#z&%#@uZn&V&?Z_iMF>&_M&+4>L(uIyeRR1vOjsZC{A|ByL0*Qlta~= zqiMmWkjCl*`=$YPH-paDYIafHbMnh!0>bS!&By$!FAFcFgSr2*(+4KFvqzh1+J$Xi zaBn+4ID6LaT`QL_pO}*34)WyO>Z(L1hG`8i_Hx18&>r z@TrEnMBDBpsAXhBLqm>7%&?CX@0%7-f1bu-xuuk2j3-3nj#n1wO*YQ z?e6a{bg@tW89Qlo<3+!y0uJpvi_wp8v?2j!sMu&YI5_`HS@luW)7QU`hnG5N**o?D zR^a*b=khpA&*p)zI0{!%RBM(RG8IqN4diP4rP-3vg3|w|o}?%2AhzcQL!s&6I-dYq z>e?%9bAJUFY89%m6VnKXar&aDeD_oX?p;gWJFS89BUCr8$3#fsWM5gkuI_8KKOZ*P z|5SPxUutnnD^0BN@oPy=*UNJay>5?9pcXR+;n~_cI^I_P6}AzTka*wdg{{Cv4SoWk z7qcaX8mey`-1B>4dU@A8x^&5np&^fnes<71EBZd4pUuMnmXf{tWn04z5(wU5zEP>EJvPm3m^2!_KZF!l zY;A|8yaMdsr>3QWSOv`6-G^noz*`mL2Xeus4qH(@(iIiTQxmt4AD=ozX zTu)bivJwW<1oG@ty{2M?Y?P~L{KpP(GT7Tz6kbuuIzby~1#tw%+V)9{_OPc!4Dr-O zg2>^lAcL}UPW`kKY)D|prPr#F5EBz?LP33H^rZxvfE=yb05Efm-TC4D{fUy8mlzSB zbD2wLQo7Ku>}*Jcvk)3UZOE0HYk>7`A=16fS1tpoR{?PW9t?QohMt~WIg@qJ1=c2s zUIo&gXwkW+D}poc_~axwI$9JsApm>u5gCTHP}E7Fx}c(I>*#!mj8qE0?+yr3*7(gb z8dB~_0iq`IjHikw0kn18iuj(b`{4}XJ zF~GD<++b_)%gtrz?#BDYPhspnv74>~^^k71a3To1@b|YAvUk|pS3bBsm>!MUU9sm`K55oFqXd#}* z{^>7+?_f_j17ZRLq6f$zCf4*2&Wbte-U3N8;E@=Ta~B{*@_*O41eT*&O@^=_w}H|X zB4aoSv^SEF2M^6}u!1~WVgWj-shGr9YC^vURSX3E6|S&Kum!!)u0vp8&?daS+U;Pd zIv7%WAqK{2=-#9~o`v{)2cbe0S}AIP?SM0LBlese9e2<7diH=Sl)RUv6W}z{318F`*u8fix1WY-|{c0|w5|%bS4(uDG{I4-H|$20jEkdTrSymA$ z_jDidVt^X*KdaRRV>4d&tN@63esMI-+89?T~KxNe^ z`|t%`fp`Or%mh4W+xz>!f=gD1vc-Yk0)2(GKb{RR_srkF!j_g`V=eauk!PtVAy-}< z)Q+JnherZl{Wc+B3(2(hldsBt_~PkXap<@{SX=+v{RsUps0(-O?2c$jfoyI7Ed-7g z+!phtmjpj5O&rnzhR}4qumNl-3@Hi$Je)y)is(xt@|>OdmaMVE-Q6D$3D31^ zVj&J*Ff%)VQHzO%<<~oDybs2c2EQik+!~9X?}qCukCxAjr=iT=7@N~qZyLDhsH;Of zdGe&hY`nQQNw8zm4ybcrp;ba4LzRObm=D5M4I~7ja>Hq@8;&)G?L2S*~~J<^bv#kz;)OaZ!9dh!{Lcl8)s)8*gnAK$>;y{ zc~gb$`K6G7jyUFRtYy6JPx_ssG=_2?oIMoQUvnpyy5L1V*;_*PX$Nb=3|6(^%zdNt zjC{XSoS|BNak1m>-MfG$cz}+00+8X>9ega1$|SM*xNyZjRb*(~!p6pSWW_d^sryoO zx|-$g?k;~a0v+F1@QW)v%>Ww?ADC}52AKl6Q&Ur+H)w+OKCatbVyBRi> ziS&cP{f^9h{r#ypIHG?4766or+*&NAlBh{{!Sk#Qo6{MpdezaClX0A2l1xxSLJuen zq9Aag-MKRl$#LxNJ~lZx^C;m8t*vj6V(t)xWgyB-#?SaUFn2Q8rM%mksy6m_U-pF% zE&!W5l>f|F+|;Bne&;SeZ0d~iKh%9P&0BDaxz>x%u86-Po=Kw}mz^sOo6|A0^ zWYi1{m~g5`wZyco-VNpH^XH8adKMNIQn4Jb;Bb%4+E7z_yU*HiZn6vwkXVl>DBOUl z#<+Xe2tE%cYH|vS8(>2Mq2L%OT?I3{<=!ul>4JgR27VSKF}w6l&mY09^p=zy^#Ddm0GxBI7|HWIGRN3y5hTmghm| zMS9912!JjI*#U^}cVH3M4diWx{3~z9ZZ;MMx&VmYd4+`y(Ah&}1JhG}X(@E%uLoN| zO$FSAJQ@@nj15n>2&g@?1UwFPUv8jm8au(FWb&DVGZn54-+&+s7$B*vjF6j~8z5Er z$NAg9{R6o(c)$v-Ku{!s7Rfp-`>{5jqrv@{f}*1uEa>?a;88|e%WXb>gwrT{dYaSR zXUfD#TQmLE>C}m9`mZ%73o`I{-jGpO?*y3#sr15yL0m_+6QS8_e=-Dt^fuO80*Y!Y z96Dvlj7UWlR^lh9E2<}^4)U#Zm~^6&lBuwTK#UUeNAtWone$~(u6DF37i~fs`IVIk zL9%>XNvd%Q*|Xf`qT=-6NF2RQjHdwvY3+eU0l)7&Ee3BZb+UBSfniJpa`-5_0fYsK zmoE`e$wB%9gd>9FWRUTn0h4SA8az~DU?Ju~CdmHt(Qay2hBh2l5Pl012M9$-{+h_i zcuPm_thw_*9{M<=v7)E&=)=oAunmyO1c^v6C*pw}0ob)XQC=v#u?$WrF7T)*Fc52o z2`Nt1mgjz9Ql#%PoHf&^SZTh?lHCB<-N)a*>jr_1)rSu=%YDg65ewE^l-j))2OP2U z;}*4xu)lJxU+g@BXTf;z!X2z7dGDIgMXCQ#!j@cyq%sH3P4)aa1_i}IB{E~^;eS3| z2$d$}b+nEaw5`bdFe`xg{rfkE~_5J`q2L>2Yz_pjMQ~r=e?F2w9QUgWSc4rqekfIb* z55?cSc?9R&vK>j4|2#>|AmwE5-ae|iIJe@wBQEXAh3V{Xr)B6tea8pt*!;@MQ}IW4 zcDxwb*?)o?1Bmq?yD0~xC0)aFUnYnXR~4U_SR6c3$53F4D`b>`@B~}9GvFWVttkzY zrNM6iJ^NwE=tpOlXIru~xC)u2vXh`Ya)QSNjw1Z-?v{e%Eab*7KYq+N&Kdu&6<;DW zm+CseuQf<*$~DfM5EQd8992#O*xfyRXU~Bb{Zsk=dLpojc$-#`w1B8RppE|AFB=Mo zP5cFUBgN}b@4X+UAu{rAirK39urQEIe*>Sv$~6ovEC z(*je6BsN)bWfvjyz=;WAs2J98hG#!_kD~(EfWF76@n)PBxs3vo@l5Bkek|hkU+CDhCL8@ zYze690^oY9bJ=AzZF+wEcx<~lkn36v2k+;QM(Howa4YQCC} zYx`t4UBJ{OV32>*NTj(zD$t>S$#b%!V`;uMZ1^a2%06C};`ho!HgEoSnKkCYJZ!39 zf#{54F}e@*tTvqeK-S7wj*nj$kGj6T|6W00qQ`CJ#N}p7^F7KV z*Q|2J6Eo-5_ZO=a`;LoFejQ`3vp6(}IeJ}Qdj_i*BAhcln(7)pv1t{$6;3ns>|qjv zv7tbR^;aL&Re$>?5WTplBX=B|&=###3@h``m~bk?mV9;f=CF(T{WziP?J6GkyJ{3C zv2;As@91l*_qheH&2_&!?40hj&=qSYW9Z%NDz-jfd5|fW`0~CF5whck=CUD*N%xJo zI8x65%W!a5n0s+CD`37qu%+5NqSh*5wV^@X-z@nG@ET0R$Rl`3EtW^-^V;io)4m-& zzwrElfh(mXx=Y;p((Qv{f=i51PHo}pdyA=M!`GX+dh3`R*UGFD#RvKsn9N;jD9I|& zi+(W%th)uPZ}COM+aexfd}4FAJ5t=03N7}1R(J#rY5h}JmK~#H2we(!-5vSiK)? z#iB?J89yNNxr1K5rtgt~mUeYH*xQTs7H9Ao#$8`o4;lG4vm3fTzBI32Ee@Gq1a>B2 z5kZ#~mm>&A53>tV(Wnet9^=Pd9@&~rN&b+Ez&}0u|MnQ`j(t_A4EQr%>A!sav&7|u z?aT0&Gk-qcZd>Aryc5)<#f>R&7N)3YONE_L!)@J>~#C9T|(=3BQwkrMgtKg z6t0gpr#7Y-b_d1T9hglSDMTdyMhO*O3BUAn+C7bNaPF_+Epy!@cem%57^_>T*RovAaV<(YLNReDoYrnN#qmG3C;p>6Pp;hcVSD1IN2+dfu zqpoH7V3$3%b?}w*tzd~2=5WDVcw4H`#XRr`@?SLw_e#w5)-+r*nWB4tc=9E^QDaYN zy~of$NK)sce`9Jk=3v0cbvl0O*m4e&<@h~;E7@r0NEN2TS!5$oAb~UC`2qgDORKPC z*_a2(u{|-bOHfNT0_F7H=I?y|l38*#hjw{gv)MIVrgotuy!Jtkf81X0)gs-{JQ|Rh}9>4i5YB1 z;D%`3ME5AL+%e*87MA`Nl4nVZ$Gg8jVb!CM=ZPiY-poD5hW01TRv259V)76 z_206Kv;MqzVyXQ6#v7%PMeWmoY&# z=;wbOt?c-Q=N-+*ohj>vYDI>!-uLhYP54R&n3v4>n~6B^9?{m;9P*HvFE?D3%abG=>RLDUQOB3Pm(hEH$!OG1`5+ahL}a2N zP9`c^M=?m6>M!2c7-IAMkBxGt{kV7F&k+`ieV8jlq@? z$-|an9hrYy^2{%;O%Sm+7v0}6;%#1s6=m{YiK;!;M1&#T`W}^d%^r7@=xq_sau=%CDR1!38U8xJ4brH?e3b88bznp)l3pQv;H~B zDO`*@4d}Ca+{(l#Y@{tc!mZVhNSykB&)~C^{W0l0HaGF+v+FG`^^uZs^=2Htd^Po9 zSG@Ie?`9k=UGGm2Qf^roe`nO3;=HiMW0^A|j3*QIG2>sqhpKGB?y5lzi{r-++S zqH9`g!4O$_keg?zs;a8@f(<4Uv(U44|Injng}Eb`%!N?mmR6C8V!IuNQ&%iQjVnI4 zM$7KYm0zD+up+4~=KS>+G2e@>n32CWqe?9;ULq$;LO2UE|J=H5z967srGa9Fa)y0= zT%F@aBT|1iEUEB;+N&3E6M?$qAQ^3`nK}a-($^ch{juAx!p9F{ENU{Ql;PE7-jma( zEG0nD!m$(hxdnJ)qEL5WSluBjBmZIpxKadk0qhUJ!E01|6{|v#I}v$! zUKhFuq*Y*kwa$W%WHzyo_xN`SXqyKA-%Bv5kVwGVU6tCg=k^|4BWz)?m`@Fk z>@@XyjP4JOnH)ZDL8)9TzqSz__tjTZIBnG`ASTCFAS-MH+J3Kl)m_WYrh(`sm-czv zvuKwkzbykh{GT~0aa-0>OIYy7O6mw-UDM51?mWDS_^fyHmLK$wBk>4Co?qg2`re+^ zI1U1F%<}cO8*n|i-mi=D^L3osfBqR99Q~g+ML|E^)zf1F$GQdv25P)~;1C+}LlM}x z59gYpZU6So)3dmuV%0Z&d2Owb_ZmX`!K+#9+LqU5X4#Y(+1Xv}veyvDqp0YZ_6*eO zE?z!UQ=0#CfrwAIIP|$9O>o|70}X-b08)ex2Zx?Pr0Mv$?04}Li&(6+5QgU+WHPqFY7ZzUk^K0Soa&YuEHF;1P zy?_52803oD+Q>#aOa$VUPE%iBFer=)ii(G+ds&&8`{(Ds=I3=e11Ll-Nkl-E2}uD| zRb5k)76`)W!Tx?2A3YtN0`MiLWoc=`z-_L~4-F}5Xlh zibC{zLc&%576S1;kff#h@ku8b*tod3OnZ(CMn_eEz%w*6+sOU>IQWeru)LrQeuo}* zMgY<3(q|}dW@bi9OIyI(bY%bScL>t8XJ=<(Xh?G*eI@OMhWM<6jf-C{%f!KP+>-u? z8iCLhmQiMd6Kr57#Y~1vrq|XcL?ICN_^)QKkqLPa$Ok)8yM%upWpEiaCCBd`y#rCmx==UD6w#^NkdC3tD<7;Fznatcw$gW zL;V0p7m`o~R_5H?tq2oOBSMuaIZK zUq4Q2ZWi`v&>6QX@Y!+`I4xxhZA)oyZl^{?um2AXebwXu diff --git a/example/Cpp/src/AlarmClock/images/entire_program.png b/example/Cpp/src/AlarmClock/images/entire_program.png deleted file mode 100755 index 8249326de1f48db187cdd36bfcebef7497fc3a49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56985 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 TqHzh>OJXuHv@odAcaHflGk;%) diff --git a/example/Cpp/src/AlarmClock/shared_header.hpp b/example/Cpp/src/AlarmClock/shared_header.hpp deleted file mode 100644 index 1221ea486a..0000000000 --- a/example/Cpp/src/AlarmClock/shared_header.hpp +++ /dev/null @@ -1,34 +0,0 @@ -/* -* This is a minimal example of an alarmclock implemeted using the -* features lingua franca supplies. -* -* This is just an extract and simplification from the main project -* which you can find here: https://github.com/revol-xut/lf-alarm-clock -* -* Author: Tassilo Tanneberer -*/ - -#ifndef SHARED_HEADER_INCLUDE_GUARD -#define SHARED_HEADER_INCLUDE_GUARD - -#include -#include -#include -#include -#include - -struct Event { - std::string message_; - long time_stamp_; -}; - -constexpr const char* kMusicDir = "./sounds/"; -constexpr const char* kFile = "./alarm_clock_events.csv"; -constexpr const char* kPlayerCommand = "mpg321"; -constexpr const char* kKillCommand = "kill"; -constexpr const char* kPidofCommand = "pidof"; - -constexpr unsigned short kPort = 8680; - -#endif //SHARED_HEADER_INCLUDE_GUARD - diff --git a/experimental/C/src/AnytimePrime.lf b/experimental/C/src/AnytimePrime.lf index 2fe50bef10..3e20b80598 100644 --- a/experimental/C/src/AnytimePrime.lf +++ b/experimental/C/src/AnytimePrime.lf @@ -32,7 +32,7 @@ reactor Prime { vector_t primes = vector_new(10000); vector_push(&primes, (void*)2); - while (!check_deadline(self, true)) { + while (!check_deadline(self)) { current_num++; int i = 0; for (i = 0; i < num_primes; i++) { @@ -55,7 +55,7 @@ reactor Prime { } reactor Print { - input in:{=long long=}; + input in:int; reaction(in) {= printf("Largest prime found within the deadline: %d\n", in->value); =} diff --git a/gradle.properties b/gradle.properties index 2bc765d3c5..4754184f83 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,7 +15,7 @@ lsp4jVersion=0.12.0 mwe2LaunchVersion=2.12.1 openTest4jVersion=1.2.0 resourcesVersion=3.16.0 -runtimeVersion=3.23.0 +runtimeVersion=3.24.0 shadowJarVersion=7.1.2 xtextGradleVersion=3.0.0 xtextVersion=2.25.0 diff --git a/org.lflang.lfc/src/org/lflang/lfc/Main.java b/org.lflang.lfc/src/org/lflang/lfc/Main.java index 829a854204..9eb7b1d9ce 100644 --- a/org.lflang.lfc/src/org/lflang/lfc/Main.java +++ b/org.lflang.lfc/src/org/lflang/lfc/Main.java @@ -46,14 +46,11 @@ /** * Standalone version of the Lingua Franca compiler (lfc). * - * @author {Marten Lohstroh } - * @author {Christian Menard } + * @author{Marten Lohstroh } + * @author{Christian Menard } */ public class Main { - /// current lfc version as printed by --version - private static final String VERSION = "0.1.0-beta"; - /** * Object for interpreting command line arguments. */ @@ -106,20 +103,19 @@ public class Main { * @author Marten Lohstroh */ enum CLIOption { - CLEAN("c", "clean", false, false, "Clean before building.", true), COMPILER(null, "target-compiler", true, false, "Target compiler to invoke.", true), - EXTERNAL_RUNTIME_PATH(null, "external-runtime-path", true, false, "Specify an external runtime library to be used by the compiled binary.", true), - FEDERATED("f", "federated", false, false, "Treat main reactor as federated.", false), + CLEAN("c", "clean", false, false, "Clean before building.", true), HELP("h", "help", false, false, "Display this information.", true), LINT("l", "lint", false, false, "Enable or disable linting of generated code.", true), NO_COMPILE("n", "no-compile", false, false, "Do not invoke target compiler.", true), + FEDERATED("f", "federated", false, false, "Treat main reactor as federated.", false), + THREADS("t", "threads", true, false, "Specify the default number of threads.", true), + SCHEDULER("s", "scheduler", true, false, "Specify the runtime scheduler (if supported).", true), OUTPUT_PATH("o", "output-path", true, false, "Specify the root output directory.", false), - QUIET("q", "quiet", false, false, "Suppress output of the target compiler and other commands", true), - RTI("r", "rti", true, false, "Specify the location of the RTI.", true), RUNTIME_VERSION(null, "runtime-version", true, false, "Specify the version of the runtime library used for compiling LF programs.", true), - SCHEDULER("s", "scheduler", true, false, "Specify the runtime scheduler (if supported).", true), - THREADS("t", "threads", true, false, "Specify the default number of threads.", true), - VERSION(null, "version", false, false, "Print version inforomation.", false); + EXTERNAL_RUNTIME_PATH(null, "external-runtime-path", true, false, "Specify an external runtime library to be used by the compiled binary.", true), + QUIET("q", "quiet", false, false, "Suppress output of the target compiler and other commands", true), + RTI("r", "rti", true, false, "Specify the location of the RTI.", true); /** * The corresponding Apache CLI Option object. @@ -199,18 +195,11 @@ public static void main(final String[] args) { try { main.cmd = parser.parse(options, args, true); - // If requested, print help and abort if (main.cmd.hasOption(CLIOption.HELP.option.getOpt())) { formatter.printHelp("lfc", options); System.exit(0); } - // If requested, print version and abort - if (main.cmd.hasOption(CLIOption.VERSION.option.getLongOpt())) { - System.out.println("lfc " + VERSION); - System.exit(0); - } - List files = main.cmd.getArgList(); if (files.size() < 1) { @@ -304,7 +293,7 @@ private void runGenerator(List files, Injector injector) { * If some errors were collected, print them and abort execution. Otherwise return. */ private void exitIfCollectedErrors() { - if (issueCollector.getErrorsOccurred() ) { + if (issueCollector.getErrorsOccurred()) { // if there are errors, don't print warnings. List errors = printErrorsIfAny(); String cause = errors.size() == 1 ? "previous error" @@ -330,7 +319,6 @@ public List printErrorsIfAny() { // visible in tests public Resource getValidatedResource(Path path) { final Resource resource = getResource(path); - assert resource != null; if (cmd != null && cmd.hasOption(CLIOption.FEDERATED.option.getOpt())) { if (!ASTUtils.makeFederated(resource)) { @@ -354,13 +342,8 @@ public Resource getValidatedResource(Path path) { return resource; } - private Resource getResource(Path path) { + public Resource getResource(Path path) { final ResourceSet set = this.resourceSetProvider.get(); - try { - return set.getResource(URI.createFileURI(path.toString()), true); - } catch (RuntimeException e) { - reporter.printFatalErrorAndExit(path + " is not an LF file. Use the .lf file extension to denote LF files."); - return null; - } + return set.getResource(URI.createFileURI(path.toString()), true); } } diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java index 77fa72d11f..75d49d89f8 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java @@ -484,45 +484,6 @@ public void connectionToEffectPort3() throws Exception { // reactor Foo { // input in:int; // } -// reactor Bar { -// input in:int; -// x1 = new Foo(); -// x2 = new Foo(); -// in -> x1.in; -// reaction(startup) -> x2.in {= -// =} -// } -// """ -// Java 11: - String testCase = String.join(System.getProperty("line.separator"), - "target C;", - "", - "reactor Foo {", - " input in:int;", - "}", - "reactor Bar {", - " input in:int;", - " x1 = new Foo();", - " x2 = new Foo();", - " in -> x1.in;", - " reaction(startup) -> x2.in {=", - " =}", - "}"); - validator.assertNoErrors(parseWithoutError(testCase)); - } - - /** - * Allow connection to the port of a contained reactor if another port with same name is effect of a reaction. - */ - @Test - public void connectionToEffectPort3_5() throws Exception { -// Java 17: -// String testCase = """ -// target C; -// -// reactor Foo { -// input in:int; -// } // main reactor { // input in:int; // x1 = new Foo(); @@ -547,8 +508,7 @@ public void connectionToEffectPort3_5() throws Exception { " reaction(startup) -> x2.in {=", " =}", "}"); - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getVariable(), null, - "Main reactor cannot have inputs."); + validator.assertNoErrors(parseWithoutError(testCase)); } /** @@ -2064,8 +2024,9 @@ public void testMultipleMainReactorUnnamed() throws Exception { "main reactor {}", "main reactor {}" ); - validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getReactor(), null, - "Multiple definitions of main or federated reactor."); + // TODO: Uncomment and fix test. See issue #905 on Github. + // validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getReactor(), null, + // "Multiple definitions of main or federated reactor."); } @Test diff --git a/org.lflang/META-INF/MANIFEST.MF b/org.lflang/META-INF/MANIFEST.MF index 72990dd479..4cde9f638e 100644 --- a/org.lflang/META-INF/MANIFEST.MF +++ b/org.lflang/META-INF/MANIFEST.MF @@ -22,8 +22,7 @@ Require-Bundle: org.eclipse.xtext, org.eclipse.lsp4j;bundle-version="0.12.0", com.fasterxml.jackson.core.jackson-core, com.fasterxml.jackson.core.jackson-annotations, - com.fasterxml.jackson.core.jackson-databind, - org.eclipse.core.runtime;bundle-version="3.23.0" + com.fasterxml.jackson.core.jackson-databind Bundle-RequiredExecutionEnvironment: JavaSE-11 Export-Package: org.lflang, org.lflang.generator, diff --git a/org.lflang/pom.xml b/org.lflang/pom.xml index a48eff8fae..f972ba405b 100644 --- a/org.lflang/pom.xml +++ b/org.lflang/pom.xml @@ -60,11 +60,6 @@ ${mwe2LaunchVersion} - - org.eclipse.platform - org.eclipse.core.runtime - ${runtimeVersion} - org.eclipse.xtext org.eclipse.xtext.common.types diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index 4a821000b6..f9de1f8083 100644 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ b/org.lflang/src/org/lflang/ASTUtils.java @@ -32,8 +32,9 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.function.Function; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Iterators; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; @@ -46,11 +47,12 @@ import org.eclipse.xtext.nodemodel.impl.HiddenLeafNode; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.xbase.lib.CollectionExtensions; import org.eclipse.xtext.xbase.lib.IterableExtensions; import org.eclipse.xtext.xbase.lib.IteratorExtensions; import org.eclipse.xtext.xbase.lib.StringExtensions; -import org.lflang.generator.CodeMap; import org.lflang.generator.GeneratorBase; +import org.lflang.generator.CodeMap; import org.lflang.generator.InvalidSourceException; import org.lflang.lf.Action; import org.lflang.lf.ActionOrigin; @@ -82,9 +84,6 @@ import org.lflang.lf.WidthSpec; import org.lflang.lf.WidthTerm; -import com.google.common.collect.Iterables; -import com.google.common.collect.Iterators; - /** * A helper class for modifying and analyzing the AST. * @author{Marten Lohstroh } @@ -448,7 +447,13 @@ public static String getUniqueIdentifier(Reactor reactor, String name) { * @param definition Reactor class definition. */ public static List allActions(Reactor definition) { - return ASTUtils.collectElements(definition, (Reactor r) -> r.getActions()); + List result = new ArrayList<>(); + List superClasses = convertToEmptyListIfNull(definition.getSuperClasses()); + for (ReactorDecl base : superClasses) { + result.addAll(allActions(toDefinition(base))); + } + result.addAll(definition.getActions()); + return result; } /** @@ -457,19 +462,28 @@ public static List allActions(Reactor definition) { * @param definition Reactor class definition. */ public static List allConnections(Reactor definition) { - return ASTUtils.collectElements(definition, (Reactor r) -> r.getConnections()); + List result = new ArrayList<>(); + List superClasses = convertToEmptyListIfNull(definition.getSuperClasses()); + for (ReactorDecl base : superClasses) { + result.addAll(allConnections(toDefinition(base))); + } + result.addAll(definition.getConnections()); + return result; } /** * Given a reactor class, return a list of all its inputs, * which includes inputs of base classes that it extends. - * If the base classes include a cycle, where X extends Y and Y extends X, - * then return only the input defined in the base class. - * The returned list may be empty. * @param definition Reactor class definition. */ public static List allInputs(Reactor definition) { - return ASTUtils.collectElements(definition, (Reactor r) -> r.getInputs()); + List result = new ArrayList<>(); + List superClasses = convertToEmptyListIfNull(definition.getSuperClasses()); + for (ReactorDecl base : superClasses) { + result.addAll(allInputs(toDefinition(base))); + } + result.addAll(definition.getInputs()); + return result; } /** @@ -478,7 +492,13 @@ public static List allInputs(Reactor definition) { * @param definition Reactor class definition. */ public static List allInstantiations(Reactor definition) { - return ASTUtils.collectElements(definition, (Reactor r) -> r.getInstantiations()); + List result = new ArrayList<>(); + List superClasses = convertToEmptyListIfNull(definition.getSuperClasses()); + for (ReactorDecl base : superClasses) { + result.addAll(allInstantiations(toDefinition(base))); + } + result.addAll(definition.getInstantiations()); + return result; } /** @@ -487,7 +507,13 @@ public static List allInstantiations(Reactor definition) { * @param definition Reactor class definition. */ public static List allOutputs(Reactor definition) { - return ASTUtils.collectElements(definition, (Reactor r) -> r.getOutputs()); + List result = new ArrayList<>(); + List superClasses = convertToEmptyListIfNull(definition.getSuperClasses()); + for (ReactorDecl base : superClasses) { + result.addAll(allOutputs(toDefinition(base))); + } + result.addAll(definition.getOutputs()); + return result; } /** @@ -496,7 +522,13 @@ public static List allOutputs(Reactor definition) { * @param definition Reactor class definition. */ public static List allParameters(Reactor definition) { - return ASTUtils.collectElements(definition, (Reactor r) -> r.getParameters()); + List result = new ArrayList<>(); + List superClasses = convertToEmptyListIfNull(definition.getSuperClasses()); + for (ReactorDecl base : superClasses) { + result.addAll(allParameters(toDefinition(base))); + } + result.addAll(definition.getParameters()); + return result; } /** @@ -505,7 +537,13 @@ public static List allParameters(Reactor definition) { * @param definition Reactor class definition. */ public static List allReactions(Reactor definition) { - return ASTUtils.collectElements(definition, (Reactor r) -> r.getReactions()); + List result = new ArrayList<>(); + List superClasses = convertToEmptyListIfNull(definition.getSuperClasses()); + for (ReactorDecl base : superClasses) { + result.addAll(allReactions(toDefinition(base))); + } + result.addAll(definition.getReactions()); + return result; } /** @@ -514,7 +552,13 @@ public static List allReactions(Reactor definition) { * @param definition Reactor class definition. */ public static List allStateVars(Reactor definition) { - return ASTUtils.collectElements(definition, (Reactor r) -> r.getStateVars()); + List result = new ArrayList<>(); + List superClasses = convertToEmptyListIfNull(definition.getSuperClasses()); + for (ReactorDecl base : superClasses) { + result.addAll(allStateVars(toDefinition(base))); + } + result.addAll(definition.getStateVars()); + return result; } /** @@ -523,43 +567,12 @@ public static List allStateVars(Reactor definition) { * @param definition Reactor class definition. */ public static List allTimers(Reactor definition) { - return ASTUtils.collectElements(definition, (Reactor r) -> r.getTimers()); - } - - /** - * Return all the superclasses of the specified reactor - * in deepest-first order. For example, if A extends B and C, and - * B and C both extend D, this will return the list [D, B, C, A]. - * Duplicates are removed. If the specified reactor does not extend - * any other reactor, then return an empty list. - * If a cycle is found, where X extends Y and Y extends X, or if - * a superclass is declared that is not found, then return null. - * @param reactor The specified reactor. - */ - public static LinkedHashSet superClasses(Reactor reactor) { - return superClasses(reactor, new LinkedHashSet()); - } - - /** - * Collect elements of type T from the class hierarchy defined by - * a given reactor definition. - * @param definition The reactor definition. - * @param elements A function that maps a reactor definition to a list of - * elements of type T. - * @param The type of elements to collect (e.g., Port, Timer, etc.) - * @return - */ - public static List collectElements(Reactor definition, Function> elements) { - List result = new ArrayList(); - // Add elements of elements defined in superclasses. - LinkedHashSet s = superClasses(definition); - if (s != null) { - for (Reactor superClass : s) { - result.addAll(elements.apply(superClass)); - } + List result = new ArrayList<>(); + List superClasses = convertToEmptyListIfNull(definition.getSuperClasses()); + for (ReactorDecl base : superClasses) { + result.addAll(allTimers(toDefinition(base))); } - // Add elements of the current reactor. - result.addAll(elements.apply(definition)); + result.addAll(definition.getTimers()); return result; } @@ -881,7 +894,7 @@ public static String baseType(Type type) { /** * Report whether the given literal is zero or not. - * @param literal AST node to inspect. + * @param literalOrCode AST node to inspect. * @return True if the given literal denotes the constant `0`, false * otherwise. */ @@ -984,7 +997,7 @@ public static boolean isValidTime(Value value) { /** * Report whether the given time denotes a valid time or not. - * @param t AST node to inspect. + * @param value AST node to inspect. * @return True if the argument denotes a valid time, false otherwise. */ public static boolean isValidTime(Time t) { @@ -997,7 +1010,7 @@ public static boolean isValidTime(Time t) { /** * Report whether the given parameter denotes time list, meaning it is a list * of which all elements are valid times. - * @param p AST node to inspect. + * @param value AST node to inspect. * @return True if the argument denotes a valid time list, false otherwise. */ // TODO: why does this function always return true ??? @@ -1081,7 +1094,7 @@ public static boolean isValidTimeList(Parameter p) { * ``` * * @param parameter The parameter. - * @param instantiations The (optional) list of instantiations. + * @param instantiation The (optional) instantiation. * * @return The value of the parameter. * @@ -1148,7 +1161,7 @@ public static List initialValue(Parameter parameter, List * belongs to the specified instantiation, meaning that it is defined in * the reactor class being instantiated or one of its base classes. * @param eobject The object. - * @param instantiation The instantiation. + * @param instnatiation The instantiation. */ public static boolean belongsTo(EObject eobject, Instantiation instantiation) { Reactor reactor = toDefinition(instantiation.getReactorClass()); @@ -1160,7 +1173,7 @@ public static boolean belongsTo(EObject eobject, Instantiation instantiation) { * belongs to the specified reactor, meaning that it is defined in * reactor class or one of its base classes. * @param eobject The object. - * @param reactor The reactor. + * @param instnatiation The instantiation. */ public static boolean belongsTo(EObject eobject, Reactor reactor) { if (eobject.eContainer() == reactor) return true; @@ -1177,7 +1190,7 @@ public static boolean belongsTo(EObject eobject, Reactor reactor) { * if it does not have an integer value. * If the value of the parameter is a list of integers, * return the sum of value in the list. - * The instantiations parameter is as in + * The instantiations parameter is as in * {@link initialValue(Parameter, List)}. * * @param parameter The parameter. @@ -1477,7 +1490,7 @@ public static boolean isGeneric(Reactor r) { * return the imported reactor class definition. Otherwise, * just return the argument. * @param r A Reactor or an ImportedReactor. - * @return The Reactor class definition or null if no definition is found. + * @return The Reactor class definition. */ public static Reactor toDefinition(ReactorDecl r) { if (r == null) @@ -1627,43 +1640,11 @@ public static TargetDecl targetDecl(Model model) { public static TargetDecl targetDecl(Resource model) { return IteratorExtensions.head(Iterators.filter(model.getAllContents(), TargetDecl.class)); } - - ///////////////////////////////////////////////////////// - //// Private methods - + /** * Returns the list if it is not null. Otherwise return an empty list. */ - public static List convertToEmptyListIfNull(List list) { + private static List convertToEmptyListIfNull(List list) { return list != null ? list : new ArrayList<>(); } - - /** - * Return all the superclasses of the specified reactor - * in deepest-first order. For example, if A extends B and C, and - * B and C both extend D, this will return the list [D, B, C, A]. - * Duplicates are removed. If the specified reactor does not extend - * any other reactor, then return an empty list. - * If a cycle is found, where X extends Y and Y extends X, or if - * a superclass is declared that is not found, then return null. - * @param reactor The specified reactor. - * @param extensions A set of reactors extending the specified reactor - * (used to detect circular extensions). - */ - private static LinkedHashSet superClasses(Reactor reactor, Set extensions) { - LinkedHashSet result = new LinkedHashSet(); - for (ReactorDecl superDecl : convertToEmptyListIfNull(reactor.getSuperClasses())) { - Reactor r = toDefinition(superDecl); - if (r == reactor || r == null) return null; - // If r is in the extensions, then we have a circular inheritance structure. - if (extensions.contains(r)) return null; - extensions.add(r); - LinkedHashSet baseExtends = superClasses(r, extensions); - extensions.remove(r); - if (baseExtends == null) return null; - result.addAll(baseExtends); - result.add(r); - } - return result; - } } diff --git a/org.lflang/src/org/lflang/FileConfig.java b/org.lflang/src/org/lflang/FileConfig.java index 889851304a..2f9b497f92 100644 --- a/org.lflang/src/org/lflang/FileConfig.java +++ b/org.lflang/src/org/lflang/FileConfig.java @@ -166,7 +166,7 @@ public FileConfig(Resource resource, Path srcGenBasePath, LFGeneratorContext con this.srcFile = toPath(this.resource); this.srcPath = srcFile.getParent(); - this.srcPkgPath = getPkgPath(resource); + this.srcPkgPath = getPkgPath(resource, context); this.srcGenBasePath = srcGenBasePath; this.name = nameWithoutExtension(this.srcFile); @@ -220,10 +220,12 @@ public IResource getIResource(Resource r) throws IOException { java.net.URI uri = toPath(r).toFile().toURI(); if (r.getURI().isPlatform()) { IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); - IFile[] files = workspaceRoot.findFilesForLocationURI(uri); - if (files != null && files.length > 0 && files[0] != null) { - iResource = files[0]; - } + if (uri != null) { + IFile[] files = workspaceRoot.findFilesForLocationURI(uri); + if (files != null && files.length > 0 && files[0] != null) { + iResource = files[0]; + } + } } else { // FIXME: find the iResource outside Eclipse } @@ -239,6 +241,15 @@ public IResource getIResource(Path path) { return getIResource(path.toUri()); } + /** + * Get the specified path as an Eclipse IResource or, if it is not found, then + * return the iResource for the main file. + * + */ + public IResource getIResource(File file) { + return getIResource(file.toURI()); + } + /** * Get the specified uri as an Eclipse IResource or, if it is not found, then * return the iResource for the main file. @@ -419,6 +430,17 @@ public static void copyDirectory(Path src, Path dest) throws IOException { } } + /** + * Copy a given file from 'source' to 'destination'. + * + * @param source The source file path string. + * @param destination The destination file path string. + * @throws IOException if copy fails. + */ + public static void copyFile(String source, String destination) throws IOException { + copyFile(Paths.get(source), Paths.get(destination)); + } + /** * Copy a given file from 'source' to 'destination'. * @@ -441,7 +463,7 @@ public static void copyFile(Path source, Path destination) throws IOException { * @param destination The file system path that the source file is copied to. * @throws IOException If the given source cannot be copied. */ - public void copyFileFromClassPath(String source, Path destination) throws IOException { + public void copyFileFromClassPath(String source, String destination) throws IOException { InputStream sourceStream = this.getClass().getResourceAsStream(source); // Copy the file. @@ -455,10 +477,10 @@ public void copyFileFromClassPath(String source, Path destination) throws IOExce "Also try to refresh and clean the project explorer if working from eclipse."); } // Make sure the directory exists - //noinspection ResultOfMethodCallIgnored - destination.toFile().getParentFile().mkdirs(); + File destFile = new File(destination); + destFile.getParentFile().mkdirs(); - Files.copy(sourceStream, destination, StandardCopyOption.REPLACE_EXISTING); + Files.copy(sourceStream, Paths.get(destination), StandardCopyOption.REPLACE_EXISTING); } catch (IOException ex) { throw new IOException( "A required target resource could not be copied: " + source + "\n" + @@ -475,9 +497,9 @@ public void copyFileFromClassPath(String source, Path destination) throws IOExce * @param files The list of files to copy. * @throws IOException If any of the given files cannot be copied. */ - public void copyFilesFromClassPath(String srcDir, Path dstDir, List files) throws IOException { + public void copyFilesFromClassPath(String srcDir, String dstDir, List files) throws IOException { for (String file : files) { - copyFileFromClassPath(srcDir + '/' + file, dstDir.resolve(file)); + copyFileFromClassPath(srcDir + '/' + file, dstDir + File.separator + file); } } @@ -518,7 +540,7 @@ public String copyFileOrResource(String fileName, Path srcDir, Path dstDir) { if (lastSeparator > 0) { filenameWithoutPath = fileName.substring(lastSeparator + 1); // FIXME: brittle. What if the file is in a subdirectory? } - copyFileFromClassPath(fileName, dstDir.resolve(filenameWithoutPath)); + copyFileFromClassPath(fileName, dstDir + File.separator + filenameWithoutPath); return filenameWithoutPath; } catch (IOException ex) { // Ignore. Previously reported as a warning. @@ -549,7 +571,7 @@ public void cleanIfNeeded() { */ public void deleteDirectory(Path dir) throws IOException { if (Files.isDirectory(dir)) { - System.out.println("Cleaning " + dir); + System.out.println("Cleaning " + dir.toString()); List pathsToDelete = Files.walk(dir) .sorted(Comparator.reverseOrder()) .collect(Collectors.toList()); @@ -581,7 +603,7 @@ public void doClean() throws IOException { public void deleteBinFiles() { String name = nameWithoutExtension(this.srcFile); String[] files = this.binPath.toFile().list(); - List federateNames = new LinkedList<>(); // FIXME: put this in ASTUtils? + List federateNames = new LinkedList(); // FIXME: put this in ASTUtils? resource.getAllContents().forEachRemaining(node -> { if (node instanceof Reactor) { Reactor r = (Reactor) node; @@ -597,13 +619,11 @@ public void deleteBinFiles() { // Delete RTI file, if any. if (f.equals(name) || f.equals(getRTIBinName()) || f.equals(getRTIDistributionScriptName())) { - //noinspection ResultOfMethodCallIgnored this.binPath.resolve(f).toFile().delete(); } // Delete federate executable files, if any. for (String federateName : federateNames) { if (f.equals(name + "_" + federateName)) { - //noinspection ResultOfMethodCallIgnored this.binPath.resolve(f).toFile().delete(); } } @@ -616,7 +636,7 @@ public static String nameWithoutExtension(Path file) { return idx < 0 ? name : name.substring(0, idx); } - private static Path getPkgPath(Resource resource) throws IOException { + private static Path getPkgPath(Resource resource, LFGeneratorContext context) throws IOException { if (resource.getURI().isPlatform()) { // We are in the RCA. File srcFile = toPath(resource).toFile(); @@ -686,7 +706,7 @@ public static IPath toIPath(URI uri) throws IOException { } else if (uri.isFile()) { return new org.eclipse.core.runtime.Path(uri.toFileString()); } else { - throw new IOException("Unrecognized file protocol in URI " + uri); + throw new IOException("Unrecognized file protocol in URI " + uri.toString()); } } @@ -699,6 +719,42 @@ public static String toUnixString(Path path) { return path.toString().replace('\\', '/'); } + /** + * Convert a given string path to a unix-style string. + * + * This ensures that '/' is used instead of '\' as file separator. + */ + public static String toUnixString(String path) { + return path.replace('\\', '/'); + } + + /** + * Check whether a given file (i.e., a relative path) exists in the given + *directory. + * @param filename String representation of the filename to search for. + * @param directory String representation of the director to search in. + */ + public static boolean fileExists(String filename, Path directory) { + // Make sure the file exists and issue a warning if not. + Path file = findFile(filename, directory); + if (file == null) { + // See if it can be found as a resource. + InputStream stream = FileConfig.class.getResourceAsStream(filename); + if (stream == null) { + return false; + } else { + // Sadly, even with this not null, the file may not exist. + try { + stream.read(); + stream.close(); + } catch (IOException ex) { + return false; + } + } + } + return true; + } + /** * Search for a given file name in the given directory. * If not found, search in directories in LF_CLASSPATH. @@ -763,6 +819,14 @@ public String getRTIDistributionScriptName() { return nameWithoutExtension(srcFile) + RTI_DISTRIBUTION_SCRIPT_SUFFIX; } + /** + * Return the file location of the RTI distribution script. + * @return The file location of the RTI distribution script. + */ + public File getRTIDistributionScriptFile() { + return this.binPath.resolve(getRTIDistributionScriptName()).toFile(); + } + /** * Return the name of the file associated with the given resource, * excluding its file extension. diff --git a/org.lflang/src/org/lflang/ModelInfo.java b/org.lflang/src/org/lflang/ModelInfo.java index d1748040a9..5c2da9c2c0 100644 --- a/org.lflang/src/org/lflang/ModelInfo.java +++ b/org.lflang/src/org/lflang/ModelInfo.java @@ -43,7 +43,6 @@ import org.lflang.lf.Model; import org.lflang.lf.Parameter; import org.lflang.lf.Reactor; -import org.lflang.lf.STP; /** @@ -79,12 +78,6 @@ public class ModelInfo { * interval. */ public Set overflowingDeadlines; - - /** - * The set of STP offsets that use a too-large constant to specify their time - * interval. - */ - public Set overflowingSTP; /** * The set of parameters used to specify a deadline while having been @@ -152,7 +145,6 @@ private void collectOverflowingNodes() { this.overflowingAssignments = new HashSet<>(); this.overflowingDeadlines = new HashSet<>(); this.overflowingParameters = new HashSet<>(); - this.overflowingSTP = new HashSet<>(); // Visit all deadlines in the model; detect possible overflow. for (var deadline : filter(toIterable(model.eAllContents()), Deadline.class)) { @@ -166,13 +158,6 @@ private void collectOverflowingNodes() { this.overflowingDeadlines.add(deadline); } } - // Visit all STP offsets in the model; detect possible overflow. - for (var stp : filter(toIterable(model.eAllContents()), STP.class)) { - // If the time value overflows, mark this deadline as overflowing. - if (isTooLarge(JavaAstUtils.getLiteralTimeValue(stp.getValue()))) { - this.overflowingSTP.add(stp); - } - } } /** diff --git a/org.lflang/src/org/lflang/federated/CGeneratorExtension.java b/org.lflang/src/org/lflang/federated/CGeneratorExtension.java index 90d3ba2994..edd5ca02ac 100644 --- a/org.lflang/src/org/lflang/federated/CGeneratorExtension.java +++ b/org.lflang/src/org/lflang/federated/CGeneratorExtension.java @@ -32,10 +32,10 @@ import org.lflang.generator.ReactorInstance; import org.lflang.generator.c.CGenerator; import org.lflang.generator.c.CUtil; -import org.lflang.lf.Action; import org.lflang.lf.Delay; import org.lflang.lf.Input; import org.lflang.lf.Parameter; +import org.lflang.lf.Port; import org.lflang.lf.Reactor; import org.lflang.lf.ReactorDecl; import org.lflang.lf.VarRef; @@ -102,7 +102,8 @@ public static String allocateTriggersForFederate( builder.append( "// Initialize the array of pointers to network input port triggers\n" + "_fed.triggers_for_network_input_control_reactions_size = " - + federate.networkInputControlReactionsTriggers.size() + + federate.networkInputControlReactionsTriggers + .size() + ";\n" + "_fed.triggers_for_network_input_control_reactions = (trigger_t**)malloc(" + "_fed.triggers_for_network_input_control_reactions_size * sizeof(trigger_t*)" @@ -150,7 +151,7 @@ public static StringBuilder initializeTriggerForControlReactions( String nameOfSelfStruct = CUtil.reactorRef(instance); // Initialize triggers for network input control reactions - for (Action trigger : federate.networkInputControlReactionsTriggers) { + for (Port trigger : federate.networkInputControlReactionsTriggers) { // Check if the trigger belongs to this reactor instance if (ASTUtils.allReactions(reactor).stream().anyMatch(r -> { return r.getTriggers().stream().anyMatch(t -> { @@ -166,7 +167,8 @@ public static StringBuilder initializeTriggerForControlReactions( + trigger.getName() + " to the global list of network input ports.\n" + "_fed.triggers_for_network_input_control_reactions[" - + federate.networkInputControlReactionsTriggers.indexOf(trigger) + + federate.networkInputControlReactionsTriggers + .indexOf(trigger) + "]= &" + nameOfSelfStruct + "" + "->_lf__" + trigger.getName() + ";\n"); } @@ -174,7 +176,7 @@ public static StringBuilder initializeTriggerForControlReactions( nameOfSelfStruct = CUtil.reactorRef(instance); - // Initialize the trigger for network output control reactions if it doesn't exist. + // Initialize the trigger for network output control reactions if it doesn't exists if (federate.networkOutputControlReactionsTrigger != null) { builder.append("_fed.trigger_for_network_output_control_reactions=&" + nameOfSelfStruct @@ -230,7 +232,7 @@ public static String createPortStatusFieldForInput(Input input, * @param generator * @return */ - public static String getNetworkDelayLiteral(Delay delay) { + public static String getNetworkDelayLiteral(Delay delay, CGenerator generator) { String additionalDelayString = "NEVER"; if (delay != null) { Parameter p = delay.getParameter(); diff --git a/org.lflang/src/org/lflang/federated/FedASTUtils.java b/org.lflang/src/org/lflang/federated/FedASTUtils.java index 03c408eef8..81c7e5d5e0 100644 --- a/org.lflang/src/org/lflang/federated/FedASTUtils.java +++ b/org.lflang/src/org/lflang/federated/FedASTUtils.java @@ -40,14 +40,15 @@ import org.lflang.InferredType; import org.lflang.JavaAstUtils; import org.lflang.TargetProperty.CoordinationType; -import org.lflang.TimeValue; import org.lflang.federated.serialization.SupportedSerializers; +import org.lflang.TimeValue; import org.lflang.generator.GeneratorBase; import org.lflang.generator.PortInstance; import org.lflang.lf.Action; import org.lflang.lf.ActionOrigin; import org.lflang.lf.Connection; import org.lflang.lf.Delay; +import org.lflang.lf.Input; import org.lflang.lf.Instantiation; import org.lflang.lf.LfFactory; import org.lflang.lf.Parameter; @@ -284,15 +285,15 @@ private static void addNetworkInputControlReaction( Reaction reaction = factory.createReaction(); VarRef sourceRef = factory.createVarRef(); VarRef destRef = factory.createVarRef(); + Type portType = EcoreUtil.copy(destination.getDefinition().getType()); // If the sender or receiver is in a bank of reactors, then we want // these reactions to appear only in the federate whose bank ID matches. generator.setReactionBankIndex(reaction, bankIndex); - // Create a new action that will be used to trigger the + // Create a new phantom Input port that will be used to trigger the // input control reactions. - Action newTriggerForControlReactionInput = factory.createAction(); - newTriggerForControlReactionInput.setOrigin(ActionOrigin.LOGICAL); + Input newTriggerForControlReactionInput = factory.createInput(); // Set the container and variable according to the network port destRef.setContainer(destination.getParent().getDefinition()); @@ -302,11 +303,11 @@ private static void addNetworkInputControlReaction( Reactor top = destination.getParent().getParent().reactorDefinition; - newTriggerForControlReactionInput.setName( - ASTUtils.getUniqueIdentifier(top, "inputControlReactionTrigger")); + newTriggerForControlReactionInput.setName(ASTUtils.getUniqueIdentifier(top, "inputControlReactionTrigger")); + newTriggerForControlReactionInput.setType(portType); - // Add the newly created Action to the action list of the federated reactor. - top.getActions().add(newTriggerForControlReactionInput); + // Add the newly created Input to the input list of inputs of the federated reactor + top.getInputs().add(newTriggerForControlReactionInput); // Create the trigger for the reaction VarRef newTriggerForControlReaction = factory.createVarRef(); @@ -597,10 +598,10 @@ private static void addNetworkOutputControlReaction( newPortRef.setVariable(source.getDefinition()); reaction.getSources().add(newPortRef); - // We use an action at the top-level to manually - // trigger output control reactions. That action is created once + // We use a phantom input port at the top-level to manually + // trigger output control reactions. That port is created once // and recorded in the federate instance. - // Check whether the action already has been created. + // Check whether the port already has been created. if (instance.networkOutputControlReactionsTrigger == null) { // The port has not been created. String triggerName = "outputControlReactionTrigger"; @@ -608,22 +609,27 @@ private static void addNetworkOutputControlReaction( // Find the trigger definition in the reactor definition, which could have been // generated for another federate instance if there are multiple instances // of the same reactor that are each distinct federates. - Optional optTriggerInput - = top.getActions().stream().filter( - I -> I.getName().equals(triggerName)).findFirst(); + Optional optTriggerInput = top.getInputs().stream() + .filter(I -> I.getName().equals(triggerName)).findFirst(); if (optTriggerInput.isEmpty()) { // If no trigger with the name "outputControlReactionTrigger" is // already added to the reactor definition, we need to create it - // for the first time. The trigger is a logical action. - Action newTriggerForControlReactionVariable = factory.createAction(); + // for the first time. + Input newTriggerForControlReactionVariable = factory.createInput(); newTriggerForControlReactionVariable.setName(triggerName); - newTriggerForControlReactionVariable.setOrigin(ActionOrigin.LOGICAL); - top.getActions().add(newTriggerForControlReactionVariable); + + if (generator.getTarget().requiresTypes) { + // The input needs a type. All targets have a Time type, so we use that. + Type portType = factory.createType(); + portType.setId(generator.getTargetTypes().getTargetTimeType()); + newTriggerForControlReactionVariable.setType(portType); + } + + top.getInputs().add(newTriggerForControlReactionVariable); // Now that the variable is created, store it in the federate instance - instance.networkOutputControlReactionsTrigger - = newTriggerForControlReactionVariable; + instance.networkOutputControlReactionsTrigger = newTriggerForControlReactionVariable; } else { // If the "outputControlReactionTrigger" trigger is already // there, we can re-use it for this new reaction since a single trigger diff --git a/org.lflang/src/org/lflang/federated/FederateInstance.java b/org.lflang/src/org/lflang/federated/FederateInstance.java index 274d513657..f4693a2aae 100644 --- a/org.lflang/src/org/lflang/federated/FederateInstance.java +++ b/org.lflang/src/org/lflang/federated/FederateInstance.java @@ -35,6 +35,8 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.stream.Collectors; import java.util.stream.Stream; +import com.google.common.base.Objects; + import org.eclipse.xtext.xbase.lib.IterableExtensions; import org.lflang.ASTUtils; import org.lflang.ErrorReporter; @@ -51,6 +53,7 @@ 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.Output; +import org.lflang.lf.Port; import org.lflang.lf.Reaction; import org.lflang.lf.Reactor; import org.lflang.lf.Timer; @@ -58,8 +61,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.VarRef; import org.lflang.lf.Variable; -import com.google.common.base.Objects; - /** * Instance of a federate, or marker that no federation has been defined @@ -204,7 +205,7 @@ public FederateInstance( * A list of triggers for network input control reactions. This is used to trigger * all the input network control reactions that might be nested in a hierarchy. */ - public List networkInputControlReactionsTriggers = new ArrayList<>(); + public List networkInputControlReactionsTriggers = new ArrayList<>(); /** * The trigger that triggers the output control reaction of this @@ -287,17 +288,59 @@ public boolean contains(Action action) { return false; } - + + /** + * Return true if the specified reactor is not the top-level federated reactor, + * or if it is and the port should be included in the code generated + * for the federate. This means that the port has been used as a trigger, + * a source, or an effect in a top-level reaction that belongs to this federate. + * + * @param port The Port + * @return True if this federate contains the action in the specified reactor + */ + public boolean contains(Port port) { + Reactor reactor = (Reactor) port.eContainer(); + if (!reactor.isFederated() || this.isSingleton()) return true; + + // If the port is used as a trigger, a source, or an effect for a top-level reaction + // that belongs to this federate, then return true. + for (Reaction react : ASTUtils.allReactions(reactor)) { + if (contains(react)) { + // Look in triggers + for (TriggerRef trigger : react.getTriggers() ) { + if (trigger instanceof VarRef) { + VarRef triggerAsVarRef = (VarRef) trigger; + if (Objects.equal(triggerAsVarRef.getVariable(), (Variable) port)) { + return true; + } + } + } + // Look in sources + for (VarRef source : convertToEmptyListIfNull(react.getSources())) { + if (Objects.equal(source.getVariable(), (Variable) port)) { + return true; + } + } + // Look in effects + for (VarRef effect : convertToEmptyListIfNull(react.getEffects())) { + if (Objects.equal(effect.getVariable(), (Variable) port)) { + return true; + } + } + } + } + + return false; + } + /** * Return true if the specified reaction should be included in the code generated for this * federate at the top-level. This means that if the reaction is triggered by or * sends data to a port of a contained reactor, then that reaction * is in the federate. Otherwise, return false. * - * NOTE: This method assumes that it will not be called with reaction arguments - * that are within other federates. It should only be called on reactions that are - * either at the top level or within this federate. For this reason, for any reaction - * not at the top level, it returns true. + * As a convenience measure, also return true if the reaction is not defined in the top-level + * (federated) reactor, or if the top-level reactor is not federated. * * @param reaction The reaction. */ diff --git a/org.lflang/src/org/lflang/federated/PythonGeneratorExtension.java b/org.lflang/src/org/lflang/federated/PythonGeneratorExtension.java index 32de20d71b..2d5b5117ab 100644 --- a/org.lflang/src/org/lflang/federated/PythonGeneratorExtension.java +++ b/org.lflang/src/org/lflang/federated/PythonGeneratorExtension.java @@ -33,6 +33,7 @@ import org.lflang.federated.serialization.FedSerialization; import org.lflang.federated.serialization.SupportedSerializers; import org.lflang.generator.c.CUtil; +import org.lflang.generator.python.PythonGenerator; import org.lflang.lf.Action; import org.lflang.lf.Delay; import org.lflang.lf.VarRef; @@ -73,7 +74,7 @@ public static String generateNetworkSenderBody( boolean isPhysical, Delay delay, SupportedSerializers serializer, - CoordinationType coordinationType + PythonGenerator generator ) { String sendRef = CUtil.portRefInReaction(sendingPort, sendingBankIndex, sendingChannelIndex); String receiveRef = JavaAstUtils.generateVarRef(receivingPort); // Used for comments only, so no need for bank/multiport index. @@ -88,11 +89,15 @@ public static String generateNetworkSenderBody( String next_destination_name = "\"federate " + receivingFed.id + "\""; // Get the delay literal - String additionalDelayString = CGeneratorExtension.getNetworkDelayLiteral(delay); + String additionalDelayString = + CGeneratorExtension.getNetworkDelayLiteral( + delay, + generator + ); if (isPhysical) { messageType = "MSG_TYPE_P2P_MESSAGE"; - } else if (coordinationType == CoordinationType.DECENTRALIZED) { + } else if (generator.getTargetConfig().coordination == CoordinationType.DECENTRALIZED) { messageType = "MSG_TYPE_P2P_TAGGED_MESSAGE"; } else { // Logical connection @@ -117,8 +122,8 @@ public static String generateNetworkSenderBody( + next_destination_name + ", message_length"; } - String lengthExpression = ""; - String pointerExpression = ""; + var lengthExpression = ""; + var pointerExpression = ""; switch (serializer) { case NATIVE: { var variableToSerialize = sendRef+"->value"; @@ -171,7 +176,8 @@ public static String generateNetworkReceiverBody( int receivingChannelIndex, InferredType type, boolean isPhysical, - SupportedSerializers serializer + SupportedSerializers serializer, + PythonGenerator generator ) { String receiveRef = CUtil.portRefInReaction(receivingPort, receivingBankIndex, receivingChannelIndex); diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index fb2de5efee..0df87ef633 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -535,7 +535,7 @@ abstract class GeneratorBase extends AbstractLFValidator { * @param time A TimeValue that represents a time. * @return A string, such as "MSEC(100)" for 100 milliseconds. */ - static def String timeInTargetLanguage(TimeValue time) { + def String timeInTargetLanguage(TimeValue time) { if (time !== null) { if (time.unit !== null) { return time.unit.cMacroName + '(' + time.magnitude + ')' @@ -547,7 +547,7 @@ abstract class GeneratorBase extends AbstractLFValidator { } // note that this is moved out by #544 - static def String cMacroName(TimeUnit unit) { + final def String cMacroName(TimeUnit unit) { return unit.canonicalName.toUpperCase } @@ -741,35 +741,7 @@ abstract class GeneratorBase extends AbstractLFValidator { def writeDockerFile(File dockerComposeDir, String dockerFileName, String federateName) { throw new UnsupportedOperationException("This target does not support docker file generation.") } - - /** - * Write a Dockerfile for the current federate as given by filename. - * @param The directory where the docker compose file is generated. - * @param The name of the docker file. - * @param The name of the federate. - */ - def getDockerComposeCommand() { - val OS = System.getProperty("os.name").toLowerCase(); - return (OS.indexOf("nux") >= 0) ? "docker-compose" : "docker compose" - } - - /** - * Write a Dockerfile for the current federate as given by filename. - * @param The directory where the docker compose file is generated. - * @param The name of the docker file. - * @param The name of the federate. - */ - def getDockerBuildCommand(String dockerFile, File dockerComposeDir, String federateName) { - return String.join("\n", - '''Dockerfile for «topLevelName» written to «dockerFile»''', - '''#####################################''', - '''To build the docker image, go to «dockerComposeDir» and run:''', - "", - ''' «getDockerComposeCommand()» build «federateName»''', - "", - '''#####################################''' - ); - } + /** * Parsed error message from a compiler is returned here. @@ -1258,7 +1230,7 @@ abstract class GeneratorBase extends AbstractLFValidator { * @param t A time AST node * @return A time string in the target language */ - static def getTargetTime(Time t) { + protected def getTargetTime(Time t) { val value = new TimeValue(t.interval, TimeUnit.fromName(t.unit)) return value.timeInTargetLanguage } @@ -1271,7 +1243,7 @@ abstract class GeneratorBase extends AbstractLFValidator { * @param v A time AST node * @return A time string in the target language */ - static def getTargetValue(Value v) { + protected def getTargetValue(Value v) { if (v.time !== null) { return v.time.targetTime } @@ -1286,7 +1258,7 @@ abstract class GeneratorBase extends AbstractLFValidator { * @param v A time AST node * @return A time string in the target language */ - static def getTargetTime(Value v) { + protected def getTargetTime(Value v) { if (v.time !== null) { return v.time.targetTime } else if (v.isZero) { @@ -1296,7 +1268,7 @@ abstract class GeneratorBase extends AbstractLFValidator { return v.toText } - static def getTargetTime(Delay d) { + protected def getTargetTime(Delay d) { if (d.parameter !== null) { return d.toText } else { diff --git a/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java b/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java index c46de3bd23..840bad278d 100644 --- a/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java +++ b/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java @@ -1,9 +1,11 @@ package org.lflang.generator; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; @@ -15,6 +17,7 @@ import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.xtext.generator.IFileSystemAccess2; import org.eclipse.xtext.generator.IGeneratorContext; import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.validation.CheckMode; @@ -233,12 +236,7 @@ public static void validate( bad.contains(resource) || issues.size() > 0 ) { // Report the error on this resource. - Path path = null; - try { - path = FileConfig.toPath(resource); - } catch (IOException e) { - path = Paths.get("Unknown file"); // Not sure if this is what we want. - } + Path path = fileConfig.srcPath; for (Issue issue : issues) { errorReporter.reportError(path, issue.getLineNumber(), issue.getMessage()); } diff --git a/org.lflang/src/org/lflang/generator/NamedInstance.java b/org.lflang/src/org/lflang/generator/NamedInstance.java index ce99549e5a..b047a53bbd 100644 --- a/org.lflang/src/org/lflang/generator/NamedInstance.java +++ b/org.lflang/src/org/lflang/generator/NamedInstance.java @@ -126,21 +126,6 @@ public ReactorInstance getParent() { return parent; } - /** - * Return the parent at the given depth or null if there is - * no parent at the given depth. - * @param d The depth. - */ - public ReactorInstance getParent(int d) { - if (d >= depth || d < 0) return null; - ReactorInstance p = parent; - while (p != null) { - if (p.depth == d) return p; - p = p.parent; - } - return null; - } - /** * Return the width of this instance, which in this base class is 1. * Subclasses PortInstance and ReactorInstance change this to the @@ -182,6 +167,19 @@ public List parents() { return result; } + /** + * Return the root reactor if it is marked as as main or federated, + * and otherwise return null. + * @return The main/federated top-level parent. + */ + public ReactorInstance main() { + ReactorInstance r = this.root(); + if (r != null && r.isMainOrFederated()) { + return r; + } + return null; + } + /** * Return the root reactor, which is the top-level parent. * @return The top-level parent. diff --git a/org.lflang/src/org/lflang/generator/c/CCmakeCompiler.java b/org.lflang/src/org/lflang/generator/c/CCmakeCompiler.java index 4c31b8da77..089a967cb7 100644 --- a/org.lflang/src/org/lflang/generator/c/CCmakeCompiler.java +++ b/org.lflang/src/org/lflang/generator/c/CCmakeCompiler.java @@ -30,6 +30,9 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; + +import org.eclipse.xtext.util.CancelIndicator; + import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.TargetConfig.Mode; diff --git a/org.lflang/src/org/lflang/generator/c/CCompiler.java b/org.lflang/src/org/lflang/generator/c/CCompiler.java index 7d8ee9ec09..8efcd23559 100644 --- a/org.lflang/src/org/lflang/generator/c/CCompiler.java +++ b/org.lflang/src/org/lflang/generator/c/CCompiler.java @@ -29,6 +29,9 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; + +import org.eclipse.xtext.util.CancelIndicator; + import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.TargetConfig.Mode; diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 1c30aa654f..52d4b1f153 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -38,6 +38,7 @@ import java.util.concurrent.TimeUnit import java.util.regex.Pattern import org.eclipse.emf.ecore.resource.Resource import org.eclipse.xtext.util.CancelIndicator +import org.lflang.ASTUtils import org.lflang.ErrorReporter import org.lflang.FileConfig import org.lflang.InferredType @@ -56,21 +57,25 @@ import org.lflang.federated.FederateInstance import org.lflang.federated.launcher.FedCLauncher import org.lflang.federated.serialization.FedROS2CPPSerialization import org.lflang.federated.serialization.SupportedSerializers +import org.lflang.generator.ActionInstance import org.lflang.generator.CodeBuilder import org.lflang.generator.GeneratorBase import org.lflang.generator.GeneratorResult import org.lflang.generator.IntegratedBuilder import org.lflang.generator.JavaGeneratorUtils import org.lflang.generator.LFGeneratorContext +import org.lflang.generator.ParameterInstance import org.lflang.generator.PortInstance import org.lflang.generator.ReactionInstance import org.lflang.generator.ReactorInstance import org.lflang.generator.RuntimeRange import org.lflang.generator.SendRange import org.lflang.generator.SubContext +import org.lflang.generator.TimerInstance import org.lflang.generator.TriggerInstance import org.lflang.lf.Action import org.lflang.lf.ActionOrigin +import org.lflang.lf.Assignment import org.lflang.lf.Delay import org.lflang.lf.Input import org.lflang.lf.Instantiation @@ -428,8 +433,39 @@ class CGenerator extends GeneratorBase { if (!isOSCompatible()) return; // Incompatible OS and configuration - // Create the main reactor instance if there is a main reactor. - createMainReactorInstance(); + // Check for duplicate declarations. + val names = newLinkedHashSet + for (r : reactors) { + // Get the declarations for reactors that are instantiated somewhere. + // A declaration is either a reactor definition or an import statement. + val declarations = this.instantiationGraph.getDeclarations(r); + for (d : declarations) { + if (!names.add(d.name)) { + // Report duplicate declaration. + errorReporter.reportError("Multiple declarations for reactor class '" + d.name + "'.") + } + } + } + + // Build the instantiation tree if a main reactor is present. + if (this.mainDef !== null) { + if (this.main === null) { + // Recursively build instances. This is done once because + // it is the same for all federates. + this.main = new ReactorInstance(mainDef.reactorClass.toDefinition, errorReporter, + this.unorderedReactions) + if (this.main.assignLevels().nodeCount > 0) { + errorReporter.reportError("Main reactor has causality cycles. Skipping code generation."); + return; + } + // Avoid compile errors by removing disconnected network ports. + // This must be done after assigning levels. + removeRemoteFederateConnectionPorts(main); + // Force reconstruction of dependence information. + // FIXME: Probably only need to do this for federated execution. + this.main.clearCaches(false); + } + } // Create the output directories if they don't yet exist. var dir = fileConfig.getSrcGenPath.toFile @@ -572,7 +608,7 @@ class CGenerator extends GeneratorBase { } // Copy the core lib - fileConfig.copyFilesFromClassPath("/lib/c/reactor-c/core", fileConfig.getSrcGenPath.resolve("core"), coreFiles) + fileConfig.copyFilesFromClassPath("/lib/c/reactor-c/core", fileConfig.getSrcGenPath + File.separator + "core", coreFiles) // Copy the header files copyTargetHeaderFile() @@ -599,21 +635,7 @@ class CGenerator extends GeneratorBase { // Note that any main reactors in imported files are ignored. // Skip generation if there are cycles. if (this.main !== null) { - initializeTriggerObjects.pr(''' - int _lf_startup_reactions_count = 0; - int _lf_shutdown_reactions_count = 0; - int _lf_timer_triggers_count = 0; - int _lf_tokens_with_ref_count_count = 0; - '''); - - // 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(this.main) + generateMain() // Generate function to set default command-line options. // A literal array needs to be given outside any function definition, // so start with that. @@ -1310,6 +1332,8 @@ class CGenerator extends GeneratorBase { } var dockerCompiler = CCppMode ? 'g++' : 'gcc' var fileExtension = CCppMode ? 'cpp' : 'c' + val OS = System.getProperty("os.name").toLowerCase(); + var dockerComposeCommand = (OS.indexOf("nux") >= 0) ? "docker-compose" : "docker compose" contents.pr(''' # Generated docker file for «topLevelName» in «srcGenPath». @@ -1335,7 +1359,15 @@ class CGenerator extends GeneratorBase { ENTRYPOINT ["./bin/«topLevelName»"] ''') contents.writeToFile(dockerFile) - println(getDockerBuildCommand(dockerFile, dockerComposeDir, federateName)) + println('''Dockerfile for «topLevelName» written to ''' + dockerFile) + println(''' + ##################################### + To build the docker image, go to «dockerComposeDir.toString()» and run: + + «dockerComposeCommand» build «federateName» + + ##################################### + ''') } /** @@ -1572,8 +1604,8 @@ class CGenerator extends GeneratorBase { * Copy target-specific header file to the src-gen directory. */ def copyTargetHeaderFile() { - fileConfig.copyFileFromClassPath("/lib/c/reactor-c/include/ctarget.h", fileConfig.getSrcGenPath.resolve("ctarget.h")) - fileConfig.copyFileFromClassPath("/lib/c/reactor-c/lib/ctarget.c", fileConfig.getSrcGenPath.resolve("ctarget.c")) + fileConfig.copyFileFromClassPath("/lib/c/reactor-c/include/ctarget.h", fileConfig.getSrcGenPath + File.separator + "ctarget.h") + fileConfig.copyFileFromClassPath("/lib/c/reactor-c/lib/ctarget.c", fileConfig.getSrcGenPath + File.separator + "ctarget.c") } //////////////////////////////////////////// @@ -1736,8 +1768,8 @@ class CGenerator extends GeneratorBase { // Some of the following methods create lines of code that need to // go into the constructor. Collect those lines of code here: val constructorCode = new CodeBuilder() - generateAuxiliaryStructs(reactor) - generateSelfStruct(reactor, constructorCode) + generateAuxiliaryStructs(reactor, currentFederate) + generateSelfStruct(reactor, currentFederate, constructorCode) generateReactions(reactor, currentFederate) generateConstructor(reactor, currentFederate, constructorCode) @@ -1780,10 +1812,13 @@ class CGenerator extends GeneratorBase { /** * Generate the struct type definitions for inputs, outputs, and - * actions of the specified reactor. + * actions of the specified reactor in the specified federate. * @param reactor The parsed reactor data structure. + * @param federate A federate name, or null to unconditionally generate. */ - protected def generateAuxiliaryStructs(ReactorDecl decl) { + protected def generateAuxiliaryStructs( + ReactorDecl decl, FederateInstance federate + ) { val reactor = decl.toDefinition // In the case where there are incoming // p2p logical connections in decentralized @@ -1805,48 +1840,54 @@ class CGenerator extends GeneratorBase { } // First, handle inputs. for (input : reactor.allInputs) { - var token = '' - if (CUtil.isTokenType(input.inferredType, types)) { - token = ''' - lf_token_t* token; - int length; - ''' - } - code.pr(input, ''' - typedef struct { - «input.valueDeclaration» - bool is_present; - int num_destinations; - «token» - «federatedExtension.toString» - } «variableStructType(input, decl)»; - ''') + if (federate === null || federate.contains(input as Port)) { + var token = '' + if (input.inferredType.isTokenType) { + token = ''' + lf_token_t* token; + int length; + ''' + } + code.pr(input, ''' + typedef struct { + «input.valueDeclaration» + bool is_present; + int num_destinations; + «token» + «federatedExtension.toString» + } «variableStructType(input, decl)»; + ''') + } + } // Next, handle outputs. - for (output : reactor.allOutputs) { - var token = '' - if (CUtil.isTokenType(output.inferredType, types)) { - token = ''' - lf_token_t* token; - int length; - ''' - } - code.pr(output, ''' - typedef struct { - «output.valueDeclaration» - bool is_present; - int num_destinations; - «token» - «federatedExtension.toString» - } «variableStructType(output, decl)»; - ''') + for (output : reactor.allOutputs) { + if (federate === null || federate.contains(output as Port)) { + var token = '' + if (output.inferredType.isTokenType) { + token = ''' + lf_token_t* token; + int length; + ''' + } + code.pr(output, ''' + typedef struct { + «output.valueDeclaration» + bool is_present; + int num_destinations; + «token» + «federatedExtension.toString» + } «variableStructType(output, decl)»; + ''') + } + } // Finally, handle actions. // The very first item on this struct needs to be // a trigger_t* because the struct will be cast to (trigger_t*) // by the schedule() functions to get to the trigger. for (action : reactor.allActions) { - if (currentFederate.contains(action)) { + if (federate === null || federate.contains(action)) { code.pr(action, ''' typedef struct { trigger_t* trigger; @@ -1909,10 +1950,15 @@ class CGenerator extends GeneratorBase { * Generate the self struct type definition for the specified reactor * in the specified federate. * @param reactor The parsed reactor data structure. + * @param federate A federate name, or null to unconditionally generate. * @param constructorCode Place to put lines of code that need to * go into the constructor. */ - private def generateSelfStruct(ReactorDecl decl, CodeBuilder constructorCode) { + protected def generateSelfStruct( + ReactorDecl decl, + FederateInstance federate, + CodeBuilder constructorCode + ) { val reactor = decl.toDefinition val selfType = CUtil.selfType(decl) @@ -1922,7 +1968,7 @@ class CGenerator extends GeneratorBase { var body = new CodeBuilder() // Extensions can add functionality to the CGenerator - generateSelfStructExtension(body, decl, constructorCode) + generateSelfStructExtension(body, decl, federate, constructorCode) // Next handle parameters. generateParametersForReactor(body, reactor) @@ -1932,7 +1978,7 @@ class CGenerator extends GeneratorBase { // Next handle actions. for (action : reactor.allActions) { - if (currentFederate.contains(action)) { + if (federate === null || federate.contains(action)) { body.pr(action, ''' «variableStructType(action, decl)» _lf_«action.name»; ''') @@ -1945,57 +1991,61 @@ class CGenerator extends GeneratorBase { // Next handle inputs. for (input : reactor.allInputs) { - // If the port is a multiport, the input field is an array of - // pointers that will be allocated separately for each instance - // because the sizes may be different. Otherwise, it is a simple - // pointer. - if (JavaAstUtils.isMultiport(input)) { - body.pr(input, ''' - // Multiport input array will be malloc'd later. - «variableStructType(input, decl)»** _lf_«input.name»; - int _lf_«input.name»_width; - // Default input (in case it does not get connected) - «variableStructType(input, decl)» _lf_default__«input.name»; - ''') - } else { - // input is not a multiport. - body.pr(input, ''' - «variableStructType(input, decl)»* _lf_«input.name»; - // width of -2 indicates that it is not a multiport. - int _lf_«input.name»_width; - // Default input (in case it does not get connected) - «variableStructType(input, decl)» _lf_default__«input.name»; - ''') - - constructorCode.pr(input, ''' - // Set input by default to an always absent default input. - self->_lf_«input.name» = &self->_lf_default__«input.name»; - ''') + if (federate === null || federate.contains(input as Port)) { + // If the port is a multiport, the input field is an array of + // pointers that will be allocated separately for each instance + // because the sizes may be different. Otherwise, it is a simple + // pointer. + if (JavaAstUtils.isMultiport(input)) { + body.pr(input, ''' + // Multiport input array will be malloc'd later. + «variableStructType(input, decl)»** _lf_«input.name»; + int _lf_«input.name»_width; + // Default input (in case it does not get connected) + «variableStructType(input, decl)» _lf_default__«input.name»; + ''') + } else { + // input is not a multiport. + body.pr(input, ''' + «variableStructType(input, decl)»* _lf_«input.name»; + // width of -2 indicates that it is not a multiport. + int _lf_«input.name»_width; + // Default input (in case it does not get connected) + «variableStructType(input, decl)» _lf_default__«input.name»; + ''') + + constructorCode.pr(input, ''' + // Set input by default to an always absent default input. + self->_lf_«input.name» = &self->_lf_default__«input.name»; + ''') + } } } // Next handle outputs. for (output : reactor.allOutputs) { - // If the port is a multiport, create an array to be allocated - // at instantiation. - if (JavaAstUtils.isMultiport(output)) { - body.pr(output, ''' - // Array of output ports. - «variableStructType(output, decl)»* _lf_«output.name»; - int _lf_«output.name»_width; - // An array of pointers to the individual ports. Useful - // for the SET macros to work out-of-the-box for - // multiports in the body of reactions because their - // value can be accessed via a -> operator (e.g.,foo[i]->value). - // So we have to handle multiports specially here a construct that - // array of pointers. - «variableStructType(output, decl)»** _lf_«output.name»_pointers; - ''') - } else { - body.pr(output, ''' - «variableStructType(output, decl)» _lf_«output.name»; - int _lf_«output.name»_width; - ''') + if (federate === null || federate.contains(output as Port)) { + // If the port is a multiport, create an array to be allocated + // at instantiation. + if (JavaAstUtils.isMultiport(output)) { + body.pr(output, ''' + // Array of output ports. + «variableStructType(output, decl)»* _lf_«output.name»; + int _lf_«output.name»_width; + // An array of pointers to the individual ports. Useful + // for the SET macros to work out-of-the-box for + // multiports in the body of reactions because their + // value can be accessed via a -> operator (e.g.,foo[i]->value). + // So we have to handle multiports specially here a construct that + // array of pointers. + «variableStructType(output, decl)»** _lf_«output.name»_pointers; + ''') + } else { + body.pr(output, ''' + «variableStructType(output, decl)» _lf_«output.name»; + int _lf_«output.name»_width; + ''') + } } } @@ -2006,10 +2056,10 @@ class CGenerator extends GeneratorBase { // 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); + generateInteractingContainedReactors(reactor, federate, body, constructorCode); // Next, generate the fields needed for each reaction. - generateReactionAndTriggerStructs(body, decl, constructorCode); + generateReactionAndTriggerStructs(body, decl, constructorCode, federate) // 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. @@ -2035,17 +2085,19 @@ class CGenerator extends GeneratorBase { * the contained reactors. * * @param reactor The reactor. + * @param federate The federate instance. * @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 def generateInteractingContainedReactors( Reactor reactor, + FederateInstance federate, 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. - val contained = new InteractingContainedReactors(reactor, currentFederate); + val contained = new InteractingContainedReactors(reactor, federate); // Next generate the relevant code. for (containedReactor : contained.containedReactors) { // First define an _width variable in case it is a bank. @@ -2054,7 +2106,7 @@ class CGenerator extends GeneratorBase { // If the instantiation is a bank, find the maximum bank width // to define an array. if (containedReactor.widthSpec !== null) { - width = CReactionGenerator.maxContainedReactorBankWidth(containedReactor, null, 0, mainDef); + width = maxContainedReactorBankWidth(containedReactor, null, 0); array = "[" + width + "]"; } // NOTE: The following needs to be done for each instance @@ -2178,11 +2230,13 @@ class CGenerator extends GeneratorBase { * 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 instance The current federate instance * @param constructorCode Code that is executed when the reactor is instantiated */ def void generateSelfStructExtension( CodeBuilder body, ReactorDecl decl, + FederateInstance instance, CodeBuilder constructorCode ) { // Do nothing @@ -2222,11 +2276,13 @@ class CGenerator extends GeneratorBase { * @param body The place to put the code for the self struct. * @param reactor The reactor. * @param constructorCode The place to put the constructor code. + * @param federate The federate instance, or null if there is no federation. */ protected def void generateReactionAndTriggerStructs( CodeBuilder body, ReactorDecl decl, - CodeBuilder constructorCode + CodeBuilder constructorCode, + FederateInstance federate ) { var reactionCount = 0; val reactor = decl.toDefinition @@ -2242,7 +2298,7 @@ class CGenerator extends GeneratorBase { val startupReactions = new LinkedHashSet val shutdownReactions = new LinkedHashSet for (reaction : reactor.allReactions) { - if (currentFederate.contains(reaction)) { + if (federate === null || federate.contains(reaction)) { // Create the reaction_t struct. body.pr(reaction, '''reaction_t _lf__reaction_«reactionCount»;''') @@ -2278,7 +2334,7 @@ class CGenerator extends GeneratorBase { var deadlineFunctionPointer = "NULL" if (reaction.deadline !== null) { // The following has to match the name chosen in generateReactions - val deadlineFunctionName = CReactionGenerator.generateDeadlineFunctionName(decl, reactionCount) + val deadlineFunctionName = decl.name.toLowerCase + '_deadline_function' + reactionCount deadlineFunctionPointer = "&" + deadlineFunctionName } @@ -2300,7 +2356,7 @@ class CGenerator extends GeneratorBase { // self->_lf__reaction_«reactionCount».is_STP_violated = false; constructorCode.pr(reaction, ''' self->_lf__reaction_«reactionCount».number = «reactionCount»; - self->_lf__reaction_«reactionCount».function = «CReactionGenerator.generateReactionFunctionName(decl, reactionCount)»; + self->_lf__reaction_«reactionCount».function = «reactionFunctionName(decl, reactionCount)»; self->_lf__reaction_«reactionCount».self = self; self->_lf__reaction_«reactionCount».deadline_violation_handler = «deadlineFunctionPointer»; self->_lf__reaction_«reactionCount».STP_handler = «STPFunctionPointer»; @@ -2382,7 +2438,7 @@ class CGenerator extends GeneratorBase { // Next handle actions. for (action : reactor.allActions) { - if (currentFederate.contains(action)) { + if (federate === null || federate.contains(action)) { createTriggerT(body, action, triggerMap, constructorCode) var isPhysical = "true"; if (action.origin == ActionOrigin.LOGICAL) { @@ -2409,7 +2465,9 @@ class CGenerator extends GeneratorBase { // Next handle inputs. for (input : reactor.allInputs) { - createTriggerT(body, input, triggerMap, constructorCode) + if (federate === null || federate.contains(input as Port)) { + createTriggerT(body, input, triggerMap, constructorCode) + } } } @@ -2520,14 +2578,14 @@ class CGenerator extends GeneratorBase { * @param reactionIndex The position of the reaction within the reactor. */ def generateReaction(Reaction reaction, ReactorDecl decl, int reactionIndex) { - val functionName = CReactionGenerator.generateReactionFunctionName(decl, reactionIndex) + val functionName = reactionFunctionName(decl, reactionIndex) code.pr('void ' + functionName + '(void* instance_args) {') code.indent() var body = reaction.code.toText - code.pr(CReactionGenerator.generateInitializationForReaction(body, reaction, decl, reactionIndex, types, errorReporter, mainDef, isFederatedAndDecentralized, target.requiresTypes)) + generateInitializationForReaction(body, reaction, decl, reactionIndex) // Code verbatim from 'reaction' code.prSourceLineNumber(reaction.code) @@ -2543,7 +2601,7 @@ class CGenerator extends GeneratorBase { code.pr('void ' + lateFunctionName + '(void* instance_args) {') code.indent(); - code.pr(CReactionGenerator.generateInitializationForReaction(body, reaction, decl, reactionIndex, types, errorReporter, mainDef, isFederatedAndDecentralized, target.requiresTypes)) + generateInitializationForReaction(body, reaction, decl, reactionIndex) // Code verbatim from 'late' code.prSourceLineNumber(reaction.stp.code) code.pr(reaction.stp.code.toText) @@ -2554,11 +2612,11 @@ class CGenerator extends GeneratorBase { // Now generate code for the deadline violation function, if there is one. if (reaction.deadline !== null) { // The following name has to match the choice in generateReactionInstances - val deadlineFunctionName = CReactionGenerator.generateDeadlineFunctionName(decl, reactionIndex) + val deadlineFunctionName = decl.name.toLowerCase + '_deadline_function' + reactionIndex code.pr('void ' + deadlineFunctionName + '(void* instance_args) {') code.indent(); - code.pr(CReactionGenerator.generateInitializationForReaction(body, reaction, decl, reactionIndex, types, errorReporter, mainDef, isFederatedAndDecentralized, target.requiresTypes)) + generateInitializationForReaction(body, reaction, decl, reactionIndex) // Code verbatim from 'deadline' code.prSourceLineNumber(reaction.deadline.code) code.pr(reaction.deadline.code.toText) @@ -2569,52 +2627,447 @@ class CGenerator extends GeneratorBase { /** * Record startup and shutdown reactions. - * @param instance A reactor instance. + * @param reactions A list of reactions. */ - private def void recordStartupAndShutdown(ReactorInstance instance) { + private def void recordStartupAndShutdown(Iterable reactions) { // For each reaction instance, allocate the arrays that will be used to // trigger downstream reactions. - for (reaction : instance.reactions) { - if (currentFederate.contains(reaction.getDefinition())) { - val reactor = reaction.parent; - - val temp = new CodeBuilder(); - var foundOne = false; + for (reaction : reactions) { + val reactor = reaction.parent; + + val temp = new CodeBuilder(); + var foundOne = false; + + val 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 (trigger : reaction.triggers) { + if (trigger.isStartup) { + temp.pr(''' + _lf_startup_reactions[_lf_startup_reactions_count++] = &«reactionRef»; + ''') + startupReactionCount += currentFederate.numRuntimeInstances(reactor); + foundOne = true; + } else if (trigger.isShutdown) { + temp.pr(''' + _lf_shutdown_reactions[_lf_shutdown_reactions_count++] = &«reactionRef»; + ''') + foundOne = true; + shutdownReactionCount += currentFederate.numRuntimeInstances(reactor); + + if (targetConfig.tracing !== null) { + val description = getShortenedName(reactor) + val reactorRef = CUtil.reactorRef(reactor) + temp.pr(''' + _lf_register_trace_event(«reactorRef», &(«reactorRef»->_lf__shutdown), + trace_trigger, "«description».shutdown"); + ''') + } + } + } + if (foundOne) initializeTriggerObjects.pr(temp.toString); + } + } + + /** + * Generate code that passes existing intended tag to all output ports + * and actions. This intended tag is the minimum intended tag of the + * triggering inputs of the reaction. + * + * @param body The body of the reaction. Used to check for the DISABLE_REACTION_INITIALIZATION_MARKER. + * @param reaction The initialization code will be generated for this specific reaction + * @param decl The reactor that has the reaction + * @param reactionIndex The index of the reaction relative to other reactions in the reactor, starting from 0 + */ + def generateIntendedTagInheritence(String body, Reaction reaction, ReactorDecl decl, int reactionIndex) { + // Construct the intended_tag inheritance code to go into + // the body of the function. + var CodeBuilder intendedTagInheritenceCode = new CodeBuilder() + // Check if the coordination mode is decentralized and if the reaction has any effects to inherit the STP violation + if (isFederatedAndDecentralized && !reaction.effects.nullOrEmpty) { + intendedTagInheritenceCode.pr(''' + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunused-variable" + if (self->_lf__reaction_«reactionIndex».is_STP_violated == true) { + ''') + intendedTagInheritenceCode.indent(); + intendedTagInheritenceCode.pr(''' + // The operations inside this if clause (if any exists) are expensive + // and must only be done if the reaction has unhandled STP violation. + // Otherwise, all intended_tag values are (NEVER, 0) by default. - val reactionRef = CUtil.reactionRef(reaction) + // Inherited intended tag. This will take the minimum + // intended_tag of all input triggers + «types.getTargetTagType» inherited_min_intended_tag = («types.getTargetTagType») { .time = FOREVER, .microstep = UINT_MAX }; + ''') + intendedTagInheritenceCode.pr(''' + // Find the minimum intended tag + ''') + // Go through every trigger of the reaction and check the + // value of intended_tag to choose the minimum. + for (TriggerRef inputTrigger : reaction.triggers ?: emptyList) { + if (inputTrigger instanceof VarRef) { + if (inputTrigger.variable instanceof Output) { + // Output from a contained reactor + val outputPort = inputTrigger.variable as Output + if (JavaAstUtils.isMultiport(outputPort)) { + intendedTagInheritenceCode.pr(''' + for (int i=0; i < «inputTrigger.container.name».«inputTrigger.variable.name»_width; i++) { + if (compare_tags(«inputTrigger.container.name».«inputTrigger.variable.name»[i]->intended_tag, + inherited_min_intended_tag) < 0) { + inherited_min_intended_tag = «inputTrigger.container.name».«inputTrigger.variable.name»[i]->intended_tag; + } + } + ''') - // Next handle triggers of the reaction that come from a multiport output - // of a contained reactor. Also, handle startup and shutdown triggers. - for (trigger : reaction.triggers) { - if (trigger.isStartup) { - temp.pr(''' - _lf_startup_reactions[_lf_startup_reactions_count++] = &«reactionRef»; + } else + intendedTagInheritenceCode.pr(''' + if (compare_tags(«inputTrigger.container.name».«inputTrigger.variable.name»->intended_tag, + inherited_min_intended_tag) < 0) { + inherited_min_intended_tag = «inputTrigger.container.name».«inputTrigger.variable.name»->intended_tag; + } + ''') + } else if (inputTrigger.variable instanceof Port) { + // Input port + val inputPort = inputTrigger.variable as Port + if (JavaAstUtils.isMultiport(inputPort)) { + intendedTagInheritenceCode.pr(''' + for (int i=0; i < «inputTrigger.variable.name»_width; i++) { + if (compare_tags(«inputTrigger.variable.name»[i]->intended_tag, inherited_min_intended_tag) < 0) { + inherited_min_intended_tag = «inputTrigger.variable.name»[i]->intended_tag; + } + } + ''') + } else { + intendedTagInheritenceCode.pr(''' + if (compare_tags(«inputTrigger.variable.name»->intended_tag, inherited_min_intended_tag) < 0) { + inherited_min_intended_tag = «inputTrigger.variable.name»->intended_tag; + } + ''') + } + } else if (inputTrigger.variable instanceof Action) { + intendedTagInheritenceCode.pr(''' + if (compare_tags(«inputTrigger.variable.name»->trigger->intended_tag, inherited_min_intended_tag) < 0) { + inherited_min_intended_tag = «inputTrigger.variable.name»->trigger->intended_tag; + } ''') - startupReactionCount += currentFederate.numRuntimeInstances(reactor); - foundOne = true; - } else if (trigger.isShutdown) { - temp.pr(''' - _lf_shutdown_reactions[_lf_shutdown_reactions_count++] = &«reactionRef»; + } + + } + } + if (reaction.triggers === null || reaction.triggers.size === 0) { + // No triggers are given, which means the reaction would react to any input. + // We need to check the intended tag for every input. + // NOTE: this does not include contained outputs. + for (input : (reaction.eContainer as Reactor).inputs) { + intendedTagInheritenceCode.pr(''' + if (compare_tags(«input.name»->intended_tag, inherited_min_intended_tag) > 0) { + inherited_min_intended_tag = «input.name»->intended_tag; + } + ''') + } + } + + // Once the minimum intended tag has been found, + // it will be passed down to the port effects + // of the reaction. Note that the intended tag + // will not pass on to actions downstream. + // Last reaction that sets the intended tag for the effect + // will be seen. + intendedTagInheritenceCode.pr(''' + // All effects inherit the minimum intended tag of input triggers + if (inherited_min_intended_tag.time != NEVER) { + ''') + intendedTagInheritenceCode.indent(); + for (effect : reaction.effects ?: emptyList) { + if (effect.variable instanceof Input) { + if (JavaAstUtils.isMultiport(effect.variable as Port)) { + intendedTagInheritenceCode.pr(''' + for(int i=0; i < «effect.container.name».«effect.variable.name»_width; i++) { + «effect.container.name».«effect.variable.name»[i]->intended_tag = inherited_min_intended_tag; + } ''') - foundOne = true; - shutdownReactionCount += currentFederate.numRuntimeInstances(reactor); - - if (targetConfig.tracing !== null) { - val description = getShortenedName(reactor) - val reactorRef = CUtil.reactorRef(reactor) - temp.pr(''' - _lf_register_trace_event(«reactorRef», &(«reactorRef»->_lf__shutdown), - trace_trigger, "«description».shutdown"); + } else { + if (effect.container.widthSpec !== null) { + // Contained reactor is a bank. + intendedTagInheritenceCode.pr(''' + for (int bankIndex = 0; bankIndex < self->_lf_«effect.container.name»_width; bankIndex++) { + «effect.container.name»[bankIndex].«effect.variable.name» = &(self->_lf_«effect.container.name»[bankIndex].«effect.variable.name»); + } ''') + } else { + // Input to a contained reaction + intendedTagInheritenceCode.pr(''' + // Don't reset the intended tag of the output port if it has already been set. + «effect.container.name».«effect.variable.name»->intended_tag = inherited_min_intended_tag; + ''') } - } + } + } + } + intendedTagInheritenceCode.unindent() + intendedTagInheritenceCode.pr(''' } - if (foundOne) initializeTriggerObjects.pr(temp.toString); + ''') + intendedTagInheritenceCode.unindent() + intendedTagInheritenceCode.pr(''' } + #pragma GCC diagnostic pop + ''') + + // Write the the intended tag inheritance initialization + // to the main code. + code.pr(intendedTagInheritenceCode.toString) } + return intendedTagInheritenceCode } + + /** + * Generate necessary initialization code inside the body of the reaction that belongs to reactor decl. + * @param body The body of the reaction. Used to check for the DISABLE_REACTION_INITIALIZATION_MARKER. + * @param reaction The initialization code will be generated for this specific reaction + * @param decl The reactor that has the reaction + * @param reactionIndex The index of the reaction relative to other reactions in the reactor, starting from 0 + */ + def generateInitializationForReaction(String body, Reaction reaction, ReactorDecl decl, int reactionIndex) { + val reactor = decl.toDefinition + + // Construct the reactionInitialization code to go into + // the body of the function before the verbatim code. + var CodeBuilder reactionInitialization = new CodeBuilder() + + // Define the "self" struct. + var structType = CUtil.selfType(decl) + // A null structType means there are no inputs, state, + // or anything else. No need to declare it. + if (structType !== null) { + code.pr(''' + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunused-variable" + «structType»* self = («structType»*)instance_args; + ((self_base_t*)self)->executing_reaction = &self->_lf__reaction_«reactionIndex»; + ''') + } + + // Do not generate the initialization code if the body is marked + // to not generate it. + if (body.startsWith(CGenerator.DISABLE_REACTION_INITIALIZATION_MARKER)) { + code.pr(''' + #pragma GCC diagnostic pop + ''') + return; + } - + // A reaction may send to or receive from multiple ports of + // a contained reactor. The variables for these ports need to + // all be declared as fields of the same struct. Hence, we first + // collect the fields to be defined in the structs and then + // generate the structs. + var fieldsForStructsForContainedReactors = new LinkedHashMap + + // Actions may appear twice, first as a trigger, then with the outputs. + // But we need to declare it only once. Collect in this data structure + // the actions that are declared as triggered so that if they appear + // again with the outputs, they are not defined a second time. + // That second redefinition would trigger a compile error. + var actionsAsTriggers = new LinkedHashSet(); + + // Next, add the triggers (input and actions; timers are not needed). + // This defines a local variable in the reaction function whose + // name matches that of the trigger. The value of the local variable + // is a struct with a value and is_present field, the latter a boolean + // that indicates whether the input/action is present. + // If the trigger is an output, then it is an output of a + // contained reactor. In this case, a struct with the name + // of the contained reactor is created with one field that is + // a pointer to a struct with a value and is_present field. + // E.g., if the contained reactor is named 'c' and its output + // port is named 'out', then c.out->value c.out->is_present are + // defined so that they can be used in the verbatim code. + for (TriggerRef trigger : reaction.triggers ?: emptyList) { + if (trigger instanceof VarRef) { + if (trigger.variable instanceof Port) { + generatePortVariablesInReaction( + reactionInitialization, + fieldsForStructsForContainedReactors, + trigger, + decl) + } else if (trigger.variable instanceof Action) { + generateActionVariablesInReaction( + reactionInitialization, + trigger.variable as Action, + decl + ) + actionsAsTriggers.add(trigger.variable as Action); + } + } + } + if (reaction.triggers === null || reaction.triggers.size === 0) { + // No triggers are given, which means react to any input. + // Declare an argument for every input. + // NOTE: this does not include contained outputs. + for (input : reactor.inputs) { + generateInputVariablesInReaction(reactionInitialization, input, decl) + } + } + // Define argument for non-triggering inputs. + for (VarRef src : reaction.sources ?: emptyList) { + if (src.variable instanceof Port) { + generatePortVariablesInReaction(reactionInitialization, fieldsForStructsForContainedReactors, src, decl) + } else if (src.variable instanceof Action) { + // It's a bit odd to read but not be triggered by an action, but + // OK, I guess we allow it. + generateActionVariablesInReaction( + reactionInitialization, + src.variable as Action, + decl + ) + actionsAsTriggers.add(src.variable as Action); + } + } + + // Define variables for each declared output or action. + // In the case of outputs, the variable is a pointer to where the + // output is stored. This gives the reaction code access to any previous + // value that may have been written to that output in an earlier reaction. + if (reaction.effects !== null) { + for (effect : reaction.effects) { + if (effect.variable instanceof Action) { + // It is an action, not an output. + // If it has already appeared as trigger, do not redefine it. + if (!actionsAsTriggers.contains(effect.variable)) { + reactionInitialization.pr(''' + «variableStructType(effect.variable, decl)»* «effect.variable.name» = &self->_lf_«effect.variable.name»; + ''') + } + } else { + if (effect.variable instanceof Output) { + generateOutputVariablesInReaction( + reactionInitialization, + effect, + decl + ) + } else if (effect.variable instanceof Input) { + // It is the input of a contained reactor. + generateVariablesForSendingToContainedReactors( + reactionInitialization, + fieldsForStructsForContainedReactors, + effect.container, + effect.variable as Input + ) + } else { + errorReporter.reportError( + reaction, + "In generateReaction(): effect is neither an input nor an output." + ) + } + } + } + } + // Before the reaction initialization, + // generate the structs used for communication to and from contained reactors. + for (containedReactor : fieldsForStructsForContainedReactors.keySet) { + var array = ""; + if (containedReactor.widthSpec !== null) { + code.pr(''' + int «containedReactor.name»_width = self->_lf_«containedReactor.name»_width; + ''') + // Windows does not support variables in arrays declared on the stack, + // so we use the maximum size over all bank members. + array = '''[«maxContainedReactorBankWidth(containedReactor, null, 0)»]'''; + } + code.pr(''' + struct «containedReactor.name» { + «fieldsForStructsForContainedReactors.get(containedReactor)» + } «containedReactor.name»«array»; + ''') + } + // Next generate all the collected setup code. + code.pr(reactionInitialization.toString) + code.pr(''' + #pragma GCC diagnostic pop + ''') + + if (reaction.stp === null) { + // Pass down the intended_tag to all input and output effects + // downstream if the current reaction does not have a STP + // handler. + generateIntendedTagInheritence(body, reaction, decl, reactionIndex) + } + } + + /** + * Return the maximum bank width for the given instantiation within all + * instantiations of its parent reactor. + * On the first call to this method, the breadcrumbs should be null and the max + * argument should be zero. On recursive calls, breadcrumbs is a list of nested + * instantiations, the max is the maximum width found so far. The search for + * instances of the parent reactor will begin with the last instantiation + * in the specified list. + * + * This rather complicated method is used when a reaction sends or receives data + * to or from a bank of contained reactors. There will be an array of structs on + * the self struct of the parent, and the size of the array is conservatively set + * to the maximum of all the identified bank widths. This is a bit wasteful of + * memory, but it avoids having to malloc the array for each instance, and in + * typical usage, there will be few instances or instances that are all the same + * width. + * + * @param containedReactor The contained reactor instantiation. + * @param breadcrumbs null on first call (non-recursive). + * @param max 0 on first call. + */ + private def int maxContainedReactorBankWidth( + Instantiation containedReactor, + LinkedList breadcrumbs, + int max + ) { + // If the instantiation is not a bank, return 1. + if (containedReactor.widthSpec === null) { + return 1 + } + // If there is no main, then we just use the default width. + if (mainDef === null) { + return ASTUtils.width(containedReactor.widthSpec, null) + } + var nestedBreadcrumbs = breadcrumbs + if (nestedBreadcrumbs === null) { + nestedBreadcrumbs = new LinkedList + nestedBreadcrumbs.add(mainDef) + } + var result = max + var parent = containedReactor.eContainer as Reactor + if (parent == mainDef.reactorClass.toDefinition) { + // The parent is main, so there can't be any other instantiations of it. + return ASTUtils.width(containedReactor.widthSpec, null) + } + // Search for instances of the parent within the tail of the breadcrumbs list. + val container = nestedBreadcrumbs.first.reactorClass.toDefinition + for (instantiation: container.instantiations) { + // Put this new instantiation at the head of the list. + nestedBreadcrumbs.add(0, instantiation) + if (instantiation.reactorClass.toDefinition == parent) { + // Found a matching instantiation of the parent. + // Evaluate the original width specification in this context. + val candidate = ASTUtils.width(containedReactor.widthSpec, nestedBreadcrumbs) + if (candidate > result) { + result = candidate + } + } else { + // Found some other instantiation, not the parent. + // Search within it for instantiations of the parent. + // Note that we assume here that the parent cannot contain + // instances of itself. + val candidate = maxContainedReactorBankWidth(containedReactor, nestedBreadcrumbs, result) + if (candidate > result) { + result = candidate + } + } + nestedBreadcrumbs.remove + } + return result + } /** * Generate code to set up the tables used in _lf_start_time_step to decrement reference @@ -2634,7 +3087,7 @@ class CGenerator extends GeneratorBase { startScopedBlock(temp, child, true); for (input : child.inputs) { - if (CUtil.isTokenType((input.definition as Input).inferredType, types)) { + if (isTokenType((input.definition as Input).inferredType)) { foundOne = true; val portRef = CUtil.portRefName(input); if (input.isMultiport()) { @@ -2735,7 +3188,7 @@ class CGenerator extends GeneratorBase { if (port.isOutput && !portsSeen.contains(port)) { portsSeen.add(port) // This reaction is receiving data from the port. - if (CUtil.isTokenType((port.definition as Output).inferredType, types)) { + if (isTokenType((port.definition as Output).inferredType)) { foundOne = true; temp.pr(''' @@ -2838,58 +3291,55 @@ class CGenerator extends GeneratorBase { } /** - * For each action of the specified reactor instance, generate initialization code - * for the offset and period fields. - * @param instance The reactor. + * For each action given, generate initialization code for the offset + * and period fields. + * + * @param actions The actions. */ - private def generateActionInitializations(ReactorInstance instance) { - for (action : instance.actions) { - if (currentFederate.contains(action.definition)) { - if (!action.isShutdown) { - val triggerStructName = CUtil.reactorRef(action.parent) + "->_lf__" + action.name; - var minDelay = action.minDelay - var minSpacing = action.minSpacing - initializeTriggerObjects.pr(''' - // Initializing action «action.fullName» - «triggerStructName».offset = «minDelay.timeInTargetLanguage»; - «IF minSpacing !== null» - «triggerStructName».period = «minSpacing.timeInTargetLanguage»; - «ELSE» - «triggerStructName».period = «CGenerator.UNDEFINED_MIN_SPACING»; - «ENDIF» - ''') - } - triggerCount += currentFederate.numRuntimeInstances(action.parent); + private def generateActionInitializations(Iterable actions) { + for (action : actions) { + if (!action.isShutdown) { + val triggerStructName = CUtil.reactorRef(action.parent) + "->_lf__" + action.name; + var minDelay = action.minDelay + var minSpacing = action.minSpacing + initializeTriggerObjects.pr(''' + // Initializing action «action.fullName» + «triggerStructName».offset = «minDelay.timeInTargetLanguage»; + «IF minSpacing !== null» + «triggerStructName».period = «minSpacing.timeInTargetLanguage»; + «ELSE» + «triggerStructName».period = «CGenerator.UNDEFINED_MIN_SPACING»; + «ENDIF» + ''') } + triggerCount += currentFederate.numRuntimeInstances(action.parent); } } /** - * For each timer in the given reactor, generate initialization code for the offset + * For each timer given, 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. + * @param timers The timers. */ - private def generateTimerInitializations(ReactorInstance instance) { - for (timer : instance.timers) { - if (currentFederate.contains(timer.getDefinition())) { - if (!timer.isStartup) { - val triggerStructName = CUtil.reactorRef(timer.parent) + "->_lf__" + timer.name; - val offset = timer.offset.timeInTargetLanguage - val period = timer.period.timeInTargetLanguage - initializeTriggerObjects.pr(''' - // Initializing timer «timer.fullName». - «triggerStructName».offset = «offset»; - «triggerStructName».period = «period»; - _lf_timer_triggers[_lf_timer_triggers_count++] = &«triggerStructName»; - ''') - timerCount += currentFederate.numRuntimeInstances(timer.parent); - } - triggerCount += currentFederate.numRuntimeInstances(timer.parent); + private def generateTimerInitializations(Iterable timers) { + for (timer : timers) { + if (!timer.isStartup) { + val triggerStructName = CUtil.reactorRef(timer.parent) + "->_lf__" + timer.name; + val offset = timer.offset.timeInTargetLanguage + val period = timer.period.timeInTargetLanguage + initializeTriggerObjects.pr(''' + // Initializing timer «timer.fullName». + «triggerStructName».offset = «offset»; + «triggerStructName».period = «period»; + _lf_timer_triggers[_lf_timer_triggers_count++] = &«triggerStructName»; + ''') + timerCount += currentFederate.numRuntimeInstances(timer.parent); } + triggerCount += currentFederate.numRuntimeInstances(timer.parent); } } @@ -2958,6 +3408,17 @@ class CGenerator extends GeneratorBase { static def variableStructType(TriggerInstance portOrAction) { '''«portOrAction.parent.reactorDeclaration.name.toLowerCase»_«portOrAction.name»_t''' } + + /** + * Return the function name for specified reaction of the + * specified reactor. + * @param reactor The reactor + * @param reactionIndex The reaction index. + * @return The function name for the reaction. + */ + static def reactionFunctionName(ReactorDecl reactor, int reactionIndex) { + reactor.name.toLowerCase + "reaction_function_" + reactionIndex + } /** * Generates C code to retrieve port->member @@ -2999,8 +3460,12 @@ class CGenerator extends GeneratorBase { * 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. + * @param actions The actions of this reactor. + * @param timers The timers of this reactor. */ - private def void generateTraceTableEntries(ReactorInstance instance) { + private def void generateTraceTableEntries( + ReactorInstance instance, Iterable actions, Iterable timers + ) { // If tracing is turned on, record the address of this reaction // in the _lf_trace_object_descriptions table that is used to generate // the header information in the trace file. @@ -3010,21 +3475,82 @@ class CGenerator extends GeneratorBase { initializeTriggerObjects.pr(''' _lf_register_trace_event(«selfStruct», NULL, trace_reactor, "«description»"); ''') - for (action : instance.actions) { - if (currentFederate.contains(action.getDefinition())) { - initializeTriggerObjects.pr(''' - _lf_register_trace_event(«selfStruct», &(«selfStruct»->_lf__«action.name»), trace_trigger, "«description».«action.name»"); - ''') - } + for (action : actions) { + initializeTriggerObjects.pr(''' + _lf_register_trace_event(«selfStruct», &(«selfStruct»->_lf__«action.name»), trace_trigger, "«description».«action.name»"); + ''') } - for (timer : instance.timers) { - if (currentFederate.contains(timer.getDefinition())) { - initializeTriggerObjects.pr(''' - _lf_register_trace_event(«selfStruct», &(«selfStruct»->_lf__«timer.name»), trace_trigger, "«description».«timer.name»"); - ''') - } + for (timer : timers) { + initializeTriggerObjects.pr(''' + _lf_register_trace_event(«selfStruct», &(«selfStruct»->_lf__«timer.name»), trace_trigger, "«description».«timer.name»"); + ''') + } + } + } + + /** + * Generate code to instantiate the main reactor and any contained reactors + * that are in the current federate. + */ + private def void generateMain() { + + // Create lists of the actions, timers, and reactions that are in the federate. + // These default to the full list for non-federated programs. + var actionsInFederate = main.actions.filter[ + a | return currentFederate.contains(a.definition); + ]; + var reactionsInFederate = main.reactions.filter[ + r | return currentFederate.contains(r.definition); + ]; + var timersInFederate = main.timers.filter[ + t | return currentFederate.contains(t.definition); + ]; + + // 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); + + initializeTriggerObjects.pr( + '// ***** Start initializing ' + main.name) + + // Generate the self struct declaration for the top level. + initializeTriggerObjects.pr(''' + «CUtil.reactorRef(main)» = new_«main.name»(); + ''') + + // Generate code for top-level parameters, actions, timers, and reactions that + // are in the federate. + generateTraceTableEntries(main, actionsInFederate, timersInFederate); + generateReactorInstanceExtension(main, reactionsInFederate); + generateParameterInitialization(main); + + initializeTriggerObjects.pr(''' + int _lf_startup_reactions_count = 0; + int _lf_shutdown_reactions_count = 0; + int _lf_timer_triggers_count = 0; + int _lf_tokens_with_ref_count_count = 0; + '''); + + for (child: main.children) { + if (currentFederate.contains(child)) { + // NOTE: child could be a bank, in which case, for federated + // systems, only one of the bank members will be part of the federate. + generateReactorInstance(child); } } + + recordStartupAndShutdown(reactionsInFederate); + generateStateVariableInitializations(main); + generateTimerInitializations(timersInFederate); + generateActionInitializations(actionsInFederate); + generateInitializeActionToken(actionsInFederate); + generateSetDeadline(reactionsInFederate); + generateStartTimeStep(main); + + initializeTriggerObjects.pr("// ***** End initializing " + main.name); } /** @@ -3035,11 +3561,21 @@ class CGenerator extends GeneratorBase { * contained reactors or null if there are no federates. */ def void generateReactorInstance(ReactorInstance instance) { + // FIXME: Consolidate this with generateMain. The only difference is that + // generateMain is the version of this method that is run on main, the + // top-level reactor. + var reactorClass = instance.definition.reactorClass var fullName = instance.fullName initializeTriggerObjects.pr( '// ***** Start initializing ' + fullName + ' of class ' + reactorClass.name) + // 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. + startScopedBlock(startTimeStep, instance, true); + startScopedBlock(initializeTriggerObjects, instance, true); + // Generate the instance self struct containing parameters, state variables, // and outputs (the "self" struct). initializeTriggerObjects.pr(''' @@ -3049,40 +3585,29 @@ class CGenerator extends GeneratorBase { // Generate code to initialize the "self" struct in the // _lf_initialize_trigger_objects function. - generateTraceTableEntries(instance) - generateReactorInstanceExtension(instance) + generateTraceTableEntries(instance, instance.actions, instance.timers) + generateReactorInstanceExtension(instance, instance.reactions) generateParameterInitialization(instance) initializeOutputMultiports(instance) initializeInputMultiports(instance) - recordStartupAndShutdown(instance); + recordStartupAndShutdown(instance.reactions); // 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); + generateTimerInitializations(instance.timers); + generateActionInitializations(instance.actions); - generateInitializeActionToken(instance); - generateSetDeadline(instance); + generateInitializeActionToken(instance.actions); + generateSetDeadline(instance.reactions); // Recursively generate code for the children. for (child : instance.children) { - if (currentFederate.contains(child)) { - // 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. - startScopedBlock(startTimeStep, child, true); - startScopedBlock(initializeTriggerObjects, child, true); - - generateReactorInstance(child); - - endScopedBlock(initializeTriggerObjects); - endScopedBlock(startTimeStep); - } + generateReactorInstance(child); } // If this program is federated with centralized coordination and this reactor @@ -3124,6 +3649,9 @@ class CGenerator extends GeneratorBase { // so that it can deallocate any memory. generateStartTimeStep(instance) + endScopedBlock(initializeTriggerObjects); + endScopedBlock(startTimeStep); + initializeTriggerObjects.pr("//***** End initializing " + fullName) } @@ -3132,19 +3660,18 @@ class CGenerator extends GeneratorBase { * 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. + * @param actions The actions. */ - private def void generateInitializeActionToken(ReactorInstance reactor) { - for (action : reactor.actions) { + private def void generateInitializeActionToken(Iterable actions) { + for (action : actions) { // Skip this step if the action is not in use. - if (action.parent.triggers.contains(action) - && currentFederate.contains(action.definition) - ) { + if (action.parent.triggers.contains(action)) { var type = action.definition.inferredType var payloadSize = "0" if (!type.isUndefined) { var String typeStr = types.getTargetType(type) - if (CUtil.isTokenType(type, types)) { + if (isTokenType(type)) { typeStr = typeStr.rootType } if (typeStr !== null && !typeStr.equals("") && !typeStr.equals("void")) { @@ -3185,7 +3712,10 @@ class CGenerator extends GeneratorBase { * @param instance The reactor instance. * @param reactions The reactions of this instance. */ - def void generateReactorInstanceExtension(ReactorInstance instance) { + def void generateReactorInstanceExtension( + ReactorInstance instance, + Iterable reactions + ) { // Do nothing } @@ -3229,15 +3759,12 @@ class CGenerator extends GeneratorBase { } /** - * Generate code to set the deadline field of the reactions in the - * specified reactor instance. - * @param instance The reactor instance. + * Generate code to set the deadline field of the specified reactions. + * @param reactions The reactions. */ - private def void generateSetDeadline(ReactorInstance instance) { - for (reaction : instance.reactions) { - if (reaction.declaredDeadline !== null - && currentFederate.contains(reaction.getDefinition()) - ) { + private def void generateSetDeadline(Iterable reactions) { + for (reaction : reactions) { + if (reaction.declaredDeadline !== null) { var deadline = reaction.declaredDeadline.maxDelay val selfRef = '''«CUtil.reactorRef(reaction.parent)»->_lf__reaction_«reaction.index»''' initializeTriggerObjects.pr(''' @@ -3262,7 +3789,7 @@ class CGenerator extends GeneratorBase { // 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? - val initializer = CParameterGenerator.getInitializer(parameter); + val initializer = getInitializer(parameter); if (initializer.startsWith("{")) { val temporaryVariableName = parameter.uniqueID initializeTriggerObjects.pr(''' @@ -3492,7 +4019,7 @@ class CGenerator extends GeneratorBase { val ref = JavaAstUtils.generateVarRef(port); // Note that the action.type set by the base class is actually // the port type. - if (CUtil.isTokenType(action.inferredType, types)) { + if (action.inferredType.isTokenType) { ''' if («ref»->is_present) { // Put the whole token on the event queue, not just the payload. @@ -3517,7 +4044,7 @@ class CGenerator extends GeneratorBase { */ override generateForwardBody(Action action, VarRef port) { val outputName = JavaAstUtils.generateVarRef(port) - if (CUtil.isTokenType(action.inferredType, types)) { + if (action.inferredType.isTokenType) { // Forward the entire token and prevent freeing. // Increment the ref_count because it will be decremented // by both the action handling code and the input handling code. @@ -3602,7 +4129,7 @@ class CGenerator extends GeneratorBase { // NOTE: Docs say that malloc'd char* is freed on conclusion of the time step. // So passing it downstream should be OK. value = '''«action.name»->value'''; - if (CUtil.isTokenType(type, types)) { + if (isTokenType(type)) { result.append(''' SET_TOKEN(«receiveRef», «action.name»->token); ''') @@ -3618,7 +4145,7 @@ class CGenerator extends GeneratorBase { case SupportedSerializers.ROS2: { val portType = (receivingPort.variable as Port).inferredType var portTypeStr = types.getTargetType(portType) - if (CUtil.isTokenType(portType, types)) { + if (isTokenType(portType)) { throw new UnsupportedOperationException("Cannot handle ROS serialization when ports are pointers."); } else if (isSharedPtrType(portType)) { val matcher = sharedPointerVariable.matcher(portTypeStr) @@ -3698,7 +4225,11 @@ class CGenerator extends GeneratorBase { var String next_destination_name = '''"federate «receivingFed.id»"''' // Get the delay literal - var String additionalDelayString = CGeneratorExtension.getNetworkDelayLiteral(delay); + var String additionalDelayString = + CGeneratorExtension.getNetworkDelayLiteral( + delay, + this + ); if (isPhysical) { messageType = "MSG_TYPE_P2P_MESSAGE" @@ -3732,7 +4263,7 @@ class CGenerator extends GeneratorBase { switch (serializer) { case SupportedSerializers.NATIVE: { // Handle native types. - if (CUtil.isTokenType(type, types)) { + if (isTokenType(type)) { // NOTE: Transporting token types this way is likely to only work if the sender and receiver // both have the same endianness. Otherwise, you have to use protobufs or some other serialization scheme. result.append(''' @@ -3764,7 +4295,7 @@ class CGenerator extends GeneratorBase { case SupportedSerializers.ROS2: { var variableToSerialize = sendRef; var typeStr = types.getTargetType(type) - if (CUtil.isTokenType(type, types)) { + if (isTokenType(type)) { throw new UnsupportedOperationException("Cannot handle ROS serialization when ports are pointers."); } else if (isSharedPtrType(type)) { val matcher = sharedPointerVariable.matcher(typeStr) @@ -3858,7 +4389,11 @@ class CGenerator extends GeneratorBase { var sendRef = CUtil.portRefInReaction(port, sendingBankIndex, sendingChannelIndex); // Get the delay literal - var String additionalDelayString = CGeneratorExtension.getNetworkDelayLiteral(delay); + var String additionalDelayString = + CGeneratorExtension.getNetworkDelayLiteral( + delay, + this + ); result.append(''' // If the output port has not been SET for the current logical time, @@ -3934,23 +4469,40 @@ class CGenerator extends GeneratorBase { code.pr(this.defineLogLevel) if (isFederated) { - code.pr(CPreambleGenerator.generateFederatedDirective(targetConfig.coordination)) + targetConfig.compileDefinitions.put("FEDERATED", ""); + if (targetConfig.coordination === CoordinationType.CENTRALIZED) { + // The coordination is centralized. + targetConfig.compileDefinitions.put("FEDERATED_CENTRALIZED", ""); + } else if (targetConfig.coordination === CoordinationType.DECENTRALIZED) { + // The coordination is decentralized + targetConfig.compileDefinitions.put("FEDERATED_DECENTRALIZED", ""); + } + // Handle target parameters. // First, if there are federates, then ensure that threading is enabled. - targetConfig.threads = CUtil.minThreadsToHandleInputPorts(federates) + for (federate : federates) { + // The number of threads needs to be at least one larger than the input ports + // to allow the federate to wait on all input ports while allowing an additional + // worker thread to process incoming messages. + if (targetConfig.threads < federate.networkMessageActions.size + 1) { + targetConfig.threads = federate.networkMessageActions.size + 1; + } + } } includeTargetLanguageHeaders() - code.pr(CPreambleGenerator.generateNumFederatesDirective(federates.size)); - code.pr('#define TARGET_FILES_DIRECTORY "' + fileConfig.srcGenPath + '"'); + code.pr('#define NUMBER_OF_FEDERATES ' + federates.size); + + code.pr('#define TARGET_FILES_DIRECTORY "' + fileConfig.srcGenPath + '"'); + if (targetConfig.coordinationOptions.advance_message_interval !== null) { code.pr('#define ADVANCE_MESSAGE_INTERVAL ' + targetConfig.coordinationOptions.advance_message_interval.timeInTargetLanguage) } includeTargetLanguageSourceFiles() - code.pr(CPreambleGenerator.generateMixedRadixIncludeHeader()); + code.pr("#include \"core/mixed_radix.h\""); // Do this after the above includes so that the preamble can // call built-in functions. @@ -4571,10 +5123,374 @@ class CGenerator extends GeneratorBase { } } + /** Generate action variables for a reaction. + * @param builder Where to write the code. + * @param action The action. + * @param reactor The reactor. + */ + private def generateActionVariablesInReaction( + CodeBuilder builder, + Action action, + ReactorDecl decl + ) { + val structType = variableStructType(action, decl) + // If the action has a type, create variables for accessing the value. + val type = action.inferredType + // Pointer to the lf_token_t sent as the payload in the trigger. + val tokenPointer = '''(self->_lf__«action.name».token)''' + builder.pr(action, ''' + // Expose the action struct as a local variable whose name matches the action name. + «structType»* «action.name» = &self->_lf_«action.name»; + // Set the fields of the action struct to match the current trigger. + «action.name»->is_present = (bool)self->_lf__«action.name».status; + «action.name»->has_value = («tokenPointer» != NULL && «tokenPointer»->value != NULL); + «action.name»->token = «tokenPointer»; + ''') + // Set the value field only if there is a type. + if (!type.isUndefined) { + // The value field will either be a copy (for primitive types) + // or a pointer (for types ending in *). + builder.pr(action, ''' + if («action.name»->has_value) { + «IF type.isTokenType» + «action.name»->value = («types.getTargetType(type)»)«tokenPointer»->value; + «ELSE» + «action.name»->value = *(«types.getTargetType(type)»*)«tokenPointer»->value; + «ENDIF» + } + ''') + } + } + + /** Generate into the specified string builder the code to + * initialize local variables for the specified input port + * in a reaction function from the "self" struct. + * @param builder The string builder. + * @param input The input statement from the AST. + * @param reactor The reactor. + */ + private def generateInputVariablesInReaction( + CodeBuilder builder, + Input input, + ReactorDecl decl + ) { + val structType = variableStructType(input, decl) + val inputType = input.inferredType + + // Create the local variable whose name matches the input name. + // If the input has not been declared mutable, then this is a pointer + // to the upstream output. Otherwise, it is a copy of the upstream output, + // which nevertheless points to the same token and value (hence, as done + // below, we have to use writable_copy()). There are 8 cases, + // depending on whether the input is mutable, whether it is a multiport, + // and whether it is a token type. + // Easy case first. + if (!input.isMutable && !inputType.isTokenType && !JavaAstUtils.isMultiport(input)) { + // Non-mutable, non-multiport, primitive type. + builder.pr(''' + «structType»* «input.name» = self->_lf_«input.name»; + ''') + } else if (input.isMutable && !inputType.isTokenType && !JavaAstUtils.isMultiport(input)) { + // Mutable, non-multiport, primitive type. + builder.pr(''' + // Mutable input, so copy the input into a temporary variable. + // The input value on the struct is a copy. + «structType» _lf_tmp_«input.name» = *(self->_lf_«input.name»); + «structType»* «input.name» = &_lf_tmp_«input.name»; + ''') + } else if (!input.isMutable && inputType.isTokenType && !JavaAstUtils.isMultiport(input)) { + // Non-mutable, non-multiport, token type. + builder.pr(''' + «structType»* «input.name» = self->_lf_«input.name»; + if («input.name»->is_present) { + «input.name»->length = «input.name»->token->length; + «input.name»->value = («types.getTargetType(inputType)»)«input.name»->token->value; + } else { + «input.name»->length = 0; + } + ''') + } else if (input.isMutable && inputType.isTokenType && !JavaAstUtils.isMultiport(input)) { + // Mutable, non-multiport, token type. + builder.pr(''' + // Mutable input, so copy the input struct into a temporary variable. + «structType» _lf_tmp_«input.name» = *(self->_lf_«input.name»); + «structType»* «input.name» = &_lf_tmp_«input.name»; + if («input.name»->is_present) { + «input.name»->length = «input.name»->token->length; + lf_token_t* _lf_input_token = «input.name»->token; + «input.name»->token = writable_copy(_lf_input_token); + if («input.name»->token != _lf_input_token) { + // A copy of the input token has been made. + // This needs to be reference counted. + «input.name»->token->ref_count = 1; + // Repurpose the next_free pointer on the token to add to the list. + «input.name»->token->next_free = _lf_more_tokens_with_ref_count; + _lf_more_tokens_with_ref_count = «input.name»->token; + } + «input.name»->value = («types.getTargetType(inputType)»)«input.name»->token->value; + } else { + «input.name»->length = 0; + } + ''') + } else if (!input.isMutable && JavaAstUtils.isMultiport(input)) { + // Non-mutable, multiport, primitive or token type. + builder.pr(''' + «structType»** «input.name» = self->_lf_«input.name»; + ''') + } else if (inputType.isTokenType) { + // Mutable, multiport, token type + builder.pr(''' + // Mutable multiport input, so copy the input structs + // into an array of temporary variables on the stack. + «structType» _lf_tmp_«input.name»[«CUtil.multiportWidthExpression(input)»]; + «structType»* «input.name»[«CUtil.multiportWidthExpression(input)»]; + for (int i = 0; i < «CUtil.multiportWidthExpression(input)»; i++) { + «input.name»[i] = &_lf_tmp_«input.name»[i]; + _lf_tmp_«input.name»[i] = *(self->_lf_«input.name»[i]); + // If necessary, copy the tokens. + if («input.name»[i]->is_present) { + «input.name»[i]->length = «input.name»[i]->token->length; + lf_token_t* _lf_input_token = «input.name»[i]->token; + «input.name»[i]->token = writable_copy(_lf_input_token); + if («input.name»[i]->token != _lf_input_token) { + // A copy of the input token has been made. + // This needs to be reference counted. + «input.name»[i]->token->ref_count = 1; + // Repurpose the next_free pointer on the token to add to the list. + «input.name»[i]->token->next_free = _lf_more_tokens_with_ref_count; + _lf_more_tokens_with_ref_count = «input.name»[i]->token; + } + «input.name»[i]->value = («types.getTargetType(inputType)»)«input.name»[i]->token->value; + } else { + «input.name»[i]->length = 0; + } + } + ''') + } else { + // Mutable, multiport, primitive type + builder.pr(''' + // Mutable multiport input, so copy the input structs + // into an array of temporary variables on the stack. + «structType» _lf_tmp_«input.name»[«CUtil.multiportWidthExpression(input)»]; + «structType»* «input.name»[«CUtil.multiportWidthExpression(input)»]; + for (int i = 0; i < «CUtil.multiportWidthExpression(input)»; i++) { + «input.name»[i] = &_lf_tmp_«input.name»[i]; + // Copy the struct, which includes the value. + _lf_tmp_«input.name»[i] = *(self->_lf_«input.name»[i]); + } + ''') + } + // Set the _width variable for all cases. This will be -1 + // for a variable-width multiport, which is not currently supported. + // It will be -2 if it is not multiport. + builder.pr(''' + int «input.name»_width = self->_lf_«input.name»_width; + ''') + } + + /** + * Generate into the specified string builder the code to + * initialize local variables for ports in a reaction function + * from the "self" struct. The port may be an input of the + * reactor or an output of a contained reactor. The second + * argument provides, for each contained reactor, a place to + * write the declaration of the output of that reactor that + * is triggering reactions. + * @param builder The place into which to write the code. + * @param structs A map from reactor instantiations to a place to write + * struct fields. + * @param port The port. + * @param reactor The reactor or import statement. + */ + private def generatePortVariablesInReaction( + CodeBuilder builder, + LinkedHashMap structs, + VarRef port, + ReactorDecl decl + ) { + if (port.variable instanceof Input) { + generateInputVariablesInReaction(builder, port.variable as Input, decl) + } else { + // port is an output of a contained reactor. + val output = port.variable as Output + val portStructType = variableStructType(output, port.container.reactorClass) + + var structBuilder = structs.get(port.container) + if (structBuilder === null) { + structBuilder = new CodeBuilder() + structs.put(port.container, structBuilder) + } + val reactorName = port.container.name + // First define the struct containing the output value and indicator + // of its presence. + if (!JavaAstUtils.isMultiport(output)) { + // Output is not a multiport. + structBuilder.pr(''' + «portStructType»* «output.name»; + ''') + } else { + // Output is a multiport. + structBuilder.pr(''' + «portStructType»** «output.name»; + int «output.name»_width; + ''') + } + + // Next, initialize the struct with the current values. + if (port.container.widthSpec !== null) { + // Output is in a bank. + builder.pr(''' + for (int i = 0; i < «port.container.name»_width; i++) { + «reactorName»[i].«output.name» = self->_lf_«reactorName»[i].«output.name»; + } + ''') + if (JavaAstUtils.isMultiport(output)) { + builder.pr(''' + for (int i = 0; i < «port.container.name»_width; i++) { + «reactorName»[i].«output.name»_width = self->_lf_«reactorName»[i].«output.name»_width; + } + ''') + } + } else { + // Output is not in a bank. + builder.pr(''' + «reactorName».«output.name» = self->_lf_«reactorName».«output.name»; + ''') + if (JavaAstUtils.isMultiport(output)) { + builder.pr(''' + «reactorName».«output.name»_width = self->_lf_«reactorName».«output.name»_width; + ''') + } + } + } + } + + /** + * Generate into the specified string builder the code to + * initialize local variables for outputs in a reaction function + * from the "self" struct. + * @param builder The string builder. + * @param effect The effect declared by the reaction. This must refer to an output. + * @param decl The reactor containing the reaction or the import statement. + */ + private def generateOutputVariablesInReaction( + CodeBuilder builder, + VarRef effect, + ReactorDecl decl + ) { + val output = effect.variable as Output + if (output.type === null && target.requiresTypes === true) { + errorReporter.reportError(output, "Output is required to have a type: " + output.name) + } else { + // The container of the output may be a contained reactor or + // the reactor containing the reaction. + val outputStructType = (effect.container === null) ? + variableStructType(output, decl) + : + variableStructType(output, effect.container.reactorClass) + if (!JavaAstUtils.isMultiport(output)) { + // Output port is not a multiport. + builder.pr(''' + «outputStructType»* «output.name» = &self->_lf_«output.name»; + ''') + } else { + // Output port is a multiport. + // Set the _width variable. + builder.pr(''' + int «output.name»_width = self->_lf_«output.name»_width; + ''') + builder.pr(''' + «outputStructType»** «output.name» = self->_lf_«output.name»_pointers; + ''') + } + } + } + + /** + * Generate into the specified string builder the code to + * initialize local variables for sending data to an input + * of a contained reactor. This will also, if necessary, + * generate entries for local struct definitions into the + * struct argument. These entries point to where the data + * is stored. + * + * @param builder The string builder. + * @param structs A map from reactor instantiations to a place to write + * struct fields. + * @param definition AST node defining the reactor within which this occurs + * @param input Input of the contained reactor. + */ + private def generateVariablesForSendingToContainedReactors( + CodeBuilder builder, + LinkedHashMap structs, + Instantiation definition, + Input input + ) { + var structBuilder = structs.get(definition) + if (structBuilder === null) { + structBuilder = new CodeBuilder() + structs.put(definition, structBuilder) + } + val inputStructType = variableStructType(input, definition.reactorClass) + if (!JavaAstUtils.isMultiport(input)) { + // Contained reactor's input is not a multiport. + structBuilder.pr(''' + «inputStructType»* «input.name»; + ''') + if (definition.widthSpec !== null) { + // Contained reactor is a bank. + builder.pr(''' + for (int bankIndex = 0; bankIndex < self->_lf_«definition.name»_width; bankIndex++) { + «definition.name»[bankIndex].«input.name» = &(self->_lf_«definition.name»[bankIndex].«input.name»); + } + ''') + } else { + // Contained reactor is not a bank. + builder.pr(''' + «definition.name».«input.name» = &(self->_lf_«definition.name».«input.name»); + ''') + } + } else { + // Contained reactor's input is a multiport. + structBuilder.pr(''' + «inputStructType»** «input.name»; + int «input.name»_width; + ''') + // If the contained reactor is a bank, then we have to set the + // pointer for each element of the bank. + if (definition.widthSpec !== null) { + builder.pr(''' + for (int _i = 0; _i < self->_lf_«definition.name»_width; _i++) { + «definition.name»[_i].«input.name» = self->_lf_«definition.name»[_i].«input.name»; + «definition.name»[_i].«input.name»_width = self->_lf_«definition.name»[_i].«input.name»_width; + } + ''') + } else { + builder.pr(''' + «definition.name».«input.name» = self->_lf_«definition.name».«input.name»; + «definition.name».«input.name»_width = self->_lf_«definition.name».«input.name»_width; + ''') + } + } + } protected def isSharedPtrType(InferredType type) { return !type.isUndefined && sharedPointerVariable.matcher(types.getTargetType(type)).find() } + + /** + * Given a type for an input or output, return true if it should be + * carried by a lf_token_t struct rather than the type itself. + * It should be carried by such a struct if the type ends with * + * (it is a pointer) or [] (it is a array with unspecified length). + * @param type The type specification. + */ + protected def isTokenType(InferredType type) { + if (type.isUndefined) return false + // This is a hacky way to do this. It is now considered to be a bug (#657) + val targetType = types.getVariableDeclaration(type, "", false) + return type.isVariableSizeList || targetType.trim.endsWith("*") + } /** If the type specification of the form {@code type[]}, * {@code type*}, or {@code type}, return the type. @@ -4638,43 +5554,63 @@ class CGenerator extends GeneratorBase { } } - override generateDelayGeneric() { - throw new UnsupportedOperationException("TODO: auto-generated method stub") - } - - //////////////////////////////////////////////////////////// - //// Private methods - /** - * If a main or federted 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. + * Return a C expression that can be used to initialize the specified + * parameter instance. If the parameter initializer refers to other + * parameters, then those parameter references are replaced with + * accesses to the self struct of the parents of those parameters. */ - private def void createMainReactorInstance() { - if (this.mainDef !== null) { - if (this.main === null) { - // Recursively build instances. This is done once because - // it is the same for all federates. - this.main = new ReactorInstance(mainDef.reactorClass.toDefinition, errorReporter, - this.unorderedReactions) - if (this.main.assignLevels().nodeCount > 0) { - errorReporter.reportError("Main reactor has causality cycles. Skipping code generation."); - return; + protected def String getInitializer(ParameterInstance p) { + // Handle the bank_index parameter. + if (p.name.equals("bank_index")) { + return CUtil.bankIndex(p.parent); + } + + // Handle overrides in the intantiation. + // In case there is more than one assignment to this parameter, we need to + // find the last one. + var lastAssignment = null as Assignment; + for (assignment: p.parent.definition.parameters) { + if (assignment.lhs == p.definition) { + lastAssignment = assignment; + } + } + var list = new LinkedList(); + if (lastAssignment !== null) { + // The parameter has an assignment. + // Right hand side can be a list. Collect the entries. + for (value: lastAssignment.rhs) { + if (value.parameter !== null) { + // The parameter is being assigned a parameter value. + // Assume that parameter belongs to the parent's parent. + // This should have been checked by the validator. + list.add(CUtil.reactorRef(p.parent.parent) + "->" + value.parameter.name); + } else { + list.add(value.targetTime) } - // Force reconstruction of dependence information. - if (isFederated) { - // Avoid compile errors by removing disconnected network ports. - // This must be done after assigning levels. - removeRemoteFederateConnectionPorts(main); - // There will be AST transformations that invalidate some info - // cached in ReactorInstance. - this.main.clearCaches(false); + } + } else { + // there was no assignment in the instantiation. So just use the + // parameter's initial value. + for (i : p.parent.initialParameterValue(p.definition)) { + if (p.definition.isOfTimeType) { + list.add(i.targetTime) + } else { + list.add(i.targetTime) } - } + } + } + if (list.size == 1) { + return list.get(0) + } else { + return list.join('{', ', ', '}', [it]) } } - + + override generateDelayGeneric() { + throw new UnsupportedOperationException("TODO: auto-generated method stub") + } + /** * Perform initialization functions that must be performed after * all reactor runtime instances have been created. @@ -4794,7 +5730,7 @@ class CGenerator extends GeneratorBase { // Look for outputs with token types. for (output : reactor.outputs) { val type = (output.definition as Output).inferredType; - if (CUtil.isTokenType(type, types)) { + if (type.isTokenType) { // Create the template token that goes in the trigger struct. // Its reference count is zero, enabling it to be used immediately. var rootType = types.getTargetType(type).rootType; @@ -5542,9 +6478,6 @@ class CGenerator extends GeneratorBase { /** The main place to put generated code. */ protected var code = new CodeBuilder(); - /** The current federate for which we are generating code. */ - protected var currentFederate = null as FederateInstance; - /** Place to collect code to initialize the trigger objects for all reactor instances. */ protected var initializeTriggerObjects = new CodeBuilder() @@ -5579,4 +6512,7 @@ class CGenerator extends GeneratorBase { var boolean CCppMode = false; var CTypes types; + + /** The current federate for which we are generating code. */ + var currentFederate = null as FederateInstance; } diff --git a/org.lflang/src/org/lflang/generator/c/CParameterGenerator.java b/org.lflang/src/org/lflang/generator/c/CParameterGenerator.java deleted file mode 100644 index 56b11c9c5b..0000000000 --- a/org.lflang/src/org/lflang/generator/c/CParameterGenerator.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.lflang.generator.c; - -import java.util.LinkedList; -import java.util.List; -import org.lflang.generator.ParameterInstance; -import org.lflang.JavaAstUtils; -import org.lflang.generator.GeneratorBase; -import org.lflang.lf.Assignment; -import org.lflang.lf.Value; - -public class CParameterGenerator { - /** - * Return a C expression that can be used to initialize the specified - * parameter instance. If the parameter initializer refers to other - * parameters, then those parameter references are replaced with - * accesses to the self struct of the parents of those parameters. - */ - public static String getInitializer(ParameterInstance p) { - // Handle the bank_index parameter. - if (p.getName().equals("bank_index")) { - return CUtil.bankIndex(p.getParent()); - } - - // Handle overrides in the intantiation. - // In case there is more than one assignment to this parameter, we need to - // find the last one. - Assignment lastAssignment = null; - for (Assignment assignment: p.getParent().getDefinition().getParameters()) { - if (assignment.getLhs() == p.getDefinition()) { - lastAssignment = assignment; - } - } - List list = new LinkedList<>(); - if (lastAssignment != null) { - // The parameter has an assignment. - // Right hand side can be a list. Collect the entries. - for (Value value: lastAssignment.getRhs()) { - if (value.getParameter() != null) { - // The parameter is being assigned a parameter value. - // Assume that parameter belongs to the parent's parent. - // This should have been checked by the validator. - list.add(CUtil.reactorRef(p.getParent().getParent()) + "->" + value.getParameter().getName()); - } else { - list.add(GeneratorBase.getTargetTime(value)); - } - } - } else { - // there was no assignment in the instantiation. So just use the - // parameter's initial value. - for (Value i : p.getParent().initialParameterValue(p.getDefinition())) { - if (JavaAstUtils.isOfTimeType(p.getDefinition())) { - list.add(GeneratorBase.getTargetTime(i)); - } else { - list.add(GeneratorBase.getTargetTime(i)); - } - } - } - if (list.size() == 1) { - return list.get(0); - } else { - return "{" + String.join(", ", list) + "}"; - } - } -} diff --git a/org.lflang/src/org/lflang/generator/c/CPreambleGenerator.java b/org.lflang/src/org/lflang/generator/c/CPreambleGenerator.java deleted file mode 100644 index 8d41f94bec..0000000000 --- a/org.lflang/src/org/lflang/generator/c/CPreambleGenerator.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.lflang.generator.c; - -import java.util.ArrayList; -import java.util.List; -import org.lflang.TargetProperty.CoordinationType; - -public class CPreambleGenerator { - /** - * Returns the #define directive for the given coordination type. - * - * NOTE: Instead of checking #ifdef FEDERATED, we could - * use #if (NUMBER_OF_FEDERATES > 1). - * To Soroush Bateni, the former is more accurate. - */ - public static String generateFederatedDirective(CoordinationType coordinationType) { - List directives = new ArrayList<>(); - directives.add("#define FEDERATED"); - if (coordinationType == CoordinationType.CENTRALIZED) { - directives.add("#define FEDERATED_CENTRALIZED"); - } else if (coordinationType == CoordinationType.DECENTRALIZED) { - directives.add("#define FEDERATED_DECENTRALIZED"); - } - return String.join("\n", directives); - } - - public static String generateMixedRadixIncludeHeader() { - return "#include \"core/mixed_radix.h\""; - } - - public static String generateNumFederatesDirective(int numFederates) { - return "#define NUMBER_OF_FEDERATES " + numFederates; - } -} diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java deleted file mode 100644 index e64c787ca3..0000000000 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ /dev/null @@ -1,803 +0,0 @@ -package org.lflang.generator.c; - -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.Map; -import java.util.Set; - -import org.lflang.ASTUtils; -import org.lflang.ErrorReporter; -import org.lflang.InferredType; -import org.lflang.JavaAstUtils; -import org.lflang.generator.CodeBuilder; -import org.lflang.lf.Action; -import org.lflang.lf.Input; -import org.lflang.lf.Instantiation; -import org.lflang.lf.Output; -import org.lflang.lf.Port; -import org.lflang.lf.Reaction; -import org.lflang.lf.Reactor; -import org.lflang.lf.ReactorDecl; -import org.lflang.lf.TriggerRef; -import org.lflang.lf.VarRef; -import org.lflang.lf.Variable; - -import static org.lflang.generator.c.CUtil.generateWidthVariable; - -public class CReactionGenerator { - - /** - * Generate necessary initialization code inside the body of the reaction that belongs to reactor decl. - * @param body The body of the reaction. Used to check for the DISABLE_REACTION_INITIALIZATION_MARKER. - * @param reaction The initialization code will be generated for this specific reaction - * @param decl The reactor that has the reaction - * @param reactionIndex The index of the reaction relative to other reactions in the reactor, starting from 0 - */ - public static String generateInitializationForReaction(String body, - Reaction reaction, - ReactorDecl decl, - int reactionIndex, - CTypes types, - ErrorReporter errorReporter, - Instantiation mainDef, - boolean isFederatedAndDecentralized, - boolean requiresTypes) { - Reactor reactor = ASTUtils.toDefinition(decl); - - // Construct the reactionInitialization code to go into - // the body of the function before the verbatim code. - CodeBuilder reactionInitialization = new CodeBuilder(); - - CodeBuilder code = new CodeBuilder(); - - // Define the "self" struct. - String structType = CUtil.selfType(decl); - // A null structType means there are no inputs, state, - // or anything else. No need to declare it. - if (structType != null) { - code.pr(String.join("\n", - "#pragma GCC diagnostic push", - "#pragma GCC diagnostic ignored \"-Wunused-variable\"", - structType+"* self = ("+structType+"*)instance_args;" - )); - } - - // Do not generate the initialization code if the body is marked - // to not generate it. - if (body.startsWith(CGenerator.DISABLE_REACTION_INITIALIZATION_MARKER)) { - code.pr("#pragma GCC diagnostic pop"); - return code.toString(); - } - - // A reaction may send to or receive from multiple ports of - // a contained reactor. The variables for these ports need to - // all be declared as fields of the same struct. Hence, we first - // collect the fields to be defined in the structs and then - // generate the structs. - Map fieldsForStructsForContainedReactors = new LinkedHashMap<>(); - - // Actions may appear twice, first as a trigger, then with the outputs. - // But we need to declare it only once. Collect in this data structure - // the actions that are declared as triggered so that if they appear - // again with the outputs, they are not defined a second time. - // That second redefinition would trigger a compile error. - Set actionsAsTriggers = new LinkedHashSet<>(); - - // Next, add the triggers (input and actions; timers are not needed). - // This defines a local variable in the reaction function whose - // name matches that of the trigger. The value of the local variable - // is a struct with a value and is_present field, the latter a boolean - // that indicates whether the input/action is present. - // If the trigger is an output, then it is an output of a - // contained reactor. In this case, a struct with the name - // of the contained reactor is created with one field that is - // a pointer to a struct with a value and is_present field. - // E.g., if the contained reactor is named 'c' and its output - // port is named 'out', then c.out->value c.out->is_present are - // defined so that they can be used in the verbatim code. - for (TriggerRef trigger : ASTUtils.convertToEmptyListIfNull(reaction.getTriggers())) { - if (trigger instanceof VarRef) { - VarRef triggerAsVarRef = (VarRef) trigger; - if (triggerAsVarRef.getVariable() instanceof Port) { - generatePortVariablesInReaction( - reactionInitialization, - fieldsForStructsForContainedReactors, - triggerAsVarRef, - decl, - types); - } else if (triggerAsVarRef.getVariable() instanceof Action) { - reactionInitialization.pr(generateActionVariablesInReaction( - (Action) triggerAsVarRef.getVariable(), - decl, - types - )); - actionsAsTriggers.add((Action) triggerAsVarRef.getVariable()); - } - } - } - if (reaction.getTriggers() == null || reaction.getTriggers().size() == 0) { - // No triggers are given, which means react to any input. - // Declare an argument for every input. - // NOTE: this does not include contained outputs. - for (Input input : reactor.getInputs()) { - reactionInitialization.pr(generateInputVariablesInReaction(input, decl, types)); - } - } - // Define argument for non-triggering inputs. - for (VarRef src : ASTUtils.convertToEmptyListIfNull(reaction.getSources())) { - if (src.getVariable() instanceof Port) { - generatePortVariablesInReaction(reactionInitialization, fieldsForStructsForContainedReactors, src, decl, types); - } else if (src.getVariable() instanceof Action) { - // It's a bit odd to read but not be triggered by an action, but - // OK, I guess we allow it. - reactionInitialization.pr(generateActionVariablesInReaction( - (Action) src.getVariable(), - decl, - types - )); - actionsAsTriggers.add((Action) src.getVariable()); - } - } - - // Define variables for each declared output or action. - // In the case of outputs, the variable is a pointer to where the - // output is stored. This gives the reaction code access to any previous - // value that may have been written to that output in an earlier reaction. - if (reaction.getEffects() != null) { - for (VarRef effect : reaction.getEffects()) { - Variable variable = effect.getVariable(); - if (variable instanceof Action) { - // It is an action, not an output. - // If it has already appeared as trigger, do not redefine it. - if (!actionsAsTriggers.contains(effect.getVariable())) { - reactionInitialization.pr(CGenerator.variableStructType(variable, decl)+"* "+variable.getName()+" = &self->_lf_"+variable.getName()+";"); - } - } else { - if (variable instanceof Output) { - reactionInitialization.pr(generateOutputVariablesInReaction( - effect, - decl, - errorReporter, - requiresTypes - )); - } else if (variable instanceof Input) { - // It is the input of a contained reactor. - generateVariablesForSendingToContainedReactors( - reactionInitialization, - fieldsForStructsForContainedReactors, - effect.getContainer(), - (Input) variable - ); - } else { - errorReporter.reportError( - reaction, - "In generateReaction(): effect is neither an input nor an output." - ); - } - } - } - } - // Before the reaction initialization, - // generate the structs used for communication to and from contained reactors. - for (Instantiation containedReactor : fieldsForStructsForContainedReactors.keySet()) { - String array = ""; - if (containedReactor.getWidthSpec() != null) { - String containedReactorWidthVar = generateWidthVariable(containedReactor.getName()); - code.pr("int "+containedReactorWidthVar+" = self->_lf_"+containedReactorWidthVar+";"); - // Windows does not support variables in arrays declared on the stack, - // so we use the maximum size over all bank members. - array = "["+maxContainedReactorBankWidth(containedReactor, null, 0, mainDef)+"]"; - } - code.pr(String.join("\n", - "struct "+containedReactor.getName()+" {", - " "+fieldsForStructsForContainedReactors.get(containedReactor)+"", - "} "+containedReactor.getName()+array+";" - )); - } - // Next generate all the collected setup code. - code.pr(reactionInitialization.toString()); - code.pr("#pragma GCC diagnostic pop"); - - if (reaction.getStp() == null) { - // Pass down the intended_tag to all input and output effects - // downstream if the current reaction does not have a STP - // handler. - code.pr(generateIntendedTagInheritence(body, reaction, decl, reactionIndex, types, isFederatedAndDecentralized)); - } - return code.toString(); - } - - /** - * Return the maximum bank width for the given instantiation within all - * instantiations of its parent reactor. - * On the first call to this method, the breadcrumbs should be null and the max - * argument should be zero. On recursive calls, breadcrumbs is a list of nested - * instantiations, the max is the maximum width found so far. The search for - * instances of the parent reactor will begin with the last instantiation - * in the specified list. - * - * This rather complicated method is used when a reaction sends or receives data - * to or from a bank of contained reactors. There will be an array of structs on - * the self struct of the parent, and the size of the array is conservatively set - * to the maximum of all the identified bank widths. This is a bit wasteful of - * memory, but it avoids having to malloc the array for each instance, and in - * typical usage, there will be few instances or instances that are all the same - * width. - * - * @param containedReactor The contained reactor instantiation. - * @param breadcrumbs null on first call (non-recursive). - * @param max 0 on first call. - */ - public static int maxContainedReactorBankWidth( - Instantiation containedReactor, - LinkedList breadcrumbs, - int max, - Instantiation mainDef - ) { - // If the instantiation is not a bank, return 1. - if (containedReactor.getWidthSpec() == null) { - return 1; - } - // If there is no main, then we just use the default width. - if (mainDef == null) { - return ASTUtils.width(containedReactor.getWidthSpec(), null); - } - LinkedList nestedBreadcrumbs = breadcrumbs; - if (nestedBreadcrumbs == null) { - nestedBreadcrumbs = new LinkedList<>(); - nestedBreadcrumbs.add(mainDef); - } - int result = max; - Reactor parent = (Reactor) containedReactor.eContainer(); - if (parent == ASTUtils.toDefinition(mainDef.getReactorClass())) { - // The parent is main, so there can't be any other instantiations of it. - return ASTUtils.width(containedReactor.getWidthSpec(), null); - } - // Search for instances of the parent within the tail of the breadcrumbs list. - Reactor container = ASTUtils.toDefinition(nestedBreadcrumbs.get(0).getReactorClass()); - for (Instantiation instantiation : container.getInstantiations()) { - // Put this new instantiation at the head of the list. - nestedBreadcrumbs.add(0, instantiation); - if (ASTUtils.toDefinition(instantiation.getReactorClass()) == parent) { - // Found a matching instantiation of the parent. - // Evaluate the original width specification in this context. - int candidate = ASTUtils.width(containedReactor.getWidthSpec(), nestedBreadcrumbs); - if (candidate > result) { - result = candidate; - } - } else { - // Found some other instantiation, not the parent. - // Search within it for instantiations of the parent. - // Note that we assume here that the parent cannot contain - // instances of itself. - int candidate = maxContainedReactorBankWidth(containedReactor, nestedBreadcrumbs, result, mainDef); - if (candidate > result) { - result = candidate; - } - } - nestedBreadcrumbs.remove(); - } - return result; - } - - /** - * Generate code that passes existing intended tag to all output ports - * and actions. This intended tag is the minimum intended tag of the - * triggering inputs of the reaction. - * - * @param body The body of the reaction. Used to check for the DISABLE_REACTION_INITIALIZATION_MARKER. - * @param reaction The initialization code will be generated for this specific reaction - * @param decl The reactor that has the reaction - * @param reactionIndex The index of the reaction relative to other reactions in the reactor, starting from 0 - */ - public static String generateIntendedTagInheritence(String body, Reaction reaction, ReactorDecl decl, int reactionIndex, CTypes types, boolean isFederatedAndDecentralized) { - // Construct the intended_tag inheritance code to go into - // the body of the function. - CodeBuilder intendedTagInheritenceCode = new CodeBuilder(); - // Check if the coordination mode is decentralized and if the reaction has any effects to inherit the STP violation - if (isFederatedAndDecentralized && !(reaction.getEffects() == null || reaction.getEffects().isEmpty())) { - intendedTagInheritenceCode.pr(String.join("\n", - "#pragma GCC diagnostic push", - "#pragma GCC diagnostic ignored \"-Wunused-variable\"", - "if (self->_lf__reaction_"+reactionIndex+".is_STP_violated == true) {" - )); - intendedTagInheritenceCode.indent(); - intendedTagInheritenceCode.pr(String.join("\n", - "// The operations inside this if clause (if any exists) are expensive ", - "// and must only be done if the reaction has unhandled STP violation.", - "// Otherwise, all intended_tag values are (NEVER, 0) by default.", - "", - "// Inherited intended tag. This will take the minimum", - "// intended_tag of all input triggers", - types.getTargetTagType()+" inherited_min_intended_tag = ("+types.getTargetTagType()+") { .time = FOREVER, .microstep = UINT_MAX };" - )); - intendedTagInheritenceCode.pr("// Find the minimum intended tag"); - // Go through every trigger of the reaction and check the - // value of intended_tag to choose the minimum. - for (TriggerRef inputTrigger : ASTUtils.convertToEmptyListIfNull(reaction.getTriggers())) { - if (inputTrigger instanceof VarRef) { - VarRef inputTriggerAsVarRef = (VarRef) inputTrigger; - Variable variable = inputTriggerAsVarRef.getVariable(); - String variableName = inputTriggerAsVarRef.getVariable().getName(); - if (variable instanceof Output) { - // Output from a contained reactor - String containerName = inputTriggerAsVarRef.getContainer().getName(); - Output outputPort = (Output) variable; - if (JavaAstUtils.isMultiport(outputPort)) { - intendedTagInheritenceCode.pr(String.join("\n", - "for (int i=0; i < "+containerName+"."+generateWidthVariable(variableName)+"; i++) {", - " if (compare_tags("+containerName+"."+variableName+"[i]->intended_tag,", - " inherited_min_intended_tag) < 0) {", - " inherited_min_intended_tag = "+containerName+"."+variableName+"[i]->intended_tag;", - " }", - "}" - )); - } else - intendedTagInheritenceCode.pr(String.join("\n", - "if (compare_tags("+containerName+"."+variableName+"->intended_tag,", - " inherited_min_intended_tag) < 0) {", - " inherited_min_intended_tag = "+containerName+"."+variableName+"->intended_tag;", - "}" - )); - } else if (variable instanceof Port) { - // Input port - Port inputPort = (Port) variable; - if (JavaAstUtils.isMultiport(inputPort)) { - intendedTagInheritenceCode.pr(String.join("\n", - "for (int i=0; i < "+generateWidthVariable(variableName)+"; i++) {", - " if (compare_tags("+variableName+"[i]->intended_tag, inherited_min_intended_tag) < 0) {", - " inherited_min_intended_tag = "+variableName+"[i]->intended_tag;", - " }", - "}" - )); - } else { - intendedTagInheritenceCode.pr(String.join("\n", - "if (compare_tags("+variableName+"->intended_tag, inherited_min_intended_tag) < 0) {", - " inherited_min_intended_tag = "+variableName+"->intended_tag;", - "}" - )); - } - } else if (variable instanceof Action) { - intendedTagInheritenceCode.pr(String.join("\n", - "if (compare_tags("+variableName+"->trigger->intended_tag, inherited_min_intended_tag) < 0) {", - " inherited_min_intended_tag = "+variableName+"->trigger->intended_tag;", - "}" - )); - } - - } - } - if (reaction.getTriggers() == null || reaction.getTriggers().size() == 0) { - // No triggers are given, which means the reaction would react to any input. - // We need to check the intended tag for every input. - // NOTE: this does not include contained outputs. - for (Input input : ((Reactor) reaction.eContainer()).getInputs()) { - intendedTagInheritenceCode.pr(String.join("\n", - "if (compare_tags("+input.getName()+"->intended_tag, inherited_min_intended_tag) > 0) {", - " inherited_min_intended_tag = "+input.getName()+"->intended_tag;", - "}" - )); - } - } - - // Once the minimum intended tag has been found, - // it will be passed down to the port effects - // of the reaction. Note that the intended tag - // will not pass on to actions downstream. - // Last reaction that sets the intended tag for the effect - // will be seen. - intendedTagInheritenceCode.pr(String.join("\n", - "// All effects inherit the minimum intended tag of input triggers", - "if (inherited_min_intended_tag.time != NEVER) {" - )); - intendedTagInheritenceCode.indent(); - for (VarRef effect : ASTUtils.convertToEmptyListIfNull(reaction.getEffects())) { - Variable effectVar = effect.getVariable(); - Instantiation effContainer = effect.getContainer(); - if (effectVar instanceof Input) { - if (JavaAstUtils.isMultiport((Port) effectVar)) { - intendedTagInheritenceCode.pr(String.join("\n", - "for(int i=0; i < "+effContainer.getName()+"."+generateWidthVariable(effectVar.getName())+"; i++) {", - " "+effContainer.getName()+"."+effectVar.getName()+"[i]->intended_tag = inherited_min_intended_tag;", - "}" - )); - } else { - if (effContainer.getWidthSpec() != null) { - // Contained reactor is a bank. - intendedTagInheritenceCode.pr(String.join("\n", - "for (int bankIndex = 0; bankIndex < self->_lf_"+generateWidthVariable(effContainer.getName())+"; bankIndex++) {", - " "+effContainer.getName()+"[bankIndex]."+effectVar.getName()+" = &(self->_lf_"+effContainer.getName()+"[bankIndex]."+effectVar.getName()+");", - "}" - )); - } else { - // Input to a contained reaction - intendedTagInheritenceCode.pr(String.join("\n", - "// Don't reset the intended tag of the output port if it has already been set.", - effContainer.getName()+"."+effectVar.getName()+"->intended_tag = inherited_min_intended_tag;" - )); - } - } - } - } - intendedTagInheritenceCode.unindent(); - intendedTagInheritenceCode.pr("}"); - intendedTagInheritenceCode.unindent(); - intendedTagInheritenceCode.pr("#pragma GCC diagnostic pop"); - intendedTagInheritenceCode.pr("}"); - - } - return intendedTagInheritenceCode.toString(); - } - - /** - * Generate into the specified string builder the code to - * initialize local variables for sending data to an input - * of a contained reactor. This will also, if necessary, - * generate entries for local struct definitions into the - * struct argument. These entries point to where the data - * is stored. - * - * @param builder The string builder. - * @param structs A map from reactor instantiations to a place to write - * struct fields. - * @param definition AST node defining the reactor within which this occurs - * @param input Input of the contained reactor. - */ - private static void generateVariablesForSendingToContainedReactors( - CodeBuilder builder, - Map structs, - Instantiation definition, - Input input - ) { - CodeBuilder structBuilder = structs.get(definition); - if (structBuilder == null) { - structBuilder = new CodeBuilder(); - structs.put(definition, structBuilder); - } - String inputStructType = CGenerator.variableStructType(input, definition.getReactorClass()).toString(); - String defName = definition.getName(); - String defWidth = generateWidthVariable(defName); - String inputName = input.getName(); - String inputWidth = generateWidthVariable(inputName); - if (!JavaAstUtils.isMultiport(input)) { - // Contained reactor's input is not a multiport. - structBuilder.pr(inputStructType+"* "+inputName+";"); - if (definition.getWidthSpec() != null) { - // Contained reactor is a bank. - builder.pr(String.join("\n", - "for (int bankIndex = 0; bankIndex < self->_lf_"+defWidth+"; bankIndex++) {", - " "+defName+"[bankIndex]."+inputName+" = &(self->_lf_"+defName+"[bankIndex]."+inputName+");", - "}" - )); - } else { - // Contained reactor is not a bank. - builder.pr(defName+"."+inputName+" = &(self->_lf_"+defName+"."+inputName+");"); - } - } else { - // Contained reactor's input is a multiport. - structBuilder.pr(String.join("\n", - inputStructType+"** "+inputName+";", - "int "+inputWidth+";" - )); - // If the contained reactor is a bank, then we have to set the - // pointer for each element of the bank. - if (definition.getWidthSpec() != null) { - builder.pr(String.join("\n", - "for (int _i = 0; _i < self->_lf_"+defWidth+"; _i++) {", - " "+defName+"[_i]."+inputName+" = self->_lf_"+defName+"[_i]."+inputName+";", - " "+defName+"[_i]."+inputWidth+" = self->_lf_"+defName+"[_i]."+inputWidth+";", - "}" - )); - } else { - builder.pr(String.join("\n", - defName+"."+inputName+" = self->_lf_"+defName+"."+inputName+";", - defName+"."+inputWidth+" = self->_lf_"+defName+"."+inputWidth+";" - )); - } - } - } - - /** - * Generate into the specified string builder the code to - * initialize local variables for ports in a reaction function - * from the "self" struct. The port may be an input of the - * reactor or an output of a contained reactor. The second - * argument provides, for each contained reactor, a place to - * write the declaration of the output of that reactor that - * is triggering reactions. - * @param builder The place into which to write the code. - * @param structs A map from reactor instantiations to a place to write - * struct fields. - * @param port The port. - * @param reactor The reactor or import statement. - */ - private static void generatePortVariablesInReaction( - CodeBuilder builder, - Map structs, - VarRef port, - ReactorDecl decl, - CTypes types - ) { - if (port.getVariable() instanceof Input) { - builder.pr(generateInputVariablesInReaction((Input) port.getVariable(), decl, types)); - } else { - // port is an output of a contained reactor. - Output output = (Output) port.getVariable(); - String portStructType = CGenerator.variableStructType(output, port.getContainer().getReactorClass()).toString(); - - CodeBuilder structBuilder = structs.get(port.getContainer()); - if (structBuilder == null) { - structBuilder = new CodeBuilder(); - structs.put(port.getContainer(), structBuilder); - } - String reactorName = port.getContainer().getName(); - String reactorWidth = generateWidthVariable(reactorName); - String outputName = output.getName(); - String outputWidth = generateWidthVariable(outputName); - // First define the struct containing the output value and indicator - // of its presence. - if (!JavaAstUtils.isMultiport(output)) { - // Output is not a multiport. - structBuilder.pr(portStructType+"* "+outputName+";"); - } else { - // Output is a multiport. - structBuilder.pr(String.join("\n", - portStructType+"** "+outputName+";", - "int "+outputWidth+";" - )); - } - - // Next, initialize the struct with the current values. - if (port.getContainer().getWidthSpec() != null) { - // Output is in a bank. - builder.pr(String.join("\n", - "for (int i = 0; i < "+reactorWidth+"; i++) {", - " "+reactorName+"[i]."+outputName+" = self->_lf_"+reactorName+"[i]."+outputName+";", - "}" - )); - if (JavaAstUtils.isMultiport(output)) { - builder.pr(String.join("\n", - "for (int i = 0; i < "+reactorWidth+"; i++) {", - " "+reactorName+"[i]."+outputWidth+" = self->_lf_"+reactorName+"[i]."+outputWidth+";", - "}" - )); - } - } else { - // Output is not in a bank. - builder.pr(reactorName+"."+outputName+" = self->_lf_"+reactorName+"."+outputName+";"); - if (JavaAstUtils.isMultiport(output)) { - builder.pr(reactorName+"."+outputWidth+" = self->_lf_"+reactorName+"."+outputWidth+";"); - } - } - } - } - - /** Generate action variables for a reaction. - * @param builder Where to write the code. - * @param action The action. - * @param reactor The reactor. - */ - private static String generateActionVariablesInReaction( - Action action, - ReactorDecl decl, - CTypes types - ) { - String structType = CGenerator.variableStructType(action, decl).toString(); - // If the action has a type, create variables for accessing the value. - InferredType type = JavaAstUtils.getInferredType(action); - // Pointer to the lf_token_t sent as the payload in the trigger. - String tokenPointer = "(self->_lf__"+action.getName()+".token)"; - CodeBuilder builder = new CodeBuilder(); - - builder.pr( - String.join("\n", - "// Expose the action struct as a local variable whose name matches the action name.", - structType+"* "+action.getName()+" = &self->_lf_"+action.getName()+";", - "// Set the fields of the action struct to match the current trigger.", - action.getName()+"->is_present = (bool)self->_lf__"+action.getName()+".status;", - action.getName()+"->has_value = ("+tokenPointer+" != NULL && "+tokenPointer+"->value != NULL);", - action.getName()+"->token = "+tokenPointer+";") - ); - // Set the value field only if there is a type. - if (!type.isUndefined()) { - // The value field will either be a copy (for primitive types) - // or a pointer (for types ending in *). - builder.pr("if ("+action.getName()+"->has_value) {"); - builder.indent(); - if (CUtil.isTokenType(type, types)) { - builder.pr(action.getName()+"->value = ("+types.getTargetType(type)+")"+tokenPointer+"->value;"); - } else { - builder.pr(action.getName()+"->value = *("+types.getTargetType(type)+"*)"+tokenPointer+"->value;"); - } - builder.unindent(); - builder.pr("}"); - } - return builder.toString(); - } - - /** Generate into the specified string builder the code to - * initialize local variables for the specified input port - * in a reaction function from the "self" struct. - * @param builder The string builder. - * @param input The input statement from the AST. - * @param reactor The reactor. - */ - private static String generateInputVariablesInReaction( - Input input, - ReactorDecl decl, - CTypes types - ) { - String structType = CGenerator.variableStructType(input, decl).toString(); - InferredType inputType = JavaAstUtils.getInferredType(input); - CodeBuilder builder = new CodeBuilder(); - String inputName = input.getName(); - String inputWidth = generateWidthVariable(inputName); - - // Create the local variable whose name matches the input name. - // If the input has not been declared mutable, then this is a pointer - // to the upstream output. Otherwise, it is a copy of the upstream output, - // which nevertheless points to the same token and value (hence, as done - // below, we have to use writable_copy()). There are 8 cases, - // depending on whether the input is mutable, whether it is a multiport, - // and whether it is a token type. - // Easy case first. - if (!input.isMutable() && !CUtil.isTokenType(inputType, types) && !JavaAstUtils.isMultiport(input)) { - // Non-mutable, non-multiport, primitive type. - builder.pr(structType+"* "+inputName+" = self->_lf_"+inputName+";"); - } else if (input.isMutable()&& !CUtil.isTokenType(inputType, types) && !JavaAstUtils.isMultiport(input)) { - // Mutable, non-multiport, primitive type. - builder.pr(String.join("\n", - "// Mutable input, so copy the input into a temporary variable.", - "// The input value on the struct is a copy.", - structType+" _lf_tmp_"+inputName+" = *(self->_lf_"+inputName+");", - structType+"* "+inputName+" = &_lf_tmp_"+inputName+";" - )); - } else if (!input.isMutable()&& CUtil.isTokenType(inputType, types) && !JavaAstUtils.isMultiport(input)) { - // Non-mutable, non-multiport, token type. - builder.pr(String.join("\n", - structType+"* "+inputName+" = self->_lf_"+inputName+";", - "if ("+inputName+"->is_present) {", - " "+inputName+"->length = "+inputName+"->token->length;", - " "+inputName+"->value = ("+types.getTargetType(inputType)+")"+inputName+"->token->value;", - "} else {", - " "+inputName+"->length = 0;", - "}" - )); - } else if (input.isMutable()&& CUtil.isTokenType(inputType, types) && !JavaAstUtils.isMultiport(input)) { - // Mutable, non-multiport, token type. - builder.pr(String.join("\n", - "// Mutable input, so copy the input struct into a temporary variable.", - structType+" _lf_tmp_"+inputName+" = *(self->_lf_"+inputName+");", - structType+"* "+inputName+" = &_lf_tmp_"+inputName+";", - "if ("+inputName+"->is_present) {", - " "+inputName+"->length = "+inputName+"->token->length;", - " lf_token_t* _lf_input_token = "+inputName+"->token;", - " "+inputName+"->token = writable_copy(_lf_input_token);", - " if ("+inputName+"->token != _lf_input_token) {", - " // A copy of the input token has been made.", - " // This needs to be reference counted.", - " "+inputName+"->token->ref_count = 1;", - " // Repurpose the next_free pointer on the token to add to the list.", - " "+inputName+"->token->next_free = _lf_more_tokens_with_ref_count;", - " _lf_more_tokens_with_ref_count = "+inputName+"->token;", - " }", - " "+inputName+"->value = ("+types.getTargetType(inputType)+")"+inputName+"->token->value;", - "} else {", - " "+inputName+"->length = 0;", - "}" - )); - } else if (!input.isMutable()&& JavaAstUtils.isMultiport(input)) { - // Non-mutable, multiport, primitive or token type. - builder.pr(structType+"** "+inputName+" = self->_lf_"+inputName+";"); - } else if (CUtil.isTokenType(inputType, types)) { - // Mutable, multiport, token type - builder.pr(String.join("\n", - "// Mutable multiport input, so copy the input structs", - "// into an array of temporary variables on the stack.", - structType+" _lf_tmp_"+inputName+"["+CUtil.multiportWidthExpression(input)+"];", - structType+"* "+inputName+"["+CUtil.multiportWidthExpression(input)+"];", - "for (int i = 0; i < "+CUtil.multiportWidthExpression(input)+"; i++) {", - " "+inputName+"[i] = &_lf_tmp_"+inputName+"[i];", - " _lf_tmp_"+inputName+"[i] = *(self->_lf_"+inputName+"[i]);", - " // If necessary, copy the tokens.", - " if ("+inputName+"[i]->is_present) {", - " "+inputName+"[i]->length = "+inputName+"[i]->token->length;", - " lf_token_t* _lf_input_token = "+inputName+"[i]->token;", - " "+inputName+"[i]->token = writable_copy(_lf_input_token);", - " if ("+inputName+"[i]->token != _lf_input_token) {", - " // A copy of the input token has been made.", - " // This needs to be reference counted.", - " "+inputName+"[i]->token->ref_count = 1;", - " // Repurpose the next_free pointer on the token to add to the list.", - " "+inputName+"[i]->token->next_free = _lf_more_tokens_with_ref_count;", - " _lf_more_tokens_with_ref_count = "+inputName+"[i]->token;", - " }", - " "+inputName+"[i]->value = ("+types.getTargetType(inputType)+")"+inputName+"[i]->token->value;", - " } else {", - " "+inputName+"[i]->length = 0;", - " }", - "}" - )); - } else { - // Mutable, multiport, primitive type - builder.pr(String.join("\n", - "// Mutable multiport input, so copy the input structs", - "// into an array of temporary variables on the stack.", - structType+" _lf_tmp_"+inputName+"["+CUtil.multiportWidthExpression(input)+"];", - structType+"* "+inputName+"["+CUtil.multiportWidthExpression(input)+"];", - "for (int i = 0; i < "+CUtil.multiportWidthExpression(input)+"; i++) {", - " "+inputName+"[i] = &_lf_tmp_"+inputName+"[i];", - " // Copy the struct, which includes the value.", - " _lf_tmp_"+inputName+"[i] = *(self->_lf_"+inputName+"[i]);", - "}" - )); - } - // Set the _width variable for all cases. This will be -1 - // for a variable-width multiport, which is not currently supported. - // It will be -2 if it is not multiport. - builder.pr("int "+inputWidth+" = self->_lf_"+inputWidth+";"); - return builder.toString(); - } - - /** - * Generate into the specified string builder the code to - * initialize local variables for outputs in a reaction function - * from the "self" struct. - * @param effect The effect declared by the reaction. This must refer to an output. - * @param decl The reactor containing the reaction or the import statement. - */ - public static String generateOutputVariablesInReaction( - VarRef effect, - ReactorDecl decl, - ErrorReporter errorReporter, - boolean requiresTypes - ) { - Output output = (Output) effect.getVariable(); - String outputName = output.getName(); - String outputWidth = generateWidthVariable(outputName); - if (output.getType() == null && requiresTypes) { - errorReporter.reportError(output, "Output is required to have a type: " + outputName); - return ""; - } else { - // The container of the output may be a contained reactor or - // the reactor containing the reaction. - String outputStructType = (effect.getContainer() == null) ? - CGenerator.variableStructType(output, decl).toString() - : - CGenerator.variableStructType(output, effect.getContainer().getReactorClass()).toString(); - if (!JavaAstUtils.isMultiport(output)) { - // Output port is not a multiport. - return outputStructType+"* "+outputName+" = &self->_lf_"+outputName+";"; - } else { - // Output port is a multiport. - // Set the _width variable. - return String.join("\n", - "int "+outputWidth+" = self->_lf_"+outputWidth+";", - outputStructType+"** "+outputName+" = self->_lf_"+outputName+"_pointers;" - ); - - } - } - } - - /** - * Returns the name of the deadline function for reaction. - * @param decl The reactor with the deadline - * @param index The number assigned to this reaction deadline - */ - public static String generateDeadlineFunctionName(ReactorDecl decl, int index) { - return decl.getName().toLowerCase() + "_deadline_function" + index; - } - - /** - * Return the function name for specified reaction of the - * specified reactor. - * @param reactor The reactor - * @param reactionIndex The reaction index. - * @return The function name for the reaction. - */ - public static String generateReactionFunctionName(ReactorDecl reactor, int reactionIndex) { - return reactor.getName().toLowerCase() + "reaction_function_" + reactionIndex; - } -} diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index 0327aab4d2..9e561cb8b6 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -34,10 +34,8 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.ErrorReporter; import org.lflang.FileConfig; -import org.lflang.InferredType; import org.lflang.TargetConfig; import org.lflang.TargetConfig.Mode; -import org.lflang.federated.FederateInstance; import org.lflang.generator.GeneratorCommandFactory; import org.lflang.generator.PortInstance; import org.lflang.generator.ReactionInstance; @@ -674,38 +672,4 @@ private static List multiportWidthTerms(Variable variable) { } return result; } - - /** - * Given a type for an input or output, return true if it should be - * carried by a lf_token_t struct rather than the type itself. - * It should be carried by such a struct if the type ends with * - * (it is a pointer) or [] (it is a array with unspecified length). - * @param type The type specification. - */ - public static boolean isTokenType(InferredType type, CTypes types) { - if (type.isUndefined()) return false; - // This is a hacky way to do this. It is now considered to be a bug (#657) - String targetType = types.getVariableDeclaration(type, "", false); - return type.isVariableSizeList || targetType.trim().endsWith("*"); - } - - /** - * The number of threads needs to be at least one larger than the input ports - * to allow the federate to wait on all input ports while allowing an additional - * worker thread to process incoming messages. - * - * @param federates - * @return The minimum number of threads needed. - */ - public static int minThreadsToHandleInputPorts(List federates) { - int nthreads = 1; - for (FederateInstance federate : federates) { - nthreads = Math.max(nthreads, federate.networkMessageActions.size() + 1); - } - return nthreads; - } - - public static String generateWidthVariable(String var) { - return var + "_width"; - } } diff --git a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt index 4d12c7e38c..7ab527bbda 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt @@ -27,11 +27,11 @@ package org.lflang.generator.cpp import org.eclipse.emf.ecore.resource.Resource -import org.lflang.ErrorReporter -import org.lflang.Target +import org.eclipse.xtext.generator.IFileSystemAccess2 +import org.lflang.* import org.lflang.TargetConfig.Mode -import org.lflang.TimeUnit -import org.lflang.TimeValue +import org.lflang.Target +import org.lflang.generator.canGenerate import org.lflang.generator.CodeMap import org.lflang.generator.GeneratorBase import org.lflang.generator.GeneratorResult @@ -39,13 +39,9 @@ import org.lflang.generator.IntegratedBuilder import org.lflang.generator.JavaGeneratorUtils import org.lflang.generator.LFGeneratorContext import org.lflang.generator.TargetTypes -import org.lflang.generator.canGenerate -import org.lflang.isGeneric import org.lflang.lf.Action import org.lflang.lf.VarRef import org.lflang.scoping.LFGlobalScopeProvider -import org.lflang.toDefinition -import org.lflang.toUnixString import org.lflang.util.LFCommand import java.nio.file.Files import java.nio.file.Path @@ -107,9 +103,9 @@ class CppGenerator( // copy static library files over to the src-gen directory val genIncludeDir = srcGenPath.resolve("__include__") - fileConfig.copyFileFromClassPath("$libDir/lfutil.hh", genIncludeDir.resolve("lfutil.hh")) - fileConfig.copyFileFromClassPath("$libDir/time_parser.hh", genIncludeDir.resolve("time_parser.hh")) - fileConfig.copyFileFromClassPath("$libDir/3rd-party/cxxopts.hpp", genIncludeDir.resolve("CLI").resolve("cxxopts.hpp")) + fileConfig.copyFileFromClassPath("$libDir/lfutil.hh", genIncludeDir.resolve("lfutil.hh").toString()) + fileConfig.copyFileFromClassPath("$libDir/time_parser.hh", genIncludeDir.resolve("time_parser.hh").toString()) + fileConfig.copyFileFromClassPath("$libDir/3rd-party/cxxopts.hpp", genIncludeDir.resolve("CLI").resolve("cxxopts.hpp").toString()) // keep a list of all source files we generate val cppSources = mutableListOf() diff --git a/org.lflang/src/org/lflang/generator/python/PyUtil.java b/org.lflang/src/org/lflang/generator/python/PyUtil.java index ff65be5ce6..8dd38ec174 100644 --- a/org.lflang/src/org/lflang/generator/python/PyUtil.java +++ b/org.lflang/src/org/lflang/generator/python/PyUtil.java @@ -27,14 +27,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY package org.lflang.generator.python; import org.lflang.generator.ReactorInstance; -import org.lflang.generator.GeneratorBase; import org.lflang.generator.c.CUtil; -import org.lflang.lf.Value; - -import java.io.IOException; - -import org.lflang.ASTUtils; -import org.lflang.FileConfig; /** @@ -55,7 +48,7 @@ public class PyUtil extends CUtil { * * @param instance The reactor instance. */ - public static String reactorRefName(ReactorInstance instance) { + static public String reactorRefName(ReactorInstance instance) { return instance.uniqueID() + "_lf"; } @@ -73,7 +66,7 @@ public static String reactorRefName(ReactorInstance instance) { * that returned by * {@link #runtimeIndex(ReactorInstance)}. */ - public static String reactorRef(ReactorInstance instance, String runtimeIndex) { + static public String reactorRef(ReactorInstance instance, String runtimeIndex) { if (runtimeIndex == null) runtimeIndex = runtimeIndex(instance); return PyUtil.reactorRefName(instance) + "[" + runtimeIndex + "]"; } @@ -88,104 +81,8 @@ public static String reactorRef(ReactorInstance instance, String runtimeIndex) { * * @param instance The reactor instance. */ - public static String reactorRef(ReactorInstance instance) { + static public String reactorRef(ReactorInstance instance) { return PyUtil.reactorRef(instance, null); } - /** - * Convert C types to formats used in Py_BuildValue and PyArg_PurseTuple. - * This is unused but will be useful to enable inter-compatibility between - * C and Python reactors. - * @param type C type - */ - public static String pyBuildValueArgumentType(String type) { - switch (type) { - case "int": return "i"; - case "string": return "s"; - case "char": return "b"; - case "short int": return "h"; - case "long": return "l"; - case "unsigned char": return "B"; - case "unsigned short int": return "H"; - case "unsigned int": return "I"; - case "unsigned long": return "k"; - case "long long": return "L"; - case "interval_t": return "L"; - case "unsigned long long": return "K"; - case "double": return "d"; - case "float": return "f"; - case "Py_complex": return "D"; - case "Py_complex*": return "D"; - case "Py_Object": return "O"; - case "Py_Object*": return "O"; - default: return "O"; - } - } - - public static String generateGILAcquireCode() { - return String.join("\n", - "// Acquire the GIL (Global Interpreter Lock) to be able to call Python APIs.", - "PyGILState_STATE gstate;", - "gstate = PyGILState_Ensure();" - ); - } - - public static String generateGILReleaseCode() { - return String.join("\n", - "/* Release the thread. No Python API allowed beyond this point. */", - "PyGILState_Release(gstate);" - ); - } - - /** - * Override to convert some C types to their - * Python equivalent. - * Examples: - * true/false -> True/False - * @param v A value - * @return A value string in the target language - */ - protected static String getPythonTargetValue(Value v) { - String returnValue = ""; - switch (ASTUtils.toText(v)) { - case "false": - returnValue = "False"; - break; - case "true": - returnValue = "True"; - break; - default: - returnValue = GeneratorBase.getTargetValue(v); - } - - // Parameters in Python are always prepended with a 'self.' - // predicate. Therefore, we need to append the returned value - // if it is a parameter. - if (v.getParameter() != null) { - returnValue = "self." + returnValue; - } - - return returnValue; - } - - - /** - * Copy Python specific target code to the src-gen directory - */ - public static void copyTargetFiles(FileConfig fileConfig) throws IOException { - // Copy the required target language files into the target file system. - // This will also overwrite previous versions. - fileConfig.copyFileFromClassPath( - "/lib/py/reactor-c-py/include/pythontarget.h", - fileConfig.getSrcGenPath().resolve("pythontarget.h") - ); - fileConfig.copyFileFromClassPath( - "/lib/py/reactor-c-py/lib/pythontarget.c", - fileConfig.getSrcGenPath().resolve("pythontarget.c") - ); - fileConfig.copyFileFromClassPath( - "/lib/c/reactor-c/include/ctarget.h", - fileConfig.getSrcGenPath().resolve("ctarget.h") - ); - } -} \ No newline at end of file +} diff --git a/org.lflang/src/org/lflang/generator/python/PythonActionGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonActionGenerator.java deleted file mode 100644 index 980a835d37..0000000000 --- a/org.lflang/src/org/lflang/generator/python/PythonActionGenerator.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.lflang.generator.python; - -import org.lflang.lf.Action; -import org.lflang.lf.ReactorDecl; -import org.lflang.generator.c.CGenerator; - -public class PythonActionGenerator { - public static String generateAliasTypeDef(ReactorDecl decl, Action action, - String genericActionType) { - - return "typedef "+genericActionType+" "+CGenerator.variableStructType(action, decl)+";"; - } -} diff --git a/org.lflang/src/org/lflang/generator/python/PythonDockerGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonDockerGenerator.java deleted file mode 100644 index f364258d6b..0000000000 --- a/org.lflang/src/org/lflang/generator/python/PythonDockerGenerator.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.lflang.generator.python; - -import java.nio.file.Path; - -public class PythonDockerGenerator { - public static String generateDockerFileContent(String topLevelName, Path srcGenPath) { - return String.join("\n", - "# Generated docker file for "+topLevelName+".lf in "+srcGenPath+".", - "# For instructions, see: https://github.com/icyphy/lingua-franca/wiki/Containerized-Execution", - "FROM python:slim", - "WORKDIR /lingua-franca/"+topLevelName, - "RUN set -ex && apt-get update && apt-get install -y python3-pip", - "COPY . src-gen", - "RUN cd src-gen && python3 setup.py install && cd ..", - "ENTRYPOINT [\"python3\", \"src-gen/"+topLevelName+".py\"]" - ); - } -} diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonGenerator.java deleted file mode 100644 index 4c6cf4be18..0000000000 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java +++ /dev/null @@ -1,938 +0,0 @@ -/* Generator for the Python target. */ - -/************* - * Copyright (c) 2022, The University of California at Berkeley. - * Copyright (c) 2022, The University of Texas at Dallas. - - * 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.python; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import org.eclipse.emf.ecore.resource.Resource; -import org.eclipse.xtext.util.CancelIndicator; -import org.eclipse.xtext.xbase.lib.Exceptions; -import org.eclipse.xtext.xbase.lib.IterableExtensions; -import org.lflang.ASTUtils; -import org.lflang.ErrorReporter; -import org.lflang.FileConfig; -import org.lflang.InferredType; -import org.lflang.JavaAstUtils; -import org.lflang.Target; -import org.lflang.TargetConfig.Mode; -import org.lflang.federated.FedFileConfig; -import org.lflang.federated.FederateInstance; -import org.lflang.federated.launcher.FedPyLauncher; -import org.lflang.federated.serialization.FedNativePythonSerialization; -import org.lflang.federated.serialization.SupportedSerializers; -import org.lflang.generator.CodeBuilder; -import org.lflang.generator.CodeMap; -import org.lflang.generator.GeneratorResult; -import org.lflang.generator.IntegratedBuilder; -import org.lflang.generator.JavaGeneratorUtils; -import org.lflang.generator.LFGeneratorContext; -import org.lflang.generator.ReactorInstance; -import org.lflang.generator.SubContext; -import org.lflang.generator.TargetTypes; -import org.lflang.generator.c.CGenerator; -import org.lflang.generator.c.CPreambleGenerator; -import org.lflang.generator.c.CUtil; -import org.lflang.lf.Action; -import org.lflang.lf.Delay; -import org.lflang.lf.Input; -import org.lflang.lf.Model; -import org.lflang.lf.Output; -import org.lflang.lf.Port; -import org.lflang.lf.Reaction; -import org.lflang.lf.Reactor; -import org.lflang.lf.ReactorDecl; -import org.lflang.lf.VarRef; -import org.lflang.util.LFCommand; - -import com.google.common.base.Objects; - - -/** - * Generator for Python target. This class generates Python code defining each reactor - * class given in the input .lf file and imported .lf files. - * - * Each class will contain all the reaction functions defined by the user in order, with the necessary ports/actions given as parameters. - * Moreover, each class will contain all state variables in native Python format. - * - * A backend is also generated using the CGenerator that interacts with the C code library (see CGenerator.xtend). - * The backend is responsible for passing arguments to the Python reactor functions. - * - * @author{Soroush Bateni } - */ -public class PythonGenerator extends CGenerator { - - // Used to add statements that come before reactor classes and user code - private CodeBuilder pythonPreamble = new CodeBuilder(); - - // Used to add module requirements to setup.py (delimited with ,) - private List pythonRequiredModules = new ArrayList<>(); - - private PythonTypes types; - - public PythonGenerator(FileConfig fileConfig, ErrorReporter errorReporter) { - this(fileConfig, errorReporter, new PythonTypes(errorReporter)); - } - - private PythonGenerator(FileConfig fileConfig, ErrorReporter errorReporter, PythonTypes types) { - super(fileConfig, errorReporter, false, types); - this.targetConfig.compiler = "gcc"; - this.targetConfig.compilerFlags = new ArrayList<>(); - this.targetConfig.linkerFlags = ""; - this.types = types; - } - - /** - * Generic struct for ports with primitive types and - * statically allocated arrays in Lingua Franca. - * This template is defined as - * typedef struct { - * PyObject* value; - * bool is_present; - * int num_destinations; - * FEDERATED_CAPSULE_EXTENSION - * } generic_port_instance_struct; - * - * @see reactor-c-py/lib/pythontarget.h - */ - String genericPortType = "generic_port_instance_struct"; - - /** - * Generic struct for ports with dynamically allocated - * array types (a.k.a. token types) in Lingua Franca. - * This template is defined as - * typedef struct { - * PyObject_HEAD - * PyObject* value; - * bool is_present; - * int num_destinations; - * lf_token_t* token; - * int length; - * FEDERATED_CAPSULE_EXTENSION - * } generic_port_instance_with_token_struct; - * - * @see reactor-c-py/lib/pythontarget.h - */ - String genericPortTypeWithToken = "generic_port_instance_with_token_struct"; - - /** - * Generic struct for actions. - * This template is defined as - * typedef struct { - * trigger_t* trigger; - * PyObject* value; - * bool is_present; - * bool has_value; - * lf_token_t* token; - * FEDERATED_CAPSULE_EXTENSION - * } generic_action_instance_struct; - * - * @see reactor-c-py/lib/pythontarget.h - */ - String genericActionType = "generic_action_instance_struct"; - - /** Returns the Target enum for this generator */ - @Override - public Target getTarget() { - return Target.Python; - } - - private Set protoNames = new HashSet<>(); - - // ////////////////////////////////////////// - // // Public methods - @Override - public String printInfo() { - System.out.println("Generating code for: " + fileConfig.resource.getURI().toString()); - System.out.println("******** Mode: " + fileConfig.context.getMode()); - System.out.println("******** Generated sources: " + fileConfig.getSrcGenPath()); - return null; - } - - @Override - public TargetTypes getTargetTypes() { - return types; - } - - // ////////////////////////////////////////// - // // Protected methods - /** - * Generate all Python classes if they have a reaction - * @param federate The federate instance used to generate classes - */ - public String generatePythonReactorClasses(FederateInstance federate) { - CodeBuilder pythonClasses = new CodeBuilder(); - CodeBuilder pythonClassesInstantiation = new CodeBuilder(); - - // Generate reactor classes in Python - pythonClasses.pr(PythonReactorGenerator.generatePythonClass(main, federate, main, types)); - - // Create empty lists to hold reactor instances - pythonClassesInstantiation.pr(PythonReactorGenerator.generateListsToHoldClassInstances(main, federate)); - - // Instantiate generated classes - pythonClassesInstantiation.pr(PythonReactorGenerator.generatePythonClassInstantiations(main, federate, main)); - - return String.join("\n", - pythonClasses.toString(), - "", - "# Instantiate classes", - pythonClassesInstantiation.toString() - ); - } - - /** - * Generate the Python code constructed from reactor classes and user-written classes. - * @return the code body - */ - public String generatePythonCode(FederateInstance federate) { - return String.join("\n", - "# List imported names, but do not use pylint's --extension-pkg-allow-list option", - "# so that these names will be assumed present without having to compile and install.", - "from LinguaFranca"+topLevelName+" import ( # pylint: disable=no-name-in-module", - " Tag, action_capsule_t, compare_tags, get_current_tag, get_elapsed_logical_time,", - " get_elapsed_physical_time, get_logical_time, get_microstep, get_physical_time,", - " get_start_time, port_capsule, port_instance_token, request_stop, schedule_copy,", - " start", - ")", - "from LinguaFrancaBase.constants import BILLION, FOREVER, NEVER, instant_t, interval_t", - "from LinguaFrancaBase.functions import (", - " DAY, DAYS, HOUR, HOURS, MINUTE, MINUTES, MSEC, MSECS, NSEC, NSECS, SEC, SECS, USEC,", - " USECS, WEEK, WEEKS", - ")", - "from LinguaFrancaBase.classes import Make", - "import sys", - "import copy", - "", - pythonPreamble.toString(), - "", - generatePythonReactorClasses(federate), - "", - PythonMainGenerator.generateCode() - ); - } - - /** - * Generate the setup.py required to compile and install the module. - * Currently, the package name is based on filename which does not support sharing the setup.py for multiple .lf files. - * TODO: use an alternative package name (possibly based on folder name) - * - * If the LF program itself is threaded or if tracing is enabled, NUMBER_OF_WORKERS is added as a macro - * so that platform-specific C files will contain the appropriate functions. - */ - public String generatePythonSetupFile() { - String moduleName = "LinguaFranca" + topLevelName; - - List sources = new ArrayList<>(targetConfig.compileAdditionalSources); - sources.add(topLevelName + ".c"); - sources = sources.stream() - .map(Paths::get) - .map(FileConfig::toUnixString) - .map(PythonGenerator::addDoubleQuotes) - .collect(Collectors.toList()); - - List macros = new ArrayList<>(); - macros.add(generateMacroEntry("MODULE_NAME", moduleName)); - - for (var entry : targetConfig.compileDefinitions.entrySet()) { - macros.add(generateMacroEntry(entry.getKey(), entry.getValue())); - } - - if (targetConfig.threads != 0 || targetConfig.tracing != null) { - macros.add(generateMacroEntry("NUMBER_OF_WORKERS", String.valueOf(targetConfig.threads))); - } - - List installRequires = new ArrayList<>(pythonRequiredModules); - installRequires.add("LinguaFrancaBase"); - installRequires.replaceAll(PythonGenerator::addDoubleQuotes); - - return String.join("\n", - "from setuptools import setup, Extension", - "", - "linguafranca"+topLevelName+"module = Extension("+addDoubleQuotes(moduleName)+",", - " sources = ["+String.join(", ", sources)+"],", - " define_macros=["+String.join(", ", macros)+"])", - "", - "setup(name="+addDoubleQuotes(moduleName)+", version=\"1.0\",", - " ext_modules = [linguafranca"+topLevelName+"module],", - " install_requires=["+String.join(", ", installRequires)+"])" - ); - } - - /** - * Generate the necessary Python files. - * @param federate The federate instance - */ - public Map generatePythonFiles(FederateInstance federate) throws IOException { - Path filePath = fileConfig.getSrcGenPath().resolve(topLevelName + ".py"); - File file = filePath.toFile(); - Files.deleteIfExists(filePath); - // Create the necessary directories - if (!file.getParentFile().exists()) { - file.getParentFile().mkdirs(); - } - Map codeMaps = new HashMap<>(); - codeMaps.put(filePath, CodeMap.fromGeneratedCode(generatePythonCode(federate).toString())); - JavaGeneratorUtils.writeToFile(codeMaps.get(filePath).getGeneratedCode(), filePath); - - Path setupPath = fileConfig.getSrcGenPath().resolve("setup.py"); - // Handle Python setup - System.out.println("Generating setup file to " + setupPath); - Files.deleteIfExists(setupPath); - - // Create the setup file - JavaGeneratorUtils.writeToFile(generatePythonSetupFile(), setupPath); - return codeMaps; - } - - /** - * Execute the command that compiles and installs the current Python module - */ - public void pythonCompileCode(LFGeneratorContext context) { - // if we found the compile command, we will also find the install command - LFCommand installCmd = commandFactory.createCommand( - "python3", List.of("-m", "pip", "install", "--force-reinstall", "."), fileConfig.getSrcGenPath() - ); - - if (installCmd == null) { - errorReporter.reportError( - "The Python target requires Python >= 3.6, pip >= 20.0.2, and setuptools >= 45.2.0-1 to compile the generated code. " + - "Auto-compiling can be disabled using the \"no-compile: true\" target property."); - return; - } - - // Set compile time environment variables - installCmd.setEnvironmentVariable("CC", targetConfig.compiler); // Use gcc as the compiler - installCmd.setEnvironmentVariable("LDFLAGS", targetConfig.linkerFlags); // The linker complains about including pythontarget.h twice (once in the generated code and once in pythontarget.c) - // To avoid this, we force the linker to allow multiple definitions. Duplicate names would still be caught by the - // compiler. - if (installCmd.run(context.getCancelIndicator()) == 0) { - System.out.println("Successfully installed python extension."); - } else { - errorReporter.reportError("Failed to install python extension due to the following errors:\n" + - installCmd.getErrors()); - } - } - - /** - * Generate top-level preambles and #include of pqueue.c and either reactor.c or reactor_threaded.c - * depending on whether threads are specified in target directive. - * As a side effect, this populates the runCommand and compileCommand - * private variables if such commands are specified in the target directive. - * - * TODO: This function returns a boolean because xtend-generated parent function in CGenerator returns boolean - */ - @Override - public boolean generatePreamble() { - Set models = new LinkedHashSet<>(); - for (Reactor r : ASTUtils.convertToEmptyListIfNull(reactors)) { - // The following assumes all reactors have a container. - // This means that generated reactors **have** to be - // added to a resource; not doing so will result in a NPE. - models.add((Model) ASTUtils.toDefinition(r).eContainer()); - } - // Add the main reactor if it is defined - if (this.mainDef != null) { - models.add((Model) ASTUtils.toDefinition(this.mainDef.getReactorClass()).eContainer()); - } - for (Model m : models) { - pythonPreamble.pr(PythonPreambleGenerator.generatePythonPreambles(m.getPreambles())); - } - code.pr(CGenerator.defineLogLevel(this)); - if (isFederated) { - code.pr(CPreambleGenerator.generateFederatedDirective(targetConfig.coordination)); - // Handle target parameters. - // First, if there are federates, then ensure that threading is enabled. - targetConfig.threads = CUtil.minThreadsToHandleInputPorts(federates); - } - includeTargetLanguageHeaders(); - code.pr(CPreambleGenerator.generateNumFederatesDirective(federates.size())); - code.pr(CPreambleGenerator.generateMixedRadixIncludeHeader()); - super.includeTargetLanguageSourceFiles(); - super.parseTargetParameters(); - return false; // placeholder return value. See comment above - } - - /** - * Add necessary code to the source and necessary build supports to - * enable the requested serializations in 'enabledSerializations' - */ - @Override - public void enableSupportForSerializationIfApplicable(CancelIndicator cancelIndicator) { - if (!IterableExtensions.isNullOrEmpty(targetConfig.protoFiles)) { - // Enable support for proto serialization - enabledSerializers.add(SupportedSerializers.PROTO); - } - for (SupportedSerializers serialization : enabledSerializers) { - switch (serialization) { - case NATIVE: { - FedNativePythonSerialization pickler = new FedNativePythonSerialization(); - code.pr(pickler.generatePreambleForSupport().toString()); - } - case PROTO: { - // Handle .proto files. - for (String name : targetConfig.protoFiles) { - this.processProtoFile(name, cancelIndicator); - int dotIndex = name.lastIndexOf("."); - String rootFilename = dotIndex > 0 ? name.substring(0, dotIndex) : name; - pythonPreamble.pr("import "+rootFilename+"_pb2 as "+rootFilename); - protoNames.add(rootFilename); - } - } - case ROS2: { - // FIXME: Not supported yet - } - } - } - } - - /** - * 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. - */ - @Override - public void processProtoFile(String filename, CancelIndicator cancelIndicator) { - LFCommand protoc = commandFactory.createCommand( - "protoc", List.of("--python_out="+fileConfig.getSrcGenPath(), filename), fileConfig.srcPath); - - if (protoc == null) { - errorReporter.reportError("Processing .proto files requires libprotoc >= 3.6.1"); - return; - } - int returnCode = protoc.run(cancelIndicator); - if (returnCode == 0) { - pythonRequiredModules.add("google-api-python-client"); - } else { - errorReporter.reportError("protoc returns error code " + returnCode); - } - } - - /** - * Generate code for the body of a reaction that handles the - * action that is triggered by receiving a message from a remote - * federate. - * @param action The action. - * @param sendingPort The output port providing the data to send. - * @param receivingPort The ID of the destination port. - * @param receivingPortID The ID of the destination port. - * @param sendingFed The sending federate. - * @param receivingFed The destination federate. - * @param receivingBankIndex The receiving federate's bank index, if it is in a bank. - * @param receivingChannelIndex The receiving federate's channel index, if it is a multiport. - * @param type The type. - * @param isPhysical Indicates whether or not the connection is physical - * @param serializer The serializer used on the connection. - */ - @Override - public String generateNetworkReceiverBody( - Action action, - VarRef sendingPort, - VarRef receivingPort, - int receivingPortID, - FederateInstance sendingFed, - FederateInstance receivingFed, - int receivingBankIndex, - int receivingChannelIndex, - InferredType type, - boolean isPhysical, - SupportedSerializers serializer - ) { - return PythonNetworkGenerator.generateNetworkReceiverBody( - action, - sendingPort, - receivingPort, - receivingPortID, - sendingFed, - receivingFed, - receivingBankIndex, - receivingChannelIndex, - type, - isPhysical, - serializer - ); - } - - /** - * Generate code for the body of a reaction that handles an output - * that is to be sent over the network. - * @param sendingPort The output port providing the data to send. - * @param receivingPort The variable reference to the destination port. - * @param receivingPortID The ID of the destination port. - * @param sendingFed The sending federate. - * @param sendingBankIndex The bank index of the sending federate, if it is a bank. - * @param sendingChannelIndex The channel index of the sending port, if it is a multiport. - * @param receivingFed The destination federate. - * @param type The type. - * @param isPhysical Indicates whether the connection is physical or not - * @param delay The delay value imposed on the connection using after - * @param serializer The serializer used on the connection. - */ - @Override - public String generateNetworkSenderBody( - VarRef sendingPort, - VarRef receivingPort, - int receivingPortID, - FederateInstance sendingFed, - int sendingBankIndex, - int sendingChannelIndex, - FederateInstance receivingFed, - InferredType type, - boolean isPhysical, - Delay delay, - SupportedSerializers serializer - ) { - return PythonNetworkGenerator.generateNetworkSenderBody( - sendingPort, - receivingPort, - receivingPortID, - sendingFed, - sendingBankIndex, - sendingChannelIndex, - receivingFed, - type, - isPhysical, - delay, - serializer, - targetConfig.coordination - ); - } - - /** - * Create a launcher script that executes all the federates and the RTI. - * - * @param coreFiles The files from the core directory that must be - * copied to the remote machines. - */ - @Override - public void createFederatedLauncher(ArrayList coreFiles) { - FedPyLauncher launcher = new FedPyLauncher( - targetConfig, - fileConfig, - errorReporter - ); - try { - launcher.createLauncher( - coreFiles, - federates, - federationRTIProperties - ); - } catch (IOException e) { - // ignore - } - } - - /** - * Generate the aliases for inputs, outputs, and struct type definitions for - * actions of the specified reactor in the specified federate. - * @param reactor The parsed reactor data structure. - * @param federate A federate name, or null to unconditionally generate. - */ - @Override - public void generateAuxiliaryStructs( - ReactorDecl decl - ) { - Reactor reactor = ASTUtils.toDefinition(decl); - // First, handle inputs. - for (Input input : ASTUtils.allInputs(reactor)) { - generateAuxiliaryStructsForPort(decl, input); - } - // Next, handle outputs. - for (Output output : ASTUtils.allOutputs(reactor)) { - generateAuxiliaryStructsForPort(decl, output); - } - // Finally, handle actions. - for (Action action : ASTUtils.allActions(reactor)) { - generateAuxiliaryStructsForAction(decl, currentFederate, action); - } - } - - private void generateAuxiliaryStructsForPort(ReactorDecl decl, - Port port) { - boolean isTokenType = CUtil.isTokenType(JavaAstUtils.getInferredType(port), types); - code.pr(port, - PythonPortGenerator.generateAliasTypeDef(decl, port, isTokenType, - genericPortTypeWithToken, - genericPortType)); - } - - private void generateAuxiliaryStructsForAction(ReactorDecl decl, - FederateInstance federate, - Action action) { - if (federate != null && !federate.contains(action)) { - return; - } - code.pr(action, PythonActionGenerator.generateAliasTypeDef(decl, action, genericActionType)); - } - - /** - * For the specified action, return a declaration for action struct to - * contain the value of the action. - * This will return an empty string for an action with no type. - * @param action The action. - * @return A string providing the value field of the action struct. - */ - @Override - public String valueDeclaration(Action action) { - return "PyObject* value;"; - } - - /** Add necessary include files specific to the target language. - * Note. The core files always need to be (and will be) copied - * uniformly across all target languages. - */ - @Override - public void includeTargetLanguageHeaders() { - code.pr("#define _LF_GARBAGE_COLLECTED"); - if (targetConfig.tracing != null) { - var filename = ""; - if (targetConfig.tracing.traceFileName != null) { - filename = targetConfig.tracing.traceFileName; - } - code.pr("#define LINGUA_FRANCA_TRACE " + filename); - } - - code.pr("#include \"pythontarget.c\""); - if (targetConfig.tracing != null) { - code.pr("#include \"core/trace.c\""); - } - } - - /** - * Return true if the host operating system is compatible and - * otherwise report an error and return false. - */ - @Override - public boolean isOSCompatible() { - if (JavaGeneratorUtils.isHostWindows() && isFederated) { - errorReporter.reportError( - "Federated LF programs with a Python target are currently not supported on Windows. Exiting code generation." - ); - // Return to avoid compiler errors - 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 Context relating to invocation of the code generator. - */ - @Override - public void doGenerate(Resource resource, LFGeneratorContext context) { - // If there are federates, assign the number of threads in the CGenerator to 1 - if (isFederated) { - targetConfig.threads = 1; - } - - // Prevent the CGenerator from compiling the C code. - // The PythonGenerator will compiler it. - boolean compileStatus = targetConfig.noCompile; - targetConfig.noCompile = true; - targetConfig.useCmake = false; // Force disable the CMake because - // it interferes with the Python target functionality - int cGeneratedPercentProgress = (IntegratedBuilder.VALIDATED_PERCENT_PROGRESS + 100) / 2; - super.doGenerate(resource, new SubContext( - context, - IntegratedBuilder.VALIDATED_PERCENT_PROGRESS, - cGeneratedPercentProgress - )); - SubContext compilingFederatesContext = new SubContext(context, cGeneratedPercentProgress, 100); - targetConfig.noCompile = compileStatus; - - if (errorsOccurred()) { - context.unsuccessfulFinish(); - return; - } - - String baseFileName = topLevelName; - // Keep a separate file config for each federate - FileConfig oldFileConfig = fileConfig; - var federateCount = 0; - Map codeMaps = new HashMap<>(); - for (FederateInstance federate : federates) { - federateCount++; - if (isFederated) { - topLevelName = baseFileName + '_' + federate.name; - try { - fileConfig = new FedFileConfig(fileConfig, federate.name); - } catch (IOException e) { - throw Exceptions.sneakyThrow(e); - } - } - // Don't generate code if there is no main reactor - if (this.main != null) { - try { - Map codeMapsForFederate = generatePythonFiles(federate); - codeMaps.putAll(codeMapsForFederate); - PyUtil.copyTargetFiles(fileConfig); - if (!targetConfig.noCompile) { - compilingFederatesContext.reportProgress( - String.format("Validating %d/%d sets of generated files...", federateCount, federates.size()), - 100 * federateCount / federates.size() - ); - // If there are no federates, compile and install the generated code - new PythonValidator(fileConfig, errorReporter, codeMaps, protoNames).doValidate(context); - if (!errorsOccurred() && !Objects.equal(context.getMode(), Mode.LSP_MEDIUM)) { - compilingFederatesContext.reportProgress( - String.format("Validation complete. Compiling and installing %d/%d Python modules...", - federateCount, federates.size()), - 100 * federateCount / federates.size() - ); - pythonCompileCode(context); // Why is this invoked here if the current federate is not a parameter? - } - } else { - System.out.println(PythonInfoGenerator.generateSetupInfo(fileConfig)); - } - } catch (Exception e) { - throw Exceptions.sneakyThrow(e); - } - - if (!isFederated) { - System.out.println(PythonInfoGenerator.generateRunInfo(fileConfig, topLevelName)); - } - } - fileConfig = oldFileConfig; - } - if (isFederated) { - System.out.println(PythonInfoGenerator.generateFedRunInfo(fileConfig)); - } - // Restore filename - topLevelName = baseFileName; - if (errorReporter.getErrorsOccurred()) { - context.unsuccessfulFinish(); - } else if (!isFederated) { - context.finish(GeneratorResult.Status.COMPILED, topLevelName+".py", fileConfig.getSrcGenPath(), fileConfig, - codeMaps, "python3"); - } else { - context.finish(GeneratorResult.Status.COMPILED, fileConfig.name, fileConfig.binPath, fileConfig, codeMaps, - "bash"); - } - } - - - /** - * Generate code for the body of a reaction that takes an input and - * schedules an action with the value of that input. - * @param action The action to schedule - * @param port The port to read from - */ - @Override - public String generateDelayBody(Action action, VarRef port) { - return PythonReactionGenerator.generateCDelayBody(action, port, CUtil.isTokenType(JavaAstUtils.getInferredType(action), types)); - } - - /** - * Generate code for the body of a reaction that is triggered by the - * given action and writes its value to the given port. This realizes - * the receiving end of a logical delay specified with the 'after' - * keyword. - * @param action The action that triggers the reaction - * @param port The port to write to. - */ - @Override - public String generateForwardBody(Action action, VarRef port) { - String outputName = JavaAstUtils.generateVarRef(port); - if (CUtil.isTokenType(JavaAstUtils.getInferredType(action), types)) { - return super.generateForwardBody(action, port); - } else { - return "SET("+outputName+", "+action.getName()+"->token->value);"; - } - } - - /** Generate a reaction function definition for a reactor. - * This function has 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 reactor The reactor. - * @param reactionIndex The position of the reaction within the reactor. - */ - @Override - public void generateReaction(Reaction reaction, ReactorDecl decl, int reactionIndex) { - Reactor reactor = ASTUtils.toDefinition(decl); - - // Delay reactors and top-level reactions used in the top-level reactor(s) in federated execution are generated in C - if (reactor.getName().contains(GEN_DELAY_CLASS_NAME) || - ((mainDef != null && decl == mainDef.getReactorClass() || mainDef == decl) && reactor.isFederated())) { - super.generateReaction(reaction, decl, reactionIndex); - return; - } - code.pr(PythonReactionGenerator.generateCReaction(reaction, decl, reactionIndex, mainDef, errorReporter, types, isFederatedAndDecentralized())); - } - - /** - * 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. This task is left to Python code to allow for more liberal - * state variable assignments. - * @param instance The reactor class instance - * @return Initialization code fore state variables of instance - */ - @Override - public void generateStateVariableInitializations(ReactorInstance instance) { - // Do nothing - } - - /** - * Generate code for parameter variables of a reactor in C in the form "parameter.type parameter.name;" - * - * @param reactor The reactor. - * @param builder The place that the generated code is written to. - * @return - */ - @Override - public void generateParametersForReactor(CodeBuilder builder, Reactor reactor) { - // Do nothing - // Parameters are generated in Python - } - - /** - * Generate runtime initialization code in C for parameters of a given reactor instance - * - * @param instance The reactor instance. - */ - @Override - public void generateParameterInitialization(ReactorInstance instance) { - // Do nothing - // Parameters are initialized in Python - } - - /** - * This function is overridden in the Python generator to do nothing. - * The state variables are initialized in Python code directly. - * @param reactor The reactor. - * @param builder The place that the generated code is written to. - * @return - */ - @Override - public void generateStateVariablesForReactor(CodeBuilder builder, Reactor reactor) { - // Do nothing - } - - /** - * Generates C preambles defined by user for a given reactor - * Since the Python generator expects preambles written in C, - * this function is overridden and does nothing. - * @param reactor The given reactor - */ - @Override - public void generateUserPreamblesForReactor(Reactor reactor) { - // Do nothing - } - - /** - * Generate code that is executed while the reactor instance is being initialized. - * This wraps the reaction functions in a Python function. - * @param instance The reactor instance. - * @param reactions The reactions of this instance. - */ - @Override - public void generateReactorInstanceExtension( - ReactorInstance instance - ) { - initializeTriggerObjects.pr(PythonReactionGenerator.generateCPythonReactionLinkers(instance, mainDef, topLevelName)); - } - - /** - * This function is provided to allow extensions of the CGenerator to append the structure of the self struct - * @param selfStructBody The body of the self struct - * @param decl The reactor declaration for the self struct - * @param instance The current federate instance - * @param constructorCode Code that is executed when the reactor is instantiated - */ - @Override - public void generateSelfStructExtension( - CodeBuilder selfStructBody, - ReactorDecl decl, - CodeBuilder constructorCode - ) { - Reactor reactor = ASTUtils.toDefinition(decl); - // Add the name field - selfStructBody.pr("char *_lf_name;"); - int reactionIndex = 0; - for (Reaction reaction : ASTUtils.allReactions(reactor)) { - // Create a PyObject for each reaction - selfStructBody.pr("PyObject* "+PythonReactionGenerator.generateCPythonReactionFunctionName(reactionIndex)+";"); - if (reaction.getDeadline() != null) { - selfStructBody.pr("PyObject* "+PythonReactionGenerator.generateCPythonDeadlineFunctionName(reactionIndex)+";"); - } - reactionIndex++; - } - } - - /** - * Write a Dockerfile for the current federate as given by filename. - * The file will go into src-gen/filename.Dockerfile. - * If there is no main reactor, then no Dockerfile will be generated - * (it wouldn't be very useful). - * @param The directory where the docker compose file is generated. - * @param The name of the docker file. - * @param The name of the federate. - */ - @Override - public void writeDockerFile(File dockerComposeDir, String dockerFileName, String federateName) { - if (mainDef == null) { - return; - } - Path srcGenPath = fileConfig.getSrcGenPath(); - String dockerFile = srcGenPath + File.separator + dockerFileName; - CodeBuilder contents = new CodeBuilder(); - contents.pr(PythonDockerGenerator.generateDockerFileContent(topLevelName, srcGenPath)); - try { - // If a dockerfile exists, remove it. - Files.deleteIfExists(srcGenPath.resolve(dockerFileName)); - contents.writeToFile(dockerFile); - } catch (IOException e) { - Exceptions.sneakyThrow(e); - } - System.out.println(getDockerBuildCommand(dockerFile, dockerComposeDir, federateName)); - } - - private static String addDoubleQuotes(String str) { - return "\""+str+"\""; - } - - private static String generateMacroEntry(String key, String val) { - return "(" + addDoubleQuotes(key) + ", " + addDoubleQuotes(val) + ")"; - } -} diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend new file mode 100644 index 0000000000..c7880c5ef6 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -0,0 +1,2159 @@ +/* Generator for the Python target. */ + +/************* + * Copyright (c) 2022, The University of California at Berkeley. + * Copyright (c) 2022, The University of Texas at Dallas. + + * 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.python + +import java.io.File +import java.nio.file.Path +import java.util.ArrayList +import java.util.HashMap +import java.util.HashSet +import java.util.LinkedHashSet +import java.util.LinkedList +import java.util.List +import org.eclipse.emf.ecore.resource.Resource +import org.eclipse.xtext.util.CancelIndicator +import org.lflang.ErrorReporter +import org.lflang.FileConfig +import org.lflang.InferredType +import org.lflang.JavaAstUtils +import org.lflang.Target +import org.lflang.TargetConfig.Mode +import org.lflang.TargetProperty.CoordinationType +import org.lflang.federated.FedFileConfig +import org.lflang.federated.FederateInstance +import org.lflang.federated.PythonGeneratorExtension +import org.lflang.federated.launcher.FedPyLauncher +import org.lflang.federated.serialization.FedNativePythonSerialization +import org.lflang.federated.serialization.SupportedSerializers +import org.lflang.generator.CodeBuilder +import org.lflang.generator.CodeMap +import org.lflang.generator.GeneratorResult +import org.lflang.generator.IntegratedBuilder +import org.lflang.generator.JavaGeneratorUtils +import org.lflang.generator.LFGeneratorContext +import org.lflang.generator.ParameterInstance +import org.lflang.generator.ReactionInstance +import org.lflang.generator.ReactorInstance +import org.lflang.generator.SubContext +import org.lflang.generator.c.CGenerator +import org.lflang.generator.c.CUtil +import org.lflang.lf.Action +import org.lflang.lf.Assignment +import org.lflang.lf.Delay +import org.lflang.lf.Input +import org.lflang.lf.Instantiation +import org.lflang.lf.Model +import org.lflang.lf.Output +import org.lflang.lf.Parameter +import org.lflang.lf.Port +import org.lflang.lf.Reaction +import org.lflang.lf.Reactor +import org.lflang.lf.ReactorDecl +import org.lflang.lf.StateVar +import org.lflang.lf.TriggerRef +import org.lflang.lf.Value +import org.lflang.lf.VarRef + +import static extension org.lflang.ASTUtils.* +import static extension org.lflang.JavaAstUtils.* + +/** + * Generator for Python target. This class generates Python code defining each reactor + * class given in the input .lf file and imported .lf files. + * + * Each class will contain all the reaction functions defined by the user in order, with the necessary ports/actions given as parameters. + * Moreover, each class will contain all state variables in native Python format. + * + * A backend is also generated using the CGenerator that interacts with the C code library (see CGenerator.xtend). + * The backend is responsible for passing arguments to the Python reactor functions. + * + * @author{Soroush Bateni } + */ +class PythonGenerator extends CGenerator { + + // Used to add statements that come before reactor classes and user code + var pythonPreamble = new StringBuilder() + + // Used to add module requirements to setup.py (delimited with ,) + var pythonRequiredModules = new HashSet; + + var PythonTypes types; + + new(FileConfig fileConfig, ErrorReporter errorReporter) { + this(fileConfig, errorReporter, new PythonTypes(errorReporter)) + } + + private new(FileConfig fileConfig, ErrorReporter errorReporter, PythonTypes types) { + super(fileConfig, errorReporter, false, types) + // set defaults + targetConfig.compiler = "gcc" + targetConfig.compilerFlags = newArrayList // -Wall -Wconversion" + targetConfig.linkerFlags = "" + this.types = types + } + + /** + * Generic struct for ports with primitive types and + * statically allocated arrays in Lingua Franca. + * This template is defined as + * typedef struct { + * PyObject* value; + * bool is_present; + * int num_destinations; + * FEDERATED_CAPSULE_EXTENSION + * } generic_port_instance_struct; + * + * @see reactor-c-py/lib/pythontarget.h + */ + val generic_port_type = "generic_port_instance_struct" + + /** + * Generic struct for ports with dynamically allocated + * array types (a.k.a. token types) in Lingua Franca. + * This template is defined as + * typedef struct { + * PyObject_HEAD + * PyObject* value; + * bool is_present; + * int num_destinations; + * lf_token_t* token; + * int length; + * FEDERATED_CAPSULE_EXTENSION + * } generic_port_instance_with_token_struct; + * + * @see reactor-c-py/lib/pythontarget.h + */ + val generic_port_type_with_token = "generic_port_instance_with_token_struct" + + /** + * Generic struct for actions. + * This template is defined as + * typedef struct { + * trigger_t* trigger; + * PyObject* value; + * bool is_present; + * bool has_value; + * lf_token_t* token; + * FEDERATED_CAPSULE_EXTENSION + * } generic_action_instance_struct; + * + * @see reactor-c-py/lib/pythontarget.h + */ + val generic_action_type = "generic_action_instance_struct" + + /** Returns the Target enum for this generator */ + override getTarget() { + return Target.Python + } + + val protoNames = new HashSet() + + // ////////////////////////////////////////// + // // Public methods + override printInfo() { + println("Generating code for: " + fileConfig.resource.getURI.toString) + println('******** Mode: ' + fileConfig.context.mode) + println('******** Generated sources: ' + fileConfig.getSrcGenPath) + } + + /** + * Print information about necessary steps to install the supporting + * Python C extension for the generated program. + * + * @note Only needed if no-compile is set to true + */ + def printSetupInfo() { + println(''' + + ##################################### + To compile and install the generated code, do: + + cd «fileConfig.srcGenPath»«File.separator» + python3 -m pip install --force-reinstall . + '''); + } + + /** + * Print information on how to execute the generated program. + */ + def printRunInfo() { + println(''' + + ##################################### + To run the generated program, use: + + python3 «fileConfig.srcGenPath»«File.separator»«topLevelName».py + + ##################################### + '''); + } + + /** + * Print information on how to execute the generated federation. + */ + def printFedRunInfo() { + println(''' + + ##################################### + To run the generated program, run: + + bash «fileConfig.binPath»/«fileConfig.name» + + ##################################### + '''); + } + + override getTargetTypes() { + return types; + } + + // ////////////////////////////////////////// + // // Protected methods + /** + * Override to convert some C types to their + * Python equivalent. + * Examples: + * true/false -> True/False + * @param v A value + * @return A value string in the target language + */ + private def getPythonTargetValue(Value v) { + var String returnValue = ""; + switch (v.toText) { + case "false": returnValue = "False" + case "true": returnValue = "True" + default: returnValue = v.targetValue + } + + // Parameters in Python are always prepended with a 'self.' + // predicate. Therefore, we need to append the returned value + // if it is a parameter. + if (v.parameter !== null) { + returnValue = "self." + returnValue; + } + + return returnValue; + } + + /** + * Create a list of state initializers in target code. + * + * @param state The state variable to create initializers for + * @return A list of initializers in target code + */ + protected def List getPythonInitializerList(StateVar state) { + if (!state.isInitialized) { + return null + } + + var list = new ArrayList(); + + for (i : state?.init) { + if (i.parameter !== null) { + list.add(i.parameter.name) + } else if (state.isOfTimeType) { + list.add(i.targetTime) + } else { + list.add(i.pythonTargetValue) + } + } + return list + } + + /** + * Create a Python tuple for parameter initialization in target code. + * + * @param p The parameter instance to create initializers for + * @return Initialization code + */ + protected def String getPythonInitializer(StateVar state) throws Exception { + if (state.init.size > 1) { + // state variables are initialized as mutable lists + return state.init.join('[', ', ', ']', [it.pythonTargetValue]) + } else if (state.isInitialized) { + return state.init.get(0).getPythonTargetValue + } else { + return "None" + } + + } + + /** + * Return a Python expression that can be used to initialize the specified + * parameter instance. If the parameter initializer refers to other + * parameters, then those parameter references are replaced with + * accesses to the Python reactor instance class of the parents of + * those parameters. + * + * @param p The parameter instance to create initializer for + * @return Initialization code + */ + protected def String getPythonInitializer(ParameterInstance p) { + // Handle overrides in the intantiation. + // In case there is more than one assignment to this parameter, we need to + // find the last one. + var lastAssignment = null as Assignment; + for (assignment : p.parent.definition.parameters) { + if (assignment.lhs == p.definition) { + lastAssignment = assignment; + } + } + + var list = new LinkedList(); + if (lastAssignment !== null) { + // The parameter has an assignment. + // Right hand side can be a list. Collect the entries. + for (value : lastAssignment.rhs) { + if (value.parameter !== null) { + // The parameter is being assigned a parameter value. + // Assume that parameter belongs to the parent's parent. + // This should have been checked by the validator. + list.add(PyUtil.reactorRef(p.parent.parent) + "." + value.parameter.name); + } else { + list.add(value.targetTime) + } + } + } else { + for (i : p.parent.initialParameterValue(p.definition)) { + list.add(i.getPythonTargetValue) + } + } + + if (list.size == 1) { + return list.get(0) + } else { + return list.join('(', ', ', ')', [it]) + } + + } + + /** + * Create a Python list for parameter initialization in target code. + * + * @param p The parameter to create initializers for + * @return Initialization code + */ + protected def String getPythonInitializer(Parameter p) { + if (p.init.size > 1) { + // parameters are initialized as immutable tuples + return p.init.join('(', ', ', ')', [it.pythonTargetValue]) + } else { + return p.init.get(0).pythonTargetValue + } + } + + /** + * Generate parameters and their respective initialization code for a reaction function + * The initialization code is put at the beginning of the reaction before user code + * @param parameters The parameters used for function definition + * @param inits The initialization code for those paramters + * @param decl Reactor declaration + * @param reaction The reaction to be used to generate parameters for + */ + def generatePythonReactionParametersAndInitializations(StringBuilder parameters, CodeBuilder inits, + ReactorDecl decl, Reaction reaction) { + val reactor = decl.toDefinition + var generatedParams = new LinkedHashSet() + + // Handle triggers + for (TriggerRef trigger : reaction.triggers ?: emptyList) { + if (trigger instanceof VarRef) { + if (trigger.variable instanceof Port) { + if (trigger.variable instanceof Input) { + if ((trigger.variable as Input).isMutable) { + generatedParams.add('''mutable_«trigger.variable.name»''') + + // Create a deep copy + if (JavaAstUtils.isMultiport(trigger.variable as Input)) { + inits. + pr('''«trigger.variable.name» = [Make() for i in range(len(mutable_«trigger.variable.name»))]''') + inits.pr('''for i in range(len(mutable_«trigger.variable.name»)):''') + inits. + pr(''' «trigger.variable.name»[i].value = copy.deepcopy(mutable_«trigger.variable.name»[i].value)''') + } else { + inits.pr('''«trigger.variable.name» = Make()''') + inits. + pr('''«trigger.variable.name».value = copy.deepcopy(mutable_«trigger.variable.name».value)''') + } + } else { + generatedParams.add(trigger.variable.name) + } + } else { + // Handle contained reactors' ports + generatedParams.add('''«trigger.container.name»_«trigger.variable.name»''') + generatePythonPortVariableInReaction(trigger, inits) + } + + } else if (trigger.variable instanceof Action) { + generatedParams.add(trigger.variable.name) + } + } + } + + // Handle non-triggering inputs + if (reaction.triggers === null || reaction.triggers.size === 0) { + for (input : reactor.inputs ?: emptyList) { + generatedParams.add(input.name) + if (input.isMutable) { + // Create a deep copy + inits.pr('''«input.name» = copy.deepcopy(«input.name»)''') + } + } + } + for (src : reaction.sources ?: emptyList) { + if (src.variable instanceof Output) { + // Output of a contained reactor + generatedParams.add('''«src.container.name»_«src.variable.name»''') + generatePythonPortVariableInReaction(src, inits) + } else { + generatedParams.add(src.variable.name) + if (src.variable instanceof Input) { + if ((src.variable as Input).isMutable) { + // Create a deep copy + inits.pr('''«src.variable.name» = copy.deepcopy(«src.variable.name»)''') + } + } + } + } + + // Handle effects + for (effect : reaction.effects ?: emptyList) { + if (effect.variable instanceof Input) { + generatedParams.add('''«effect.container.name»_«effect.variable.name»''') + generatePythonPortVariableInReaction(effect, inits) + } else { + generatedParams.add(effect.variable.name) + if (effect.variable instanceof Port) { + if (JavaAstUtils.isMultiport(effect.variable as Port)) { + // Handle multiports + } + } + } + } + + // Fill out the StrinBuilder parameters + for (s : generatedParams) { + parameters.append(''', «s»''') + } + + } + + /** + * Generate into the specified string builder (inits) the code to + * initialize local variable for port so that it can be used in the body of + * the Python reaction. + * @param port The port to generate code for. + * @param inits The generated code will be put in inits. + */ + protected def CodeBuilder generatePythonPortVariableInReaction(VarRef port, CodeBuilder inits) { + if (port.container.widthSpec !== null) { + // It's a bank + inits.pr(''' + «port.container.name» = [None] * len(«port.container.name»_«port.variable.name») + for i in range(len(«port.container.name»_«port.variable.name»)): + «port.container.name»[i] = Make() + «port.container.name»[i].«port.variable.name» = «port.container.name»_«port.variable.name»[i] + ''') + + } else { + inits.pr('''«port.container.name» = Make''') + inits.pr('''«port.container.name».«port.variable.name» = «port.container.name»_«port.variable.name»''') + } + + return inits; + } + + /** + * Handle initialization for state variable + * @param state a state variable + */ + def String getTargetInitializer(StateVar state) { + if (!state.isInitialized) { + return '''None''' + } + + '''«FOR init : state.pythonInitializerList SEPARATOR ", "»«init»«ENDFOR»''' + } + + /** + * Wrapper function for the more elaborate generatePythonReactorClass that keeps track + * of visited reactors to avoid duplicate generation + * @param instance The reactor instance to be generated + * @param pythonClasses The class definition is appended to this code builder + * @param federate The federate instance for the reactor instance + * @param instantiatedClasses A list of visited instances to avoid generating duplicates + */ + def generatePythonReactorClass(ReactorInstance instance, CodeBuilder pythonClasses, FederateInstance federate) { + var instantiatedClasses = new ArrayList() + generatePythonReactorClass(instance, pythonClasses, federate, instantiatedClasses) + } + + /** + * Generate a Python class corresponding to decl + * @param instance The reactor instance to be generated + * @param pythonClasses The class definition is appended to this code builder + * @param federate The federate instance for the reactor instance + * @param instantiatedClasses A list of visited instances to avoid generating duplicates + */ + def void generatePythonReactorClass(ReactorInstance instance, CodeBuilder pythonClasses, + FederateInstance federate, ArrayList instantiatedClasses) { + if (instance !== this.main && !federate.contains(instance)) { + return + } + + // Invalid use of the function + if (instantiatedClasses === null) { + return + } + + val decl = instance.definition.reactorClass + val className = instance.definition.reactorClass.name + + // Do not generate code for delay reactors in Python + if (className.contains(GEN_DELAY_CLASS_NAME)) { + return + } + + if (federate.contains(instance) && !instantiatedClasses.contains(className)) { + + pythonClasses.pr(''' + + # Python class for reactor «className» + class _«className»: + '''); + + // Generate preamble code + pythonClasses.indent() + pythonClasses.pr(''' + + «generatePythonPreamblesForReactor(decl.toDefinition)» + ''') + + val reactor = decl.toDefinition + + // Handle runtime initializations + pythonClasses.pr(''' + def __init__(self, **kwargs): + ''') + + pythonClasses.pr(generateParametersAndStateVariables(decl)) + + var reactionToGenerate = reactor.allReactions + + if (reactor.isFederated) { + // Filter out reactions that are automatically generated in C in the top level federated reactor + reactionToGenerate.removeIf([ + if (!federate.contains(it)) return true; + if (federate.networkReactions.contains(it)) return true + return false + + ]) + } + + var reactionIndex = 0 + for (reaction : reactionToGenerate) { + val reactionParameters = new StringBuilder() // Will contain parameters for the function (e.g., Foo(x,y,z,...) + val inits = new CodeBuilder() // Will contain initialization code for some parameters + generatePythonReactionParametersAndInitializations(reactionParameters, inits, reactor, reaction) + pythonClasses.pr('''def «pythonReactionFunctionName(reactionIndex)»(self«reactionParameters»):''') + pythonClasses.indent() + pythonClasses.pr(inits); + pythonClasses.pr(reaction.code.toText) + pythonClasses.pr('''return 0''') + pythonClasses.pr(""); + pythonClasses.unindent() + + // Now generate code for the deadline violation function, if there is one. + if (reaction.deadline !== null) { + pythonClasses. + pr('''«generateDeadlineFunctionForReaction(reaction, reactionIndex, reactionParameters.toString)»''') + } + + reactionIndex = reactionIndex + 1; + } + + pythonClasses.unindent() + instantiatedClasses.add(className) + } + + for (child : instance.children) { + generatePythonReactorClass(child, pythonClasses, federate, instantiatedClasses) + } + } + + /** + * Generate code that instantiates and initializes parameters and state variables for a reactor 'decl'. + * + * @param decl The reactor declaration + * @return The generated code as a StringBuilder + */ + protected def CodeBuilder generateParametersAndStateVariables(ReactorDecl decl) { + val reactor = decl.toDefinition + var CodeBuilder temporary_code = new CodeBuilder() + + temporary_code.indent(); + + temporary_code.pr('''# Define parameters and their default values + ''') + + for (param : decl.toDefinition.allParameters) { + if (!types.getTargetType(param).equals("PyObject*")) { + // If type is given, use it + temporary_code. + pr('''self._«param.name»:«types.getPythonType(param.inferredType)» = «param.pythonInitializer» + ''') + } else { + // If type is not given, just pass along the initialization + temporary_code.pr('''self._«param.name» = «param.pythonInitializer» + ''') + + } + } + + // Handle parameters that are set in instantiation + temporary_code.pr('''# Handle parameters that are set in instantiation + ''') + temporary_code.pr('''self.__dict__.update(kwargs) + + ''') + + temporary_code.pr('''# Define state variables + ''') + // Next, handle state variables + for (stateVar : reactor.allStateVars) { + if (stateVar.isInitialized) { + // If initialized, pass along the initialization directly if it is present + temporary_code.pr('''self.«stateVar.name» = «stateVar.pythonInitializer» + ''') + } else { + // If neither the type nor the initialization is given, use None + temporary_code.pr('''self.«stateVar.name» = None + ''') + } + } + + + temporary_code.pr(''' + + ''') + + temporary_code.unindent(); + + // Next, create getters for parameters + for (param : decl.toDefinition.allParameters) { + if (!param.name.equals("bank_index")) { + temporary_code.pr('''@property + ''') + temporary_code.pr('''def «param.name»(self): + ''') + temporary_code.pr(''' return self._«param.name» # pylint: disable=no-member + + ''') + } + } + + // Create a special property for bank_index + temporary_code.pr('''@property + ''') + temporary_code.pr('''def bank_index(self): + ''') + temporary_code.pr(''' return self._bank_index # pylint: disable=no-member + + ''') + + return temporary_code; + } + + /** + * Generate the function that is executed whenever the deadline of the reaction + * with the given reaction index is missed + * @param reaction The reaction to generate deadline miss code for + * @param reactionIndex The agreed-upon index of the reaction in the reactor (should match the C generated code) + * @param reactionParameters The parameters to the deadline violation function, which are the same as the reaction function + */ + def generateDeadlineFunctionForReaction(Reaction reaction, int reactionIndex, String reactionParameters) ''' + «val deadlineFunctionName = 'deadline_function_' + reactionIndex» + + def «deadlineFunctionName»(self «reactionParameters»): + «reaction.deadline.code.toText» + return 0 + + ''' + + /** + * Generates preambles defined by user for a given reactor. + * The preamble code is put inside the reactor class. + */ + def generatePythonPreamblesForReactor(Reactor reactor) ''' + «FOR p : reactor.preambles ?: emptyList» + # From the preamble, verbatim: + «p.code.toText» + # End of preamble. + «ENDFOR» + ''' + + /** + * Instantiate classes in Python. + * Instances are always instantiated as a list of className = [_className, _className, ...] depending on the size of the bank. + * If there is no bank or the size is 1, the instance would be generated as className = [_className] + * @param instance The reactor instance to be instantiated + * @param pythonClassesInstantiation The class instantiations are appended to this code builder + * @param federate The federate instance for the reactor instance + */ + def void generatePythonClassInstantiation(ReactorInstance instance, CodeBuilder pythonClassesInstantiation, + FederateInstance federate) { + // If this is not the main reactor and is not in the federate, nothing to do. + if (instance !== this.main && !federate.contains(instance)) { + return + } + + val className = instance.definition.reactorClass.name + + // Do not instantiate delay reactors in Python + if (className.contains(GEN_DELAY_CLASS_NAME)) { + return + } + + if (federate.contains(instance) && instance.width > 0) { + // For each reactor instance, create a list regardless of whether it is a bank or not. + // Non-bank reactor instances will be a list of size 1. var reactorClass = instance.definition.reactorClass + var fullName = instance.fullName + pythonClassesInstantiation.pr( ''' + + # Start initializing «fullName» of class «className» + for «PyUtil.bankIndexName(instance)» in range(«instance.width»): + ''') + pythonClassesInstantiation.indent(); + pythonClassesInstantiation.pr(''' + «PyUtil.reactorRef(instance)» = \ + _«className»( + _bank_index = «PyUtil.bankIndex(instance)», + «FOR param : instance.parameters» + «IF !param.name.equals("bank_index")» + _«param.name»=«param.pythonInitializer», + «ENDIF»«ENDFOR» + ) + ''') + } + + for (child : instance.children) { + generatePythonClassInstantiation(child, pythonClassesInstantiation, federate) + } + pythonClassesInstantiation.unindent(); + } + + /** + * Generate code to instantiate a Python list that will hold the Python + * class instance of reactor instance. Will recursively do + * the same for the children of instance as well. + * + * @param instance The reactor instance for which the Python list will be created. + * @param pythonClassesInstantiation StringBuilder to hold the generated code. + * @param federate Will check if instance (or any of its children) belong to + * federate before generating code for them. + */ + def void generateListsToHoldClassInstances( + ReactorInstance instance, + CodeBuilder pythonClassesInstantiation, + FederateInstance federate + ) { + if(federate !== null && !federate.contains(instance)) return; + pythonClassesInstantiation.pr(''' + «PyUtil.reactorRefName(instance)» = [None] * «instance.totalWidth» + ''') + for (child : instance.children) { + generateListsToHoldClassInstances(child, pythonClassesInstantiation, federate); + } + } + + /** + * Generate all Python classes if they have a reaction + * @param federate The federate instance used to generate classes + */ + def generatePythonReactorClasses(FederateInstance federate) { + + var CodeBuilder pythonClasses = new CodeBuilder() + var CodeBuilder pythonClassesInstantiation = new CodeBuilder() + + // Generate reactor classes in Python + this.main.generatePythonReactorClass(pythonClasses, federate) + + // Create empty lists to hold reactor instances + this.main.generateListsToHoldClassInstances(pythonClassesInstantiation, federate) + + // Instantiate generated classes + this.main.generatePythonClassInstantiation(pythonClassesInstantiation, federate) + + '''«pythonClasses» + + ''' + '''# Instantiate classes + ''' + '''«pythonClassesInstantiation» + ''' + } + + /** + * Generate the Python code constructed from reactor classes and user-written classes. + * @return the code body + */ + def generatePythonCode(FederateInstance federate) ''' + # List imported names, but do not use pylint's --extension-pkg-allow-list option + # so that these names will be assumed present without having to compile and install. + from LinguaFranca«topLevelName» import ( # pylint: disable=no-name-in-module + Tag, action_capsule_t, compare_tags, get_current_tag, get_elapsed_logical_time, + get_elapsed_physical_time, get_logical_time, get_microstep, get_physical_time, + get_start_time, port_capsule, port_instance_token, request_stop, schedule_copy, + start + ) + from LinguaFrancaBase.constants import BILLION, FOREVER, NEVER, instant_t, interval_t + from LinguaFrancaBase.functions import ( + DAY, DAYS, HOUR, HOURS, MINUTE, MINUTES, MSEC, MSECS, NSEC, NSECS, SEC, SECS, USEC, + USECS, WEEK, WEEKS + ) + from LinguaFrancaBase.classes import Make + import sys + import copy + + «pythonPreamble.toString» + + «generatePythonReactorClasses(federate)» + + «PythonMainGenerator.generateCode()» + ''' + + /** + * Generate the setup.py required to compile and install the module. + * Currently, the package name is based on filename which does not support sharing the setup.py for multiple .lf files. + * TODO: use an alternative package name (possibly based on folder name) + * + * If the LF program itself is threaded or if tracing is enabled, NUMBER_OF_WORKERS is added as a macro + * so that platform-specific C files will contain the appropriate functions. + */ + def generatePythonSetupFile() ''' + from setuptools import setup, Extension + + linguafranca«topLevelName»module = Extension("LinguaFranca«topLevelName»", + sources = ["«topLevelName».c", «FOR src : targetConfig.compileAdditionalSources SEPARATOR ", "» "«FileConfig.toUnixString(src)»"«ENDFOR»], + define_macros=[('MODULE_NAME', 'LinguaFranca«topLevelName»')«IF (targetConfig.threads !== 0 || (targetConfig.tracing !== null))», + ('NUMBER_OF_WORKERS', '«targetConfig.threads»')«ENDIF»,«FOR definition: targetConfig.compileDefinitions.entrySet»('«definition.key»', '«definition.value»'),«ENDFOR»]) + + setup(name="LinguaFranca«topLevelName»", version="1.0", + ext_modules = [linguafranca«topLevelName»module], + install_requires=['LinguaFrancaBase', «pythonRequiredModules.join(", ")»],) + ''' + + /** + * Generate the necessary Python files. + * @param federate The federate instance + */ + def generatePythonFiles(FederateInstance federate) { + var file = new File(fileConfig.getSrcGenPath.toFile, topLevelName + ".py") + if (file.exists) { + file.delete + } + // Create the necessary directories + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + val codeMaps = #{file.toPath -> CodeMap.fromGeneratedCode(generatePythonCode(federate).toString)} + JavaGeneratorUtils.writeToFile(codeMaps.get(file.toPath).generatedCode, file.toPath) + + val setupPath = fileConfig.getSrcGenPath.resolve("setup.py") + // Handle Python setup + System.out.println("Generating setup file to " + setupPath) + file = setupPath.toFile + if (file.exists) { + // Append + file.delete + } + + // Create the setup file + JavaGeneratorUtils.writeToFile(generatePythonSetupFile, setupPath) + + return codeMaps + } + + /** + * Execute the command that compiles and installs the current Python module + */ + def pythonCompileCode(LFGeneratorContext context) { + // if we found the compile command, we will also find the install command + val installCmd = commandFactory.createCommand( + '''python3''', #["-m", "pip", "install", "--force-reinstall", "."], fileConfig.srcGenPath) + + if (installCmd === null) { + errorReporter.reportError( + "The Python target requires Python >= 3.6, pip >= 20.0.2, and setuptools >= 45.2.0-1 to compile the generated code. " + + "Auto-compiling can be disabled using the \"no-compile: true\" target property.") + return + } + + // Set compile time environment variables + installCmd.setEnvironmentVariable("CC", targetConfig.compiler) // Use gcc as the compiler + installCmd.setEnvironmentVariable("LDFLAGS", targetConfig.linkerFlags) // The linker complains about including pythontarget.h twice (once in the generated code and once in pythontarget.c) + // To avoid this, we force the linker to allow multiple definitions. Duplicate names would still be caught by the + // compiler. + if (installCmd.run(context.cancelIndicator) == 0) { + println("Successfully installed python extension.") + } else { + errorReporter.reportError("Failed to install python extension due to the following errors:\n" + + installCmd.getErrors()) + } + } + + /** + * Generate top-level preambles and #include of pqueue.c and either reactor.c or reactor_threaded.c + * depending on whether threads are specified in target directive. + * As a side effect, this populates the runCommand and compileCommand + * private variables if such commands are specified in the target directive. + */ + override generatePreamble() { + + if (isFederated) { + targetConfig.compileDefinitions.put("FEDERATED", ""); + if (targetConfig.coordination === CoordinationType.CENTRALIZED) { + // The coordination is centralized. + targetConfig.compileDefinitions.put("FEDERATED_CENTRALIZED", ""); + } else if (targetConfig.coordination === CoordinationType.DECENTRALIZED) { + // The coordination is decentralized + targetConfig.compileDefinitions.put("FEDERATED_DECENTRALIZED", ""); + } + } + + val models = new LinkedHashSet + + for (r : this.reactors ?: emptyList) { + // The following assumes all reactors have a container. + // This means that generated reactors **have** to be + // added to a resource; not doing so will result in a NPE. + models.add(r.toDefinition.eContainer as Model) + } + // Add the main reactor if it is defined + if (this.mainDef !== null) { + models.add(this.mainDef.reactorClass.toDefinition.eContainer as Model) + } + for (m : models) { + for (p : m.preambles) { + pythonPreamble.append('''«p.code.toText» + ''') + } + } + + code.pr(CGenerator.defineLogLevel(this)) + + // Handle target parameters. + // First, if there are federates, then ensure that threading is enabled. + if (isFederated) { + for (federate : federates) { + // The number of threads needs to be at least one larger than the input ports + // to allow the federate to wait on all input ports while allowing an additional + // worker thread to process incoming messages. + if (targetConfig.threads < federate.networkMessageActions.size + 1) { + targetConfig.threads = federate.networkMessageActions.size + 1; + } + } + } + + includeTargetLanguageHeaders() + + code.pr("#include \"core/mixed_radix.h\""); + + code.pr('#define NUMBER_OF_FEDERATES ' + federates.size); + + // Handle target parameters. + // First, if there are federates, then ensure that threading is enabled. + if (targetConfig.threads === 0 && isFederated) { + targetConfig.threads = 1 + } + + super.includeTargetLanguageSourceFiles() + + super.parseTargetParameters() + } + + /** + * Add necessary code to the source and necessary build supports to + * enable the requested serializations in 'enabledSerializations' + */ + override enableSupportForSerializationIfApplicable(CancelIndicator cancelIndicator) { + if (!targetConfig.protoFiles.isNullOrEmpty) { + // Enable support for proto serialization + enabledSerializers.add(SupportedSerializers.PROTO) + } + for (serialization : enabledSerializers) { + switch (serialization) { + case NATIVE: { + val pickler = new FedNativePythonSerialization(); + code.pr(pickler.generatePreambleForSupport.toString); + } + case PROTO: { + // Handle .proto files. + for (name : targetConfig.protoFiles) { + this.processProtoFile(name, cancelIndicator) + val dotIndex = name.lastIndexOf('.') + var rootFilename = name + if (dotIndex > 0) { + rootFilename = name.substring(0, dotIndex) + } + pythonPreamble.append(''' + import «rootFilename»_pb2 as «rootFilename» + ''') + protoNames.add(rootFilename) + } + } + case ROS2: { + // FIXME: Not supported yet + } + } + } + } + + /** + * 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. + */ + override processProtoFile(String filename, CancelIndicator cancelIndicator) { + val protoc = commandFactory.createCommand("protoc", + #['''--python_out=«this.fileConfig.getSrcGenPath»''', filename], fileConfig.srcPath) + // val protoc = createCommand("protoc", #['''--python_out=src-gen/«topLevelName»''', topLevelName], codeGenConfig.outPath) + if (protoc === null) { + errorReporter.reportError("Processing .proto files requires libprotoc >= 3.6.1") + return + } + val returnCode = protoc.run(cancelIndicator) + if (returnCode == 0) { + pythonRequiredModules.add(''' 'google-api-python-client' ''') + } else { + errorReporter.reportError("protoc returns error code " + returnCode) + } + } + + /** + * Generate code for the body of a reaction that handles the + * action that is triggered by receiving a message from a remote + * federate. + * @param action The action. + * @param sendingPort The output port providing the data to send. + * @param receivingPort The ID of the destination port. + * @param receivingPortID The ID of the destination port. + * @param sendingFed The sending federate. + * @param receivingFed The destination federate. + * @param receivingBankIndex The receiving federate's bank index, if it is in a bank. + * @param receivingChannelIndex The receiving federate's channel index, if it is a multiport. + * @param type The type. + * @param isPhysical Indicates whether or not the connection is physical + * @param serializer The serializer used on the connection. + */ + override generateNetworkReceiverBody( + Action action, + VarRef sendingPort, + VarRef receivingPort, + int receivingPortID, + FederateInstance sendingFed, + FederateInstance receivingFed, + int receivingBankIndex, + int receivingChannelIndex, + InferredType type, + boolean isPhysical, + SupportedSerializers serializer + ) { + var result = new StringBuilder(); + + // We currently have no way to mark a reaction "unordered" + // in the AST, so we use a magic string at the start of the body. + result.append("// " + ReactionInstance.UNORDERED_REACTION_MARKER + "\n"); + + result.append(''' + // Acquire the GIL (Global Interpreter Lock) to be able to call Python APIs. + PyGILState_STATE gstate; + gstate = PyGILState_Ensure(); + ''') + result.append(PythonGeneratorExtension.generateNetworkReceiverBody( + action, + sendingPort, + receivingPort, + receivingPortID, + sendingFed, + receivingFed, + receivingBankIndex, + receivingChannelIndex, + type, + isPhysical, + serializer, + this + )); + result.append(''' + /* Release the thread. No Python API allowed beyond this point. */ + PyGILState_Release(gstate); + '''); + return result.toString(); + } + + /** + * Generate code for the body of a reaction that handles an output + * that is to be sent over the network. + * @param sendingPort The output port providing the data to send. + * @param receivingPort The variable reference to the destination port. + * @param receivingPortID The ID of the destination port. + * @param sendingFed The sending federate. + * @param sendingBankIndex The bank index of the sending federate, if it is a bank. + * @param sendingChannelIndex The channel index of the sending port, if it is a multiport. + * @param receivingFed The destination federate. + * @param type The type. + * @param isPhysical Indicates whether the connection is physical or not + * @param delay The delay value imposed on the connection using after + * @param serializer The serializer used on the connection. + */ + override generateNetworkSenderBody( + VarRef sendingPort, + VarRef receivingPort, + int receivingPortID, + FederateInstance sendingFed, + int sendingBankIndex, + int sendingChannelIndex, + FederateInstance receivingFed, + InferredType type, + boolean isPhysical, + Delay delay, + SupportedSerializers serializer + ) { + var result = new StringBuilder(); + + // We currently have no way to mark a reaction "unordered" + // in the AST, so we use a magic string at the start of the body. + result.append("// " + ReactionInstance.UNORDERED_REACTION_MARKER + "\n"); + + result.append(''' + // Acquire the GIL (Global Interpreter Lock) to be able to call Python APIs. + PyGILState_STATE gstate; + gstate = PyGILState_Ensure(); + ''') + result.append(PythonGeneratorExtension.generateNetworkSenderBody( + sendingPort, + receivingPort, + receivingPortID, + sendingFed, + sendingBankIndex, + sendingChannelIndex, + receivingFed, + type, + isPhysical, + delay, + serializer, + this + )); + result.append(''' + /* Release the thread. No Python API allowed beyond this point. */ + PyGILState_Release(gstate); + '''); + return result.toString(); + } + + /** + * Create a launcher script that executes all the federates and the RTI. + * + * @param coreFiles The files from the core directory that must be + * copied to the remote machines. + */ + override createFederatedLauncher(ArrayList coreFiles) { + val launcher = new FedPyLauncher( + targetConfig, + fileConfig, + errorReporter + ); + launcher.createLauncher( + coreFiles, + federates, + federationRTIProperties + ); + } + + /** + * Generate the aliases for inputs, outputs, and struct type definitions for + * actions of the specified reactor in the specified federate. + * @param reactor The parsed reactor data structure. + * @param federate A federate name, or null to unconditionally generate. + */ + override generateAuxiliaryStructs( + ReactorDecl decl, + FederateInstance federate + ) { + val reactor = decl.toDefinition + // First, handle inputs. + for (input : reactor.allInputs) { + if (federate === null || federate.contains(input as Port)) { + if (input.inferredType.isTokenType) { + code.pr(input, ''' + typedef «generic_port_type_with_token» «variableStructType(input, decl)»; + ''') + } else { + code.pr(input, ''' + typedef «generic_port_type» «variableStructType(input, decl)»; + ''') + } + + } + + } + // Next, handle outputs. + for (output : reactor.allOutputs) { + if (federate === null || federate.contains(output as Port)) { + if (output.inferredType.isTokenType) { + code.pr(output, ''' + typedef «generic_port_type_with_token» «variableStructType(output, decl)»; + ''') + } else { + code.pr(output, ''' + typedef «generic_port_type» «variableStructType(output, decl)»; + ''') + } + + } + } + // Finally, handle actions. + for (action : reactor.allActions) { + if (federate === null || federate.contains(action)) { + code.pr(action, ''' + typedef «generic_action_type» «variableStructType(action, decl)»; + ''') + } + + } + } + + /** + * For the specified action, return a declaration for action struct to + * contain the value of the action. + * This will return an empty string for an action with no type. + * @param action The action. + * @return A string providing the value field of the action struct. + */ + override valueDeclaration(Action action) { + return "PyObject* value;" + } + + /** Add necessary include files specific to the target language. + * Note. The core files always need to be (and will be) copied + * uniformly across all target languages. + */ + override includeTargetLanguageHeaders() { + code.pr('''#define _LF_GARBAGE_COLLECTED''') + if (targetConfig.tracing !== null) { + var filename = ""; + if (targetConfig.tracing.traceFileName !== null) { + filename = targetConfig.tracing.traceFileName; + } + code.pr('#define LINGUA_FRANCA_TRACE ' + filename) + } + + code.pr('#include "pythontarget.c"') + if (targetConfig.tracing !== null) { + code.pr('#include "core/trace.c"') + } + } + + /** + * Return true if the host operating system is compatible and + * otherwise report an error and return false. + */ + override isOSCompatible() { + if (JavaGeneratorUtils.isHostWindows) { + if (isFederated) { + errorReporter.reportError( + "Federated LF programs with a Python target are currently not supported on Windows. Exiting code generation." + ) + // Return to avoid compiler errors + 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 Context relating to invocation of the code generator. + */ + override void doGenerate(Resource resource, LFGeneratorContext context) { + // If there are federates, assign the number of threads in the CGenerator to 1 + if (isFederated) { + targetConfig.threads = 1; + } + + // Prevent the CGenerator from compiling the C code. + // The PythonGenerator will compiler it. + val compileStatus = targetConfig.noCompile; + targetConfig.noCompile = true; + targetConfig.useCmake = false; // Force disable the CMake because + // it interferes with the Python target functionality + val cGeneratedPercentProgress = (IntegratedBuilder.VALIDATED_PERCENT_PROGRESS + 100) / 2 + super.doGenerate(resource, new SubContext( + context, + IntegratedBuilder.VALIDATED_PERCENT_PROGRESS, + cGeneratedPercentProgress + )) + val compilingFederatesContext = new SubContext(context, cGeneratedPercentProgress, 100) + + targetConfig.noCompile = compileStatus + + if (errorsOccurred) { + context.unsuccessfulFinish() + return; + } + + var baseFileName = topLevelName + // Keep a separate file config for each federate + val oldFileConfig = fileConfig; + var federateCount = 0; + val codeMaps = new HashMap + for (federate : federates) { + federateCount++ + if (isFederated) { + topLevelName = baseFileName + '_' + federate.name + fileConfig = new FedFileConfig(fileConfig, federate.name); + } + // Don't generate code if there is no main reactor + if (this.main !== null) { + val codeMapsForFederate = generatePythonFiles(federate) + codeMaps.putAll(codeMapsForFederate) + copyTargetFiles(); + if (!targetConfig.noCompile) { + compilingFederatesContext.reportProgress( + String.format("Validating %d/%d sets of generated files...", federateCount, federates.size()), + 100 * federateCount / federates.size() + ) + // If there are no federates, compile and install the generated code + new PythonValidator(fileConfig, errorReporter, codeMaps, protoNames).doValidate(context) + if (!errorsOccurred() && context.mode != Mode.LSP_MEDIUM) { + compilingFederatesContext.reportProgress( + String.format("Validation complete. Compiling and installing %d/%d Python modules...", + federateCount, federates.size()), + 100 * federateCount / federates.size() + ) + pythonCompileCode(context) // Why is this invoked here if the current federate is not a parameter? + } + } else { + printSetupInfo(); + } + + if (!isFederated) { + printRunInfo(); + } + } + fileConfig = oldFileConfig; + } + if (isFederated) { + printFedRunInfo(); + } + // Restore filename + topLevelName = baseFileName + if (errorReporter.getErrorsOccurred()) { + context.unsuccessfulFinish() + } else if (!isFederated) { + context.finish(GeneratorResult.Status.COMPILED, '''«topLevelName».py''', fileConfig.srcGenPath, fileConfig, + codeMaps, "python3") + } else { + context.finish(GeneratorResult.Status.COMPILED, fileConfig.name, fileConfig.binPath, fileConfig, codeMaps, + "bash") + } + } + + /** + * Copy Python specific target code to the src-gen directory + */ + def copyTargetFiles() { + // Copy the required target language files into the target file system. + // This will also overwrite previous versions. + fileConfig.copyFileFromClassPath( + "/lib/py/reactor-c-py/include/pythontarget.h", + fileConfig.getSrcGenPath.resolve("pythontarget.h").toString + ) + fileConfig.copyFileFromClassPath( + "/lib/py/reactor-c-py/lib/pythontarget.c", + fileConfig.getSrcGenPath.resolve("pythontarget.c").toString + ) + fileConfig.copyFileFromClassPath( + "/lib/c/reactor-c/include/ctarget.h", + fileConfig.getSrcGenPath.resolve("ctarget.h").toString + ) + } + + /** Return the function name in Python + * @param reactor The reactor + * @param reactionIndex The reaction index. + * @return The function name for the reaction. + */ + def pythonReactionFunctionName(int reactionIndex) { + "reaction_function_" + reactionIndex + } + + /** + * Generate code for the body of a reaction that takes an input and + * schedules an action with the value of that input. + * @param action The action to schedule + * @param port The port to read from + */ + override generateDelayBody(Action action, VarRef port) { + val ref = JavaAstUtils.generateVarRef(port); + // Note that the action.type set by the base class is actually + // the port type. + if (action.inferredType.isTokenType) { + ''' + if («ref»->is_present) { + // Put the whole token on the event queue, not just the payload. + // This way, the length and element_size are transported. + schedule_token(«action.name», 0, «ref»->token); + } + ''' + } else { + ''' + // Create a token. + #if NUMBER_OF_WORKERS > 0 + // Need to lock the mutex first. + lf_mutex_lock(&mutex); + #endif + lf_token_t* t = create_token(sizeof(PyObject*)); + #if NUMBER_OF_WORKERS > 0 + lf_mutex_unlock(&mutex); + #endif + t->value = self->_lf_«ref»->value; + t->length = 1; // Length is 1 + + // Pass the token along + schedule_token(«action.name», 0, t); + ''' + } + } + + /** + * Generate code for the body of a reaction that is triggered by the + * given action and writes its value to the given port. This realizes + * the receiving end of a logical delay specified with the 'after' + * keyword. + * @param action The action that triggers the reaction + * @param port The port to write to. + */ + override generateForwardBody(Action action, VarRef port) { + val outputName = JavaAstUtils.generateVarRef(port) + if (action.inferredType.isTokenType) { + super.generateForwardBody(action, port) + } else { + ''' + SET(«outputName», «action.name»->token->value); + ''' + } + } + + + /** + * Generate necessary Python-specific initialization code for reaction that belongs to reactor + * decl. + * + * @param reaction The reaction to generate Python-specific initialization for. + * @param decl The reactor to which reaction belongs to. + * @param pyObjectDescriptor For each port object created, a Python-specific descriptor will be added to this that + * then can be used as an argument to Py_BuildValue + * (@see docs.python.org/3/c-api). + * @param pyObjects A "," delimited list of expressions that would be (or result in a creation of) a PyObject. + */ + protected def void generatePythonInitializationForReaction( + Reaction reaction, + ReactorDecl decl, + StringBuilder pyObjectDescriptor, + StringBuilder pyObjects + ) { + var actionsAsTriggers = new LinkedHashSet(); + val Reactor reactor = decl.toDefinition; + + // Next, add the triggers (input and actions; timers are not needed). + // TODO: handle triggers + for (TriggerRef trigger : reaction.triggers ?: emptyList) { + if (trigger instanceof VarRef) { + if (trigger.variable instanceof Port) { + generatePortVariablesToSendToPythonReaction(pyObjectDescriptor, pyObjects, trigger, decl) + } else if (trigger.variable instanceof Action) { + actionsAsTriggers.add(trigger.variable as Action) + generateActionVariableToSendToPythonReaction(pyObjectDescriptor, pyObjects, + trigger.variable as Action, decl) + } + } + } + if (reaction.triggers === null || reaction.triggers.size === 0) { + // No triggers are given, which means react to any input. + // Declare an argument for every input. + // NOTE: this does not include contained outputs. + for (input : reactor.inputs) { + generateInputVariablesToSendToPythonReaction(pyObjectDescriptor, pyObjects, input, decl) + } + } + + // Next add non-triggering inputs. + for (VarRef src : reaction.sources ?: emptyList) { + if (src.variable instanceof Port) { + generatePortVariablesToSendToPythonReaction(pyObjectDescriptor, pyObjects, src, decl) + } else if (src.variable instanceof Action) { + // TODO: handle actions + actionsAsTriggers.add(src.variable as Action) + generateActionVariableToSendToPythonReaction(pyObjectDescriptor, pyObjects, src.variable as Action, + decl) + } + } + + // Next, handle effects + if (reaction.effects !== null) { + for (effect : reaction.effects) { + if (effect.variable instanceof Action) { + // It is an action, not an output. + // If it has already appeared as trigger, do not redefine it. + if (!actionsAsTriggers.contains(effect.variable)) { + generateActionVariableToSendToPythonReaction(pyObjectDescriptor, pyObjects, + effect.variable as Action, decl) + } + } else { + if (effect.variable instanceof Output) { + generateOutputVariablesToSendToPythonReaction(pyObjectDescriptor, pyObjects, + effect.variable as Output, decl) + } else if (effect.variable instanceof Input) { + // It is the input of a contained reactor. + generateVariablesForSendingToContainedReactors(pyObjectDescriptor, pyObjects, effect.container, + effect.variable as Input, decl) + } else { + errorReporter.reportError( + reaction, + "In generateReaction(): " + effect.variable.name + " is neither an input nor an output." + ) + } + + } + } + } + } + + /** Generate a reaction function definition for a reactor. + * This function has 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 reactor The reactor. + * @param reactionIndex The position of the reaction within the reactor. + */ + override generateReaction(Reaction reaction, ReactorDecl decl, int reactionIndex) { + + val reactor = decl.toDefinition + + // Delay reactors and top-level reactions used in the top-level reactor(s) in federated execution are generated in C + if (reactor.name.contains(GEN_DELAY_CLASS_NAME) || + ((decl === this.mainDef?.reactorClass) && reactor.isFederated)) { + super.generateReaction(reaction, decl, reactionIndex) + return + } + + // Contains "O" characters. The number of these characters depend on the number of inputs to the reaction + val StringBuilder pyObjectDescriptor = new StringBuilder() + + // Contains the actual comma separated list of inputs to the reaction of type generic_port_instance_struct or generic_port_instance_with_token_struct. + // Each input must be cast to (PyObject *) + val StringBuilder pyObjects = new StringBuilder() + + // Create a unique function name for each reaction. + val functionName = reactionFunctionName(decl, reactionIndex) + + // Generate the function name in Python + val pythonFunctionName = pythonReactionFunctionName(reactionIndex); + + code.pr('void ' + functionName + '(void* instance_args) {') + code.indent() + + // First, generate C initializations + super.generateInitializationForReaction("", reaction, decl, reactionIndex) + + code.prSourceLineNumber(reaction.code) + + // Ensure that GIL is locked + code.pr(''' + // Acquire the GIL (Global Interpreter Lock) to be able to call Python APIs. + PyGILState_STATE gstate; + gstate = PyGILState_Ensure(); + ''') + + // Generate Python-related initializations + generatePythonInitializationForReaction(reaction, decl, pyObjectDescriptor, pyObjects) + + // Call the Python reaction + code.pr(''' + + DEBUG_PRINT("Calling reaction function «decl.name».«pythonFunctionName»"); + PyObject *rValue = PyObject_CallObject( + self->_lf_py_reaction_function_«reactionIndex», + Py_BuildValue("(«pyObjectDescriptor»)" «pyObjects») + ); + if (rValue == NULL) { + error_print("FATAL: Calling reaction «decl.name».«pythonFunctionName» failed."); + if (PyErr_Occurred()) { + PyErr_PrintEx(0); + PyErr_Clear(); // this will reset the error indicator so we can run Python code again + } + /* Release the thread. No Python API allowed beyond this point. */ + PyGILState_Release(gstate); + Py_FinalizeEx(); + exit(1); + } + + /* Release the thread. No Python API allowed beyond this point. */ + PyGILState_Release(gstate); + ''') + + code.unindent() + code.pr("}") + + // Now generate code for the deadline violation function, if there is one. + if (reaction.deadline !== null) { + // The following name has to match the choice in generateReactionInstances + val deadlineFunctionName = decl.name.toLowerCase + '_deadline_function' + reactionIndex + + code.pr('void ' + deadlineFunctionName + '(void* instance_args) {') + code.indent(); + + super.generateInitializationForReaction("", reaction, decl, reactionIndex) + + code.pr(''' + // Acquire the GIL (Global Interpreter Lock) to be able to call Python APIs. + PyGILState_STATE gstate; + gstate = PyGILState_Ensure(); + + DEBUG_PRINT("Calling deadline function «decl.name».«deadlineFunctionName»"); + PyObject *rValue = PyObject_CallObject( + self->_lf_py_deadline_function_«reactionIndex», + Py_BuildValue("(«pyObjectDescriptor»)" «pyObjects») + ); + if (rValue == NULL) { + error_print("FATAL: Calling reaction «decl.name».«deadlineFunctionName» failed.\n"); + if (rValue == NULL) { + if (PyErr_Occurred()) { + PyErr_PrintEx(0); + PyErr_Clear(); // this will reset the error indicator so we can run Python code again + } + } + /* Release the thread. No Python API allowed beyond this point. */ + PyGILState_Release(gstate); + Py_FinalizeEx(); + exit(1); + } + + /* Release the thread. No Python API allowed beyond this point. */ + PyGILState_Release(gstate); + ''') + + code.unindent() + code.pr("}") + } + } + + /** + * Generate code for parameter variables of a reactor in the form "parameter.type parameter.name;" + * + * FIXME: for now we assume all parameters are int. This is to circumvent the issue of parameterized + * port widths for now. + * + * @param reactor The reactor. + * @param builder The place that the generated code is written to. + * @return + */ + override generateParametersForReactor(CodeBuilder builder, Reactor reactor) { + for (parameter : reactor.allParameters) { + builder.prSourceLineNumber(parameter) + // Assume all parameters are integers + builder.pr('''int «parameter.name» ;'''); + } + } + + /** + * 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. This task is left to Python code to allow for more liberal + * state variable assignments. + * @param instance The reactor class instance + * @return Initialization code fore state variables of instance + */ + override generateStateVariableInitializations(ReactorInstance instance) { + // Do nothing + } + + /** + * Generate runtime initialization code in C for parameters of a given reactor instance. + * All parameters are also initialized in Python code, but those parameters that are + * used as width must be also initialized in C. + * + * FIXME: Here, we use a hack: we attempt to convert the parameter initialization to an integer. + * If it succeeds, we proceed with the C initialization. If it fails, we defer initialization + * to Python. + * + * Generate runtime initialization code for parameters of a given reactor instance + * @param instance The reactor instance. + */ + override void generateParameterInitialization(ReactorInstance instance) { + // Mostly ignore the initialization in C + // The actual initialization will be done in Python + // Except if the parameter is a width (an integer) + // Here, we attempt to convert the parameter value to + // integer. If it succeeds, we also initialize it in C. + // If it fails, we defer the initialization to Python. + var nameOfSelfStruct = CUtil.reactorRef(instance) + for (parameter : instance.parameters) { + val initializer = parameter.getInitializer + try { + // Attempt to convert it to integer + val number = Integer.parseInt(initializer); + initializeTriggerObjects.pr(''' + «nameOfSelfStruct»->«parameter.name» = «number»; + ''') + } catch (NumberFormatException ex) { + // Ignore initialization in C for this parameter + } + } + } + + /** + * This function is overridden in the Python generator to do nothing. + * The state variables are initialized in Python code directly. + * @param reactor The reactor. + * @param builder The place that the generated code is written to. + * @return + */ + override generateStateVariablesForReactor(CodeBuilder builder, Reactor reactor) { + // Do nothing + } + + /** + * Generates C preambles defined by user for a given reactor + * Since the Python generator expects preambles written in C, + * this function is overridden and does nothing. + * @param reactor The given reactor + */ + override generateUserPreamblesForReactor(Reactor reactor) { + // Do nothing + } + + /** + * Generate code that is executed while the reactor instance is being initialized. + * This wraps the reaction functions in a Python function. + * @param instance The reactor instance. + * @param reactions The reactions of this instance. + */ + override void generateReactorInstanceExtension( + ReactorInstance instance, + Iterable reactions + ) { + var nameOfSelfStruct = CUtil.reactorRef(instance) + var reactor = instance.definition.reactorClass.toDefinition + + // Delay reactors and top-level reactions used in the top-level reactor(s) in federated execution are generated in C + if (reactor.name.contains(GEN_DELAY_CLASS_NAME) || + ((instance.definition.reactorClass === this.mainDef?.reactorClass) && reactor.isFederated)) { + return + } + + // Initialize the name field to the unique name of the instance + initializeTriggerObjects.pr(''' + «nameOfSelfStruct»->_lf_name = "«instance.uniqueID»_lf"; + '''); + + for (reaction : reactions) { + val pythonFunctionName = pythonReactionFunctionName(reaction.index) + // Create a PyObject for each reaction + initializeTriggerObjects.pr(''' + «nameOfSelfStruct»->_lf_py_reaction_function_«reaction.index» = + get_python_function("__main__", + «nameOfSelfStruct»->_lf_name, + «CUtil.runtimeIndex(instance)», + "«pythonFunctionName»"); + if («nameOfSelfStruct»->_lf_py_reaction_function_«reaction.index» == NULL) { + error_print_and_exit("Could not load function «instance.name» reaction_function_«reaction.index»."); + } + ''') + + if (reaction.definition.deadline !== null) { + initializeTriggerObjects.pr(''' + «nameOfSelfStruct»->_lf_py_deadline_function_«reaction.index» = + get_python_function("__main__", + «nameOfSelfStruct»->_lf_name, + «CUtil.runtimeIndex(instance)», + "deadline_function_«reaction.index»"); + + if («nameOfSelfStruct»->_lf_py_deadline_function_«reaction.index» == NULL) { + error_print_and_exit("Could not load function «instance.name» deadline_function_«reaction.index»."); + } + ''') + } + } + } + + /** + * This function is provided to allow extensions of the CGenerator to append the structure of the self struct + * @param selfStructBody The body of the self struct + * @param decl The reactor declaration for the self struct + * @param instance The current federate instance + * @param constructorCode Code that is executed when the reactor is instantiated + */ + override generateSelfStructExtension( + CodeBuilder selfStructBody, + ReactorDecl decl, + FederateInstance instance, + CodeBuilder constructorCode + ) { + val reactor = decl.toDefinition + // Add the name field + selfStructBody.pr('''char *_lf_name; + '''); + + var reactionIndex = 0 + for (reaction : reactor.allReactions) { + // Create a PyObject for each reaction + selfStructBody.pr('''PyObject* _lf_py_reaction_function_«reactionIndex»;''') + + if (reaction.deadline !== null) { + selfStructBody.pr('''PyObject* _lf_py_deadline_function_«reactionIndex»;''') + } + + reactionIndex++ + } + } + + /** + * Generate code to convert C actions to Python action capsules + * @see pythontarget.h. + * @param pyObjectDescriptor A string representing a list of Python format types (e.g., "O") that + * can be passed to Py_BuildValue. The object type for the converted action will + * be appended to this string (e.g., "OO"). + * @param pyObjects A string containing a list of comma-separated expressions that will create the + * action capsules. + * @param action The action itself. + * @param decl The reactor decl that contains the action. + */ + def generateActionVariableToSendToPythonReaction(StringBuilder pyObjectDescriptor, StringBuilder pyObjects, + Action action, ReactorDecl decl) { + pyObjectDescriptor.append("O") + // Values passed to an action are always stored in the token->value. + // However, sometimes token might not be initialized. Therefore, this function has an internal check for NULL in case token is not initialized. + pyObjects.append(''', convert_C_action_to_py(«action.name»)''') + } + + /** + * Generate code to convert C ports to Python ports capsules (@see pythontarget.h). + * + * The port may be an input of the reactor or an output of a contained reactor. + * + * @param pyObjectDescriptor A string representing a list of Python format types (e.g., "O") that + * can be passed to Py_BuildValue. The object type for the converted port will + * be appended to this string (e.g., "OO"). + * @param pyObjects A string containing a list of comma-separated expressions that will create the + * port capsules. + * @param port The port itself. + * @param decl The reactor decl that contains the port. + */ + private def generatePortVariablesToSendToPythonReaction( + StringBuilder pyObjectDescriptor, + StringBuilder pyObjects, + VarRef port, + ReactorDecl decl + ) { + if (port.variable instanceof Input) { + generateInputVariablesToSendToPythonReaction(pyObjectDescriptor, pyObjects, port.variable as Input, decl) + } else { + pyObjectDescriptor.append("O") + val output = port.variable as Output + val reactorName = port.container.name + // port is an output of a contained reactor. + if (port.container.widthSpec !== null) { + var String widthSpec = "-2" + if (JavaAstUtils.isMultiport(port.variable as Port)) { + widthSpec = '''self->_lf_«reactorName»[i].«output.name»_width''' + } + // Output is in a bank. + // Create a Python list + generatePythonListForContainedBank(reactorName, output, widthSpec) + pyObjects.append(''', «reactorName»_py_list''') + } else { + var String widthSpec = "-2" + if (JavaAstUtils.isMultiport(port.variable as Port)) { + widthSpec = '''«port.container.name».«port.variable.name»_width''' + } + pyObjects.append(''', convert_C_port_to_py(«reactorName».«port.variable.name», «widthSpec»)''') + } + } + } + + /** + * Generate code that creates a Python list (i.e., []) for contained banks to be passed to Python reactions. + * The Python reaction will then subsequently be able to address each individual bank member of the contained + * bank using an index or an iterator. Each list member will contain the given port + * (which could be a multiport with a width determined by widthSpec). + * + * This is to accommodate reactions like reaction() -> s.out where s is a bank. In this example, + * the generated Python function will have the signature reaction_function_0(self, s_out), where + * s_out is a list of out ports. This will later be turned into the proper s.out format using the + * Python code generated in {@link #generatePythonPortVariableInReaction}. + * + * @param reactorName The name of the bank of reactors (which is the name of the reactor class). + * @param port The port that should be put in the Python list. + * @param widthSpec A string that should be -2 for non-multiports and the width expression for multiports. + */ + protected def void generatePythonListForContainedBank(String reactorName, Port port, String widthSpec) { + code.pr(''' + PyObject* «reactorName»_py_list = PyList_New(«reactorName»_width); + + if(«reactorName»_py_list == NULL) { + error_print("Could not create the list needed for «reactorName»."); + if (PyErr_Occurred()) { + PyErr_PrintEx(0); + PyErr_Clear(); // this will reset the error indicator so we can run Python code again + } + /* Release the thread. No Python API allowed beyond this point. */ + PyGILState_Release(gstate); + Py_FinalizeEx(); + exit(1); + } + + for (int i = 0; i < «reactorName»_width; i++) { + if (PyList_SetItem( + «reactorName»_py_list, + i, + convert_C_port_to_py( + self->_lf_«reactorName»[i].«port.name», + «widthSpec» + ) + ) != 0) { + error_print("Could not add elements to the list for «reactorName»."); + if (PyErr_Occurred()) { + PyErr_PrintEx(0); + PyErr_Clear(); // this will reset the error indicator so we can run Python code again + } + /* Release the thread. No Python API allowed beyond this point. */ + PyGILState_Release(gstate); + Py_FinalizeEx(); + exit(1); + } + } + + ''') + } + + /** Generate into the specified string builder the code to + * send local variables for output ports to a Python reaction function + * from the "self" struct. + * @param builder The string builder into which to write the code. + * @param structs A map from reactor instantiations to a place to write + * struct fields. + * @param output The output port. + * @param decl The reactor declaration. + */ + private def generateOutputVariablesToSendToPythonReaction( + StringBuilder pyObjectDescriptor, + StringBuilder pyObjects, + Output output, + ReactorDecl decl + ) { + // Unfortunately, for the SET macros to work out-of-the-box for + // multiports, we need an array of *pointers* to the output structs, + // but what we have on the self struct is an array of output structs. + // So we have to handle multiports specially here a construct that + // array of pointers. + // FIXME: The C Generator also has this awkwardness. It makes the code generators + // unnecessarily difficult to maintain, and it may have performance consequences as well. + // Maybe we should change the SET macros. + if (!JavaAstUtils.isMultiport(output)) { + pyObjectDescriptor.append("O") + pyObjects.append(''', convert_C_port_to_py(«output.name», -2)''') + } else { + // Set the _width variable. + pyObjectDescriptor.append("O") + pyObjects.append(''', convert_C_port_to_py(«output.name»,«output.name»_width) ''') + } + } + + /** Generate into the specified string builder the code to + * pass local variables for sending data to an input + * of a contained reaction (e.g. for a deadline violation). + * @param builder The string builder. + * @param definition AST node defining the reactor within which this occurs + * @param input Input of the contained reactor. + */ + private def generateVariablesForSendingToContainedReactors( + StringBuilder pyObjectDescriptor, + StringBuilder pyObjects, + Instantiation definition, + Input input, + ReactorDecl decl + ) { + pyObjectDescriptor.append("O") + + if (definition.widthSpec !== null) { + var String widthSpec = "-2" + if (JavaAstUtils.isMultiport(input)) { + widthSpec = '''self->_lf_«definition.name»[i].«input.name»_width''' + } + // Contained reactor is a bank. + // Create a Python list + generatePythonListForContainedBank(definition.name, input, widthSpec); + pyObjects.append(''', «definition.name»_py_list''') + } + else { + var String widthSpec = "-2" + if (JavaAstUtils.isMultiport(input)) { + widthSpec = '''«definition.name».«input.name»_width''' + } + pyObjects. + append(''', convert_C_port_to_py(«definition.name».«input.name», «widthSpec»)''') + } + } + + /** Generate into the specified string builder the code to + * send local variables for input ports to a Python reaction function + * from the "self" struct. + * @param builder The string builder into which to write the code. + * @param structs A map from reactor instantiations to a place to write + * struct fields. + * @param input The input port. + * @param reactor The reactor. + */ + private def generateInputVariablesToSendToPythonReaction( + StringBuilder pyObjectDescriptor, + StringBuilder pyObjects, + Input input, + ReactorDecl decl + ) { + // Create the local variable whose name matches the input name. + // If the input has not been declared mutable, then this is a pointer + // to the upstream output. Otherwise, it is a copy of the upstream output, + // which nevertheless points to the same token and value (hence, as done + // below, we have to use writable_copy()). There are 8 cases, + // depending on whether the input is mutable, whether it is a multiport, + // and whether it is a token type. + // Easy case first. + if (!input.isMutable && !JavaAstUtils.isMultiport(input)) { + // Non-mutable, non-multiport, primitive type. + pyObjectDescriptor.append("O") + pyObjects.append(''', convert_C_port_to_py(«input.name», «input.name»_width)''') + } else if (input.isMutable && !JavaAstUtils.isMultiport(input)) { + // Mutable, non-multiport, primitive type. + // TODO: handle mutable + pyObjectDescriptor.append("O") + pyObjects.append(''', convert_C_port_to_py(«input.name», «input.name»_width)''') + } else if (!input.isMutable && JavaAstUtils.isMultiport(input)) { + // Non-mutable, multiport, primitive. + // TODO: support multiports + pyObjectDescriptor.append("O") + pyObjects.append(''', convert_C_port_to_py(«input.name»,«input.name»_width) ''') + } else { + // Mutable, multiport, primitive type + // TODO: support mutable multiports + pyObjectDescriptor.append("O") + pyObjects.append(''', convert_C_port_to_py(«input.name»,«input.name»_width) ''') + } + } + + /** + * Write a Dockerfile for the current federate as given by filename. + * The file will go into src-gen/filename.Dockerfile. + * If there is no main reactor, then no Dockerfile will be generated + * (it wouldn't be very useful). + * @param The directory where the docker compose file is generated. + * @param The name of the docker file. + * @param The name of the federate. + */ + override writeDockerFile(File dockerComposeDir, String dockerFileName, String federateName) { + var srcGenPath = fileConfig.getSrcGenPath + val dockerFile = srcGenPath + File.separator + dockerFileName + // If a dockerfile exists, remove it. + var file = new File(dockerFile) + if (file.exists) { + file.delete + } + + if (this.mainDef === null) { + return + } + + val OS = System.getProperty("os.name").toLowerCase(); + var dockerComposeCommand = (OS.indexOf("nux") >= 0) ? "docker-compose" : "docker compose" + + val contents = new CodeBuilder() + contents.pr(''' + # Generated docker file for «topLevelName».lf in «srcGenPath». + # For instructions, see: https://github.com/icyphy/lingua-franca/wiki/Containerized-Execution + FROM python:slim + WORKDIR /lingua-franca/«topLevelName» + RUN set -ex && apt-get update && apt-get install -y python3-pip + COPY . src-gen + RUN cd src-gen && python3 setup.py install && cd .. + ENTRYPOINT ["python3", "src-gen/«topLevelName».py"] + ''') + contents.writeToFile(dockerFile) + println('''Dockerfile for «topLevelName» written to ''' + dockerFile) + println(''' + ##################################### + To build the docker image, go to «dockerComposeDir» and run: + + «dockerComposeCommand» build «federateName» + + ##################################### + ''') + } + + /** + * Convert C types to formats used in Py_BuildValue and PyArg_PurseTuple. + * This is unused but will be useful to enable inter-compatibility between + * C and Python reactors. + * @param type C type + */ + def pyBuildValueArgumentType(String type) { + switch (type) { + case "int": "i" + case "string": "s" + case "char": "b" + case "short int": "h" + case "long": "l" + case "unsigned char": "B" + case "unsigned short int": "H" + case "unsigned int": "I" + case "unsigned long": "k" + case "long long": "L" + case "interval_t": "L" + case "unsigned long long": "K" + case "double": "d" + case "float": "f" + case "Py_complex": "D" + case "Py_complex*": "D" + case "Py_Object": "O" + case "Py_Object*": "O" + default: "O" + } + } +} diff --git a/org.lflang/src/org/lflang/generator/python/PythonInfoGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonInfoGenerator.java deleted file mode 100644 index 9f61f2021a..0000000000 --- a/org.lflang/src/org/lflang/generator/python/PythonInfoGenerator.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.lflang.generator.python; - -import java.io.File; -import org.lflang.FileConfig; - -public class PythonInfoGenerator { - /** - * Print information about necessary steps to install the supporting - * Python C extension for the generated program. - * - * @note Only needed if no-compile is set to true - */ - public static String generateSetupInfo(FileConfig fileConfig) { - return String.join("\n", - "", - "#####################################", - "To compile and install the generated code, do:", - " ", - " cd "+fileConfig.getSrcGenPath()+File.separator, - " python3 -m pip install --force-reinstall .", - "" - ); - } - - /** - * Print information on how to execute the generated program. - */ - public static String generateRunInfo(FileConfig fileConfig, String topLevelName) { - return String.join("\n", - "", - "#####################################", - "To run the generated program, use:", - " ", - " python3 "+fileConfig.getSrcGenPath()+File.separator+topLevelName+".py", - "", - "#####################################", - "" - ); - } - - /** - * Print information on how to execute the generated federation. - */ - public static String generateFedRunInfo(FileConfig fileConfig) { - return String.join("\n", - "", - "#####################################", - "To run the generated program, run:", - " ", - " bash "+fileConfig.binPath+"/"+fileConfig.name, - "", - "#####################################", - "" - ); - } -} diff --git a/org.lflang/src/org/lflang/generator/python/PythonNetworkGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonNetworkGenerator.java deleted file mode 100644 index 3c505cc97c..0000000000 --- a/org.lflang/src/org/lflang/generator/python/PythonNetworkGenerator.java +++ /dev/null @@ -1,122 +0,0 @@ -package org.lflang.generator.python; - -import org.lflang.federated.FederateInstance; -import org.lflang.federated.PythonGeneratorExtension; -import org.lflang.lf.VarRef; -import org.lflang.lf.Action; -import org.lflang.lf.Delay; -import org.lflang.InferredType; -import org.lflang.TargetConfig; -import org.lflang.TargetProperty.CoordinationType; -import org.lflang.federated.serialization.SupportedSerializers; -import org.lflang.generator.ReactionInstance; - - -public class PythonNetworkGenerator { - /** - * Generate code for the body of a reaction that handles the - * action that is triggered by receiving a message from a remote - * federate. - * @param action The action. - * @param sendingPort The output port providing the data to send. - * @param receivingPort The ID of the destination port. - * @param receivingPortID The ID of the destination port. - * @param sendingFed The sending federate. - * @param receivingFed The destination federate. - * @param receivingBankIndex The receiving federate's bank index, if it is in a bank. - * @param receivingChannelIndex The receiving federate's channel index, if it is a multiport. - * @param type The type. - * @param isPhysical Indicates whether or not the connection is physical - * @param serializer The serializer used on the connection. - */ - public static String generateNetworkReceiverBody( - Action action, - VarRef sendingPort, - VarRef receivingPort, - int receivingPortID, - FederateInstance sendingFed, - FederateInstance receivingFed, - int receivingBankIndex, - int receivingChannelIndex, - InferredType type, - boolean isPhysical, - SupportedSerializers serializer - ) { - StringBuilder result = new StringBuilder(); - - // We currently have no way to mark a reaction "unordered" - // in the AST, so we use a magic string at the start of the body. - result.append("// " + ReactionInstance.UNORDERED_REACTION_MARKER + "\n"); - - result.append(PyUtil.generateGILAcquireCode() + "\n"); - result.append(PythonGeneratorExtension.generateNetworkReceiverBody( - action, - sendingPort, - receivingPort, - receivingPortID, - sendingFed, - receivingFed, - receivingBankIndex, - receivingChannelIndex, - type, - isPhysical, - serializer - )); - result.append(PyUtil.generateGILReleaseCode() + "\n"); - return result.toString(); - } - - /** - * Generate code for the body of a reaction that handles an output - * that is to be sent over the network. - * @param sendingPort The output port providing the data to send. - * @param receivingPort The variable reference to the destination port. - * @param receivingPortID The ID of the destination port. - * @param sendingFed The sending federate. - * @param sendingBankIndex The bank index of the sending federate, if it is a bank. - * @param sendingChannelIndex The channel index of the sending port, if it is a multiport. - * @param receivingFed The destination federate. - * @param type The type. - * @param isPhysical Indicates whether the connection is physical or not - * @param delay The delay value imposed on the connection using after - * @param serializer The serializer used on the connection. - */ - public static String generateNetworkSenderBody( - VarRef sendingPort, - VarRef receivingPort, - int receivingPortID, - FederateInstance sendingFed, - int sendingBankIndex, - int sendingChannelIndex, - FederateInstance receivingFed, - InferredType type, - boolean isPhysical, - Delay delay, - SupportedSerializers serializer, - CoordinationType coordinationType - ) { - StringBuilder result = new StringBuilder(); - - // We currently have no way to mark a reaction "unordered" - // in the AST, so we use a magic string at the start of the body. - result.append("// " + ReactionInstance.UNORDERED_REACTION_MARKER + "\n"); - - result.append(PyUtil.generateGILAcquireCode() + "\n"); - result.append(PythonGeneratorExtension.generateNetworkSenderBody( - sendingPort, - receivingPort, - receivingPortID, - sendingFed, - sendingBankIndex, - sendingChannelIndex, - receivingFed, - type, - isPhysical, - delay, - serializer, - coordinationType - )); - result.append(PyUtil.generateGILReleaseCode() + "\n"); - return result.toString(); - } -} diff --git a/org.lflang/src/org/lflang/generator/python/PythonParameterGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonParameterGenerator.java deleted file mode 100644 index 046971f795..0000000000 --- a/org.lflang/src/org/lflang/generator/python/PythonParameterGenerator.java +++ /dev/null @@ -1,157 +0,0 @@ -package org.lflang.generator.python; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.stream.Collectors; - -import com.google.common.base.Objects; - -import org.lflang.ASTUtils; -import org.lflang.JavaAstUtils; -import org.lflang.generator.CodeBuilder; -import org.lflang.generator.GeneratorBase; -import org.lflang.generator.ParameterInstance; -import org.lflang.generator.ReactorInstance; -import org.lflang.generator.c.CUtil; -import org.lflang.generator.c.CParameterGenerator; -import org.lflang.lf.ReactorDecl; -import org.lflang.lf.Value; -import org.lflang.lf.Reactor; -import org.lflang.lf.Assignment; -import org.lflang.lf.Parameter; - - -public class PythonParameterGenerator { - /** - * Generate Python code that instantiates and initializes parameters for a reactor 'decl'. - * - * @param decl The reactor declaration - * @return The generated code as a StringBuilder - */ - public static String generatePythonInstantiations(ReactorDecl decl, PythonTypes types) { - List lines = new ArrayList<>(); - lines.add("# Define parameters and their default values"); - - for (Parameter param : getAllParameters(decl)) { - if (!types.getTargetType(param).equals("PyObject*")) { - // If type is given, use it - String type = types.getPythonType(JavaAstUtils.getInferredType(param)); - lines.add("self._"+param.getName()+":"+type+" = "+generatePythonInitializer(param)); - } else { - // If type is not given, just pass along the initialization - lines.add("self._"+param.getName()+" = "+generatePythonInitializer(param)); - } - } - // Handle parameters that are set in instantiation - lines.addAll(List.of( - "# Handle parameters that are set in instantiation", - "self.__dict__.update(kwargs)", - "" - )); - return String.join("\n", lines); - } - - /** - * Generate Python code getters for parameters of reactor 'decl'. - * - * @param decl The reactor declaration - * @return The generated code as a StringBuilder - */ - public static String generatePythonGetters(ReactorDecl decl) { - List lines = new ArrayList<>(); - for (Parameter param : getAllParameters(decl)) { - if (!param.getName().equals("bank_index")) { - lines.addAll(List.of( - "@property", - "def "+param.getName()+"(self):", - " return self._"+param.getName()+" # pylint: disable=no-member", - "" - )); - } - } - // Create a special property for bank_index - lines.addAll(List.of( - "@property", - "def bank_index(self):", - " return self._bank_index # pylint: disable=no-member", - "" - )); - return String.join("\n", lines); - } - - /** - * Return a list of all parameters of reactor 'decl'. - * - * @param decl The reactor declaration - * @return The list of all parameters of 'decl' - */ - private static List getAllParameters(ReactorDecl decl) { - return ASTUtils.allParameters(ASTUtils.toDefinition(decl)); - } - - /** - * Create a Python list for parameter initialization in target code. - * - * @param p The parameter to create initializers for - * @return Initialization code - */ - private static String generatePythonInitializer(Parameter p) { - List values = p.getInit().stream().map(PyUtil::getPythonTargetValue).collect(Collectors.toList()); - return values.size() > 1 ? "(" + String.join(", ", values) + ")" : values.get(0); - } - - /** - * Return a Python expression that can be used to initialize the specified - * parameter instance. If the parameter initializer refers to other - * parameters, then those parameter references are replaced with - * accesses to the Python reactor instance class of the parents of - * those parameters. - * - * @param p The parameter instance to create initializer for - * @return Initialization code - */ - public static String generatePythonInitializer(ParameterInstance p) { - // Handle overrides in the instantiation. - // In case there is more than one assignment to this parameter, we need to - // find the last one. - Assignment lastAssignment = getLastAssignment(p); - List list = new LinkedList<>(); - if (lastAssignment != null) { - // The parameter has an assignment. - // Right hand side can be a list. Collect the entries. - for (Value value : lastAssignment.getRhs()) { - if (value.getParameter() != null) { - // The parameter is being assigned a parameter value. - // Assume that parameter belongs to the parent's parent. - // This should have been checked by the validator. - list.add(PyUtil.reactorRef(p.getParent().getParent()) + "." + value.getParameter().getName()); - } else { - list.add(GeneratorBase.getTargetTime(value)); - } - } - } else { - for (Value i : p.getParent().initialParameterValue(p.getDefinition())) { - list.add(PyUtil.getPythonTargetValue(i)); - } - } - return list.size() > 1 ? "(" + String.join(", ", list) + ")" : list.get(0); - } - - /** - * Returns the last assignment to "p" if there is one, - * or null if there is no assignment to "p" - * - * @param p The parameter instance to create initializer for - * @return The last assignment of the parameter instance - */ - private static Assignment getLastAssignment(ParameterInstance p) { - Assignment lastAssignment = null; - for (Assignment assignment : p.getParent().getDefinition().getParameters()) { - if (Objects.equal(assignment.getLhs(), p.getDefinition())) { - lastAssignment = assignment; - } - } - return lastAssignment; - } -} diff --git a/org.lflang/src/org/lflang/generator/python/PythonPortGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonPortGenerator.java deleted file mode 100644 index 75befe9c00..0000000000 --- a/org.lflang/src/org/lflang/generator/python/PythonPortGenerator.java +++ /dev/null @@ -1,256 +0,0 @@ -package org.lflang.generator.python; - -import org.lflang.lf.Input; -import org.lflang.lf.Instantiation; -import org.lflang.lf.Output; -import org.lflang.lf.Port; -import org.lflang.lf.Action; -import org.lflang.lf.ReactorDecl; -import org.lflang.lf.VarRef; -import java.util.List; -import org.lflang.JavaAstUtils; -import org.lflang.generator.CodeBuilder; -import org.lflang.generator.c.CGenerator; -import static org.lflang.generator.c.CUtil.generateWidthVariable; - -public class PythonPortGenerator { - public static final String NONMULTIPORT_WIDTHSPEC = "-2"; - - /** - * Generate code to convert C actions to Python action capsules - * @see pythontarget.h. - * @param pyObjectDescriptor A string representing a list of Python format types (e.g., "O") that - * can be passed to Py_BuildValue. The object type for the converted action will - * be appended to this string (e.g., "OO"). - * @param pyObjects A string containing a list of comma-separated expressions that will create the - * action capsules. - * @param action The action itself. - * @param decl The reactor decl that contains the action. - */ - public static void generateActionVariableToSendToPythonReaction(List pyObjects, - Action action, ReactorDecl decl) { - // Values passed to an action are always stored in the token->value. - // However, sometimes token might not be initialized. Therefore, this function has an internal check for NULL in case token is not initialized. - pyObjects.add(String.format("convert_C_action_to_py(%s)", action.getName())); - } - - /** - * Generate code to convert C ports to Python ports capsules (@see pythontarget.h). - * - * The port may be an input of the reactor or an output of a contained reactor. - * - * @param pyObjectDescriptor A string representing a list of Python format types (e.g., "O") that - * can be passed to Py_BuildValue. The object type for the converted port will - * be appended to this string (e.g., "OO"). - * @param pyObjects A string containing a list of comma-separated expressions that will create the - * port capsules. - * @param port The port itself. - * @param decl The reactor decl that contains the port. - */ - public static String generatePortVariablesToSendToPythonReaction( - List pyObjects, - VarRef port, - ReactorDecl decl - ) { - if (port.getVariable() instanceof Input) { - generateInputVariablesToSendToPythonReaction(pyObjects, (Input) port.getVariable(), decl); - return ""; - } else { - // port is an output of a contained reactor. - return generateVariablesForSendingToContainedReactors(pyObjects, port.getContainer(), (Port) port.getVariable()); - } - } - - /** Generate into the specified string builder the code to - * send local variables for output ports to a Python reaction function - * from the "self" struct. - * @param builder The string builder into which to write the code. - * @param structs A map from reactor instantiations to a place to write - * struct fields. - * @param output The output port. - * @param decl The reactor declaration. - */ - public static void generateOutputVariablesToSendToPythonReaction( - List pyObjects, - Output output - ) { - // Unfortunately, for the SET macros to work out-of-the-box for - // multiports, we need an array of *pointers* to the output structs, - // but what we have on the self struct is an array of output structs. - // So we have to handle multiports specially here a construct that - // array of pointers. - // FIXME: The C Generator also has this awkwardness. It makes the code generators - // unnecessarily difficult to maintain, and it may have performance consequences as well. - // Maybe we should change the SET macros. - if (!JavaAstUtils.isMultiport(output)) { - pyObjects.add(generateConvertCPortToPy(output.getName(), NONMULTIPORT_WIDTHSPEC)); - } else { - pyObjects.add(generateConvertCPortToPy(output.getName())); - } - } - - /** Generate into the specified string builder the code to - * send local variables for input ports to a Python reaction function - * from the "self" struct. - * @param builder The string builder into which to write the code. - * @param structs A map from reactor instantiations to a place to write - * struct fields. - * @param input The input port. - * @param reactor The reactor. - */ - public static void generateInputVariablesToSendToPythonReaction( - List pyObjects, - Input input, - ReactorDecl decl - ) { - // Create the local variable whose name matches the input.getName(). - // If the input has not been declared mutable, then this is a pointer - // to the upstream output. Otherwise, it is a copy of the upstream output, - // which nevertheless points to the same token and value (hence, as done - // below, we have to use writable_copy()). There are 8 cases, - // depending on whether the input is mutable, whether it is a multiport, - // and whether it is a token type. - // Easy case first. - if (!input.isMutable() && !JavaAstUtils.isMultiport(input)) { - // Non-mutable, non-multiport, primitive type. - pyObjects.add(generateConvertCPortToPy(input.getName())); - } else if (input.isMutable() && !JavaAstUtils.isMultiport(input)) { - // Mutable, non-multiport, primitive type. - // TODO: handle mutable - pyObjects.add(generateConvertCPortToPy(input.getName())); - } else if (!input.isMutable() && JavaAstUtils.isMultiport(input)) { - // Non-mutable, multiport, primitive. - // TODO: support multiports - pyObjects.add(generateConvertCPortToPy(input.getName())); - } else { - // Mutable, multiport, primitive type - // TODO: support mutable multiports - pyObjects.add(generateConvertCPortToPy(input.getName())); - } - } - - /** Generate into the specified string builder the code to - * pass local variables for sending data to an input - * of a contained reaction (e.g. for a deadline violation). - * @param builder The string builder. - * @param definition AST node defining the reactor within which this occurs - * @param input Input of the contained reactor. - */ - public static String generateVariablesForSendingToContainedReactors( - List pyObjects, - Instantiation definition, - Port port - ) { - CodeBuilder code = new CodeBuilder(); - if (definition.getWidthSpec() != null) { - String widthSpec = NONMULTIPORT_WIDTHSPEC; - if (JavaAstUtils.isMultiport(port)) { - widthSpec = "self->_lf_"+definition.getName()+"[i]."+generateWidthVariable(port.getName()); - } - // Contained reactor is a bank. - // Create a Python list - code.pr(generatePythonListForContainedBank(definition.getName(), port, widthSpec)); - pyObjects.add(definition.getName()+"_py_list"); - } - else { - if (JavaAstUtils.isMultiport(port)) { - pyObjects.add(generateConvertCPortToPy(definition.getName()+"."+port.getName())); - } else { - pyObjects.add(generateConvertCPortToPy(definition.getName()+"."+port.getName(), NONMULTIPORT_WIDTHSPEC)); - } - } - return code.toString(); - } - - - /** - * Generate code that creates a Python list (i.e., []) for contained banks to be passed to Python reactions. - * The Python reaction will then subsequently be able to address each individual bank member of the contained - * bank using an index or an iterator. Each list member will contain the given port - * (which could be a multiport with a width determined by widthSpec). - * - * This is to accommodate reactions like reaction() -> s.out where s is a bank. In this example, - * the generated Python function will have the signature reaction_function_0(self, s_out), where - * s_out is a list of out ports. This will later be turned into the proper s.out format using the - * Python code generated in {@link #generatePythonPortVariableInReaction}. - * - * @param reactorName The name of the bank of reactors (which is the name of the reactor class). - * @param port The port that should be put in the Python list. - * @param widthSpec A string that should be -2 for non-multiports and the width expression for multiports. - */ - public static String generatePythonListForContainedBank(String reactorName, Port port, String widthSpec) { - return String.join("\n", - "PyObject* "+reactorName+"_py_list = PyList_New("+generateWidthVariable(reactorName)+");", - "if("+reactorName+"_py_list == NULL) {", - " error_print(\"Could not create the list needed for "+reactorName+".\");", - " if (PyErr_Occurred()) {", - " PyErr_PrintEx(0);", - " PyErr_Clear(); // this will reset the error indicator so we can run Python code again", - " }", - " /* Release the thread. No Python API allowed beyond this point. */", - " PyGILState_Release(gstate);", - " Py_FinalizeEx();", - " exit(1);", - "}", - "for (int i = 0; i < "+generateWidthVariable(reactorName)+"; i++) {", - " if (PyList_SetItem("+reactorName+"_py_list,", - " i,", - " "+generateConvertCPortToPy("self->_lf_"+reactorName+"[i]."+port.getName(), widthSpec), - " ) != 0) {", - " error_print(\"Could not add elements to the list for "+reactorName+".\");", - " if (PyErr_Occurred()) {", - " PyErr_PrintEx(0);", - " PyErr_Clear(); // this will reset the error indicator so we can run Python code again", - " }", - " /* Release the thread. No Python API allowed beyond this point. */", - " PyGILState_Release(gstate);", - " Py_FinalizeEx();", - " exit(1);", - " }", - "}" - ); - } - - public static String generateAliasTypeDef(ReactorDecl decl, Port port, boolean isTokenType, - String genericPortTypeWithToken, String genericPortType) { - if (isTokenType) { - return "typedef "+genericPortTypeWithToken+" "+CGenerator.variableStructType(port, decl)+";"; - } else { - return "typedef "+genericPortType+" "+CGenerator.variableStructType(port, decl)+";"; - } - } - - private static String generateConvertCPortToPy(String port) { - return String.format("convert_C_port_to_py(%s, %s)", port, generateWidthVariable(port)); - } - - private static String generateConvertCPortToPy(String port, String widthSpec) { - return String.format("convert_C_port_to_py(%s, %s)", port, widthSpec); - } - - /** - * Generate into the specified string builder (inits) the code to - * initialize local variable for port so that it can be used in the body of - * the Python reaction. - * @param port The port to generate code for. - * @param inits The generated code will be put in inits. - */ - public static String generatePythonPortVariableInReaction(VarRef port) { - String containerName = port.getContainer().getName(); - String variableName = port.getVariable().getName(); - if (port.getContainer().getWidthSpec() != null) { - // It's a bank - return String.join("\n", - containerName+" = [None] * len("+containerName+"_"+variableName+")", - "for i in range(len("+containerName+"_"+variableName+")):", - " "+containerName+"[i] = Make()", - " "+containerName+"[i]."+variableName+" = "+containerName+"_"+variableName+"[i]" - ); - } else { - return String.join("\n", - containerName+" = Make", - containerName+"."+variableName+" = "+containerName+"_"+variableName - ); - } - } -} diff --git a/org.lflang/src/org/lflang/generator/python/PythonPreambleGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonPreambleGenerator.java deleted file mode 100644 index 421aa54d40..0000000000 --- a/org.lflang/src/org/lflang/generator/python/PythonPreambleGenerator.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.lflang.generator.python; - -import java.util.ArrayList; -import java.util.List; -import org.lflang.ASTUtils; -import org.lflang.lf.Preamble; - -public class PythonPreambleGenerator { - /** - * Generates preambles defined by user for a given reactor. - * The preamble code is put inside the reactor class. - */ - public static String generatePythonPreambles(List preambles) { - List preamblesCode = new ArrayList<>(); - preambles.forEach(p -> preamblesCode.add(ASTUtils.toText(p.getCode()))); - return preamblesCode.size() > 0 ? String.join("\n", - "# From the preamble, verbatim:", - String.join("\n", preamblesCode), - "# End of preamble." - ) : ""; - } -} diff --git a/org.lflang/src/org/lflang/generator/python/PythonReactionGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonReactionGenerator.java deleted file mode 100644 index c984b9934e..0000000000 --- a/org.lflang/src/org/lflang/generator/python/PythonReactionGenerator.java +++ /dev/null @@ -1,560 +0,0 @@ -package org.lflang.generator.python; - -import java.util.Set; -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; - -import org.lflang.lf.ReactorDecl; -import org.lflang.lf.Reaction; -import org.lflang.lf.Reactor; -import org.lflang.lf.Action; -import org.lflang.lf.TriggerRef; -import org.lflang.lf.VarRef; -import org.lflang.lf.Instantiation; -import org.lflang.lf.Port; -import org.lflang.lf.Input; -import org.lflang.lf.Output; -import org.lflang.generator.c.CReactionGenerator; -import org.lflang.generator.c.CTypes; -import org.lflang.generator.c.CUtil; -import org.lflang.generator.CodeBuilder; -import org.lflang.generator.GeneratorBase; -import org.lflang.generator.ReactionInstance; -import org.lflang.generator.ReactorInstance; -import org.lflang.ErrorReporter; -import org.lflang.JavaAstUtils; -import org.lflang.Target; -import org.lflang.ASTUtils; - -public class PythonReactionGenerator { - /** - * Generate code to call reaction numbered "reactionIndex" in reactor "decl". - * @param decl The reactor containing the reaction - * @param reactionIndex The index of the reaction - * @param pyObjectDescriptor CPython related descriptors for each object in "pyObjects". - * @param pyObjects CPython related objects - */ - public static String generateCPythonReactionCaller(ReactorDecl decl, - int reactionIndex, - List pyObjects, - String inits) { - String pythonFunctionName = generatePythonReactionFunctionName(reactionIndex); - String cpythonFunctionName = generateCPythonReactionFunctionName(reactionIndex); - return generateCPythonFunctionCaller(decl.getName(), pythonFunctionName, cpythonFunctionName, pyObjects, inits); - } - - /** - * Generate code to call deadline function numbered "reactionIndex" in reactor "decl". - * @param decl The reactor containing the reaction - * @param reactionIndex The index of the reaction - * @param pyObjectDescriptor CPython related descriptors for each object in "pyObjects". - * @param pyObjects CPython related objects - */ - public static String generateCPythonDeadlineCaller(ReactorDecl decl, - int reactionIndex, - List pyObjects) { - String pythonFunctionName = generatePythonDeadlineFunctionName(reactionIndex); - String cpythonFunctionName = generateCPythonDeadlineFunctionName(reactionIndex); - return generateCPythonFunctionCaller(decl.getName(), pythonFunctionName, cpythonFunctionName, pyObjects, ""); - } - - /** - * Generate code to call a CPython function. - * @param reactorDeclName The name of the reactor for debugging purposes - * @param pythonFunctionName The name of the function in the .py file. - * @param cpythonFunctionName The name of the function in self struct of the .c file. - * @param pyObjectDescriptor CPython related descriptors for each object in "pyObjects". - * @param pyObjects CPython related objects - */ - private static String generateCPythonFunctionCaller(String reactorDeclName, - String pythonFunctionName, - String cpythonFunctionName, - List pyObjects, - String inits) { - String pyObjectsJoined = pyObjects.size() > 0 ? ", " + String.join(", ", pyObjects) : ""; - CodeBuilder code = new CodeBuilder(); - code.pr(PyUtil.generateGILAcquireCode()); - code.pr(inits); - code.pr(String.join("\n", - "DEBUG_PRINT(\"Calling reaction function "+reactorDeclName+"."+pythonFunctionName+"\");", - "PyObject *rValue = PyObject_CallObject(", - " self->"+cpythonFunctionName+", ", - " Py_BuildValue(\"("+"O".repeat(pyObjects.size())+")\""+pyObjectsJoined+")", - ");", - "if (rValue == NULL) {", - " error_print(\"FATAL: Calling reaction "+reactorDeclName+"."+pythonFunctionName+" failed.\");", - " if (PyErr_Occurred()) {", - " PyErr_PrintEx(0);", - " PyErr_Clear(); // this will reset the error indicator so we can run Python code again", - " }", - " "+PyUtil.generateGILReleaseCode(), - " Py_FinalizeEx();", - " exit(1);", - "}", - "", - "/* Release the thread. No Python API allowed beyond this point. */", - PyUtil.generateGILReleaseCode() - )); - return code.toString(); - } - - /** - * Generate the reaction in the .c file, which calls the Python reaction through the CPython interface. - * - * @param reaction The reaction to generate Python-specific initialization for. - * @param decl The reactor to which reaction belongs to. - * @param reactionIndex The index number of the reaction in decl. - * @param mainDef The main reactor. - * @param errorReporter An error reporter. - * @param types A helper class for type-related stuff. - * @param isFederatedAndDecentralized True if program is federated and coordination type is decentralized. - */ - public static String generateCReaction(Reaction reaction, - ReactorDecl decl, - int reactionIndex, - Instantiation mainDef, - ErrorReporter errorReporter, - CTypes types, - boolean isFederatedAndDecentralized) { - // Contains the actual comma separated list of inputs to the reaction of type generic_port_instance_struct or generic_port_instance_with_token_struct. - // Each input must be cast to (PyObject *) (aka their descriptors for Py_BuildValue are "O") - List pyObjects = new ArrayList<>(); - CodeBuilder code = new CodeBuilder(); - code.pr(generateCReactionFunctionHeader(decl, reactionIndex) + " {"); - code.indent(); - code.pr(CReactionGenerator.generateInitializationForReaction("", reaction, decl, reactionIndex, - types, errorReporter, mainDef, - isFederatedAndDecentralized, - Target.Python.requiresTypes)); - code.prSourceLineNumber(reaction.getCode()); - code.pr(generateCPythonReactionCaller(decl, reactionIndex, pyObjects, - generateCPythonInitializers(reaction, decl, pyObjects, errorReporter))); - code.unindent(); - code.pr("}"); - - // Now generate code for the deadline violation function, if there is one. - if (reaction.getDeadline() != null) { - code.pr(generateCDeadlineFunctionHeader(decl, reactionIndex) + " {"); - code.indent(); - code.pr(CReactionGenerator.generateInitializationForReaction("", reaction, decl, reactionIndex, - types, errorReporter, mainDef, - isFederatedAndDecentralized, - Target.Python.requiresTypes)); - code.pr(generateCPythonDeadlineCaller(decl, reactionIndex, pyObjects)); - code.unindent(); - code.pr("}"); - } - return code.toString(); - } - - /** - * Generate necessary Python-specific initialization code for reaction that belongs to reactor - * decl. - * - * @param reaction The reaction to generate Python-specific initialization for. - * @param decl The reactor to which reaction belongs to. - * @param pyObjectDescriptor For each port object created, a Python-specific descriptor will be added to this that - * then can be used as an argument to Py_BuildValue - * (@see docs.python.org/3/c-api). - * @param pyObjects A "," delimited list of expressions that would be (or result in a creation of) a PyObject. - */ - private static String generateCPythonInitializers(Reaction reaction, - ReactorDecl decl, - List pyObjects, - ErrorReporter errorReporter) { - Set actionsAsTriggers = new LinkedHashSet<>(); - Reactor reactor = ASTUtils.toDefinition(decl); - CodeBuilder code = new CodeBuilder(); - // Next, add the triggers (input and actions; timers are not needed). - // TODO: handle triggers - for (TriggerRef trigger : ASTUtils.convertToEmptyListIfNull(reaction.getTriggers())) { - if (trigger instanceof VarRef) { - VarRef triggerAsVarRef = (VarRef) trigger; - code.pr(generateVariableToSendPythonReaction(triggerAsVarRef, actionsAsTriggers, decl, pyObjects)); - } - } - if (reaction.getTriggers() == null || reaction.getTriggers().size() == 0) { - // No triggers are given, which means react to any input. - // Declare an argument for every input. - // NOTE: this does not include contained outputs. - for (Input input : reactor.getInputs()) { - PythonPortGenerator.generateInputVariablesToSendToPythonReaction(pyObjects, input, decl); - } - } - - // Next add non-triggering inputs. - for (VarRef src : ASTUtils.convertToEmptyListIfNull(reaction.getSources())) { - code.pr(generateVariableToSendPythonReaction(src, actionsAsTriggers, decl, pyObjects)); - } - - // Next, handle effects - if (reaction.getEffects() != null) { - for (VarRef effect : reaction.getEffects()) { - if (effect.getVariable() instanceof Action) { - // It is an action, not an output. - // If it has already appeared as trigger, do not redefine it. - if (!actionsAsTriggers.contains(effect.getVariable())) { - PythonPortGenerator.generateActionVariableToSendToPythonReaction(pyObjects, - (Action) effect.getVariable(), decl); - } - } else { - if (effect.getVariable() instanceof Output) { - PythonPortGenerator.generateOutputVariablesToSendToPythonReaction(pyObjects, (Output) effect.getVariable()); - } else if (effect.getVariable() instanceof Input) { - // It is the input of a contained reactor. - code.pr(PythonPortGenerator.generateVariablesForSendingToContainedReactors(pyObjects, effect.getContainer(), (Input) effect.getVariable())); - } else { - errorReporter.reportError( - reaction, - "In generateReaction(): " + effect.getVariable().getName() + " is neither an input nor an output." - ); - } - } - } - } - return code.toString(); - } - - /** - * Generate parameters and their respective initialization code for a reaction function - * The initialization code is put at the beginning of the reaction before user code - * @param parameters The parameters used for function definition - * @param inits The initialization code for those paramters - * @param decl Reactor declaration - * @param reaction The reaction to be used to generate parameters for - */ - public static void generatePythonReactionParametersAndInitializations(List parameters, CodeBuilder inits, - ReactorDecl decl, Reaction reaction) { - Reactor reactor = ASTUtils.toDefinition(decl); - LinkedHashSet generatedParams = new LinkedHashSet<>(); - - // Handle triggers - for (TriggerRef trigger : ASTUtils.convertToEmptyListIfNull(reaction.getTriggers())) { - if (!(trigger instanceof VarRef)) { - continue; - } - VarRef triggerAsVarRef = (VarRef) trigger; - if (triggerAsVarRef.getVariable() instanceof Port) { - if (triggerAsVarRef.getVariable() instanceof Input) { - if (((Input) triggerAsVarRef.getVariable()).isMutable()) { - generatedParams.add("mutable_"+triggerAsVarRef.getVariable().getName()+""); - - // Create a deep copy - if (JavaAstUtils.isMultiport((Input) triggerAsVarRef.getVariable())) { - inits. - pr(triggerAsVarRef.getVariable().getName()+" = [Make() for i in range(len(mutable_"+triggerAsVarRef.getVariable().getName()+"))]"); - inits.pr("for i in range(len(mutable_"+triggerAsVarRef.getVariable().getName()+")):"); - inits.pr(" "+triggerAsVarRef.getVariable().getName()+"[i].value = copy.deepcopy(mutable_"+triggerAsVarRef.getVariable().getName()+"[i].value)"); - } else { - inits.pr(triggerAsVarRef.getVariable().getName()+" = Make()"); - inits. - pr(triggerAsVarRef.getVariable().getName()+".value = copy.deepcopy(mutable_"+triggerAsVarRef.getVariable().getName()+".value)"); - } - } else { - generatedParams.add(triggerAsVarRef.getVariable().getName()); - } - } else { - // Handle contained reactors' ports - generatedParams.add(triggerAsVarRef.getContainer().getName()+"_"+triggerAsVarRef.getVariable().getName()); - inits.pr(PythonPortGenerator.generatePythonPortVariableInReaction(triggerAsVarRef)); - } - } else if (triggerAsVarRef.getVariable() instanceof Action) { - generatedParams.add(triggerAsVarRef.getVariable().getName()); - } - } - - // Handle non-triggering inputs - if (reaction.getTriggers() == null || reaction.getTriggers().size() == 0) { - for (Input input : ASTUtils.convertToEmptyListIfNull(reactor.getInputs())) { - generatedParams.add(input.getName()); - if (input.isMutable()) { - // Create a deep copy - inits.pr(input.getName()+" = copy.deepcopy("+input.getName()+")"); - } - } - } - for (VarRef src : ASTUtils.convertToEmptyListIfNull(reaction.getSources())) { - if (src.getVariable() instanceof Output) { - // Output of a contained reactor - generatedParams.add(src.getContainer().getName()+"_"+src.getVariable().getName()); - inits.pr(PythonPortGenerator.generatePythonPortVariableInReaction(src)); - } else { - generatedParams.add(src.getVariable().getName()); - if (src.getVariable() instanceof Input) { - if (((Input) src.getVariable()).isMutable()) { - // Create a deep copy - inits.pr(src.getVariable().getName()+" = copy.deepcopy("+src.getVariable().getName()+")"); - } - } - } - } - - // Handle effects - for (VarRef effect : ASTUtils.convertToEmptyListIfNull(reaction.getEffects())) { - if (effect.getVariable() instanceof Input) { - generatedParams.add(effect.getContainer().getName()+"_"+effect.getVariable().getName()); - inits.pr(PythonPortGenerator.generatePythonPortVariableInReaction(effect)); - } else { - generatedParams.add(effect.getVariable().getName()); - if (effect.getVariable() instanceof Port) { - if (JavaAstUtils.isMultiport((Port) effect.getVariable())) { - // Handle multiports - } - } - } - } - - for (String s : generatedParams) { - parameters.add(s); - } - } - - private static String generateVariableToSendPythonReaction(VarRef varRef, - Set actionsAsTriggers, - ReactorDecl decl, - List pyObjects) { - if (varRef.getVariable() instanceof Port) { - return PythonPortGenerator.generatePortVariablesToSendToPythonReaction(pyObjects, varRef, decl); - } else if (varRef.getVariable() instanceof Action) { - actionsAsTriggers.add((Action) varRef.getVariable()); - PythonPortGenerator.generateActionVariableToSendToPythonReaction(pyObjects, (Action) varRef.getVariable(), decl); - } - return ""; - } - - /** - * Generate code for the body of a reaction that takes an input and - * schedules an action with the value of that input. - * @param action The action to schedule - * @param port The port to read from - */ - public static String generateCDelayBody(Action action, VarRef port, boolean isTokenType) { - String ref = JavaAstUtils.generateVarRef(port); - // Note that the action.type set by the base class is actually - // the port type. - if (isTokenType) { - return String.join("\n", - "if ("+ref+"->is_present) {", - " // Put the whole token on the event queue, not just the payload.", - " // This way, the length and element_size are transported.", - " schedule_token("+action.getName()+", 0, "+ref+"->token);", - "}" - ); - } else { - return String.join("\n", - "// Create a token.", - "#if NUMBER_OF_WORKERS > 0", - "// Need to lock the mutex first.", - "lf_mutex_lock(&mutex);", - "#endif", - "lf_token_t* t = create_token(sizeof(PyObject*));", - "#if NUMBER_OF_WORKERS > 0", - "lf_mutex_unlock(&mutex);", - "#endif", - "t->value = self->_lf_"+ref+"->value;", - "t->length = 1; // Length is 1", - "", - "// Pass the token along", - "schedule_token("+action.getName()+", 0, t);" - ); - } - } - - /** - * Generate Python code to link cpython functions to python functions for each reaction. - * @param instance The reactor instance. - * @param reactions The reactions of this instance. - * @param mainDef The definition of the main reactor - * @param topLevelName The name of the module - */ - public static String generateCPythonReactionLinkers( - ReactorInstance instance, - Instantiation mainDef, - String topLevelName - ) { - String nameOfSelfStruct = CUtil.reactorRef(instance); - Reactor reactor = ASTUtils.toDefinition(instance.getDefinition().getReactorClass()); - CodeBuilder code = new CodeBuilder(); - - // Delay reactors and top-level reactions used in the top-level reactor(s) in federated execution are generated in C - if (reactor.getName().contains(GeneratorBase.GEN_DELAY_CLASS_NAME) || - instance.getDefinition().getReactorClass() == (mainDef != null ? mainDef.getReactorClass() : null) && - reactor.isFederated()) { - return ""; - } - - // Initialize the name field to the unique name of the instance - code.pr(nameOfSelfStruct+"->_lf_name = \""+instance.uniqueID()+"_lf\";"); - - for (ReactionInstance reaction : instance.reactions) { - // Create a PyObject for each reaction - code.pr(generateCPythonReactionLinker(instance, reaction, topLevelName, nameOfSelfStruct)); - } - return code.toString(); - } - - /** - * Generate Python code to link cpython functions to python functions for a reaction. - * @param instance The reactor instance. - * @param reaction The reaction of this instance to link. - * @param topLevelName The name of the module. - * @param nameOfSelfStruct The name of the self struct in cpython. - */ - public static String generateCPythonReactionLinker( - ReactorInstance instance, - ReactionInstance reaction, - String topLevelName, - String nameOfSelfStruct - ) { - CodeBuilder code = new CodeBuilder(); - code.pr(generateCPythonFunctionLinker( - nameOfSelfStruct, generateCPythonReactionFunctionName(reaction.index), - instance, generatePythonReactionFunctionName(reaction.index)) - ); - if (reaction.getDefinition().getDeadline() != null) { - code.pr(generateCPythonFunctionLinker( - nameOfSelfStruct, generateCPythonDeadlineFunctionName(reaction.index), - instance, generatePythonDeadlineFunctionName(reaction.index)) - ); - } - return code.toString(); - } - - /** - * Generate code to link "pythonFunctionName" to "cpythonFunctionName" in "nameOfSelfStruct" of "instance". - * @param nameOfSelfStruct the self struct name of instance - * @param cpythonFunctionName the name of the cpython function - * @param instance the reactor instance - * @param pythonFunctionName the name of the python function - */ - private static String generateCPythonFunctionLinker(String nameOfSelfStruct, String cpythonFunctionName, ReactorInstance instance, String pythonFunctionName) { - return String.join("\n", - nameOfSelfStruct+"->"+cpythonFunctionName+" = ", - "get_python_function(\"__main__\", ", - " "+nameOfSelfStruct+"->_lf_name,", - " "+CUtil.runtimeIndex(instance)+",", - " \""+pythonFunctionName+"\");", - "if("+nameOfSelfStruct+"->"+cpythonFunctionName+" == NULL) {", - " error_print_and_exit(\"Could not load function "+pythonFunctionName+"\");", - "}" - ); - } - - /** - * Generate the function that is executed whenever the deadline of the reaction - * with the given reaction index is missed - * @param reaction The reaction to generate deadline miss code for - * @param reactionIndex The agreed-upon index of the reaction in the reactor (should match the C generated code) - * @param reactionParameters The parameters to the deadline violation function, which are the same as the reaction function - */ - public static String generatePythonFunction(String pythonFunctionName, String inits, String reactionBody, List reactionParameters) { - String params = reactionParameters.size() > 0 ? ", " + String.join(", ", reactionParameters) : ""; - CodeBuilder code = new CodeBuilder(); - code.pr("def "+pythonFunctionName+"(self"+params+"):"); - code.indent(); - code.pr(inits); - code.pr(reactionBody); - code.pr("return 0"); - return code.toString(); - } - - - /** - * Generate the Python code for reactions in reactor - * @param reactor The reactor - * @param reactions The reactions of reactor - */ - public static String generatePythonReactions(Reactor reactor, List reactions) { - CodeBuilder code = new CodeBuilder(); - int reactionIndex = 0; - for (Reaction reaction : reactions) { - code.pr(generatePythonReaction(reactor, reaction, reactionIndex)); - reactionIndex++; - } - return code.toString(); - } - - /** - * Generate the Python code for reaction in reactor - * @param reactor The reactor - * @param reaction The reaction of reactor - */ - public static String generatePythonReaction(Reactor reactor, Reaction reaction, int reactionIndex) { - CodeBuilder code = new CodeBuilder(); - List reactionParameters = new ArrayList<>(); // Will contain parameters for the function (e.g., Foo(x,y,z,...) - CodeBuilder inits = new CodeBuilder(); // Will contain initialization code for some parameters - PythonReactionGenerator.generatePythonReactionParametersAndInitializations(reactionParameters, inits, reactor, reaction); - code.pr(generatePythonFunction( - generatePythonReactionFunctionName(reactionIndex), - inits.toString(), - ASTUtils.toText(reaction.getCode()), - reactionParameters - )); - // Now generate code for the deadline violation function, if there is one. - if (reaction.getDeadline() != null) { - code.pr(generatePythonFunction( - generatePythonDeadlineFunctionName(reactionIndex), - "", - ASTUtils.toText(reaction.getDeadline().getCode()), - reactionParameters - )); - } - return code.toString(); - } - - - /** Return the top level C function header for the deadline function numbered "reactionIndex" in "decl" - * @param decl The reactor declaration - * @param reactionIndex The reaction index. - * @return The function name for the deadline function. - */ - public static String generateCDeadlineFunctionHeader(ReactorDecl decl, - int reactionIndex) { - String deadlineFunctionName = CReactionGenerator.generateDeadlineFunctionName(decl, reactionIndex); - return "void " + deadlineFunctionName + "(void* instance_args)"; - } - - /** Return the top level C function header for the reaction numbered "reactionIndex" in "decl" - * @param decl The reactor declaration - * @param reactionIndex The reaction index. - * @return The function name for the reaction. - */ - public static String generateCReactionFunctionHeader(ReactorDecl decl, - int reactionIndex) { - String deadlineFunctionName = CReactionGenerator.generateReactionFunctionName(decl, reactionIndex); - return "void " + deadlineFunctionName + "(void* instance_args)"; - } - - /** Return the function name of the reaction inside the self struct in the .c file. - * @param reactionIndex The reaction index. - * @return The function name for the reaction. - */ - public static String generateCPythonReactionFunctionName(int reactionIndex) { - return "_lf_py_reaction_function_"+reactionIndex; - } - - /** Return the function name of the deadline function inside the self struct in the .c file. - * @param reactionIndex The reaction index. - * @return The function name for the reaction. - */ - public static String generateCPythonDeadlineFunctionName(int reactionIndex) { - return "_lf_py_deadline_function_"+reactionIndex; - } - - /** Return the function name of the reaction in the .py file. - * @param reactionIndex The reaction index. - * @return The function name for the reaction. - */ - public static String generatePythonReactionFunctionName(int reactionIndex) { - return "reaction_function_" + reactionIndex; - } - - /** Return the function name of the deadline function in the .py file. - * @param reactionIndex The reaction index. - * @return The function name for the reaction. - */ - public static String generatePythonDeadlineFunctionName(int reactionIndex) { - return "deadline_function_" + reactionIndex; - } -} diff --git a/org.lflang/src/org/lflang/generator/python/PythonReactorGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonReactorGenerator.java deleted file mode 100644 index 4b5549b738..0000000000 --- a/org.lflang/src/org/lflang/generator/python/PythonReactorGenerator.java +++ /dev/null @@ -1,181 +0,0 @@ -package org.lflang.generator.python; - -import java.util.ArrayList; -import java.util.List; -import org.lflang.ASTUtils; -import org.lflang.federated.FederateInstance; -import org.lflang.generator.CodeBuilder; -import org.lflang.generator.GeneratorBase; -import org.lflang.generator.ParameterInstance; -import org.lflang.generator.ReactorInstance; -import org.lflang.lf.Reaction; -import org.lflang.lf.Reactor; -import org.lflang.lf.ReactorDecl; - -public class PythonReactorGenerator { - /** - * Wrapper function for the more elaborate generatePythonReactorClass that keeps track - * of visited reactors to avoid duplicate generation - * @param instance The reactor instance to be generated - * @param pythonClasses The class definition is appended to this code builder - * @param federate The federate instance for the reactor instance - * @param instantiatedClasses A list of visited instances to avoid generating duplicates - */ - public static String generatePythonClass(ReactorInstance instance, FederateInstance federate, ReactorInstance main, PythonTypes types) { - List instantiatedClasses = new ArrayList(); - return generatePythonClass(instance, federate, instantiatedClasses, main, types); - } - - /** - * Generate a Python class corresponding to decl - * @param instance The reactor instance to be generated - * @param pythonClasses The class definition is appended to this code builder - * @param federate The federate instance for the reactor instance - * @param instantiatedClasses A list of visited instances to avoid generating duplicates - */ - public static String generatePythonClass(ReactorInstance instance, FederateInstance federate, - List instantiatedClasses, - ReactorInstance main, PythonTypes types) { - CodeBuilder pythonClasses = new CodeBuilder(); - ReactorDecl decl = instance.getDefinition().getReactorClass(); - Reactor reactor = ASTUtils.toDefinition(decl); - String className = instance.getDefinition().getReactorClass().getName(); - if (instance != main && !federate.contains(instance) || - instantiatedClasses == null || - // Do not generate code for delay reactors in Python - className.contains(GeneratorBase.GEN_DELAY_CLASS_NAME)) { - return ""; - } - - if (federate.contains(instance) && !instantiatedClasses.contains(className)) { - pythonClasses.pr(generatePythonClassHeader(className)); - // Generate preamble code - pythonClasses.indent(); - pythonClasses.pr(PythonPreambleGenerator.generatePythonPreambles(reactor.getPreambles())); - // Handle runtime initializations - pythonClasses.pr(generatePythonConstructor(decl, types)); - pythonClasses.pr(PythonParameterGenerator.generatePythonGetters(decl)); - List reactionToGenerate = ASTUtils.allReactions(reactor); - if (reactor.isFederated()) { - // Filter out reactions that are automatically generated in C in the top level federated reactor - reactionToGenerate.removeIf(it -> !federate.contains(it) || federate.networkReactions.contains(it)); - } - pythonClasses.pr(PythonReactionGenerator.generatePythonReactions(reactor, reactionToGenerate)); - pythonClasses.unindent(); - instantiatedClasses.add(className); - } - - for (ReactorInstance child : instance.children) { - pythonClasses.pr(generatePythonClass(child, federate, instantiatedClasses, main, types)); - } - return pythonClasses.getCode(); - } - - private static String generatePythonClassHeader(String className) { - return String.join("\n", - "# Python class for reactor "+className+"", - "class _"+className+":" - ); - } - - /** - * Generate code that instantiates and initializes parameters and state variables for a reactor 'decl'. - * - * @param decl The reactor declaration - * @return The generated code as a StringBuilder - */ - private static String generatePythonConstructor(ReactorDecl decl, PythonTypes types) { - CodeBuilder code = new CodeBuilder(); - code.pr("# Constructor"); - code.pr("def __init__(self, **kwargs):"); - code.indent(); - code.pr(PythonParameterGenerator.generatePythonInstantiations(decl, types)); - code.pr(PythonStateGenerator.generatePythonInstantiations(decl)); - return code.toString(); - } - - /** - * Generate code to instantiate a Python list that will hold the Python - * class instance of reactor instance. Will recursively do - * the same for the children of instance as well. - * - * @param instance The reactor instance for which the Python list will be created. - * @param federate Will check if instance (or any of its children) belong to - * federate before generating code for them. - */ - public static String generateListsToHoldClassInstances(ReactorInstance instance, - FederateInstance federate) { - CodeBuilder code = new CodeBuilder(); - if (federate != null && !federate.contains(instance)) { - return ""; - } - code.pr(PyUtil.reactorRefName(instance)+" = [None] * "+instance.getTotalWidth()); - for (ReactorInstance child : instance.children) { - code.pr(generateListsToHoldClassInstances(child, federate)); - } - return code.toString(); - } - - /** - * Instantiate classes in Python, as well as subclasses. - * Instances are always instantiated as a list of className = [_className, _className, ...] depending on the size of the bank. - * If there is no bank or the size is 1, the instance would be generated as className = [_className] - * @param instance The reactor instance to be instantiated - * @param federate The federate instance for the reactor instance - * @param main The main reactor - */ - public static String generatePythonClassInstantiations(ReactorInstance instance, - FederateInstance federate, - ReactorInstance main) { - CodeBuilder code = new CodeBuilder(); - // If this is not the main reactor and is not in the federate, nothing to do. - if (instance != main && !federate.contains(instance)) { - return ""; - } - String className = instance.getDefinition().getReactorClass().getName(); - // Do not instantiate delay reactors in Python - if (className.contains(GeneratorBase.GEN_DELAY_CLASS_NAME)) { - return ""; - } - - if (federate.contains(instance) && instance.getWidth() > 0) { - // For each reactor instance, create a list regardless of whether it is a bank or not. - // Non-bank reactor instances will be a list of size 1. var reactorClass = instance.definition.reactorClass - String fullName = instance.getFullName(); - code.pr(String.join("\n", - "# Start initializing "+fullName+" of class "+className, - "for "+PyUtil.bankIndexName(instance)+" in range("+instance.getWidth()+"):" - )); - code.indent(); - code.pr(generatePythonClassInstantiation(instance, className)); - } - - for (ReactorInstance child : instance.children) { - code.pr(generatePythonClassInstantiations(child, federate, main)); - } - code.unindent(); - return code.toString(); - } - - /** - * Instantiate a class with className in instance. - * @param instance The reactor instance to be instantiated - * @param className The name of the class to instantiate - */ - private static String generatePythonClassInstantiation(ReactorInstance instance, - String className) { - CodeBuilder code = new CodeBuilder(); - code.pr(PyUtil.reactorRef(instance)+" = _"+className+"("); - code.indent(); - for (ParameterInstance param : instance.parameters) { - if (param.getName().equals("bank_index")) { - code.pr("_bank_index = "+PyUtil.bankIndex(instance)+","); - } else { - code.pr("_"+param.getName()+"="+PythonParameterGenerator.generatePythonInitializer(param)+","); - } - } - code.unindent(); - code.pr(")"); - return code.toString(); - } -} diff --git a/org.lflang/src/org/lflang/generator/python/PythonStateGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonStateGenerator.java deleted file mode 100644 index efbd9236c9..0000000000 --- a/org.lflang/src/org/lflang/generator/python/PythonStateGenerator.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.lflang.generator.python; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import org.lflang.ASTUtils; -import org.lflang.JavaAstUtils; -import org.lflang.generator.GeneratorBase; -import org.lflang.lf.ReactorDecl; -import org.lflang.lf.StateVar; -import org.lflang.lf.Value; - -public class PythonStateGenerator { - /** - * Generate state variable instantiations for reactor "decl" - * @param decl The reactor declaration to generate state variables. - */ - public static String generatePythonInstantiations(ReactorDecl decl) { - List lines = new ArrayList<>(); - lines.add("# Define state variables"); - // Next, handle state variables - for (StateVar state : ASTUtils.allStateVars(ASTUtils.toDefinition(decl))) { - lines.add("self."+state.getName()+" = "+generatePythonInitializer(state)); - } - lines.add(""); - return String.join("\n", lines); - } - - /** - * Handle initialization for state variable - * @param state a state variable - */ - private static String generatePythonInitializer(StateVar state) { - if (!ASTUtils.isInitialized(state)) { - return "None"; - } - List list = state.getInit().stream().map(PyUtil::getPythonTargetValue).collect(Collectors.toList()); - return list.size() > 1 ? "[" + String.join(", ", list) + "]" : list.get(0); - } -} diff --git a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt index 3fe2fd783d..01aef2af80 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt @@ -26,6 +26,7 @@ package org.lflang.generator.ts import org.eclipse.emf.ecore.resource.Resource +import org.eclipse.xtext.generator.IFileSystemAccess2 import org.eclipse.xtext.util.CancelIndicator import org.lflang.ErrorReporter import org.lflang.inferredType @@ -213,7 +214,7 @@ class TSGenerator( for (runtimeFile in RUNTIME_FILES) { fileConfig.copyFileFromClassPath( "$LIB_PATH/reactor-ts/src/core/$runtimeFile", - tsFileConfig.tsCoreGenPath().resolve(runtimeFile) + tsFileConfig.tsCoreGenPath().resolve(runtimeFile).toString() ) } } @@ -234,7 +235,7 @@ class TSGenerator( "No '" + configFile + "' exists in " + fileConfig.srcPath + ". Using default configuration." ) - fileConfig.copyFileFromClassPath("$LIB_PATH/$configFile", configFileDest) + fileConfig.copyFileFromClassPath("$LIB_PATH/$configFile", configFileDest.toString()) } } } diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 8a95bf8f72..2d7bd1bb25 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -26,44 +26,33 @@ ***************/ package org.lflang.validation; -import static org.lflang.ASTUtils.inferPortWidth; -import static org.lflang.ASTUtils.isGeneric; -import static org.lflang.ASTUtils.isInteger; -import static org.lflang.ASTUtils.isParameterized; -import static org.lflang.ASTUtils.isValidTime; -import static org.lflang.ASTUtils.isZero; -import static org.lflang.ASTUtils.toDefinition; -import static org.lflang.ASTUtils.toText; -import static org.lflang.JavaAstUtils.isOfTimeType; +import com.google.inject.Inject; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; +import java.util.Optional; import org.eclipse.emf.common.util.BasicEList; import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EObject; -import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.xtext.validation.Check; import org.eclipse.xtext.validation.CheckType; import org.eclipse.xtext.validation.ValidationMessageAcceptor; -import org.lflang.ASTUtils; + import org.lflang.FileConfig; +import org.lflang.InferredType; import org.lflang.JavaAstUtils; import org.lflang.ModelInfo; import org.lflang.Target; import org.lflang.TargetProperty; import org.lflang.TimeValue; -import org.lflang.federated.serialization.SupportedSerializers; -import org.lflang.generator.NamedInstance; import org.lflang.lf.Action; import org.lflang.lf.ActionOrigin; import org.lflang.lf.Assignment; @@ -87,9 +76,8 @@ import org.lflang.lf.Preamble; import org.lflang.lf.Reaction; import org.lflang.lf.Reactor; -import org.lflang.lf.ReactorDecl; -import org.lflang.lf.STP; import org.lflang.lf.Serializer; +import org.lflang.lf.STP; import org.lflang.lf.StateVar; import org.lflang.lf.TargetDecl; import org.lflang.lf.Timer; @@ -102,8 +90,12 @@ import org.lflang.lf.Visibility; import org.lflang.lf.WidthSpec; import org.lflang.lf.WidthTerm; +import org.lflang.lf.ReactorDecl; +import org.lflang.generator.NamedInstance; -import com.google.inject.Inject; +import static org.lflang.ASTUtils.*; +import static org.lflang.JavaAstUtils.*; +import org.lflang.federated.serialization.SupportedSerializers; /** * Custom validation checks for Lingua Franca programs. @@ -113,26 +105,204 @@ * @author{Edward A. Lee } * @author{Marten Lohstroh } * @author{Matt Weber } - * @author{Christian Menard } - * @author{Hou Seng Wong } - * @author{Clément Fournier } + * @author(Christian Menard } + * */ public class LFValidator extends BaseLFValidator { - ////////////////////////////////////////////////////////////// - //// Public check methods. + private Target target; + + public ModelInfo info = new ModelInfo(); + + private ValidatorErrorReporter errorReporter = new ValidatorErrorReporter(getMessageAcceptor(), + new ValidatorStateAccess()); + + @Inject(optional = true) + ValidationMessageAcceptor messageAcceptor; + + /** + * Regular expression to check the validity of IPV4 addresses (due to David M. Syzdek). + */ + static String ipv4Regex = "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}" + + "(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"; + + /** + * Regular expression to check the validity of IPV6 addresses (due to David M. Syzdek), + * with minor adjustment to allow up to six IPV6 segments (without truncation) in front + * of an embedded IPv4-address. + **/ + static String ipv6Regex = + "(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" + + "([0-9a-fA-F]{1,4}:){1,7}:|" + + "([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" + + "([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" + + "([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" + + "([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" + + "([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" + + "[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" + + ":((:[0-9a-fA-F]{1,4}){1,7}|:)|" + + "fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|" + + "::(ffff(:0{1,4}){0,1}:){0,1}" + ipv4Regex + "|" + + "([0-9a-fA-F]{1,4}:){1,4}:" + ipv4Regex + "|" + + "([0-9a-fA-F]{1,4}:){1,6}" + ipv4Regex + ")"; + + static String usernameRegex = "^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\\$)$"; + + static String hostOrFQNRegex = "^([a-z0-9]+(-[a-z0-9]+)*)|(([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,})$"; + + public static String GLOBALLY_DUPLICATE_NAME = "GLOBALLY_DUPLICATE_NAME"; + + static List spacingViolationPolicies = List.of("defer", "drop", "replace"); + + private List targetPropertyErrors = new ArrayList<>(); + + private List targetPropertyWarnings = new ArrayList<>(); + + public List getTargetPropertyErrors() { + return this.targetPropertyErrors; + } + + public ValidatorErrorReporter getErrorReporter() { + return this.errorReporter; + } + + @Override + public ValidationMessageAcceptor getMessageAcceptor() { + return messageAcceptor == null ? this : messageAcceptor; + } - // These methods are automatically invoked on AST nodes matching - // the types of their arguments. - // CheckType.FAST ensures that these checks run whenever a file is modified. - // Alternatives are CheckType.NORMAL (when saving) and - // CheckType.EXPENSIVE (only when right-click, validate). - // FIXME: What is the default when nothing is specified? + /** + * Returns true if target is C or a C-based target like CCpp. + */ + private boolean isCBasedTarget() { + return (this.target == Target.C || this.target == Target.CCPP); + } + + @Check + public void checkImportedReactor(ImportedReactor reactor) { + if (isUnused(reactor)) { + warning("Unused reactor class.", + Literals.IMPORTED_REACTOR__REACTOR_CLASS); + } + + if (info.instantiationGraph.hasCycles()) { + Set cycleSet = new HashSet<>(); + for (Set cycle : info.instantiationGraph.getCycles()) { + cycleSet.addAll(cycle); + } + if (dependsOnCycle(toDefinition(reactor), cycleSet, new HashSet<>())) { + error("Imported reactor '" + toDefinition(reactor).getName() + + "' has cyclic instantiation in it.", Literals.IMPORTED_REACTOR__REACTOR_CLASS); + } + } + } + + @Check + public void checkImport(Import imp) { + if (toDefinition(imp.getReactorClasses().get(0)).eResource().getErrors().size() > 0) { + error("Error loading resource.", Literals.IMPORT__IMPORT_URI); // FIXME: print specifics. + return; + } + + // FIXME: report error if resource cannot be resolved. + for (ImportedReactor reactor : imp.getReactorClasses()) { + if (!isUnused(reactor)) { + return; + } + } + warning("Unused import.", Literals.IMPORT__IMPORT_URI); + } + + // ////////////////////////////////////////////////// + // // Helper functions for checks to be performed on multiple entities + // Check the name of a feature for illegal substrings. + private void checkName(String name, EStructuralFeature feature) { + + // Raises an error if the string starts with two underscores. + if (name.length() >= 2 && name.substring(0, 2).equals("__")) { + error(UNDERSCORE_MESSAGE + name, feature); + } + + if (this.target.isReservedIdent(name)) { + error(RESERVED_MESSAGE + name, feature); + } + + if (this.target == Target.TS) { + // "actions" is a reserved word within a TS reaction + if (name.equals("actions")) { + error(ACTIONS_MESSAGE + name, feature); + } + } + } + + /** + * Report whether a given reactor has dependencies on a cyclic + * instantiation pattern. This means the reactor has an instantiation + * in it -- directly or in one of its contained reactors -- that is + * self-referential. + * @param reactor The reactor definition to find out whether it has any + * dependencies on cyclic instantiations. + * @param cycleSet The set of all reactors that are part of an + * instantiation cycle. + * @param visited The set of nodes already visited in this graph traversal. + */ + private boolean dependsOnCycle(Reactor reactor, Set cycleSet, + Set visited) { + Set origins = info.instantiationGraph.getUpstreamAdjacentNodes(reactor); + if (visited.contains(reactor)) { + return false; + } else { + visited.add(reactor); + for (Reactor it : origins) { + if (cycleSet.contains(it) || dependsOnCycle(it, cycleSet, visited)) { + // Reached a cycle. + return true; + } + } + } + return false; + } + + /** + * Report whether a given imported reactor is used in this resource or not. + * @param reactor The imported reactor to check whether it is used. + */ + private boolean isUnused(ImportedReactor reactor) { + TreeIterator instantiations = reactor.eResource().getAllContents(); + TreeIterator subclasses = reactor.eResource().getAllContents(); + + boolean instantiationsCheck = true; + while (instantiations.hasNext() && instantiationsCheck) { + EObject obj = instantiations.next(); + if (!(obj instanceof Instantiation)) { + continue; + } + Instantiation inst = (Instantiation) obj; + instantiationsCheck &= (inst.getReactorClass() != reactor && inst.getReactorClass() != reactor.getReactorClass()); + } + + boolean subclassesCheck = true; + while (subclasses.hasNext() && subclassesCheck) { + EObject obj = subclasses.next(); + if (!(obj instanceof Reactor)) { + continue; + } + Reactor subclass = (Reactor) obj; + for (ReactorDecl decl : subclass.getSuperClasses()) { + subclassesCheck &= (decl != reactor && decl != reactor.getReactorClass()); + } + } + return instantiationsCheck && subclassesCheck; + } - // These methods are listed in alphabetical order, and, although - // it is isn't strictly required, follow a naming convention - // checkClass, where Class is the AST class, where possible. + // ////////////////////////////////////////////////// + // // Functions to set up data structures for performing checks. + // FAST ensures that these checks run whenever a file is modified. + // Alternatives are NORMAL (when saving) and EXPENSIVE (only when right-click, validate). + + // ////////////////////////////////////////////////// + // // The following checks are in alphabetical order. @Check(CheckType.FAST) public void checkAction(Action action) { checkName(action.getName(), Literals.VARIABLE__NAME); @@ -143,11 +313,11 @@ public void checkAction(Action action) { ); } if (action.getPolicy() != null && - !SPACING_VIOLATION_POLICIES.contains(action.getPolicy())) { + !spacingViolationPolicies.contains(action.getPolicy())) { error( "Unrecognized spacing violation policy: " + action.getPolicy() + ". Available policies are: " + - String.join(", ", SPACING_VIOLATION_POLICIES) + ".", + String.join(", ", spacingViolationPolicies) + ".", Literals.ACTION__POLICY); } } @@ -198,6 +368,32 @@ public void checkAssignment(Assignment assignment) { // Specifically for C: list can only be literal or time lists } + @Check(CheckType.FAST) + public void checkWidthSpec(WidthSpec widthSpec) { + if (!this.target.supportsMultiports()) { + error("Multiports and banks are currently not supported by the given target.", + Literals.WIDTH_SPEC__TERMS); + } else { + for (WidthTerm term : widthSpec.getTerms()) { + if (term.getParameter() != null) { + if (!this.target.supportsParameterizedWidths()) { + error("Parameterized widths are not supported by this target.", Literals.WIDTH_SPEC__TERMS); + } + } else if (term.getPort() != null) { + // Widths given with `widthof()` are not supported (yet?). + // This feature is currently only used for after delays. + error("widthof is not supported.", Literals.WIDTH_SPEC__TERMS); + } else if (term.getCode() != null) { + if (this.target != Target.CPP) { + error("This target does not support width given as code.", Literals.WIDTH_SPEC__TERMS); + } + } else if (term.getWidth() < 0) { + error("Width must be a positive integer.", Literals.WIDTH_SPEC__TERMS); + } + } + } + } + @Check(CheckType.FAST) public void checkConnection(Connection connection) { @@ -349,6 +545,39 @@ public void checkConnection(Connection connection) { } } + /** + * Return true if the two types match. Unfortunately, xtext does not + * seem to create a suitable equals() method for Type, so we have to + * do this manually. + */ + private boolean sameType(Type type1, Type type2) { + if (type1 == null) { + return type2 == null; + } + if (type2 == null) { + return type1 == null; + } + // Most common case first. + if (type1.getId() != null) { + if (type1.getStars() != null) { + if (type2.getStars() == null) return false; + if (type1.getStars().size() != type2.getStars().size()) return false; + } + return (type1.getId().equals(type2.getId())); + } + + // Type specification in the grammar is: + // (time?='time' (arraySpec=ArraySpec)?) | ((id=(DottedName) (stars+='*')* ('<' typeParms+=TypeParm (',' typeParms+=TypeParm)* '>')? (arraySpec=ArraySpec)?) | code=Code); + if (type1.isTime()) { + if (!type2.isTime()) return false; + // Ignore the arraySpec because that is checked when connection + // is checked for balance. + return true; + } + // Type must be given in a code body + return type1.getCode().getBody().equals(type2.getCode().getBody()); + } + @Check(CheckType.FAST) public void checkDeadline(Deadline deadline) { if (isCBasedTarget() && @@ -361,74 +590,18 @@ public void checkDeadline(Deadline deadline) { } @Check(CheckType.FAST) - public void checkHost(Host host) { - String addr = host.getAddr(); - String user = host.getUser(); - if (user != null && !user.matches(USERNAME_REGEX)) { - warning( - "Invalid user name.", - Literals.HOST__USER - ); - } - if (host instanceof IPV4Host && !addr.matches(IPV4_REGEX)) { - warning( - "Invalid IP address.", - Literals.HOST__ADDR - ); - } else if (host instanceof IPV6Host && !addr.matches(IPV6_REGEX)) { - warning( - "Invalid IP address.", - Literals.HOST__ADDR - ); - } else if (host instanceof NamedHost && !addr.matches(HOST_OR_FQN_REGEX)) { - warning( - "Invalid host name or fully qualified domain name.", - Literals.HOST__ADDR - ); - } - } - - @Check - public void checkImport(Import imp) { - if (toDefinition(imp.getReactorClasses().get(0)).eResource().getErrors().size() > 0) { - error("Error loading resource.", Literals.IMPORT__IMPORT_URI); // FIXME: print specifics. - return; - } - - // FIXME: report error if resource cannot be resolved. - for (ImportedReactor reactor : imp.getReactorClasses()) { - if (!isUnused(reactor)) { - return; - } - } - warning("Unused import.", Literals.IMPORT__IMPORT_URI); - } - - @Check - public void checkImportedReactor(ImportedReactor reactor) { - if (isUnused(reactor)) { - warning("Unused reactor class.", - Literals.IMPORTED_REACTOR__REACTOR_CLASS); - } - - if (info.instantiationGraph.hasCycles()) { - Set cycleSet = new HashSet<>(); - for (Set cycle : info.instantiationGraph.getCycles()) { - cycleSet.addAll(cycle); - } - if (dependsOnCycle(toDefinition(reactor), cycleSet, new HashSet<>())) { - error("Imported reactor '" + toDefinition(reactor).getName() + - "' has cyclic instantiation in it.", Literals.IMPORTED_REACTOR__REACTOR_CLASS); - } + public void checkSTPOffset(STP stp) { + if (isCBasedTarget() && + this.info.overflowingDeadlines.contains(stp)) { + error( + "STP offset exceeds the maximum of " + + TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", + Literals.DEADLINE__DELAY); } } @Check(CheckType.FAST) public void checkInput(Input input) { - Reactor parent = (Reactor)input.eContainer(); - if (parent.isMain() || parent.isFederated()) { - error("Main reactor cannot have inputs.", Literals.VARIABLE__NAME); - } checkName(input.getName(), Literals.VARIABLE__NAME); if (target.requiresTypes) { if (input.getType() == null) { @@ -507,26 +680,28 @@ public void checkKeyValuePair(KeyValuePair param) { // Make sure the key is valid. if (prop == null) { - String options = TargetProperty.getOptions().stream() - .map(p -> p.description).sorted() - .collect(Collectors.joining(", ")); + List optionNames = new ArrayList<>(); + for (TargetProperty p : TargetProperty.getOptions()) { + optionNames.add(p.name()); + } warning( "Unrecognized target parameter: " + param.getName() + - ". Recognized parameters are: " + options, + ". Recognized parameters are: " + + String.join(", ", optionNames) + ".", Literals.KEY_VALUE_PAIR__NAME); - } else { - // Check whether the property is supported by the target. - if (!prop.supportedBy.contains(this.target)) { - warning( - "The target parameter: " + param.getName() + - " is not supported by the " + this.target + - " target and will thus be ignored.", - Literals.KEY_VALUE_PAIR__NAME); - } + } - // Report problem with the assigned value. - prop.type.check(param.getValue(), param.getName(), this); + // Check whether the property is supported by the target. + if (!prop.supportedBy.contains(this.target)) { + warning( + "The target parameter: " + param.getName() + + " is not supported by the " + this.target + + " target and will thus be ignored.", + Literals.KEY_VALUE_PAIR__NAME); } + + // Report problem with the assigned value. + prop.type.check(param.getValue(), param.getName(), this); for (String it : targetPropertyErrors) { error(it, Literals.KEY_VALUE_PAIR__VALUE); @@ -540,6 +715,21 @@ public void checkKeyValuePair(KeyValuePair param) { } } + @Check(CheckType.FAST) + public void checkOutput(Output output) { + checkName(output.getName(), Literals.VARIABLE__NAME); + if (this.target.requiresTypes) { + if (output.getType() == null) { + error("Output must have a type.", Literals.TYPED_VARIABLE__TYPE); + } + } + + // Variable width multiports are not supported (yet?). + if (output.getWidthSpec() != null && output.getWidthSpec().isOfVariableLength()) { + error("Variable-width multiports are not supported.", Literals.PORT__WIDTH_SPEC); + } + } + @Check(CheckType.FAST) public void checkModel(Model model) { // Since we're doing a fast check, we only want to update @@ -556,25 +746,6 @@ public void updateModelInfo(Model model) { info.update(model, errorReporter); } - @Check(CheckType.FAST) - public void checkOutput(Output output) { - Reactor parent = (Reactor)output.eContainer(); - if (parent.isMain() || parent.isFederated()) { - error("Main reactor cannot have outputs.", Literals.VARIABLE__NAME); - } - checkName(output.getName(), Literals.VARIABLE__NAME); - if (this.target.requiresTypes) { - if (output.getType() == null) { - error("Output must have a type.", Literals.TYPED_VARIABLE__TYPE); - } - } - - // Variable width multiports are not supported (yet?). - if (output.getWidthSpec() != null && output.getWidthSpec().isOfVariableLength()) { - error("Variable-width multiports are not supported.", Literals.PORT__WIDTH_SPEC); - } - } - @Check(CheckType.FAST) public void checkParameter(Parameter param) { checkName(param.getName(), Literals.PARAMETER__NAME); @@ -678,7 +849,7 @@ public void checkPreamble(Preamble preamble) { } } - @Check(CheckType.FAST) + @Check(CheckType.FAST) public void checkReaction(Reaction reaction) { if (reaction.getTriggers() == null || reaction.getTriggers().size() == 0) { @@ -704,7 +875,7 @@ public void checkReaction(Reaction reaction) { } } - // Make sure input sources have no container and output sources do. + // Make sure input sources have no container and output sources do. // Also check that a source is not already listed as a trigger. for (VarRef source : reaction.getSources()) { if (triggers.contains(source.getVariable())) { @@ -831,17 +1002,23 @@ public void checkReaction(Reaction reaction) { // FIXME: improve error message. } + private int countMainOrFederated(TreeIterator iter) { + int nMain = 0; + while (iter.hasNext()) { + EObject obj = iter.next(); + if (!(obj instanceof Reactor)) { + continue; + } + Reactor r = (Reactor) obj; + if (r.isMain() || r.isFederated()) { + nMain++; + } + } + return nMain; + } + @Check(CheckType.FAST) public void checkReactor(Reactor reactor) throws IOException { - Set superClasses = ASTUtils.superClasses(reactor); - if (superClasses == null) { - error( - "Problem with superclasses: Either they form a cycle or are not defined", - Literals.REACTOR_DECL__NAME - ); - // Continue checks, but without any superclasses. - superClasses = new LinkedHashSet<>(); - } String name = FileConfig.nameWithoutExtension(reactor.eResource()); if (reactor.getName() == null) { if (!reactor.isFederated() && !reactor.isMain()) { @@ -849,43 +1026,48 @@ public void checkReactor(Reactor reactor) throws IOException { "Reactor must be named.", Literals.REACTOR_DECL__NAME ); - // Prevent NPE in tests below. - return; - } - } - TreeIterator iter = reactor.eResource().getAllContents(); - if (reactor.isFederated() || reactor.isMain()) { - if(reactor.getName() != null && !reactor.getName().equals(name)) { - // Make sure that if the name is given, it matches the expected name. - error( - "Name of main reactor must match the file name (or be omitted).", - Literals.REACTOR_DECL__NAME - ); - } - // Do not allow multiple main/federated reactors. - int nMain = countMainOrFederated(iter); - if (nMain > 1) { - EAttribute attribute = Literals.REACTOR__MAIN; - if (reactor.isFederated()) { - attribute = Literals.REACTOR__FEDERATED; - } - error( - "Multiple definitions of main or federated reactor.", - attribute - ); } + // Prevent NPE in tests below. + return; } else { - // Not federated or main. - int nMain = countMainOrFederated(iter); - if (nMain > 0 && reactor.getName().equals(name)) { - error( - "Name conflict with main reactor.", - Literals.REACTOR_DECL__NAME - ); + TreeIterator iter = reactor.eResource().getAllContents(); + if (reactor.isFederated() || reactor.isMain()) { + if(!reactor.getName().equals(name)) { + // Make sure that if the name is omitted, the reactor is indeed main. + error( + "Name of main reactor must match the file name (or be omitted).", + Literals.REACTOR_DECL__NAME + ); + } + + // Do not allow multiple main/federated reactors. + int nMain = countMainOrFederated(iter); + if (nMain > 1) { + EAttribute attribute = Literals.REACTOR__MAIN; + if (reactor.isFederated()) { + attribute = Literals.REACTOR__FEDERATED; + } + if (reactor.isMain() || reactor.isFederated()) { + error( + "Multiple definitions of main or federated reactor.", + attribute + ); + } + } + } else { + int nMain = countMainOrFederated(iter); + if (nMain > 0 && reactor.getName().equals(name)) { + error( + "Name conflict with main reactor.", + Literals.REACTOR_DECL__NAME + ); + } } } - // Check for illegal names. + // If there is a main reactor (with no name) then disallow other (non-main) reactors + // matching the file name. + checkName(reactor.getName(), Literals.REACTOR_DECL__NAME); // C++ reactors may not be called 'preamble' @@ -913,17 +1095,18 @@ public void checkReactor(Reactor reactor) throws IOException { variables.addAll(reactor.getTimers()); // Perform checks on super classes. - for (Reactor superClass : superClasses) { + EList superClasses = reactor.getSuperClasses() != null ? reactor.getSuperClasses() : new BasicEList<>(); + for (ReactorDecl superClass : superClasses) { HashSet conflicts = new HashSet<>(); // Detect input conflicts - checkConflict(superClass.getInputs(), reactor.getInputs(), variables, conflicts); + checkConflict(toDefinition(superClass).getInputs(), reactor.getInputs(), variables, conflicts); // Detect output conflicts - checkConflict(superClass.getOutputs(), reactor.getOutputs(), variables, conflicts); + checkConflict(toDefinition(superClass).getOutputs(), reactor.getOutputs(), variables, conflicts); // Detect output conflicts - checkConflict(superClass.getActions(), reactor.getActions(), variables, conflicts); + checkConflict(toDefinition(superClass).getActions(), reactor.getActions(), variables, conflicts); // Detect conflicts - for (Timer timer : superClass.getTimers()) { + for (Timer timer : toDefinition(superClass).getTimers()) { List filteredVariables = new ArrayList<>(variables); filteredVariables.removeIf(it -> reactor.getTimers().contains(it)); if (hasNameConflict(timer, filteredVariables)) { @@ -940,14 +1123,92 @@ public void checkReactor(Reactor reactor) throws IOException { names.add(it.getName()); } error( - String.format("Cannot extend %s due to the following conflicts: %s.", - superClass.getName(), String.join(",", names)), + String.format("Cannot extend %s due to the following conflicts: %s.", superClass.getName(), String.join(",", names)), Literals.REACTOR__SUPER_CLASSES ); } } } + /** + * For each input, report a conflict if: + * 1) the input exists and the type doesn't match; or + * 2) the input has a name clash with variable that is not an input. + * @param superVars List of typed variables of a particular kind (i.e., + * inputs, outputs, or actions), found in a super class. + * @param sameKind Typed variables of the same kind, found in the subclass. + * @param allOwn Accumulator of non-conflicting variables incorporated in the + * subclass. + * @param conflicts Set of variables that are in conflict, to be used by this + * function to report conflicts. + */ + private void checkConflict (EList superVars, + EList sameKind, List allOwn, + HashSet conflicts) { + for (T superVar : superVars) { + T match = null; + for (T it : sameKind) { + if (it.getName().equals(superVar.getName())) { + match = it; + break; + } + } + List rest = new ArrayList<>(allOwn); + rest.removeIf(it -> sameKind.contains(it)); + + if ((match != null && superVar.getType() != match.getType()) || hasNameConflict(superVar, rest)) { + conflicts.add(superVar); + } else { + allOwn.add(superVar); + } + } + } + + /** + * Report whether the name of the given element matches any variable in + * the ones to check against. + * @param element The element to compare against all variables in the given iterable. + * @param toCheckAgainst Iterable variables to compare the given element against. + */ + private boolean hasNameConflict(Variable element, + Iterable toCheckAgainst) { + int numNameConflicts = 0; + for (Variable it : toCheckAgainst) { + if (it.getName().equals(element.getName())) { + numNameConflicts++; + } + } + return numNameConflicts > 0; + } + + @Check(CheckType.FAST) + public void checkHost(Host host) { + String addr = host.getAddr(); + String user = host.getUser(); + if (user != null && !user.matches(usernameRegex)) { + warning( + "Invalid user name.", + Literals.HOST__USER + ); + } + if (host instanceof IPV4Host && !addr.matches(ipv4Regex)) { + warning( + "Invalid IP address.", + Literals.HOST__ADDR + ); + } else if (host instanceof IPV6Host && !addr.matches(ipv6Regex)) { + warning( + "Invalid IP address.", + Literals.HOST__ADDR + ); + } else if (host instanceof NamedHost && !addr.matches(hostOrFQNRegex)) { + warning( + "Invalid host name or fully qualified domain name.", + Literals.HOST__ADDR + ); + } + } + /** * Check if the requested serialization is supported. */ @@ -1020,17 +1281,6 @@ public void checkState(StateVar stateVar) { } } - @Check(CheckType.FAST) - public void checkSTPOffset(STP stp) { - if (isCBasedTarget() && - this.info.overflowingSTP.contains(stp)) { - error( - "STP offset exceeds the maximum of " + - TimeValue.MAX_LONG_DEADLINE + " nanoseconds.", - Literals.STP__VALUE); - } - } - @Check(CheckType.FAST) public void checkTargetDecl(TargetDecl target) throws IOException { Optional targetOpt = Target.forName(target.getName()); @@ -1111,6 +1361,33 @@ public void checkTargetProperties(KeyValuePairs targetProperties) { } } + @Check(CheckType.FAST) + public void checkValueAsTime(Value value) { + EObject container = value.eContainer(); + + if (container instanceof Timer || container instanceof Action || + container instanceof Connection || container instanceof Deadline) { + // If parameter is referenced, check that it is of the correct type. + if (value.getParameter() != null) { + if (!isOfTimeType(value.getParameter()) && target.requiresTypes) { + error("Parameter is not of time type", + Literals.VALUE__PARAMETER); + } + } else if (value.getTime() == null) { + if (value.getLiteral() != null && !isZero(value.getLiteral())) { + if (isInteger(value.getLiteral())) { + error("Missing time unit.", Literals.VALUE__LITERAL); + } else { + error("Invalid time literal.", + Literals.VALUE__LITERAL); + } + } else if (value.getCode() != null) { + error("Invalid time literal.", Literals.VALUE__CODE); + } + } + } + } + @Check(CheckType.FAST) public void checkTimer(Timer timer) { checkName(timer.getName(), Literals.VARIABLE__NAME); @@ -1143,42 +1420,15 @@ else if (this.target == Target.Python) { } @Check(CheckType.FAST) - public void checkValueAsTime(Value value) { - EObject container = value.eContainer(); - - if (container instanceof Timer || container instanceof Action || - container instanceof Connection || container instanceof Deadline) { - // If parameter is referenced, check that it is of the correct type. - if (value.getParameter() != null) { - if (!isOfTimeType(value.getParameter()) && target.requiresTypes) { - error("Parameter is not of time type", - Literals.VALUE__PARAMETER); - } - } else if (value.getTime() == null) { - if (value.getLiteral() != null && !isZero(value.getLiteral())) { - if (isInteger(value.getLiteral())) { - error("Missing time unit.", Literals.VALUE__LITERAL); - } else { - error("Invalid time literal.", - Literals.VALUE__LITERAL); - } - } else if (value.getCode() != null) { - error("Invalid time literal.", Literals.VALUE__CODE); - } - } - } - } - - @Check(CheckType.FAST) - public void checkVarRef(VarRef varRef) { - // check correct usage of interleaved - if (varRef.isInterleaved()) { - if (this.target != Target.CPP && !isCBasedTarget() && this.target != Target.Python) { - error("This target does not support interleaved port references.", Literals.VAR_REF__INTERLEAVED); - } - if (!(varRef.eContainer() instanceof Connection)) { - error("interleaved can only be used in connections.", Literals.VAR_REF__INTERLEAVED); - } + public void checkVarRef(VarRef varRef) { + // check correct usage of interleaved + if (varRef.isInterleaved()) { + if (this.target != Target.CPP && !isCBasedTarget() && this.target != Target.Python) { + error("This target does not support interleaved port references.", Literals.VAR_REF__INTERLEAVED); + } + if (!(varRef.eContainer() instanceof Connection)) { + error("interleaved can only be used in connections.", Literals.VAR_REF__INTERLEAVED); + } if (varRef.getVariable() instanceof Port) { // This test only works correctly if the variable is actually a port. If it is not a port, other @@ -1192,320 +1442,8 @@ public void checkVarRef(VarRef varRef) { } } - @Check(CheckType.FAST) - public void checkWidthSpec(WidthSpec widthSpec) { - if (!this.target.supportsMultiports()) { - error("Multiports and banks are currently not supported by the given target.", - Literals.WIDTH_SPEC__TERMS); - } else { - for (WidthTerm term : widthSpec.getTerms()) { - if (term.getParameter() != null) { - if (!this.target.supportsParameterizedWidths()) { - error("Parameterized widths are not supported by this target.", Literals.WIDTH_SPEC__TERMS); - } - } else if (term.getPort() != null) { - // Widths given with `widthof()` are not supported (yet?). - // This feature is currently only used for after delays. - error("widthof is not supported.", Literals.WIDTH_SPEC__TERMS); - } else if (term.getCode() != null) { - if (this.target != Target.CPP) { - error("This target does not support width given as code.", Literals.WIDTH_SPEC__TERMS); - } - } else if (term.getWidth() < 0) { - error("Width must be a positive integer.", Literals.WIDTH_SPEC__TERMS); - } - } - } - } - - ////////////////////////////////////////////////////////////// - //// Public methods. - - /** - * Return the error reporter for this validator. - */ - public ValidatorErrorReporter getErrorReporter() { - return this.errorReporter; - } - - /** - * Implementation required by xtext to report validation errors. - */ - @Override - public ValidationMessageAcceptor getMessageAcceptor() { - return messageAcceptor == null ? this : messageAcceptor; - } - - /** - * Return a list of error messages for the target declaration. - */ - public List getTargetPropertyErrors() { - return this.targetPropertyErrors; - } - - ////////////////////////////////////////////////////////////// - //// Private methods. - - /** - * For each input, report a conflict if: - * 1) the input exists and the type doesn't match; or - * 2) the input has a name clash with variable that is not an input. - * @param superVars List of typed variables of a particular kind (i.e., - * inputs, outputs, or actions), found in a super class. - * @param sameKind Typed variables of the same kind, found in the subclass. - * @param allOwn Accumulator of non-conflicting variables incorporated in the - * subclass. - * @param conflicts Set of variables that are in conflict, to be used by this - * function to report conflicts. - */ - private void checkConflict ( - EList superVars, EList sameKind, List allOwn, HashSet conflicts - ) { - for (T superVar : superVars) { - T match = null; - for (T it : sameKind) { - if (it.getName().equals(superVar.getName())) { - match = it; - break; - } - } - List rest = new ArrayList<>(allOwn); - rest.removeIf(it -> sameKind.contains(it)); - - if ((match != null && superVar.getType() != match.getType()) || hasNameConflict(superVar, rest)) { - conflicts.add(superVar); - } else { - allOwn.add(superVar); - } - } - } - - /** - * Check the name of a feature for illegal substrings such as reserved - * identifiers and names with double leading underscores. - * @param name The name. - * @param feature The feature containing the name (for error reporting). - */ - private void checkName(String name, EStructuralFeature feature) { - - // Raises an error if the string starts with two underscores. - if (name.length() >= 2 && name.substring(0, 2).equals("__")) { - error(UNDERSCORE_MESSAGE + name, feature); - } - - if (this.target.isReservedIdent(name)) { - error(RESERVED_MESSAGE + name, feature); - } - - if (this.target == Target.TS) { - // "actions" is a reserved word within a TS reaction - if (name.equals("actions")) { - error(ACTIONS_MESSAGE + name, feature); - } - } - } - - /** - * Return the number of main or federated reactors declared. - * @param iter An iterator over all objects in the resource. - */ - private int countMainOrFederated(TreeIterator iter) { - int nMain = 0; - while (iter.hasNext()) { - EObject obj = iter.next(); - if (!(obj instanceof Reactor)) { - continue; - } - Reactor r = (Reactor) obj; - if (r.isMain() || r.isFederated()) { - nMain++; - } - } - return nMain; - } - - /** - * Report whether a given reactor has dependencies on a cyclic - * instantiation pattern. This means the reactor has an instantiation - * in it -- directly or in one of its contained reactors -- that is - * self-referential. - * @param reactor The reactor definition to find out whether it has any - * dependencies on cyclic instantiations. - * @param cycleSet The set of all reactors that are part of an - * instantiation cycle. - * @param visited The set of nodes already visited in this graph traversal. - */ - private boolean dependsOnCycle( - Reactor reactor, Set cycleSet, Set visited - ) { - Set origins = info.instantiationGraph.getUpstreamAdjacentNodes(reactor); - if (visited.contains(reactor)) { - return false; - } else { - visited.add(reactor); - for (Reactor it : origins) { - if (cycleSet.contains(it) || dependsOnCycle(it, cycleSet, visited)) { - // Reached a cycle. - return true; - } - } - } - return false; - } - - /** - * Report whether the name of the given element matches any variable in - * the ones to check against. - * @param element The element to compare against all variables in the given iterable. - * @param toCheckAgainst Iterable variables to compare the given element against. - */ - private boolean hasNameConflict(Variable element, - Iterable toCheckAgainst) { - int numNameConflicts = 0; - for (Variable it : toCheckAgainst) { - if (it.getName().equals(element.getName())) { - numNameConflicts++; - } - } - return numNameConflicts > 0; - } - - /** - * Return true if target is C or a C-based target like CCpp. - */ - private boolean isCBasedTarget() { - return (this.target == Target.C || this.target == Target.CCPP); - } - - /** - * Report whether a given imported reactor is used in this resource or not. - * @param reactor The imported reactor to check whether it is used. - */ - private boolean isUnused(ImportedReactor reactor) { - TreeIterator instantiations = reactor.eResource().getAllContents(); - TreeIterator subclasses = reactor.eResource().getAllContents(); - - boolean instantiationsCheck = true; - while (instantiations.hasNext() && instantiationsCheck) { - EObject obj = instantiations.next(); - if (!(obj instanceof Instantiation)) { - continue; - } - Instantiation inst = (Instantiation) obj; - instantiationsCheck &= (inst.getReactorClass() != reactor && inst.getReactorClass() != reactor.getReactorClass()); - } - - boolean subclassesCheck = true; - while (subclasses.hasNext() && subclassesCheck) { - EObject obj = subclasses.next(); - if (!(obj instanceof Reactor)) { - continue; - } - Reactor subclass = (Reactor) obj; - for (ReactorDecl decl : subclass.getSuperClasses()) { - subclassesCheck &= (decl != reactor && decl != reactor.getReactorClass()); - } - } - return instantiationsCheck && subclassesCheck; - } - - /** - * Return true if the two types match. Unfortunately, xtext does not - * seem to create a suitable equals() method for Type, so we have to - * do this manually. - */ - private boolean sameType(Type type1, Type type2) { - if (type1 == null) { - return type2 == null; - } - if (type2 == null) { - return type1 == null; - } - // Most common case first. - if (type1.getId() != null) { - if (type1.getStars() != null) { - if (type2.getStars() == null) return false; - if (type1.getStars().size() != type2.getStars().size()) return false; - } - return (type1.getId().equals(type2.getId())); - } - - // Type specification in the grammar is: - // (time?='time' (arraySpec=ArraySpec)?) | ((id=(DottedName) (stars+='*')* ('<' typeParms+=TypeParm (',' typeParms+=TypeParm)* '>')? (arraySpec=ArraySpec)?) | code=Code); - if (type1.isTime()) { - if (!type2.isTime()) return false; - // Ignore the arraySpec because that is checked when connection - // is checked for balance. - return true; - } - // Type must be given in a code body - return type1.getCode().getBody().equals(type2.getCode().getBody()); - } - - ////////////////////////////////////////////////////////////// - //// Private fields. + static String UNDERSCORE_MESSAGE = "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": "; + static String ACTIONS_MESSAGE = "\"actions\" is a reserved word for the TypeScript target for objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation): "; + static String RESERVED_MESSAGE = "Reserved words in the target language are not allowed for objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation): "; - /** The error reporter. */ - private ValidatorErrorReporter errorReporter - = new ValidatorErrorReporter(getMessageAcceptor(), new ValidatorStateAccess()); - - /** Helper class containing information about the model. */ - private ModelInfo info = new ModelInfo(); - - @Inject(optional = true) - private ValidationMessageAcceptor messageAcceptor; - - /** The declared target. */ - private Target target; - - private List targetPropertyErrors = new ArrayList<>(); - - private List targetPropertyWarnings = new ArrayList<>(); - - ////////////////////////////////////////////////////////////// - //// Private static constants. - - private static String ACTIONS_MESSAGE - = "\"actions\" is a reserved word for the TypeScript target for objects " - + "(inputs, outputs, actions, timers, parameters, state, reactor definitions, " - + "and reactor instantiation): "; - - private static String HOST_OR_FQN_REGEX - = "^([a-z0-9]+(-[a-z0-9]+)*)|(([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,})$"; - - /** - * Regular expression to check the validity of IPV4 addresses (due to David M. Syzdek). - */ - private static String IPV4_REGEX = "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}" + - "(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"; - - /** - * Regular expression to check the validity of IPV6 addresses (due to David M. Syzdek), - * with minor adjustment to allow up to six IPV6 segments (without truncation) in front - * of an embedded IPv4-address. - **/ - private static String IPV6_REGEX = - "(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" + - "([0-9a-fA-F]{1,4}:){1,7}:|" + - "([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" + - "([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" + - "([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" + - "([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" + - "([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" + - "[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" + - ":((:[0-9a-fA-F]{1,4}){1,7}|:)|" + - "fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|" + - "::(ffff(:0{1,4}){0,1}:){0,1}" + IPV4_REGEX + "|" + - "([0-9a-fA-F]{1,4}:){1,4}:" + IPV4_REGEX + "|" + - "([0-9a-fA-F]{1,4}:){1,6}" + IPV4_REGEX + ")"; - - private static String RESERVED_MESSAGE = "Reserved words in the target language are not allowed for objects " - + "(inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation): "; - - private static List SPACING_VIOLATION_POLICIES = List.of("defer", "drop", "replace"); - - private static String UNDERSCORE_MESSAGE = "Names of objects (inputs, outputs, actions, timers, parameters, " - + "state, reactor definitions, and reactor instantiation) may not start with \"__\": "; - - private static String USERNAME_REGEX = "^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\\$)$"; } diff --git a/pom.xml b/pom.xml index 335b368112..83cfdcd3a4 100644 --- a/pom.xml +++ b/pom.xml @@ -17,14 +17,13 @@ 1.4 - + 1.6.10 0.12.0 2.12.1 2.25.0 2.3.0 - 3.23.0 11 diff --git a/test/C/src/RepeatedInheritance.lf b/test/C/src/RepeatedInheritance.lf deleted file mode 100644 index 9081eabaa9..0000000000 --- a/test/C/src/RepeatedInheritance.lf +++ /dev/null @@ -1,37 +0,0 @@ -/** - * This tests for the situation where a reactor extends two other reactors - * that each extend a common reactor. - * @author{Edward A. Lee} - */ -target C { - timeout: 5 sec, - fast: true -} - -import Count from "lib/Count.lf"; -import TestCount from "lib/TestCount.lf"; - -reactor D { - input d:int; -} -reactor C extends D { - input c:int; -} -reactor B extends D { - input b:int; -} -reactor A extends B, C { - input a:int; - output out:int; - reaction(a, b, c, d) -> out {= - SET(out, a->value + b->value + c->value + d->value); - =} -} - -main reactor { - c = new Count(); - a = new A(); - t = new TestCount(start = 4, stride = 4, num_inputs = 6); - (c.out)+ -> a.a, a.b, a.c, a.d; - a.out -> t.in; -} diff --git a/test/C/src/federated/TopLevelArtifacts.lf b/test/C/src/federated/TopLevelArtifacts.lf index 9cbf64673e..b6654f4e28 100644 --- a/test/C/src/federated/TopLevelArtifacts.lf +++ b/test/C/src/federated/TopLevelArtifacts.lf @@ -15,6 +15,8 @@ import TestCount from "../lib/TestCount.lf"; federated reactor { + input in:int; + output out:int; state successes:int(0); reaction (startup) {= self->successes++; @@ -25,8 +27,15 @@ schedule(act, 0); =} logical action act(0); - reaction (act) {= + reaction (act) in -> out {= self->successes++; + if (in->is_present) { + error_print_and_exit("Input is present in the top-level reactor!"); + } + SET(out, 1); + if (out->value != 1) { + error_print_and_exit("Ouput has unexpected value %d!", out->value); + } =} c = new Count(); diff --git a/test/Cpp/src/Timeout_Test.lf b/test/Cpp/src/Timeout_Test.lf index ea4e200441..1ec45272ca 100644 --- a/test/Cpp/src/Timeout_Test.lf +++ b/test/Cpp/src/Timeout_Test.lf @@ -41,4 +41,4 @@ main reactor Timeout_Test { producer = new Sender(break_interval = 1 msec); producer.out -> consumer.in; -} +} \ No newline at end of file