1
0
mirror of https://github.com/KarolS/millfork.git synced 2026-04-20 18:16:35 +00:00

Module templates

This commit is contained in:
Karol Stasiak
2020-06-03 23:13:17 +02:00
parent b5134dfbd1
commit 718245c56a
16 changed files with 279 additions and 26 deletions
+2
View File
@@ -16,6 +16,8 @@
* [Preprocessor](lang/preprocessor.md)
* [Modules](lang/modules.md)
* [Syntax](lang/syntax.md)
* [Types](lang/types.md)
+2 -1
View File
@@ -60,7 +60,8 @@ See [the list of available encodings](../lang/text.md).
* `screen_encoding` default encoding for screencodes (literals with encoding specified as `scr`).
Default: the same as `encoding`.
* `modules` comma-separated list of modules that will be automatically imported
* `modules` comma-separated list of modules that will be automatically imported.
This list cannot contain module template instantiations.
* other compilation options (they can be overridden using commandline options):
+2
View File
@@ -16,6 +16,8 @@
* [Preprocessor](lang/preprocessor.md)
* [Modules](lang/modules.md)
* [Syntax](lang/syntax.md)
* [Types](lang/types.md)
+97
View File
@@ -0,0 +1,97 @@
[< back to index](../doc_index.md)
# Program structure
A Millfork program is build from one or more modules.
Each module is stored in a single file.
All source filenames passed to the compiler are considered to be modules of that program, called _root modules_.
Each module has a name, which is its unique identifier.
A module name is a sequence of slash-separated valid Millfork identifiers.
The name also defines where the module is located:
a module named `a/b` is presumed to exist in `a/b.mfk`
and it's looked up first in the current working directory,
and then in the include directories.
A module can import other modules, using the `import` statement.
Importing the same module multiple times merely marks it as imported by multiple modules,
but the program will still contain only one copy of it.
Examples:
import string
import cbm_file
Usually, the imported module will undergo the first phase of compilation first.
This means that the constants in the imported module will be resolved first, allowing you to use them in the importing module.
The only exception to this rule is when the importing graph has a cycle, in which case the order of modules within the cycle is unspecified.
A platform may define starting modules using the `modules=` directive of the `[compilation]` section.
All starting modules are considered to be imported by all source files explicitly mentioned on the command line.
### Module templates
If the first line of a source file starts with the `#template` directive,
then the source is considered to be a _module template_.
Module templates are a tool for generating repetitive code, similar to COBOL copybooks or Go Generate.
The template directive contains a comma-separated list of parameters.
It's recommended that the names of parameters begin and end with non-alphanumeric characters:
#template $P1$, $P2$
A module template cannot be imported as-is.
When importing a module template, you import a concrete instantiation of it.
For example, if the file `temp.mfk` contains the `#template` from above,
you can import it by providing a list of numeric literals or identifiers:
import temp<1, 2>
This instantiates a new module named `temp<1,2>` (if it hasn't been instantiated anywhere else).
The code in that module is generated by replacing every instance of the parameter names with the actual argument content.
Parameters that are numeric literals are normalized to their decimal representations.
Your program may contain multiple modules created from the same template with different parameters. For example,
import temp<3, 4>
import temp<5, 6>
import temp<$5, $6>
instantiates and imports two similar, yet different modules: `temp<3,4>` and `temp<5,6>`.
The third import imports a module that has already been instantiated and imported, so it's redundant.
The instantiation works through simple text replacement. For example, if `temp.mfk` contains:
#template $P1$, $P2$
const byte a$P1$ = $P2$
then the `temp<1,2>` module will contain
const byte a1 = 2
This substitution is performed before preprocessing, so those substitutions are available for the preprocessor directives.
It applies to identifiers, string literals, keywords, preprocesor directives etc.
**Warning:** This mechanism provides no direct way for preventing duplicates of code
that does not depend on the template parameters, or depends on only some template parameters.
In such situations, it might be advisable to put the non-dependent definitions in another module that is not a template,
or in a module template with fewer parameters. For example, instead of writing:
#template $N$
const byte X = 50
array a$N$ [X]
(which would define duplicate `X`s if imported multiple times), consider writing two files:
#template $N$
import define_X
array a$N$ [X]
>
//define_X.mfk:
const byte X = 50
+15
View File
@@ -129,6 +129,16 @@ These features are used to identify the target machine in multiplatform programs
### Built-in preprocessor functions and operators
The `same` function returns 1 if given identical identifiers and 0 otherwise.
It is the only function that does not support any other kind of parameters, and it's only useful in module templates.
// prints 1:
#infoeval same(a,a)
// prints 0:
#infoeval same(a,b)
// fails to compile
#infoeval same(a,1)
The `defined` function returns 1 if the feature is defined, 0 otherwise.
All the other functions and operators treat undefined features as if they were defined as 0.
@@ -145,6 +155,11 @@ TODO
The following Millfork operators and functions are not available in the preprocessor:
`+'`, `-'`, `*'`, `<<'`, `>>'`, `:`, `>>>>`, `nonet`, all the assignment operators
### `#template`
Defines the source to be a module template. See [Modules](./modules.md) for more information.
### `#if/#elseif/#else/#endif`
#if <expr>