Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
126 commits
Select commit Hold shift + click to select a range
963f84a
Add baseline for expressions
dhedey Dec 31, 2024
c734c83
fix: Fix MSRV
dhedey Dec 31, 2024
8fc4121
tweak: Doc tweaks
dhedey Dec 31, 2024
2127299
docs: Further docs and update planning
dhedey Dec 31, 2024
8eb0f47
WIP: More commands
dhedey Jan 1, 2025
b57a688
WIP: Style fix
dhedey Jan 1, 2025
befb78d
WIP: Add `while` and `assign` commands
dhedey Jan 2, 2025
af5aaf0
feat: Add `!assign!` and remove `!increment!`
dhedey Jan 3, 2025
5532046
WIP: Refactor to separate stream types
dhedey Jan 4, 2025
f77f7e2
chore: Fix style
dhedey Jan 4, 2025
542b71e
wip: Minor refactors
dhedey Jan 4, 2025
f2d9baa
WIP: Add some notes/docs
dhedey Jan 4, 2025
08b3e7c
WIP: Write down thoughts/plans
dhedey Jan 7, 2025
9c48d1b
refactor: Have a separate parse step and interpret step
dhedey Jan 9, 2025
0eb014f
fix: Styling fix
dhedey Jan 9, 2025
ecaaa06
fix: Fix ident equality check
dhedey Jan 9, 2025
ed69555
fix: Fix style
dhedey Jan 9, 2025
1fcb5d4
Add compile error tests
dhedey Jan 11, 2025
dbf207f
fix: Fix style
dhedey Jan 11, 2025
faa96cf
fix: Error span unwraps transparent groups
dhedey Jan 11, 2025
b0f455a
feature: Add field passing
dhedey Jan 12, 2025
5f1cee0
fix: Style fix
dhedey Jan 12, 2025
88ffe18
tweak: Minor refactors
dhedey Jan 14, 2025
19348c1
refactor: Move to `ParseStream`-based parsing
dhedey Jan 15, 2025
944fb08
fix: Style fix
dhedey Jan 15, 2025
020dc76
fix: Fix for msrv
dhedey Jan 15, 2025
3393bc0
feature: Rework error command parsing to be on the input
dhedey Jan 16, 2025
8e8bb6a
tweak: Add explicit CodeInput and StreamInput
dhedey Jan 16, 2025
4332be6
feature: Add extend command
dhedey Jan 16, 2025
da1b94b
fix: Fix styling
dhedey Jan 16, 2025
0da56d9
fix: Fix style
dhedey Jan 16, 2025
039fe69
feat: Made expressions more flexible and added !range!
dhedey Jan 17, 2025
f99d4f3
feature: Add !intersperse! command
dhedey Jan 18, 2025
214b807
fix: Move the syn comment and fix styling
dhedey Jan 18, 2025
5020633
refactor: Command output types are static
dhedey Jan 18, 2025
d40b31e
feature: Added support for some !..command!s
dhedey Jan 18, 2025
55496db
tweak: Handle control flow output as always flattened
dhedey Jan 19, 2025
e1fbcd7
refactor: Finished reworking commands in expressions
dhedey Jan 19, 2025
75aa1b5
tweak: Improve range tests
dhedey Jan 19, 2025
f4d7be4
feature: Remove superfluous stream and empty commands
dhedey Jan 19, 2025
e988b62
refactor: Removed some redundant span ranges
dhedey Jan 19, 2025
c643dbc
feature: !extend! is more efficient
dhedey Jan 20, 2025
7a87450
chore: Minor docs tweaks and renames
dhedey Jan 20, 2025
9134958
feature: Add !elif!, stream and char literals
dhedey Jan 20, 2025
033ba6b
feature: Add !for! and !settings! commands
dhedey Jan 20, 2025
425f898
feature: Add !loop!, !continue! and !break!
dhedey Jan 20, 2025
03207a9
fix: Fix msrv
dhedey Jan 21, 2025
230ceb9
fix: Fix styling
dhedey Jan 21, 2025
b874009
tweak: Error has basic parse mode without fields
dhedey Jan 21, 2025
175de64
tweak: Add comment in rust-analyzer
dhedey Jan 21, 2025
7b6460b
refactor: Keep interpreted token streams separate from TokenStream
dhedey Jan 21, 2025
56e61e9
feature: Add `let` and more destructurings
dhedey Jan 22, 2025
93c7fa0
feature: Added support for named destructurers
dhedey Jan 22, 2025
975a61d
tests: Add some more destructuring tests
dhedey Jan 23, 2025
5cb35a9
feature: Added void command
dhedey Jan 23, 2025
c525242
feature: Add split commands and raw destructurers
dhedey Jan 23, 2025
7a19622
refactor: Minor tweaks
dhedey Jan 24, 2025
356b3a8
refactor: Added distinct result types
dhedey Jan 24, 2025
a4c542e
refactor: Replace parse_v2 with parse
dhedey Jan 25, 2025
3511a30
feature: Add !zip! command
dhedey Jan 26, 2025
aedc01a
tweak: Minor error message improvement when parsing groups
dhedey Jan 26, 2025
738c3cc
feature: Assign's operator is now optional
dhedey Jan 26, 2025
69c9271
tests: Add more tests and improve long expression perf
dhedey Jan 28, 2025
8867841
tweak: Simplify expression span handling
dhedey Jan 28, 2025
e602db8
tweak: Try to fix tests on nightly
dhedey Jan 28, 2025
cde9caa
refactor: Improve expression parsing
dhedey Jan 30, 2025
dea5bea
fix: || and && short-circuit
dhedey Jan 30, 2025
9571869
fix: Fix MSRV
dhedey Jan 30, 2025
17b32bd
fix: !debug! now respects punct spacing
dhedey Jan 30, 2025
80f1883
refactor: Merge peeking of Commands
dhedey Jan 31, 2025
93ac592
refactor: Simplify BinaryOperation and UnaryOperation
dhedey Feb 1, 2025
4a58485
refactor: Separate parsing from source and interpreted
dhedey Feb 2, 2025
971d529
refactor: Various renames
dhedey Feb 4, 2025
5432a9b
test: Add some more code block tests
dhedey Feb 4, 2025
117812e
tweak: Merge !void! and !extend! into !set!
dhedey Feb 4, 2025
56334d4
feature: !range! now supports characters
dhedey Feb 4, 2025
9da3e57
feature: Generalized destructurers to transformers
dhedey Feb 6, 2025
c36ad39
refactor: Simplify expression span management
dhedey Feb 9, 2025
0efe855
refactor: Command is nested in enum rather than Box
dhedey Feb 9, 2025
882d797
refactor: Minor tweak to unsupported error messages
dhedey Feb 9, 2025
b162253
refactor: Start expression rework
dhedey Feb 10, 2025
9f95191
feature: Support expression block `#(..)`
dhedey Feb 11, 2025
3b04d36
fix: Fix styling / raise clippy issue
dhedey Feb 11, 2025
e4a9e57
refactor: Variables now store expression values
dhedey Feb 12, 2025
6d8583b
tweak: Update MSRV to 1.63 for `RefMut::filter_map`
dhedey Feb 12, 2025
7b1f5ce
refactor: Trial new stream expression syntax
dhedey Feb 12, 2025
1bf1db9
refactor: Changed from experimental `%[...]` to `[!stream ...]` for s…
dhedey Feb 12, 2025
994d9d4
feature: Add array value
dhedey Feb 12, 2025
7150763
feature: Add basic support for arrays
dhedey Feb 12, 2025
89f9ba3
feature: Full support for array expressions
dhedey Feb 13, 2025
167fbdd
refactor: Got rid of flattened commands and added iterator values
dhedey Feb 13, 2025
80c2324
fix: Fix MSRV
dhedey Feb 13, 2025
d5b464e
feature: Ranges are now also in the expression model
dhedey Feb 14, 2025
bd40d1f
tweak: Update task list
dhedey Feb 14, 2025
83d0217
refactor: Minor refactors to patterns
dhedey Feb 14, 2025
2defae9
fix: Fix compilation error test
dhedey Feb 14, 2025
517f524
refactor: Assignments are inside expressions
dhedey Feb 15, 2025
ece684f
feature: Indexing works for reading
dhedey Feb 16, 2025
cc49e09
refactor: Create variable reference abstraction
dhedey Feb 16, 2025
aed0f85
tweak: #variable syntax is no longer allowed in expressions
dhedey Feb 17, 2025
276d1c9
feature: Added index and array place destructuring
dhedey Feb 18, 2025
360a802
refactor: Move a few files around
dhedey Feb 18, 2025
5ecd801
feature: Support range indices for arrays
dhedey Feb 18, 2025
388d355
feature: Support .. in array patterns
dhedey Feb 18, 2025
0610fcb
feature: += for arrays and streams is more efficient
dhedey Feb 18, 2025
18b4564
refactor: Remove `Deref` impl from `ParseStream`
dhedey Feb 18, 2025
4cfbd83
feature: Basic object support
dhedey Feb 19, 2025
3c53e1f
feature: Objects can be destructured
dhedey Feb 19, 2025
7211dc0
refactor: Commands take object inputs
dhedey Feb 20, 2025
ed266b4
feature: Zip also supports objects
dhedey Feb 20, 2025
8718e5f
tweak: Support `let x;` syntax.
dhedey Mar 4, 2025
b6d0988
docs: Update CHANGELOG with better plan
dhedey Aug 15, 2025
c7027a9
feature: Added method call support to expression parsing
dhedey Aug 15, 2025
bbbe671
Add parsing support for method calls
dhedey Aug 15, 2025
d9a9ebe
fix: Fix warnings and compiler errors
dhedey Aug 16, 2025
ebcf6df
fix: Fix right alignment change in error messages
dhedey Aug 16, 2025
d347bc2
fix: Disable UI tests on beta and nightly
dhedey Aug 16, 2025
23706a6
tweak: Fix dead code error
dhedey Aug 16, 2025
f88c614
fix: Fix style
dhedey Aug 16, 2025
e060669
refactor: Much improved expression evaluation readability
dhedey Sep 1, 2025
7b5febd
wip: Method resolutions GATs and Macros
dhedey Sep 18, 2025
f6e927c
feature: Added support for methods taking mutable references
dhedey Sep 18, 2025
327319d
feature: Add take()
dhedey Sep 19, 2025
758a6e8
feature: Added `debug()` method which throws an error
dhedey Sep 19, 2025
8264a03
refactor: Various renames
dhedey Sep 20, 2025
eb7d23d
feature: Added `swap` to test mutable references
dhedey Sep 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ permissions:
contents: read

