craftlang: initial grammar implementation

main
Katharina Fey 2 years ago
parent 2c4693ca23
commit 40d5cd5e6b
Signed by: kookie
GPG Key ID: F972AEEA2887D547
  1. 1
      prototypes/craftlang/.envrc
  2. 0
      prototypes/craftlang/.projectile
  3. 81
      prototypes/craftlang/README.md
  4. 64
      prototypes/craftlang/craftlangc
  5. 1
      prototypes/craftlang/shell.nix
  6. 6
      prototypes/craftlang/test.cl

@ -0,0 +1 @@
eval "$(lorri direnv)"

@ -0,0 +1,81 @@
# craftlang
Craftlang (`.craft` files) is an expression language for crafting
recipies and block arrangement in the popular free software block
building game [Minetest](https://minetest.org). This tool is capable
of parsing `.craft` files and generating image outputs.
## Usage
This tool is written in [Raku](https://raku.org) and such you need to have `rakudo`
installed on your system. Additionally you need to make your minetest
assets tree and environment files available via ennvironment
variables.
```console
$ env MTCRAFT_ENV_PATH=/path/to/environments \
MINETEST_ASSETS=/path/to/minetest/assets/root \
craftlangc ./path-to-recipe.mtcraft
```
## Language overview
Craftlang is a domain-specific language for expressing recipes, based
on a small set of keywords, with associated payloads. A payload is
either a primitive or scope. Scopes use two-space indentation to
handle end-of-scope separation.
### Example
The following example should be enough to explain the basic structure
of primary mtcraft files.
```mtcraft
ENV alloys
INPUTS
copper_ingot 7
tin_ingot 1
OUTPUTS
bronze_ingot 8
```
The `ENV` keyword loads an environment file. Environment files add a
set of metadata which is added to a generated output, and also
provides a way to specify how other keywords are handled.
The `INPUTS` and `OUTPUTS` keywords open scopes. A scope is a set of
statements that is specific to the loaded environment but must be one
of the following: `key-value`, `marker` or `keyword`.
The example above contains three `key-value` statements. A `marker`
statement does not contain additional data and instead provides a
boolean flag to the environment. A `keyword` statement provides a way
to have nested `ENV`, `INPUTS`, and `OUTPUTS` scopes.
### Environment files
The environment provided via the `ENV` keyword must be present in the
`MTCRAFT_ENV_PATH`. An environment specifies the input-output
mappings for `INPUTS` and `OUTPUTS` scopes.
Required keywords for an ENV file are `BG`, `INPUTS`, `MODE`, and
`OUTPUTS`. The `SPACING` keyword is permitted outside of the `INPUTS`
and `OUTPUTS` blocks to be applied to both simultaniously. The `MODE`
keyword selects the general output mode. Currently accepted modes are
`crafting` and `arrangement`
```mtcraftenv
BG alloy_furnace
MODE crafting
SPACING 15 15
INPUTS
ANCHOR 125 125
COUNT 2 1
OUTPUTS
ANCHOR 525 125
COUNT 1 1
```
The default fill-mode is Left-to-right-top-to-bottom.

@ -0,0 +1,64 @@
#!/usr/bin/env raku
grammar FileGrammar {
# The top-level file is a list of statements
token TOP { ^ <line>* %% \v* $ }
# A line is either a list of keyword-payload or scope
token line { <statement> | <scope> }
# A statement is a simple keyword-payload mapping
token statement { <keyword> \s <payload> }
# The set of simple key-value keywords
token keyword { < ANCHOR BG COUNT ENV MODE SPACING > }
# A simple payload
token payload { \S+ %% \h* }
# A scope is a scope-word + list of scope-lines
token scope { <scope-word> <scope-line>* }
# The set of scope-opening keywords
token scope-word { < INPUTS OUTPUTS > }
# A scope line is either a simple payload or a full statement
token scope-line { \v+ \h \h [ <statement> | <payload> ] }
}
class Actions {
method TOP($/) {
make $<line>>>.made
}
method line($/) {
make .made with $<statement> || $<scope>;
}
method statement($/) {
make { Keyword => $<keyword>.made, Payload => $<payload>.made }
}
method scope($/) {
make { Keyword => $<scope-word>.made, Payload => $<scope-line>>>.made }
}
method scope-line($/) {
make $<statement> ?? $<statement>.made !! { Payload => $<payload>.made }
}
method keyword($/) { make ~$/ }
method payload($/) { make ~$/ }
method scope-word($/) { make ~$/ }
}
sub MAIN($file, Bool :$verbose) {
my $input = slurp $file;
say "Parsing input: $input" if $verbose;
my @parse = FileGrammar.parse($input, actions => Actions.new).made;
for @parse -> %line {
say %line
}
}

@ -0,0 +1 @@
import <shells/raku> {}

@ -0,0 +1,6 @@
ENV alloys
INPUTS
copper_ingot 7
tin_ingot 1
OUTPUTS
bronze_ingot 8
Loading…
Cancel
Save