Final commit before submission
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
# Macros
|
||||
## Macros
|
||||
|
||||
Left-associative unparenthesized function calls are intuitive in the typical case of just applying functions to a limited number of arguments, but they're not very flexible. Haskell solves this problem by defining a diverse array of syntax primitives for individual use cases such as `do` blocks for monadic operations. This system is fairly rigid. In contrast, Rust and Lisp enable library developers to invent their own syntax that intuitively describes the concepts the library at hand encodes. In Orchid's codebase, I defined several macros to streamline tasks like defining functions in Rust that are visible to Orchid, or translating between various intermediate representations.
|
||||
Left-associative unparenthesized function calls are intuitive in the typical case of just applying functions to a limited number of arguments, but they're not very flexible. Haskell solves this problem by defining a diverse array of syntax primitives for individual use cases such as `do` blocks for monadic operations. This system is fairly rigid. In contrast, Rust enables library developers to invent their own syntax that intuitively describes the concepts the library at hand encodes. In Orchid's codebase, I defined several macros to streamline tasks like defining functions in Rust that are visible to Orchid, or translating between various intermediate representations.
|
||||
|
||||
## Generalized kerning
|
||||
### Generalized kerning
|
||||
|
||||
In the referenced video essay, a proof of the Turing completeness of generalized kerning is presented. The proof involves encoding a Turing machine in a string and some kerning rules. The state of the machine is next to the read-write head and all previous states are enumerated next to the tape because kerning rules are reversible. The end result looks something like this:
|
||||
|
||||
@@ -31,7 +31,7 @@ $1 $2 < equals $2 < $1 unless $1 is |
|
||||
|
||||
What I really appreciate in this proof is how visual it is; based on this, it's easy to imagine how one would go about encoding a pushdown automaton, lambda calculus or other interesting tree-walking procedures. This is exactly why I based my preprocessor on this system.
|
||||
|
||||
## Namespaced tokens
|
||||
### Namespaced tokens
|
||||
|
||||
Rust macros operate on the bare tokens and therefore are prone to accidental aliasing. Every other item in Rust follows a rigorous namespacing scheme, but macros break this structure, probably because macro execution happens before namespace resolution. The language doesn't suffer too much from this problem, but the relativity of namespacing
|
||||
limits their potential.
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# Implementation
|
||||
## Implementation
|
||||
|
||||
THe optimization of this macro execution algorithm is an interesting challenge with a diverse range of potential optimizations. The current solution is very far from ideal, but it scales to the small experimental workloads I've tried so far and it can accommodate future improvements without any major restructuring.
|
||||
|
||||
The scheduling of macros is delegated to a unit called the rule repository, while the matching of rules to a given clause sequence is delegated to a unit called the matcher. Other tasks are split out into distinct self-contained functions, but these two have well-defined interfaces and encapsulate data. Constants are processed by the repository one at a time, which means that the data processed by this subsystem typically corresponds to a single struct, function or other top-level source item.
|
||||
|
||||
## keyword dependencies
|
||||
### keyword dependencies
|
||||
|
||||
The most straightforward optimization is to skip patterns that doesn contain tokens that don't appear in the code at all. This is done by the repository to skip entire rules, but not by the rules on the level of individual slices. This is a possible path of improvement for the future.
|
||||
|
||||
## Matchers
|
||||
### Matchers
|
||||
|
||||
There are various ways to implement matching. To keep the architecture flexible, the repository is generic over the matcher bounded with a very small trait.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
## Execution order
|
||||
### Execution order
|
||||
|
||||
The macros describe several independent sequential programs that are expected to be able to interact with each other. To make debugging easier, the order of execution of internal steps within independent macros has to be relatively static.
|
||||
|
||||
@@ -27,30 +27,30 @@ The bands are each an even 32 orders of magnitude, with space in between for fut
|
||||
| 224-231 | 232-239 | 240-247 | 248- |
|
||||
| integrations | | | transitional |
|
||||
|
||||
### Transitional states
|
||||
#### Transitional states
|
||||
|
||||
Transitional states produced and consumed by the same macro program occupy the unbounded top region of the f64 field. Nothing in this range should be written by the user or triggered by an interaction of distinct macro programs, the purpose of this high range is to prevent devices such as carriages from interacting. Any transformation sequence in this range can assume that the tree is inert other than its own operation.
|
||||
|
||||
### Integrations
|
||||
#### Integrations
|
||||
|
||||
Integrations expect an inert syntax tree but at least one token in the pattern is external to the macro program that resolves the rule, so it's critical that all macro programs be in a documented state at the time of resolution.
|
||||
|
||||
### Aliases
|
||||
#### Aliases
|
||||
|
||||
Fragments of code extracted for readability are all at exactly 0x1p800. These may be written by programmers who are not comfortable with macros or metaprogramming. They must have unique single token patterns. Because their priority is higher than any entry point, they can safely contain parts of other macro invocations. They have a single priority number because they can't conceivably require internal ordering adjustments and their usage is meant to be be as straightforward as possible.
|
||||
|
||||
### Binding builders
|
||||
#### Binding builders
|
||||
|
||||
Syntax elements that manipulate bindings should be executed earlier. `do` blocks and (future) `match` statements are good examples of this category. Anything with a lower priority trigger can assume that all names are correctly bound.
|
||||
|
||||
### Expressions
|
||||
#### Expressions
|
||||
|
||||
Things that essentially work like function calls just with added structure, such as `if`/`then`/`else` or `loop`. These are usually just more intuitive custom forms that are otherwise identical to a macro
|
||||
|
||||
### Operators
|
||||
#### Operators
|
||||
|
||||
Binary and unary operators that process the chunks of text on either side. Within the band, these macros are prioritized in inverse precedence order and apply to the entire range of clauses before and after themselves, to ensure that function calls have the highest perceived priority.
|
||||
|
||||
### Optimizations
|
||||
#### Optimizations
|
||||
|
||||
Macros that operate on a fully resolved lambda code and look for known patterns that can be simplified. I did not manage to create a working example of this but for instance repeated string concatenation is a good example.
|
||||
Reference in New Issue
Block a user