env:
RUSTFLAGS: -Dwarnings
RUSTFLAGS: -Dwarnings # Deny warnings - i.e. warnings cause build errors

jobs:
test:
Expand All @@ -31,6 +31,8 @@ jobs:
- name: Enable type layout randomization
run: echo RUSTFLAGS=${RUSTFLAGS}\ -Zrandomize-layout >> $GITHUB_ENV
if: matrix.rust == 'nightly'
- name: Add TEST_RUST_MODE env variable
run: echo TEST_RUST_MODE=${{matrix.rust}} >> $GITHUB_ENV
- run: cargo test
- uses: actions/upload-artifact@v4
if: matrix.rust == 'nightly' && always()
Expand All @@ -39,14 +41,14 @@ jobs:
path: Cargo.lock

msrv:
name: MSRV (1.61) Compiles
name: MSRV (1.63) Compiles
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.61
toolchain: 1.63
- run: cargo check

style-check:
Expand All @@ -61,6 +63,17 @@ jobs:
toolchain: stable
- run: ./style-check.sh

book:
name: Rust Book Test
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- run: ./book/test.sh

miri:
name: Miri
runs-on: ubuntu-latest
Expand Down
44 changes: 44 additions & 0 deletions .github/workflows/publish_book.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Publish book to Github Pages

