diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index a5c64b901..d42c33b15 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -2,13 +2,18 @@ All notable changes to this project will be documented in this file. -## Unreleased +## 2024-09-15 ### Fixed * Fixed slightly wrong (too low) cost estimation in the [Case Switching](doc/syntax/SYNTAX-6-OPTIMIZATIONS.markdown#case-switching) optimization. +### Added + +* New [`remark()` function](doc/syntax/SYNTAX-4-FUNCTIONS.markdown#remark), as described + [here](https://github.com/cardillan/mindcode/issues/140). + ### Changed * Slightly improved the [Case Switching](doc/syntax/SYNTAX-6-OPTIMIZATIONS.markdown#case-switching) optimization - @@ -17,6 +22,11 @@ All notable changes to this project will be documented in this file. range are handled using conditional jumps. This change saves two instructions and potentially speeds up the execution for input values lying outside the jump table. +### Miscellaneous + +* Some of the Mindcode samples used during unit tests were updated to use semicolons in anticipation to planned + Mindcode changes. + ## 2024-09-10 ### Fixed diff --git a/README.markdown b/README.markdown index e74a377c1..ea7af20d0 100644 --- a/README.markdown +++ b/README.markdown @@ -4,7 +4,8 @@ Welcome to **Mindcode**, a high-level language for [Mindustry](https://github.com/Anuken/Mindustry). Mindustry is a game in the tower-defense genre. Mindustry added Logic in late 2020. Logic is a programming language, closer to -assembly than a high-level language. Mindcode aims to make Mindustry programming easier for everyone. +assembly than a high-level language. Mindcode aims to make Mindustry programming easier for everyone +(see [the documentation](doc/syntax/SYNTAX.markdown)). Mindcode focuses of the following priorities: @@ -13,6 +14,11 @@ Mindcode focuses of the following priorities: and limited instruction space of Mindustry processors. * Employing various [optimizations](doc/syntax/SYNTAX-6-OPTIMIZATIONS.markdown) to produce efficient code. +> [!NOTE] +> Please have a look at planned upcoming changes to Mindcode +> [here](https://github.com/cardillan/mindcode/discussions/142). The changes will be substantial, and any comments +> and suggestions are welcome now while the changes are still being discussed. + **Schemacode** is a specialized definition language designed for creating a complete Mindustry schematic as a text file. [Schematics builder](doc/syntax/SCHEMACODE.markdown) can be used to turn these definition files directly into Mindustry schematics, either as a binary `.msch` file, or as a text. Processors can be included in these schematics, @@ -22,6 +28,7 @@ complete with the code (specified in Mindcode or Mindustry Logic language) and l Some of the latest enhancements to Mindcode are: +* [Remarks](doc/syntax/SYNTAX-4-FUNCTIONS.markdown#remark) can be now included in the compiled code. * [Loop Hoisting](doc/syntax/SYNTAX-6-OPTIMIZATIONS.markdown#loop-hoisting), a new optimization designed to pull invariant code out of loops to avoid repeated execution where possible. * All instructions added in Mindustry 7 build 146 are now supported. diff --git a/ROADMAP.markdown b/ROADMAP.markdown index 532d08f64..ed1d996f4 100644 --- a/ROADMAP.markdown +++ b/ROADMAP.markdown @@ -2,15 +2,16 @@ This documents servers as a scratch pad to track ideas and possible enhancements to Mindcode. +A significant update is planned. Planned changes are described +[here](https://github.com/cardillan/mindcode/discussions/142). Comments and suggestions are welcome. + # Current priorities * Bug fixes and [incremental improvements](#incremental-improvements) -* [Updating ANTLR grammar](#updating-antlr-grammar) * [Inferring invariants of variables](#inferring-invariants-of-variables) * [Speculative optimization for speed](#speculative-optimization-for-speed) * [External variable optimizations](#external-variable-optimizations) * [Function pointers](#function-pointers) -* [Processor-variables backed arrays](#processor-variables-backed-arrays) # Incremental improvements @@ -51,84 +52,6 @@ None planned. * Warn when the generated code goes over 1000 Mindustry instructions. * Warn when potentially non-numeric value is being pushed on the stack. -# Updating ANTLR grammar - -* Rewrite the syntax using lists of elements instead of chained elements where possible. -* Allow empty bodies of ifs, loops, functions etc. -* Allow properties to be invoked on expressions. The nature of the call will be determined by the property name. - * Use mimex to obtain all metadata needed to recognize all valid properties. - * Unknown properties will be delegated to the `sensor` instruction. -* Allow empty optional arguments in function calls. At this moment, optional arguments can only be omitted at the - end of the argument list. -* Block comments `/* this is a block comment that can span several lines */`. -* Text block string literals -* Only allow dashes in REF identifiers (e.g. `@battery-large`) and property names (e.g. `vault.blast-compound`), and - only in the middle of the identifier. Then add support for `++` and `--` operators. `a-b` will become an - expression equivalent to `a - b`. - -## New and extended keywords - -* `allocate`: - * Allow specifying concrete index for external variables - * `allocate $STATE in cell1[7]` - * The index must be a constant integer expression - * Must not overlap with heap. Can use a memory cell/bank different from heap. - * Support for external arrays: - * `allocate array $ARRAY in cell1[32 ... 64]` - * Has the `$` prefix as an external variable (which it is), and to differentiate it from future in-memory arrays. - * Mindcode adds the offset whenever the array is accessed (less efficient code if the offset is not zero). - * Possible out-of-bounds checks. -* `aliased`: used to declare an external variable as aliased, e.g. `declare cell1, $STATE aliased` (see [External - variable optimization](#external-variable-optimizations)). -* `array`: - * Used in `allocate array` - * Reserved for future use in declaring in-memory arrays -* `begin`: - * Marks the beginning of a code block. - * Used mainly to apply a compiler setting to a code block, e.g. `#use (goal = size) begin a = b; c = d; end;`. -* `declare`: - * Used to apply modifiers to a variable -* `enum`: - * Possible syntax: - * `enum name(id1, id2, id3)` - * `enum name: id1, id2, id3 end` - * Mindcode assigns values to the enums as it sees fit. There are no guarantees on the numbers whatsoever, except - preserving the declaration order. They could be instruction addresses inside a case expression, for example, if - there's just one case expression. - * Mindcode provides functions to access enum properties (e.g. `enum.name`, `enum.next`, `enum.previous`). - * Implemented as inline library functions. Using them might be costly. - * Support for enums in list iteration Loops: `for i in enum_name`. - * Enum will internally be a new type of LogicLiteral. -* `fallthrough`: used in case expression to skip to the next `when` branch -* `in`: - * New use as a boolean operator: - * Tests number is in range: `n in min .. max` - * Tests value is in enumerated set: `type in (@sorter, @inverted-sorter)` - * Marks the parameter to a function as input, possible use with `out`, for example in - `def foo(a, in b, out c, in out d)`, `a` and `b` are input parameters, `c` is an output parameter and `d` is - both input/output parameter. -* `linked`: used to declare a variable as a linked block to cover cases where Mindcode doesn't recognize a name of - a linked block for some reason (e.g. `declare message1 linked`). -* `noinline`: prevent function inlining -* `restricted`: used to declare an external variable as aliased, e.g. `declare cell1, $STATE restricted` (see [External - variable optimization](#external-variable-optimizations)). -* `out`: - * Output function parameters - see `in` - * Not passed by reference - Mindustry doesn't allow that. - * On function return, the output value will be copied to the variable passed in as the argument. - * Marks the control variable in a for-each loop as output one. Any changes to the control variable will be - transferred to the variable in the list when the loop ends -* `param` or `parameter`: explicit support for code parametrization - * Defines a new read-only variable and assigns a value to it. - * Compiler will assume the value assigned to this variable can be changed in compiled code. - * Subsequently, the special protection of global variables will be removed. -* `var`: reserved for possible future use in declaring typed variables (maybe we'll use `declare` instead). -* `volatile`: - * Used to declare an external variable as aliased, e.g. `declare cell1, $STATE volatile` (see [External - variable optimization](#external-variable-optimizations)). - * A variable used as an argument to the `sync()` function will be made volatile automatically, no need to declare. -* `yield`: - * Used in when branch of case expression to set resulting value of the branch and exit the case expression. # Additional syntax enhancements @@ -139,16 +62,15 @@ None planned. * The vararg can be processed using list iteration loop, or maybe passed to another vararg function: `def foo(arg...) for a in arg print(a) end end def bar(arg...) foo(arg) end` -## #use compiler directive/statement +## #set local compiler directive/statement ``` -#use (compiler-option = value, compiler-option = value, ...) -[code-block, e.g. function declaration, while loop or begin ... end] +#set local compiler-option = value, compiler-option = value, ...; ``` -Compiles the next code block applying certain compiler options (e.g. `goal`) to it. Some compiler options -(`target`, `optimization`) will remain global and won't be available in `#use`. The intended purpose is to provide -means to compile different parts of code for size or speed. +Compiles the next statement/expression applying certain compiler options (e.g. `goal`) to it. Some compiler options +(`target`, `optimization`) will remain global and won't be available in `#set local`. The intended purpose is to +provide means to compile different parts of code for size or speed. The specific options would probably have to be stored in AST contexts. @@ -245,12 +167,12 @@ Typed variables, parameters and function return values. * Null function pointer corresponds to a special function that indicates an error and stops the program execution. The same prerequisites as above. Ask Anuken for an extension to the stop instruction. -# Processor-variables backed arrays +# Internal arrays -Processor-variables backed arrays will always have a horrible performance, but when used together with loop +Individual elements of internal arrays will be stored in processor variables. Accessing the elements via a dynamic +index will be realized through compiler-defined functions. When used together with loop unrolling or data flow optimization, they might be very useful - and as fast as regular variables. -* Declared at fixed size: `array A[] = (1, 2, 3)` or `array A[4]` * Array name is not a variable - neither an l-value nor an r-value. * No pointers to arrays * Arrays, not lists - no add/remove, no inherent size @@ -263,17 +185,17 @@ unrolling or data flow optimization, they might be very useful - and as fast as * Alternative: virtual instructions. Might allow better loop optimization without inlining. * Maybe array assignments, esp. for same sized arrays * Out-of-bound access checks: compiler directive -* For each syntax over arrays - support modification of the underlying array through specific syntax (`yield exp`) +* For each syntax over arrays - support modification of the underlying array through `out` control loop variables ## Further developments -* Pointers to processor-variables backed arrays: +* Pointers to internal arrays: * Might require typed variables. * Would allow passing arrays to out-of-line functions. -# Processor-variables backed stack +# Internal stack -TODO: add description +Stack stored in processor variables, similar to internal arrays. # Code generation improvements @@ -388,8 +310,6 @@ Two basic approaches * Only perform the optimization when the input value is a known integer * Support ranges in when branches * Omit range checking where possible (requires invariant inferring) -* Support for selecting a subset of existing `when` branches to handle, the jump table `else` entries would then - jump to the remaining branches of the original switch. * Cases with sparse sets of when branches: convert the largest segment from case values with a density higher than 0.5, leave other values to conditional jumps * On `aggressive` level, convert switches that have overlapping values. diff --git a/compiler/src/main/java/info/teksol/mindcode/cmdline/ActionHandler.java b/compiler/src/main/java/info/teksol/mindcode/cmdline/ActionHandler.java index 0d37b29e5..abd9f7646 100644 --- a/compiler/src/main/java/info/teksol/mindcode/cmdline/ActionHandler.java +++ b/compiler/src/main/java/info/teksol/mindcode/cmdline/ActionHandler.java @@ -1,9 +1,6 @@ package info.teksol.mindcode.cmdline; -import info.teksol.mindcode.compiler.CompilerProfile; -import info.teksol.mindcode.compiler.FinalCodeOutput; -import info.teksol.mindcode.compiler.GenerationGoal; -import info.teksol.mindcode.compiler.MemoryModel; +import info.teksol.mindcode.compiler.*; import info.teksol.mindcode.compiler.optimization.Optimization; import info.teksol.mindcode.compiler.optimization.OptimizationLevel; import info.teksol.mindcode.logic.ProcessorEdition; @@ -75,6 +72,12 @@ void configureMindcodeCompiler(Subparser subparser) { .type(Arguments.caseInsensitiveEnumType(GenerationGoal.class)) .setDefault(defaults.getGoal()); + subparser.addArgument("-r", "--remarks") + .help("controls remarks propagation to the compiled code: none (remarks are removed), " + + "passive (remarks are not executed), or active (remarks are printed)") + .type(Arguments.caseInsensitiveEnumType(Remarks.class)) + .setDefault(defaults.getRemarks()); + subparser.addArgument("-m", "--memory-model") .help("sets model for handling linked memory blocks: volatile (shared with different processor), " + "aliased (a memory block may be accessed through different variables), or restricted " + @@ -130,6 +133,7 @@ static CompilerProfile createCompilerProfile(Namespace arguments) { profile.setInstructionLimit(arguments.get("instruction_limit")); profile.setOptimizationPasses(arguments.get("passes")); profile.setGoal(arguments.get("goal")); + profile.setRemarks(arguments.get("remarks")); profile.setMemoryModel(arguments.get("memory_model")); profile.setFinalCodeOutput(arguments.get("print_unresolved")); profile.setPrintStackTrace(arguments.getBoolean("stacktrace")); diff --git a/compiler/src/test/java/info/teksol/mindcode/cmdline/CompileMindcodeActionTest.java b/compiler/src/test/java/info/teksol/mindcode/cmdline/CompileMindcodeActionTest.java index b77d11b98..bd6fff955 100644 --- a/compiler/src/test/java/info/teksol/mindcode/cmdline/CompileMindcodeActionTest.java +++ b/compiler/src/test/java/info/teksol/mindcode/cmdline/CompileMindcodeActionTest.java @@ -2,10 +2,7 @@ import edu.emory.mathcs.backport.java.util.Collections; import info.teksol.mindcode.cmdline.Main.Action; -import info.teksol.mindcode.compiler.CompilerProfile; -import info.teksol.mindcode.compiler.FinalCodeOutput; -import info.teksol.mindcode.compiler.GenerationGoal; -import info.teksol.mindcode.compiler.MemoryModel; +import info.teksol.mindcode.compiler.*; import info.teksol.mindcode.compiler.optimization.Optimization; import info.teksol.mindcode.compiler.optimization.OptimizationLevel; import info.teksol.mindcode.logic.ProcessorEdition; @@ -139,6 +136,12 @@ public void goalArgument() throws ArgumentParserException { assertEquals(GenerationGoal.AUTO, arguments.get("goal")); } + @Test + public void remarksArgument() throws ArgumentParserException { + Namespace arguments = parseCommandLine(Action.COMPILE_MINDCODE.getShortcut() + " -r active"); + assertEquals(Remarks.ACTIVE, arguments.get("remarks")); + } + @Test public void memoryModelArgument() throws ArgumentParserException { Namespace arguments = parseCommandLine(Action.COMPILE_MINDCODE.getShortcut() + " -m restricted"); @@ -147,7 +150,7 @@ public void memoryModelArgument() throws ArgumentParserException { @Test public void createsCompilerProfile() throws ArgumentParserException { - Namespace arguments = parseCommandLine(Action.COMPILE_MINDCODE.getShortcut() + " -t 6 -o off -p 1 -d 3 -u source -s -g size -e 100 -m restricted"); + Namespace arguments = parseCommandLine(Action.COMPILE_MINDCODE.getShortcut() + " -t 6 -o off -p 1 -d 3 -u source -s -g size -r active -e 100 -m restricted"); CompilerProfile actual = ActionHandler.createCompilerProfile(arguments); assertEquals(ProcessorEdition.STANDARD_PROCESSOR, actual.getProcessorEdition()); @@ -157,6 +160,7 @@ public void createsCompilerProfile() throws ArgumentParserException { assertEquals(3, actual.getDebugLevel()); assertEquals(100, actual.getOptimizationPasses()); assertEquals(GenerationGoal.SIZE, actual.getGoal()); + assertEquals(Remarks.ACTIVE, actual.getRemarks()); assertEquals(MemoryModel.RESTRICTED, actual.getMemoryModel()); assertEquals(FinalCodeOutput.SOURCE, actual.getFinalCodeOutput()); assertTrue(actual.isPrintStackTrace()); @@ -176,6 +180,7 @@ public void createsCompilerProfileDefault() throws ArgumentParserException { assertEquals(CompilerProfile.DEFAULT_INSTRUCTIONS, actual.getInstructionLimit()); assertEquals(CompilerProfile.DEFAULT_CMDLINE_PASSES, actual.getOptimizationPasses()); assertEquals(expected.getGoal(), actual.getGoal()); + assertEquals(expected.getRemarks(), actual.getRemarks()); assertEquals(expected.getMemoryModel(), actual.getMemoryModel()); assertEquals(expected.getFinalCodeOutput(), actual.getFinalCodeOutput()); assertEquals(expected.isPrintStackTrace(), actual.isPrintStackTrace()); diff --git a/doc/syntax/SYNTAX-4-FUNCTIONS.markdown b/doc/syntax/SYNTAX-4-FUNCTIONS.markdown index 70322541b..e69ed06a5 100644 --- a/doc/syntax/SYNTAX-4-FUNCTIONS.markdown +++ b/doc/syntax/SYNTAX-4-FUNCTIONS.markdown @@ -241,6 +241,100 @@ const fmt = "Position: $, $\n" printf(fmt, x, y) // Allowed - fmt is a string constant ``` +## remark + +The `remark` function has the same syntax as the `printf` function. It produces print instructions similarly to the +`printf` function, but the way these instructions are generated into the code can be controlled using the [`remarks` +option](SYNTAX-5-OTHER.markdown#option-remarks). + +Example: + +``` +remark("Configurable options:"); +MIN = 10; +MAX = 100; + +remark("Don't modify anything below this line."); +for i in MIN .. MAX + print("Here is some actual code"); +end +``` + +produces + +``` +jump 2 always 0 0 +print "Configurable options:" +set MIN 10 +set MAX 100 +jump 6 always 0 0 +print "Don't modify anything below this line." +set i MIN +jump 0 greaterThan MIN MAX +print "Here is some actual code" +op add i i 1 +jump 8 lessThanEq i MAX +end +``` + +Note that remarks are preceded by a jump that skips their execution - this is the default behavior. Remarks can also +be made active, which removes the jumps, or not included in the compiled code at all, depending on the `remarks` +compiler option. + +Remarks may also allow for better orientation in compiled code, especially as expressions inside remarks will get +fully evaluated when possible: + +``` +for i in 1 .. 3 + remark("Iteration $i:"); + remark("Setting cell1[$i] to $", i * i); + cell1[i] = i * i; +end; +``` + +compiles into + +``` +jump 3 always 0 0 +print "Iteration 1:" +print "Setting cell1[1] to 1" +write 1 cell1 1 +jump 7 always 0 0 +print "Iteration 2:" +print "Setting cell1[2] to 4" +write 4 cell1 2 +jump 11 always 0 0 +print "Iteration 3:" +print "Setting cell1[3] to 9" +write 9 cell1 3 +end +``` + +As you can see, remarks produced by two different `remark()` function calls are not merged together. + +If a region of code is unreachable and is optimized away, the remarks are also stripped: + +``` +const DEBUG = false; + +if DEBUG + remark("Compiled for DEBUG"); +else + remark("Compiled for RELEASE"); +end; + +print("Hello"); +``` + +produces + +``` +jump 2 always 0 0 +print "Compiled for RELEASE" +print "Hello" +end +``` + # User-defined Functions You may declare your own functions using the `def` keyword: @@ -314,10 +408,9 @@ printtext("Speed", speed, minSpeed, maxSpeed) Large inline functions called multiple times can generate lots of instructions and make the compiled code too long. If this happens, remove the `inline` keyword from some function definitions to generate less code. -The compiler will automatically make a function inline when it is called just once in the entire program. -This is safe, as in this case the program will always be both smaller and faster. if a function is called more than -once, it can still be inlined by the -[Function Inlining optimization](SYNTAX-6-OPTIMIZATIONS.markdown#function-inlining). +The compiler will automatically make a function inline when it is called just once in the entire program. This +is safe, as in this case the program will always be both smaller and faster. if a function is called more than once, +it can still be inlined by the [Function Inlining optimization](SYNTAX-6-OPTIMIZATIONS.markdown#function-inlining). ## Recursive functions diff --git a/doc/syntax/SYNTAX-5-OTHER.markdown b/doc/syntax/SYNTAX-5-OTHER.markdown index 55ebe5c2d..f1df046d0 100644 --- a/doc/syntax/SYNTAX-5-OTHER.markdown +++ b/doc/syntax/SYNTAX-5-OTHER.markdown @@ -26,6 +26,23 @@ Possible values for this option are: * `ML7AS`: compile for Mindcode Logic version 7 (revision A) standard processors * `ML7AW`: compile for Mindcode Logic version 7 (revision A) world processor +## Option `remarks` + +This option controls the way remarks, generated through the [remark() function](SYNTAX-4-FUNCTIONS.markdown#remark), +are propagated to the compiled code. Remarks are written into the compiled code as `print` instructions. Possible values +of the `remarks` option are: + +* `none`: remarks are suppressed in the compiled code - they do not appear there at all. +* `passive`: remarks are included in the compiled code, but a jump is generated in front each block of continuous + remarks, so that the print statement themselves aren't executed. This is the default value. +* `active`: remarks are included in the compiled code can be executed, producing actual output to the text buffer. + +Passive remarks can be used for putting instructions or comments in the compiled code, or to mark a specific portion +of the code. Remarks in a loop may help identifying individual iterations when the loop is unrolled, for example. + +Active remarks can be used to easily add debugging output to a program that can be deactivated using a compiler +option (potentially through a command line switch without modifying the source code). + ## Option `goal` Use the `goal` option to specify whether Mindcode should prefer to generate smaller code, or faster code. diff --git a/doc/syntax/SYNTAX-6-OPTIMIZATIONS.markdown b/doc/syntax/SYNTAX-6-OPTIMIZATIONS.markdown index f1b27e5ae..bb26f9a05 100644 --- a/doc/syntax/SYNTAX-6-OPTIMIZATIONS.markdown +++ b/doc/syntax/SYNTAX-6-OPTIMIZATIONS.markdown @@ -1376,6 +1376,18 @@ longer than 34 characters (36 when counting the double quotes). On `aggressive` will be merged regardless. This can create long string constants, but according to our tests these can be pasted into Mindustry processors even if they're longer than what the Mindustry GUI allows to enter. +### Remarks + +The print merging optimization will also merge print instructions generated by the `remark()` function. Instructions +generated by remarks are merged differently to normal print instructions: + +* `print` instructions generated from different `remark()` function calls are never merged. Only instructions + generated from a single `remark()` are merged together. +* All constant values are merged together regardless of the resulting string length, even on the `basic` + optimization level. + +If the print merging optimization is not active, instructions from `remark()` function aren't merged. + # Optimization for speed In some cases, it is possible to rearrange the code in such a way that, even though it consist of more instructions, diff --git a/doc/syntax/SYNTAX.markdown b/doc/syntax/SYNTAX.markdown index 7185bc0a8..af238876e 100644 --- a/doc/syntax/SYNTAX.markdown +++ b/doc/syntax/SYNTAX.markdown @@ -54,6 +54,9 @@ is not and could be used as a variable or function name). Anything following a `//` is a comment till the end of the line. Comments are completely ignored by Mindcode. +You can use the [remark() function](SYNTAX-4-FUNCTIONS.markdown#remark) to place comments or notes directly to the +compiled code. + ## Keywords This is a list of Mindcode keywords: @@ -144,7 +147,7 @@ To find a way around these constraints, Mindcode always reads the value of the n to Mindustry Logic compatible literal using these rules (first applicable rule is used): 1. If the value is zero, it is encoded as `0`. -2. If the value is between 1-20 and 263-1, the number is converted to standard decimal +2. If the value is between 10-20 and 263-1, the number is converted to standard decimal notation using 20 digits precision. 3. If the value is between 10-38 and 1038, the number is converted to exponential notation without using a decimal separator, using `float` precision (which will be used by Mindustry processor when diff --git a/doc/syntax/TOOLS-CMDLINE.markdown b/doc/syntax/TOOLS-CMDLINE.markdown index f4c652687..752e351d8 100644 --- a/doc/syntax/TOOLS-CMDLINE.markdown +++ b/doc/syntax/TOOLS-CMDLINE.markdown @@ -81,8 +81,8 @@ usage: mindcode cm [-h] [-c] [-l [LOG]] [-o LEVEL] [--temp-variables-elimination [--case-switching LEVEL] [--return-optimization LEVEL] [--jump-straightening LEVEL] [--jump-threading LEVEL] [--unreachable-code-elimination LEVEL] [--stack-optimization LEVEL] [--print-merging LEVEL] [-t {6,7s,7w,7as,7aw}] [-i {1..100000}] [-e {1..1000}] [-g {SIZE,SPEED,AUTO}] - [-m {VOLATILE,ALIASED,RESTRICTED}] [-p {0..2}] [-d {0..3}] [-u [{PLAIN,FLAT_AST,DEEP_AST,SOURCE}]] [-s] - [input] [output] + [-r {NONE,PASSIVE,ACTIVE}] [-m {VOLATILE,ALIASED,RESTRICTED}] [-p {0..2}] [-d {0..3}] + [-u [{PLAIN,FLAT_AST,DEEP_AST,SOURCE}]] [-s] [input] [output] Compile a mindcode source file into text mlog file. @@ -99,6 +99,9 @@ named arguments: -g, --goal {SIZE,SPEED,AUTO} sets code generation goal: minimize code size, minimize execution speed, or choose automatically + -r, --remarks {NONE,PASSIVE,ACTIVE} + controls remarks propagation to the compiled code: none (remarks are removed), passive + (remarks are not executed), or active (remarks are printed) -m, --memory-model {VOLATILE,ALIASED,RESTRICTED} sets model for handling linked memory blocks: volatile (shared with different processor), aliased (a memory block may be accessed through different variables), or restricted (a memory @@ -183,8 +186,8 @@ usage: mindcode cs [-h] [-c] [-l [LOG]] [-o LEVEL] [--temp-variables-elimination [--case-switching LEVEL] [--return-optimization LEVEL] [--jump-straightening LEVEL] [--jump-threading LEVEL] [--unreachable-code-elimination LEVEL] [--stack-optimization LEVEL] [--print-merging LEVEL] [-t {6,7s,7w,7as,7aw}] [-i {1..100000}] [-e {1..1000}] [-g {SIZE,SPEED,AUTO}] - [-m {VOLATILE,ALIASED,RESTRICTED}] [-p {0..2}] [-d {0..3}] [-u [{PLAIN,FLAT_AST,DEEP_AST,SOURCE}]] [-s] - [-a TAG [TAG ...]] [input] [output] + [-r {NONE,PASSIVE,ACTIVE}] [-m {VOLATILE,ALIASED,RESTRICTED}] [-p {0..2}] [-d {0..3}] + [-u [{PLAIN,FLAT_AST,DEEP_AST,SOURCE}]] [-s] [-a TAG [TAG ...]] [input] [output] Compile a schema definition file into binary msch file. @@ -201,6 +204,9 @@ named arguments: -g, --goal {SIZE,SPEED,AUTO} sets code generation goal: minimize code size, minimize execution speed, or choose automatically + -r, --remarks {NONE,PASSIVE,ACTIVE} + controls remarks propagation to the compiled code: none (remarks are removed), passive + (remarks are not executed), or active (remarks are printed) -m, --memory-model {VOLATILE,ALIASED,RESTRICTED} sets model for handling linked memory blocks: volatile (shared with different processor), aliased (a memory block may be accessed through different variables), or restricted (a memory diff --git a/mindcode/src/main/java/info/teksol/mindcode/compiler/CompilerProfile.java b/mindcode/src/main/java/info/teksol/mindcode/compiler/CompilerProfile.java index 0aa872095..b0a0f25d3 100644 --- a/mindcode/src/main/java/info/teksol/mindcode/compiler/CompilerProfile.java +++ b/mindcode/src/main/java/info/teksol/mindcode/compiler/CompilerProfile.java @@ -29,6 +29,7 @@ public class CompilerProfile { private int instructionLimit = 1000; private int optimizationPasses = DEFAULT_WEBAPP_PASSES; private GenerationGoal goal = GenerationGoal.AUTO; + private Remarks remarks = Remarks.PASSIVE; private MemoryModel memoryModel = MemoryModel.VOLATILE; private boolean shortCircuitEval = false; private FinalCodeOutput finalCodeOutput = null; @@ -138,6 +139,14 @@ public CompilerProfile setGoal(GenerationGoal goal) { return this; } + public Remarks getRemarks() { + return remarks; + } + + public void setRemarks(Remarks remarks) { + this.remarks = remarks; + } + public MemoryModel getMemoryModel() { return memoryModel; } diff --git a/mindcode/src/main/java/info/teksol/mindcode/compiler/DirectiveProcessor.java b/mindcode/src/main/java/info/teksol/mindcode/compiler/DirectiveProcessor.java index f21a7da6b..0522677a9 100644 --- a/mindcode/src/main/java/info/teksol/mindcode/compiler/DirectiveProcessor.java +++ b/mindcode/src/main/java/info/teksol/mindcode/compiler/DirectiveProcessor.java @@ -129,6 +129,16 @@ private void setGenerationGoal(CompilerProfile compilerProfile, String strGoal) error("Invalid value '%s' of compiler directive 'goal'.", strGoal); } + private void setRemarks(CompilerProfile compilerProfile, String strRemarks) { + for (Remarks value : Remarks.values()) { + if (value.name().equalsIgnoreCase(strRemarks)) { + compilerProfile.setRemarks(value); + return; + } + } + error("Invalid value '%s' of compiler directive 'remarks'.", strRemarks); + } + private void setMemoryModel(CompilerProfile compilerProfile, String strModel) { for (MemoryModel memoryModel : MemoryModel.values()) { if (memoryModel.name().equalsIgnoreCase(strModel)) { @@ -149,6 +159,7 @@ private Map> createOptionHandlers() { map.put("instruction-limit", this::setInstructionLimit); map.put("passes", this::setOptimizationPasses); map.put("goal", this::setGenerationGoal); + map.put("remarks", this::setRemarks); map.put("memory-model", this::setMemoryModel); for (Optimization opt : Optimization.values()) { map.put(opt.getOptionName(), (profile, level) -> setOptimizationLevel(opt, profile, level)); diff --git a/mindcode/src/main/java/info/teksol/mindcode/compiler/LogicInstructionLabelResolver.java b/mindcode/src/main/java/info/teksol/mindcode/compiler/LogicInstructionLabelResolver.java index d44b8246a..a51d29f1b 100644 --- a/mindcode/src/main/java/info/teksol/mindcode/compiler/LogicInstructionLabelResolver.java +++ b/mindcode/src/main/java/info/teksol/mindcode/compiler/LogicInstructionLabelResolver.java @@ -1,10 +1,7 @@ package info.teksol.mindcode.compiler; import info.teksol.mindcode.MindcodeInternalError; -import info.teksol.mindcode.compiler.instructions.GotoOffsetInstruction; -import info.teksol.mindcode.compiler.instructions.InstructionProcessor; -import info.teksol.mindcode.compiler.instructions.LabeledInstruction; -import info.teksol.mindcode.compiler.instructions.LogicInstruction; +import info.teksol.mindcode.compiler.instructions.*; import info.teksol.mindcode.logic.*; import java.util.ArrayList; @@ -15,23 +12,69 @@ import static info.teksol.mindcode.logic.Opcode.OP; public class LogicInstructionLabelResolver { + private final CompilerProfile profile; private final InstructionProcessor instructionProcessor; private final Map addresses = new HashMap<>(); - public LogicInstructionLabelResolver(InstructionProcessor instructionProcessor) { + public LogicInstructionLabelResolver(InstructionProcessor instructionProcessor, CompilerProfile profile) { this.instructionProcessor = instructionProcessor; + this.profile = profile; } - public static List resolve(InstructionProcessor instructionProcessor, List program) { - return new LogicInstructionLabelResolver(instructionProcessor).resolve(program); + public static List resolve(InstructionProcessor instructionProcessor, CompilerProfile profile, + List program) { + return new LogicInstructionLabelResolver(instructionProcessor, profile).resolve(program); } private List resolve(List program) { + program = resolveRemarks(program); calculateAddresses(program); return resolveAddresses(resolveVirtualInstructions(program)); } + private List resolveRemarks(List program) { + return switch (profile.getRemarks()) { + case ACTIVE -> resolveRemarksActive(program); + case NONE -> resolveRemarksNone(program); + case PASSIVE -> resolveRemarksPassive(program); + }; + } + + private List resolveRemarksActive(List program) { + return program.stream() + .map(ix -> ix instanceof RemarkInstruction r ? instructionProcessor.createPrint(r.getAstContext(), r.getValue()) : ix) + .toList(); + } + + private List resolveRemarksNone(List program) { + return program.stream() + .filter(ix -> !(ix instanceof RemarkInstruction)) + .toList(); + } + + private List resolveRemarksPassive(List program) { + List result = new ArrayList<>(); + LabelInstruction activeLabel = null; + for (LogicInstruction ix : program) { + if (ix instanceof RemarkInstruction) { + if (activeLabel == null) { + LogicLabel label = instructionProcessor.nextLabel(); + activeLabel = instructionProcessor.createLabel(ix.getAstContext(),label); + result.add(instructionProcessor.createJumpUnconditional(ix.getAstContext(), label)); + } + result.add(instructionProcessor.createPrint(ix.getAstContext(), ((RemarkInstruction) ix).getValue())); + } else { + if (activeLabel != null) { + result.add(activeLabel); + activeLabel = null; + } + result.add(ix); + } + } + + return result; + } private void calculateAddresses(List program) { int instructionPointer = 0; diff --git a/mindcode/src/main/java/info/teksol/mindcode/compiler/MindcodeCompiler.java b/mindcode/src/main/java/info/teksol/mindcode/compiler/MindcodeCompiler.java index 2ec4fa8cf..f3cd13890 100644 --- a/mindcode/src/main/java/info/teksol/mindcode/compiler/MindcodeCompiler.java +++ b/mindcode/src/main/java/info/teksol/mindcode/compiler/MindcodeCompiler.java @@ -74,7 +74,7 @@ public CompilerOutput compile(String sourceCode) { info("Performance: parsed in %,d ms, compiled in %,d ms, optimized in %,d ms.".formatted(parseTime, compileTime, optimizeTime)); - result = LogicInstructionLabelResolver.resolve(instructionProcessor, result); + result = LogicInstructionLabelResolver.resolve(instructionProcessor, profile,result); instructions = LogicInstructionPrinter.toString(instructionProcessor, result); } catch (Exception e) { diff --git a/mindcode/src/main/java/info/teksol/mindcode/compiler/Remarks.java b/mindcode/src/main/java/info/teksol/mindcode/compiler/Remarks.java new file mode 100644 index 000000000..80a89fda0 --- /dev/null +++ b/mindcode/src/main/java/info/teksol/mindcode/compiler/Remarks.java @@ -0,0 +1,7 @@ +package info.teksol.mindcode.compiler; + +public enum Remarks { + NONE, + PASSIVE, + ACTIVE +} diff --git a/mindcode/src/main/java/info/teksol/mindcode/compiler/generator/LogicInstructionGenerator.java b/mindcode/src/main/java/info/teksol/mindcode/compiler/generator/LogicInstructionGenerator.java index 81739a98b..d056a9f2a 100644 --- a/mindcode/src/main/java/info/teksol/mindcode/compiler/generator/LogicInstructionGenerator.java +++ b/mindcode/src/main/java/info/teksol/mindcode/compiler/generator/LogicInstructionGenerator.java @@ -13,6 +13,7 @@ import info.teksol.mindcode.mimex.Icons; import java.util.*; +import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.regex.Matcher; @@ -234,6 +235,10 @@ public ReadInstruction createRead(LogicVariable result, LogicVariable memory, Lo return instructionProcessor.createRead(astContext, result, memory, index); } + public RemarkInstruction createRemark(LogicValue what) { + return instructionProcessor.createRemark(astContext, what); + } + public ReturnInstruction createReturn(LogicVariable memory) { return instructionProcessor.createReturn(astContext, memory); } @@ -384,6 +389,7 @@ public LogicValue visitFunctionCall(FunctionCall node) { // Special cases LogicValue returnValue = switch (node.getFunctionName()) { case "printf" -> handlePrintf(node, arguments); + case "remark" -> handleRemark(node, arguments); default -> handleFunctionCall(node.getFunctionName(), arguments); }; @@ -1200,23 +1206,48 @@ public LogicValue visitControl(Control node) { return value; } + enum Formatter { + PRINTF("printf", LogicInstructionGenerator::createPrint), + REMARK("remark", LogicInstructionGenerator::createRemark); + + final String function; + final BiFunction creator; + + Formatter(String function, BiFunction creator) { + this.function = function; + this.creator = creator; + } + + LogicInstruction createInstruction(LogicInstructionGenerator generator, LogicValue value) { + return creator.apply(generator, value); + } + } + private LogicValue handlePrintf(FunctionCall node, List params) { + return handleFormattedOutput(Formatter.PRINTF, node, params); + } + + private LogicValue handleRemark(FunctionCall node, List params) { + return handleFormattedOutput(Formatter.REMARK, node, params); + } + + private LogicValue handleFormattedOutput(Formatter formatter, FunctionCall node, List params) { // Printf format string may contain references to variables, which is practically a code // Must be therefore handled here and not by the FunctionMapper - + if (params.isEmpty()) { - throw new MindcodeException(node.startToken(), "first parameter of printf() function must be a constant string value."); + throw new MindcodeException(node.startToken(), "first parameter of %s() function must be a constant string value.", formatter.function); } AstNode astFormat = expressionEvaluator.evaluate(node.getParams().get(0)); if (astFormat instanceof StringLiteral format) { - return handlePrintf(node, format.getText(), params); + return handleFormattedOutput(formatter, node, format.getText(), params); } else { - throw new MindcodeException(node.startToken(), "first parameter of printf() function must be a constant string value."); + throw new MindcodeException(node.startToken(), "first parameter of %s() function must be a constant string value.", formatter.function); } } - private LogicValue handlePrintf(FunctionCall node, String format, List params) { + private LogicValue handleFormattedOutput(Formatter formatter, FunctionCall node, String format, List params) { boolean escape = false; StringBuilder accumulator = new StringBuilder(); int position = 1; // Skipping the 1st param, which is the format string @@ -1235,7 +1266,7 @@ private LogicValue handlePrintf(FunctionCall node, String format, List 0) { - emit(createPrint(LogicString.create(accumulator.toString()))); + emit(formatter.createInstruction(this, LogicString.create(accumulator.toString()))); accumulator.setLength(0); } @@ -1243,16 +1274,16 @@ private LogicValue handlePrintf(FunctionCall node, String format, List 0) { - emit(createPrint(LogicString.create(accumulator.toString()))); + emit(formatter.createInstruction(this,LogicString.create(accumulator.toString()))); } if (position < params.size()) { - throw new MindcodeException(node.startToken(), "too many arguments for printf format string."); + throw new MindcodeException(node.startToken(), "too many arguments for %s() format string.", formatter.function); } return NULL; diff --git a/mindcode/src/main/java/info/teksol/mindcode/compiler/instructions/BaseInstructionProcessor.java b/mindcode/src/main/java/info/teksol/mindcode/compiler/instructions/BaseInstructionProcessor.java index 0535533db..b93929090 100644 --- a/mindcode/src/main/java/info/teksol/mindcode/compiler/instructions/BaseInstructionProcessor.java +++ b/mindcode/src/main/java/info/teksol/mindcode/compiler/instructions/BaseInstructionProcessor.java @@ -177,6 +177,11 @@ public ReadInstruction createRead(AstContext astContext, LogicVariable result, L return (ReadInstruction) createInstruction(astContext, READ, result, memory, index); } + @Override + public RemarkInstruction createRemark(AstContext astContext, LogicValue what) { + return (RemarkInstruction) createInstruction(astContext, REMARK, what); + } + @Override public ReturnInstruction createReturn(AstContext astContext, LogicVariable stack) { return (ReturnInstruction) createInstruction(astContext, RETURN, stack); @@ -239,6 +244,7 @@ public LogicInstruction createInstructionUnchecked(AstContext astContext, Opcode case PRINTFLUSH -> new PrintflushInstruction(astContext, arguments, params); case PUSH -> new PushInstruction(astContext, arguments, params); case READ -> new ReadInstruction(astContext, arguments, params); + case REMARK -> new RemarkInstruction(astContext, arguments, params); case RETURN -> new ReturnInstruction(astContext, arguments, params); case SENSOR -> new SensorInstruction(astContext, arguments, params); case SET -> new SetInstruction(astContext, arguments, params); diff --git a/mindcode/src/main/java/info/teksol/mindcode/compiler/instructions/InstructionProcessor.java b/mindcode/src/main/java/info/teksol/mindcode/compiler/instructions/InstructionProcessor.java index 0ce0917ca..e1810a3d9 100644 --- a/mindcode/src/main/java/info/teksol/mindcode/compiler/instructions/InstructionProcessor.java +++ b/mindcode/src/main/java/info/teksol/mindcode/compiler/instructions/InstructionProcessor.java @@ -47,6 +47,7 @@ public interface InstructionProcessor { PrintflushInstruction createPrintflush(AstContext astContext, LogicVariable messageBlock); PushInstruction createPush(AstContext astContext, LogicVariable memory, LogicVariable value); ReadInstruction createRead(AstContext astContext, LogicVariable result, LogicVariable memory, LogicValue index); + RemarkInstruction createRemark(AstContext astContext, LogicValue what); ReturnInstruction createReturn(AstContext astContext, LogicVariable stack); SensorInstruction createSensor(AstContext astContext, LogicVariable result, LogicValue target, LogicValue property); SetInstruction createSet(AstContext astContext, LogicVariable target, LogicValue value); diff --git a/mindcode/src/main/java/info/teksol/mindcode/compiler/instructions/RemarkInstruction.java b/mindcode/src/main/java/info/teksol/mindcode/compiler/instructions/RemarkInstruction.java new file mode 100644 index 000000000..3acd73afd --- /dev/null +++ b/mindcode/src/main/java/info/teksol/mindcode/compiler/instructions/RemarkInstruction.java @@ -0,0 +1,34 @@ +package info.teksol.mindcode.compiler.instructions; + +import info.teksol.mindcode.compiler.generator.AstContext; +import info.teksol.mindcode.logic.LogicArgument; +import info.teksol.mindcode.logic.LogicParameter; +import info.teksol.mindcode.logic.LogicValue; +import info.teksol.mindcode.logic.Opcode; + +import java.util.List; + +public class RemarkInstruction extends BaseInstruction { + + RemarkInstruction(AstContext astContext, List args, List params) { + super(astContext, Opcode.REMARK, args, params); + } + + private RemarkInstruction(BaseInstruction other, AstContext astContext) { + super(other, astContext); + } + + @Override + public RemarkInstruction copy() { + return new RemarkInstruction(this, astContext); + } + + @Override + public RemarkInstruction withContext(AstContext astContext) { + return new RemarkInstruction(this, astContext); + } + + public final LogicValue getValue() { + return (LogicValue) getArg(0); + } +} diff --git a/mindcode/src/main/java/info/teksol/mindcode/compiler/optimization/AbstractOptimizer.java b/mindcode/src/main/java/info/teksol/mindcode/compiler/optimization/AbstractOptimizer.java index cb30076bf..c7699d276 100644 --- a/mindcode/src/main/java/info/teksol/mindcode/compiler/optimization/AbstractOptimizer.java +++ b/mindcode/src/main/java/info/teksol/mindcode/compiler/optimization/AbstractOptimizer.java @@ -158,6 +158,10 @@ protected ReadInstruction createRead(AstContext astContext, LogicVariable result return instructionProcessor.createRead(astContext, result, memory, index); } + protected RemarkInstruction createRemark(AstContext astContext, LogicValue what) { + return instructionProcessor.createRemark(astContext, what); + } + protected ReturnInstruction createReturn(AstContext astContext, LogicVariable stack) { return instructionProcessor.createReturn(astContext, stack); } diff --git a/mindcode/src/main/java/info/teksol/mindcode/compiler/optimization/PrintMerger.java b/mindcode/src/main/java/info/teksol/mindcode/compiler/optimization/PrintMerger.java index 9c884e982..ea94efa81 100644 --- a/mindcode/src/main/java/info/teksol/mindcode/compiler/optimization/PrintMerger.java +++ b/mindcode/src/main/java/info/teksol/mindcode/compiler/optimization/PrintMerger.java @@ -2,7 +2,6 @@ import info.teksol.mindcode.compiler.instructions.*; import info.teksol.mindcode.compiler.optimization.OptimizationContext.LogicIterator; -import info.teksol.mindcode.logic.LogicLabel; import info.teksol.mindcode.logic.LogicLiteral; import info.teksol.mindcode.logic.LogicString; @@ -38,7 +37,7 @@ public PrintMerger(OptimizationContext optimizationContext) { super(Optimization.PRINT_TEXT_MERGING, optimizationContext); } - private PrintInstruction previous; + private LogicInstruction previous; @Override protected boolean optimizeProgram(OptimizationPhase phase) { @@ -48,6 +47,7 @@ protected boolean optimizeProgram(OptimizationPhase phase) { while (iterator.hasNext()) { switch (iterator.next()) { case PrintInstruction current -> tryMerge(iterator, current); + case RemarkInstruction current -> tryMerge(iterator, current); // Do not merge across jump, (active) label and printflush instructions // Function calls generate a label, so they prevent merging as well @@ -65,16 +65,16 @@ protected boolean optimizeProgram(OptimizationPhase phase) { return false; } - // Tries to merge previous and current. + // Tries to merge previous and current prints. // When successful, updates instructions and sets previous to the newly merged instruction. // If the merge is not possible, sets previous to current private void tryMerge(LogicIterator iterator, PrintInstruction current) { - if (previous != null && previous.getValue() instanceof LogicLiteral lit1 && current.getValue() instanceof LogicLiteral lit2) { - if (aggressive() || (lit1.getType() == STRING_LITERAL && lit2.getType() == STRING_LITERAL)) { + if (previous instanceof PrintInstruction p && p.getValue() instanceof LogicLiteral lit1 && current.getValue() instanceof LogicLiteral lit2) { + if (aggressive() || lit1.getType() == STRING_LITERAL && lit2.getType() == STRING_LITERAL) { String str1 = lit1.format(); String str2 = lit2.format(); // Do not merge strings if the combined length is over 34, unless aggressive - if (str1.length() + str2.length() <= 34 || aggressive()) { + if (aggressive() || str1.length() + str2.length() <= 34) { PrintInstruction merged = createPrint(current.getAstContext(), LogicString.create(str1 + str2)); removeInstruction(previous); iterator.set(merged); @@ -87,10 +87,23 @@ private void tryMerge(LogicIterator iterator, PrintInstruction current) { previous = current; } - // TODO find or create a function for this in OptimizationContext - // This might miss some active labels + // Tries to merge previous and current remarks. + // When successful, updates instructions and sets previous to the newly merged instruction. + // If the merge is not possible, sets previous to current + private void tryMerge(LogicIterator iterator, RemarkInstruction current) { + if (previous instanceof RemarkInstruction r && r.getAstContext() == current.getAstContext() && + r.getValue() instanceof LogicLiteral lit1 && current.getValue() instanceof LogicLiteral lit2) { + RemarkInstruction merged = createRemark(current.getAstContext(), LogicString.create(lit1.format() + lit2.format())); + removeInstruction(previous); + iterator.set(merged); + previous = merged; + return; + } + + previous = current; + } + private boolean isActive(LabelInstruction ix) { - return firstInstructionIndex(ixx -> ixx != ix && ixx.getArgs().stream() - .anyMatch(a -> a instanceof LogicLabel la && la.equals(ix.getLabel()))) >= 0; + return optimizationContext.isActive(ix.getLabel()); } } diff --git a/mindcode/src/main/java/info/teksol/mindcode/logic/MindustryOpcodeVariants.java b/mindcode/src/main/java/info/teksol/mindcode/logic/MindustryOpcodeVariants.java index 15695ea1a..e5b919f48 100644 --- a/mindcode/src/main/java/info/teksol/mindcode/logic/MindustryOpcodeVariants.java +++ b/mindcode/src/main/java/info/teksol/mindcode/logic/MindustryOpcodeVariants.java @@ -269,6 +269,7 @@ private List initialize() { add(list, V6, V7A, S, NONE, Opcode.GOTO, in("address"), label("marker")); add(list, V6, V7A, S, NONE, Opcode.GOTOOFFSET, label("address"), in("value"), in("offset"), label("marker")); add(list, V6, V7A, S, NONE, Opcode.SETADDR, out("result"), in("address")); + add(list, V6, V7A, S, NONE, Opcode.REMARK, in("remark")); return List.copyOf(list); } diff --git a/mindcode/src/main/java/info/teksol/mindcode/logic/Opcode.java b/mindcode/src/main/java/info/teksol/mindcode/logic/Opcode.java index 754d35614..962a450c5 100644 --- a/mindcode/src/main/java/info/teksol/mindcode/logic/Opcode.java +++ b/mindcode/src/main/java/info/teksol/mindcode/logic/Opcode.java @@ -56,6 +56,7 @@ public enum Opcode { GOTO ("goto", 1), GOTOOFFSET ("gotooffset", 1), SETADDR ("setaddr", 1), + REMARK ("remark", 2), ; private final String opcode; diff --git a/mindcode/src/test/java/info/teksol/mindcode/compiler/AbstractGeneratorTest.java b/mindcode/src/test/java/info/teksol/mindcode/compiler/AbstractGeneratorTest.java index d6020da2c..26732c4ce 100644 --- a/mindcode/src/test/java/info/teksol/mindcode/compiler/AbstractGeneratorTest.java +++ b/mindcode/src/test/java/info/teksol/mindcode/compiler/AbstractGeneratorTest.java @@ -209,7 +209,8 @@ protected GeneratorOutput generateInstructions(String code) { protected final AstContext mockAstRootContext = AstContext.createRootNode(); protected final AstContext mockAstContext = mockAstRootContext.createSubcontext(AstSubcontextType.BASIC, 1.0); - protected final InstructionProcessor ip = createInstructionProcessor(createCompilerProfile(), s ->{}); + protected final CompilerProfile profile = createCompilerProfile(); + protected final InstructionProcessor ip = createInstructionProcessor(profile, s ->{}); protected final LogicInstruction createInstruction(Opcode opcode) { return ip.createInstruction(mockAstContext, opcode); diff --git a/mindcode/src/test/java/info/teksol/mindcode/compiler/DirectiveProcessorTest.java b/mindcode/src/test/java/info/teksol/mindcode/compiler/DirectiveProcessorTest.java index 515212cb0..fa83855bf 100644 --- a/mindcode/src/test/java/info/teksol/mindcode/compiler/DirectiveProcessorTest.java +++ b/mindcode/src/test/java/info/teksol/mindcode/compiler/DirectiveProcessorTest.java @@ -57,6 +57,15 @@ void processesDirectiveGoal() { assertEquals(GenerationGoal.SPEED, profile.getGoal()); } + @Test + void processesDirectiveRemarks() { + CompilerProfile profile = CompilerProfile.noOptimizations(false); + profile.setRemarks(Remarks.NONE); + Seq seq = new Seq(null, new Directive(null, "remarks", "active")); + DirectiveProcessor.processDirectives(seq, profile, m -> {}); + assertEquals(Remarks.ACTIVE, profile.getRemarks()); + } + @Test void refusesInvalidOption() { CompilerProfile profile = CompilerProfile.noOptimizations(false); diff --git a/mindcode/src/test/java/info/teksol/mindcode/compiler/LogicInstructionLabelResolverTest.java b/mindcode/src/test/java/info/teksol/mindcode/compiler/LogicInstructionLabelResolverTest.java index 72e3f788c..8c59a81be 100644 --- a/mindcode/src/test/java/info/teksol/mindcode/compiler/LogicInstructionLabelResolverTest.java +++ b/mindcode/src/test/java/info/teksol/mindcode/compiler/LogicInstructionLabelResolverTest.java @@ -21,6 +21,7 @@ void resolvesAbsoluteAddressesOfLabels() { ), LogicInstructionLabelResolver.resolve( compiler.processor, + compiler.profile, generateInstructions("while true n = n + 1 end").instructions() ) ); @@ -46,6 +47,7 @@ void resolvesVirtualInstructions() { ), LogicInstructionLabelResolver.resolve( compiler.processor, + compiler.profile, List.of( createInstruction(JUMP, label0, Condition.ALWAYS), createInstruction(PUSH, cell1, a), @@ -60,4 +62,70 @@ void resolvesVirtualInstructions() { ) ); } + + @Test + void resolvesDisabledRemarks() { + TestCompiler compiler = createTestCompiler(); + compiler.profile.setRemarks(Remarks.NONE); + assertLogicInstructionsMatch(compiler, + List.of( + createInstruction(PRINT, q("Hello")), + createInstruction(END) + ), + LogicInstructionLabelResolver.resolve( + compiler.processor, + compiler.profile, + generateInstructions(""" + remark("This is a remark"); + print("Hello"); + """ + ).instructions() + ) + ); + } + + @Test + void resolvesPassiveRemarks() { + TestCompiler compiler = createTestCompiler(); + compiler.profile.setRemarks(Remarks.PASSIVE); + assertLogicInstructionsMatch(compiler, + List.of( + createInstruction(JUMP, "2", "always"), + createInstruction(PRINT, q("This is a remark")), + createInstruction(PRINT, q("Hello")), + createInstruction(END) + ), + LogicInstructionLabelResolver.resolve( + compiler.processor, + compiler.profile, + generateInstructions(""" + remark("This is a remark"); + print("Hello"); + """ + ).instructions() + ) + ); + } + + @Test + void resolvesActiveRemarks() { + TestCompiler compiler = createTestCompiler(); + compiler.profile.setRemarks(Remarks.ACTIVE); + assertLogicInstructionsMatch(compiler, + List.of( + createInstruction(PRINT, q("This is a remark")), + createInstruction(PRINT, q("Hello")), + createInstruction(END) + ), + LogicInstructionLabelResolver.resolve( + compiler.processor, + compiler.profile, + generateInstructions(""" + remark("This is a remark"); + print("Hello"); + """ + ).instructions() + ) + ); + } } diff --git a/mindcode/src/test/java/info/teksol/mindcode/compiler/LogicInstructionPrinterTest.java b/mindcode/src/test/java/info/teksol/mindcode/compiler/LogicInstructionPrinterTest.java index 64a088892..7c373ebd4 100644 --- a/mindcode/src/test/java/info/teksol/mindcode/compiler/LogicInstructionPrinterTest.java +++ b/mindcode/src/test/java/info/teksol/mindcode/compiler/LogicInstructionPrinterTest.java @@ -12,6 +12,7 @@ void printsURadarAndUControl() { LogicInstructionPrinter.toString(ip, LogicInstructionLabelResolver.resolve( ip, + profile, generateInstructions(""" target = uradar(enemy, ground, any, health, MIN_TO_MAX) if target != null @@ -33,6 +34,7 @@ void printsULocate() { LogicInstructionPrinter.toString(ip, LogicInstructionLabelResolver.resolve( ip, + profile, generateInstructions(""" ulocate(ore, @surge-alloy, outx, outy) ulocate(building, core, ENEMY, outx, outy, outbuilding) @@ -53,6 +55,7 @@ void realLifeScripts1() { LogicInstructionPrinter.toString(compiler.processor, LogicInstructionLabelResolver.resolve( compiler.processor, + compiler.profile, generateInstructions(compiler, "itemDrop(found, @silicon, @unit.totalItems)" ).instructions() @@ -68,6 +71,7 @@ void realLifeScripts2() { LogicInstructionPrinter.toString(compiler.processor, LogicInstructionLabelResolver.resolve( compiler.processor, + compiler.profile, generateInstructions(""" leader = getlink(0) count = 1 @@ -99,6 +103,7 @@ void correctlyDrawsTriangles() { LogicInstructionPrinter.toString(compiler.processor, LogicInstructionLabelResolver.resolve( compiler.processor, + compiler.profile, generateInstructions(""" triangle(x - 20, y - 20, x + 20, y - 20, x + 20, y - 20) """).instructions() @@ -160,6 +165,7 @@ void realLifeScripts() { LogicInstructionPrinter.toString(compiler.processor, LogicInstructionLabelResolver.resolve( compiler.processor, + compiler.profile, generateInstructions(""" STORAGE = nucleus1 MSG = message1 diff --git a/mindcode/src/test/java/info/teksol/mindcode/compiler/functions/BuiltInFunctionsTest.java b/mindcode/src/test/java/info/teksol/mindcode/compiler/functions/BuiltInFunctionsTest.java index c30f06a99..aaf5e716f 100644 --- a/mindcode/src/test/java/info/teksol/mindcode/compiler/functions/BuiltInFunctionsTest.java +++ b/mindcode/src/test/java/info/teksol/mindcode/compiler/functions/BuiltInFunctionsTest.java @@ -181,4 +181,30 @@ void printfHandlesAdjacentPositionalArguments() { ); } + @Test + void generatesRemarks() { + assertCompilesTo(""" + remark("foo") + remark("bar") + """, + createInstruction(REMARK, q("foo")), + createInstruction(REMARK, q("bar")), + createInstruction(END) + ); + } + + @Test + void generatesRemarksWithFormatting() { + assertCompilesTo(""" + x = 10; y = 15; + remark("Position: $x, $y"); + """, + createInstruction(SET, "x", "10"), + createInstruction(SET, "y", "15"), + createInstruction(REMARK, q("Position: ")), + createInstruction(REMARK, "x"), + createInstruction(REMARK, q(", ")), + createInstruction(REMARK, "y"), + createInstruction(END) ); + } } diff --git a/mindcode/src/test/java/info/teksol/mindcode/compiler/optimization/PrintMergerTest.java b/mindcode/src/test/java/info/teksol/mindcode/compiler/optimization/PrintMergerTest.java index 801a6bd0e..d959172b4 100644 --- a/mindcode/src/test/java/info/teksol/mindcode/compiler/optimization/PrintMergerTest.java +++ b/mindcode/src/test/java/info/teksol/mindcode/compiler/optimization/PrintMergerTest.java @@ -115,4 +115,16 @@ void mergesAcrossInstructions() { ); } + + @Test + void mergesRemarks() { + assertCompilesTo(""" + remark("Iteration: ${}", 1); + remark("bar"); + """, + createInstruction(REMARK, q("Iteration: 1")), + createInstruction(REMARK, q("bar")), + createInstruction(END) + ); + } } \ No newline at end of file diff --git a/mindcode/src/test/java/info/teksol/mindcode/processor/AbstractProcessorTest.java b/mindcode/src/test/java/info/teksol/mindcode/processor/AbstractProcessorTest.java index 76f25174c..783eb57f1 100644 --- a/mindcode/src/test/java/info/teksol/mindcode/processor/AbstractProcessorTest.java +++ b/mindcode/src/test/java/info/teksol/mindcode/processor/AbstractProcessorTest.java @@ -151,7 +151,7 @@ private void writeLogFile(Path logFile, TestCompiler compiler, List unresolved = generateInstructions(compiler, code).instructions(); assertNoUnexpectedMessages(compiler, s -> false); - List instructions = LogicInstructionLabelResolver.resolve(compiler.processor, unresolved); + List instructions = LogicInstructionLabelResolver.resolve(compiler.processor, compiler.profile, unresolved); String compiled = LogicInstructionPrinter.toString(compiler.processor, instructions); logTiming(title, compiler.getMessages()); logCompilation(title, code, compiled, instructions.size()); @@ -170,7 +170,7 @@ protected void testAndEvaluateCode(TestCompiler compiler, String title, String c processor.addBlock(MindustryMemory.createMemoryBank("bank2")); blocks.forEach(processor::addBlock); List unresolved = generateInstructions(compiler, code).instructions(); - List instructions = LogicInstructionLabelResolver.resolve(compiler.processor, unresolved); + List instructions = LogicInstructionLabelResolver.resolve(compiler.processor, compiler.profile, unresolved); writeLogFile(logFile, compiler, unresolved); //System.out.println(prettyPrint(instructions)); processor.run(instructions, MAX_STEPS); diff --git a/mindcode/src/test/resources/info/teksol/mindcode/processor/euler/ProjectEulerTest_timing.txt b/mindcode/src/test/resources/info/teksol/mindcode/processor/euler/ProjectEulerTest_timing.txt index 113a7e442..dceb287db 100644 --- a/mindcode/src/test/resources/info/teksol/mindcode/processor/euler/ProjectEulerTest_timing.txt +++ b/mindcode/src/test/resources/info/teksol/mindcode/processor/euler/ProjectEulerTest_timing.txt @@ -1,6 +1,6 @@ -project-euler-04.mnd: : parse: 0 ms, compile: 0 ms, optimize: 200 ms -project-euler-18.mnd: : parse: 0 ms, compile: 0 ms, optimize: 800 ms -project-euler-26.mnd: : parse: 0 ms, compile: 0 ms, optimize: 200 ms +project-euler-04.mnd: : parse: 0 ms, compile: 0 ms, optimize: 0 ms +project-euler-18.mnd: : parse: 0 ms, compile: 0 ms, optimize: 400 ms +project-euler-26.mnd: : parse: 0 ms, compile: 0 ms, optimize: 0 ms project-euler-28.mnd: : parse: 0 ms, compile: 0 ms, optimize: 0 ms project-euler-31.mnd: : parse: 0 ms, compile: 0 ms, optimize: 0 ms project-euler-31b.mnd: : parse: 0 ms, compile: 0 ms, optimize: 0 ms diff --git a/mindcode/src/test/resources/info/teksol/mindcode/processor/optimizer/OptimizerTest_timing.txt b/mindcode/src/test/resources/info/teksol/mindcode/processor/optimizer/OptimizerTest_timing.txt index fec907355..afb604a17 100644 --- a/mindcode/src/test/resources/info/teksol/mindcode/processor/optimizer/OptimizerTest_timing.txt +++ b/mindcode/src/test/resources/info/teksol/mindcode/processor/optimizer/OptimizerTest_timing.txt @@ -1,4 +1,4 @@ -breakout-00.mnd: : parse: 400 ms, compile: 0 ms, optimize: 200 ms +breakout-00.mnd: : parse: 200 ms, compile: 0 ms, optimize: 0 ms detector-00.mnd: : parse: 0 ms, compile: 0 ms, optimize: 0 ms factory-monitor-00.mnd: : parse: 400 ms, compile: 0 ms, optimize: 200 ms factory-monitor-silicon-00.mnd: : parse: 600 ms, compile: 0 ms, optimize: 200 ms @@ -15,11 +15,11 @@ level-meter-00.mnd: : parse: 200 ms, compile: mass-driver-monitor-00.mnd: : parse: 200 ms, compile: 0 ms, optimize: 0 ms mass-driver-monitor-surge-alloy-00.mnd: : parse: 200 ms, compile: 0 ms, optimize: 0 ms reactor-control-00.mnd: : parse: 1,400 ms, compile: 0 ms, optimize: 200 ms -reactor-control-battery-level-00.mnd: : parse: 400 ms, compile: 0 ms, optimize: 200 ms +reactor-control-battery-level-00.mnd: : parse: 400 ms, compile: 0 ms, optimize: 0 ms regulator-00.mnd: : parse: 200 ms, compile: 0 ms, optimize: 0 ms remote-vault-00.mnd: : parse: 0 ms, compile: 0 ms, optimize: 0 ms storage-display-00.mnd: : parse: 200 ms, compile: 0 ms, optimize: 200 ms -unit-housekeeping-00.mnd: : parse: 0 ms, compile: 0 ms, optimize: 0 ms +unit-housekeeping-00.mnd: : parse: 200 ms, compile: 0 ms, optimize: 0 ms unit-speed-00.mnd: : parse: 0 ms, compile: 0 ms, optimize: 0 ms unit-transport-00.mnd: : parse: 600 ms, compile: 0 ms, optimize: 600 ms unit-transport-flow-rate-00.mnd: : parse: 200 ms, compile: 0 ms, optimize: 0 ms diff --git a/webapp/src/test/java/info/teksol/mindcode/webapp/SamplesTest.java b/webapp/src/test/java/info/teksol/mindcode/webapp/SamplesTest.java index 79038398e..cb31dd268 100644 --- a/webapp/src/test/java/info/teksol/mindcode/webapp/SamplesTest.java +++ b/webapp/src/test/java/info/teksol/mindcode/webapp/SamplesTest.java @@ -116,7 +116,8 @@ private void compile(String source, File file) throws IOException { .redirectOutput(diffTarget) .start(); - final List result = LogicInstructionLabelResolver.resolve(instructionProcessor, optimized); + final List result = LogicInstructionLabelResolver.resolve(instructionProcessor, + CompilerProfile.standardOptimizations(false), optimized); final String opcodes = LogicInstructionPrinter.toString(instructionProcessor, result); assertFalse(opcodes.isEmpty(), "Failed to generateUnoptimized a Logic program out of:\n" + source);