on:
push:
branches:
- main

permissions:
contents: read # to read the docs and build job
pages: write # to deploy to pages
id-token: write # to verify the deployment originates from an appropriate source

jobs:
# Build job - https://github.com/actions/upload-pages-artifact
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
# Sets up the repository configuration for github pages to be deployed from an action
- name: Setup Pages
uses: actions/configure-pages@v5
# Builds the book and outputs to book/book
- name: Build book
run: ./book/build.sh
# Creates a correctly compressed archive from the build HTML file, and outputs it as
# a `github-pages` artifact
- name: Upload static files as artifact
uses: actions/upload-pages-artifact@v3
with:
path: book/book

# Deployment job - https://github.com/actions/deploy-pages
deploy:
runs-on: ubuntu-latest
needs: build
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
# Deploys the artifact `github-pages` created by the `upload-pages-artifact` job
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
452 changes: 452 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

11 changes: 8 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@ keywords = ["macros", "declarative-macros", "toolkit", "interpreter", "preproces
categories = ["development-tools", "compilers"]
# MSRV 1.56.0 is the start of Edition 2021
# MSRV 1.61.0 is the current MSRV of syn
# MRSV 1.63.0 is needed to support RefMut::filter_map
# If changing this, also update the local-check-msrv.sh script and ci.yml
rust-version = "1.61"
rust-version = "1.63"

[lib]
proc-macro = true

[dependencies]
proc-macro2 = { version = "1.0" }
syn = { version = "2.0", default-features = false, features = ["parsing"] }
proc-macro2 = { version = "1.0.93" }
syn = { version = "2.0.98", default-features = false, features = ["parsing", "derive", "printing", "clone-impls", "full"] }
quote = { version = "1.0.38", default-features = false }

[dev-dependencies]
trybuild = { version = "1.0.110", features = ["diff"] }
8 changes: 8 additions & 0 deletions KNOWN_ISSUES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Since Major Version 0.3.0

* `[MAX LITERAL]` - In an expression, the maximum negative integer literal is not valid, for example `-128i8`.
* By comparison, in `rustc`, there is special handling for such literals, so that e.g. `-128i8` and `--(-128i8)` are accepted.
* In rust analyzer, before the full pass, it seems to store literals as u128 and perform wrapping arithmetic on them. On a save / full pass, it does appear to reject literals which are out of bounds (e.g. 150i8).
* We could fix this by special casing maximal signed integer literals (e.g. 128 for an i8) which have yet to be involved in a non-unary expression.
* `&&` and `||` are not lazy in expressions
* This needs a custom expression parser, rather than just leveraging syn
112 changes: 29 additions & 83 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,10 +334,6 @@ macro_rules! impl_new_type {

## Future Extension Possibilities

### Add github docs page / rust book

Add a github docs page / rust book at this repository, to allow us to build out a suite of examples, like `serde` or the little book of macros.

### Destructuring / Parsing Syntax, and Declarative Macros 2.0

I have a vision for having preinterpret effectively replace the use of declarative macros in the Rust ecosystem, by:
Expand All @@ -359,17 +355,19 @@ The idea is that we create two new tools:

In more detail:

* `[!parse! (DESTRUCTURING) = (INPUT)]` is a more general `[!set!]` which acts like a `let <XX> = <YY> else { panic!() }`. It takes a `()`-wrapped parse destructuring on the left and a token stream as input on the right. Any `#x` in the parse definition acts as a binding rather than as a substitution. Parsing will handled commas intelligently, and accept intelligent parse operations to do heavy-lifting for the user. Parse operations look like `[!OPERATION! DESTRUCTURING]` with the operation name in `UPPER_SNAKE_CASE`. Some examples might be:
* `[!let! (DESTRUCTURING) = (INPUT)]` (or maybe `!let!` or `!set!`) is a more general `[!set!]` which acts like a `let <XX> = <YY> else { panic!() }`. It takes a `()`-wrapped parse destructuring on the left and a `()`-wrapped token stream as input on the right. Any `#x` in the parse definition acts as a binding rather than as a substitution. Parsing will handled commas intelligently, and accept intelligent parse operations to do heavy-lifting for the user. Parse operations look like `[!OPERATION! DESTRUCTURING]` with the operation name in `UPPER_SNAKE_CASE`. Some examples might be:
* `[!FIELDS! { hello: #a, world?: #b }]` - which can be parsed in any order, cope with trailing commas, and forbid fields in the source stream which aren't in the destructuring.
* `[!SUBFIELDS! { hello: #a, world?: #b }]` - which can parse fields in any order, cope with trailing commas, and allow fields in the source stream which aren't in the destructuring.
* `[!ITEM! { #ident, #impl_generics, ... }]` - which calls syn's parse item on the token
* `[!IDENT! #x]`, `[!LITERAL! #x]`, `[!TYPE! { tokens: #x, path: #y }]` and the like to parse idents / literals etc directly from the token stream (rather than token streams). These will either take just a variable to capture the full token stream, or support an optional-argument style binding, where the developer can request certain sub-patterns or mapped token streams.
* More tailored examples, such as `[!GENERICS! { impl: #x, type: #y, where: #z }]` which uses syn to parse the generics, and then uses subfields on the result.
* Possibly `[!GROUPED! #x]` to parse a group with no brackets, to avoid parser ambiguity in some cases
* `[!OPTIONAL! ...]` might be supported, but other complex logic (loops, matching) is delayed lazily until interpretation time - which feels more intuitive.
* `[!for! (DESTRUCTURING) in (INPUT) { ... }]` which operates like the rust `for` loop, and uses a parse destructuring on the left, and has support for optional commas between values
* `[!match! (INPUT) => { (DESTRUCTURING_1) => { ... }, (DESTRUCTURING_2) => { ... }, (#fallback) => { ... } }]` which operates like a rust `match` expression, and can replace the function of the branches of declarative macro inputs.
* `[!macro_rules! name!(DESTRUCTURING) = { ... }]` which can define a declarative macro, but just parses its inputs as a token stream, and uses preinterpret for its heavy lifting.
* `[!for! (DESTRUCTURING) in [!split_at! INPUT ,] { ... }]` which operates like the rust `for` loop over token trees, allowing them to be parsed. Instead of the destructuring, you can also just use a variable e.g. `#x` Operates well with the `[!split_at! ..]` command.
* `[!while! [!parse_start_from! #variable DESTRUCTURING] { ... }]` can be used to consume a destructuring from the start of #variable. `[!parse_start_from! ..]` returns true if the parse succeeded, false if the variable is an empty token stream, and errors otherwise.
* `[!parse_loop! #variable as (DESTRUCTURING), { ... }]` where the `,` is a separator and cleverly handles trailing `,` or `;` - similar to `syn::Punctuated`
* `[!match! (INPUT) => { (DESTRUCTURING_1) => { ... }, (DESTRUCTURING_2) => { ... }, #fallback => { ... } }]` which operates like a rust `match` expression, and can replace the function of the branches of declarative macro inputs.
* `[!macro_rules! name!(DESTRUCTURING) = { ... }]` which can define a declarative macro, but just parses its inputs as a token stream, and uses preinterpret for its heavy lifting. This could alternatively exist as `preinterpret::define_macro!{ my_macro!(DESTRUCTURING) = { ... }}`.

And then we can end up with syntax like the following:

Expand All @@ -381,7 +379,7 @@ And then we can end up with syntax like the following:
// A simple macro can just take a token stream as input
preinterpret::preinterpret! {
[!macro_rules! my_macro!(#input) {
[!for! (#trait for #type) in (#input) {
[!parse_loop! #input as (#trait for #type), {
impl #trait for #type
}]
}]
Expand All @@ -403,9 +401,9 @@ preinterpret::preinterpret! {
punctuation?: #punct = ("!") // Default
}]
) = {
[!for! (
[!parse_loop! #type_list as (
#type [!GENERICS! { impl: #impl_generics, type: #type_generics }]
) in (#type_list) {
) {
impl<#impl_generics> SuperDuper for #type #type_generics {
const Hello: &'static str = [!string! #hello " " #world #punct];
}
Expand All @@ -414,95 +412,43 @@ preinterpret::preinterpret! {
}
```

### Possible extension: Integer commands

Each of these commands functions in three steps:
* Apply the interpreter to the token stream, which recursively executes preinterpret commands.
* Iterate over each token (recursing into groups), expecting each to be an integer literal.
* Apply some command-specific mapping to this stream of integer literals, and output a single integer literal without its type suffix. The suffix can be added back manually if required with a wrapper such as `[!literal! [!add! 1 2] u64]`.
### Possible extension: Token stream commands

Integer commands under consideration are:
* `[!ungroup! #stream]` expects `#stream` to be a single group and unwraps it once
* `[!flatten! #stream]` removes all singleton groups from `#stream`, leaving a token stream of idents, literals and punctuation

* `[!add! 5u64 9 32]` outputs `46`. It takes any number of integers and outputs their sum. The calculation operates in `u128` space.
* `[!sub! 64u32 1u32]` outputs `63`. It takes two integers and outputs their difference. The calculation operates in `i128` space.
* `[!mod! $length 2]` outputs `0` if `$length` is even, else `1`. It takes two integers `a` and `b`, and outputs `a mod b`.
We could support a postfix calling convention, such as the `[!pipe! ...]` special command: `[!pipe! #stream > #x [!index! #x[4]] > #x [!ungroup! #x]]` or something like a scala for comprehension. But honestly, just saving things to variables might be cleaner.

We also support the following assignment commands:
### Possible extension: Better performance via incremental parsing

* `[!increment! #i]` is shorthand for `[!set! #i = [!add! #i 1]]` and outputs no tokens.
Incremental parsing using a fork of syn ([see issue](https://github.com/dtolnay/syn/issues/1842)) would allow:

Even better - we could even support calculator-style expression interpretation:
* Cheaper conversions between variables and parsing.
* `[!consume_from! #stream #x]` where `#x` is read as the first token tree from `#stream`
* `[!consume_from! #stream (<PARSE_DESTRUCTURING>)]` where the parser is read greedily from `#stream`

* `[!usize! (5 + 10) / mod(4, 2)]` outputs `7usize`
Forking syn may also allow some parts to be made more performant.

### Possible extension: User-defined commands

* `[!define! [!my_command! <PARSE_DESTRUCTURING>] { <OUTPUT> }]`
* `[!define_command! [!my_command! <ARGUMENTS_DESTRUCTURING>] { <OUTPUT> }]`
* Some ability to define and re-use commands across multiple invocations
without being too expensive. Still unsure how to make this work.

### Possible extension: Boolean commands
### Possible extension: Further utility commands

Each of these commands functions in three steps:
* Apply the interpreter to the token stream, which recursively executes preinterpret commands.
* Expects to read exactly two token trees (unless otherwise specified)
* Apply some command-specific comparison, and outputs the boolean literal `true` or `false`.

Comparison commands under consideration are:
* `[!eq! #foo #bar]` outputs `true` if `#foo` and `#bar` are exactly the same token tree, via structural equality. For example:
* `[!eq! (3 4) (3 4)]` outputs `true` because the token stream ignores spacing.
* `[!eq! 1u64 1]` outputs `false` because these are different literals.
* `[!lt! #foo #bar]` outputs `true` if `#foo` is an integer literal and less than `#bar`
* `[!gt! #foo #bar]` outputs `true` if `#foo` is an integer literal and greater than `#bar`
* `[!lte! #foo #bar]` outputs `true` if `#foo` is an integer literal and less than or equal to `#bar`
* `[!gte! #foo #bar]` outputs `true` if `#foo` is an integer literal and greater than or equal to `#bar`
* `[!not! #foo]` expects a single boolean literal, and outputs the negation of `#foo`
Other boolean commands could be possible, similar to numeric commands:
* `[!tokens_eq! #foo #bar]` outputs `true` if `#foo` and `#bar` are exactly the same token tree, via structural equality. For example:
* `[!tokens_eq! (3 4) (3 4)]` outputs `true` because the token stream ignores spacing.
* `[!tokens_eq! 1u64 1]` outputs `false` because these are different literals.
* This can be effectively done already with `#(x.debug_string() == y.debug_string())`
* `[!str_split! { input: Value<LitStr>, separator: Value<LitStr>, }]`
* `[!str_contains! "needle" [!string! haystack]]` expects two string literals, and outputs `true` if the first string is a substring of the second string.

### Possible extension: Token stream commands

* `[!skip! 4 from [#stream]]` reads and drops the first 4 token trees from the stream, and outputs the rest
* `[!ungroup! (#stream)]` outputs `#stream`. It expects to receive a single group (i.e. wrapped in brackets), and unwraps it.

### Possible extension: Control flow commands

#### If statement

`[!if! #cond then { #a } else { #b }]` outputs `#a` if `#cond` is `true`, else `#b` if `#cond` is false.

The `if` command works as follows:
* It starts by only interpreting its first token tree, and expects to see a single `true` or `false` literal.
* It then expects to reads an unintepreted `then` ident, following by a single `{ .. }` group, whose contents get interpreted and output only if the condition was `true`.
* It optionally also reads an `else` ident and a by a single `{ .. }` group, whose contents get interpreted and output only if the condition was `false`.

#### For loop

* `[!for! #token_tree in [#stream] { ... }]`

#### Goto and label

* `[!label! loop_start]` - defines a label which can be returned to. Effectively, it takes a clones of the remaining token stream after the label in the interpreter.
* `[!goto! loop_start]` - jumps to the last execution of `[!label! loop_start]`. It unrolls the preinterpret stack (dropping all unwritten token streams) until it finds a stackframe in which the interpreter has the defined label, and continues the token stream from there.

```rust,ignore
// Hypothetical future syntax - not yet implemented!
preinterpret::preinterpret!{
[!set! #i = 0]
[!label! loop]
const [!ident! AB #i]: u8 = 0;
[!increment! #i]
[!if! [!lte! #i 100] then { [!goto! loop] }]
}
```

### Possible extension: Eager expansion of macros

When [eager expansion of macros returning literals](https://github.com/rust-lang/rust/issues/90765) is stabilized, it would be nice to include a command to do that, which could be used to include code, for example: `[!expand_literal_macros! include!("my-poem.txt")]`.

### Possible extension: Explicit parsing feature to enable syn

The heavy `syn` library is (in basic preinterpret) only needed for literal parsing, and error conversion into compile errors.

We could add a parsing feature to speed up compile times a lot for stacks which don't need the parsing functionality.

## License

Licensed under either of the [Apache License, Version 2.0](LICENSE-APACHE)
Expand Down
2 changes: 2 additions & 0 deletions book/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
book
bin
6 changes: 6 additions & 0 deletions book/book.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[book]
authors = ["David Edey"]
language = "en"
multilingual = false
src = "src"
title = "The Preinterpret Guide"
10 changes: 10 additions & 0 deletions book/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -e pipefail

# Ensure we're in the book directory
cd "$(dirname "$0")"

# Ensure mdbook has been downloaded
/usr/bin/env bash ./download-mdbook.sh

./bin/mdbook build
33 changes: 33 additions & 0 deletions book/download-mdbook.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -e pipefail

# Ensure we're in the book directory
cd "$(dirname "$0")"

MDBOOK_VER="v0.4.31"

## Check if the right version of mdbook is already downloaded
if [ -f ./bin/mdbook ]; then
if ./bin/mdbook --version | grep $MDBOOK_VER > /dev/null; then
echo "bin/mdbook @ $MDBOOK_VER is already downloaded"
exit 0
fi
echo "bin/mdbook is already downloaded but the wrong version, so downloading the correct version"
fi

# Download and extract the mdbook binary into the bin folder
mkdir -p bin && cd bin

if [[ "$OSTYPE" == "linux-gnu"* ]]; then
TAR_FILE="mdbook-${MDBOOK_VER}-x86_64-unknown-linux-musl.tar.gz"
else
TAR_FILE="mdbook-${MDBOOK_VER}-x86_64-apple-darwin.tar.gz"
fi

echo "Downloading $TAR_FILE..."
curl -OL "https://github.com/rust-lang/mdBook/releases/download/${MDBOOK_VER}/${TAR_FILE}"
tar -xf "${TAR_FILE}"
rm "${TAR_FILE}"

echo "Extracted to bin/mdbook"
cd ..
16 changes: 16 additions & 0 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Summary

- [Introduction](./introduction.md)
- [Quick Reference](./introduction/quick-reference.md)
- [Motivation](./introduction/motivation.md)
- [Examples](./introduction/examples.md)
- [Concepts](./concepts.md)
- [Commands](./concepts/commands.md)
- [Variables](./concepts/variables.md)
- [Expressions](./concepts/expressions.md)
- [Parsing](./concepts/parsing.md)
- [Command Reference](./command-reference.md)
- [Strings and Idents](./command-reference/strings.md)
- [Expressions](./command-reference/expressions.md)
- [Control Flow](./command-reference/control-flow.md)

3 changes: 3 additions & 0 deletions book/src/command-reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Command Reference

Coming soon...
1 change: 1 addition & 0 deletions book/src/command-reference/control-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Control Flow
1 change: 1 addition & 0 deletions book/src/command-reference/expressions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Expressions
1 change: 1 addition & 0 deletions book/src/command-reference/strings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Strings and Idents
3 changes: 3 additions & 0 deletions book/src/concepts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Concepts

Coming soon...
1 change: 1 addition & 0 deletions book/src/concepts/commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Commands
1 change: 1 addition & 0 deletions book/src/concepts/expressions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Expressions
1 change: 1 addition & 0 deletions book/src/concepts/parsing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Parsing
1 change: 1 addition & 0 deletions book/src/concepts/variables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Variables
Loading
Loading