1 unstable release

0.0.1 Nov 17, 2023

#7 in #mcfunction

MIT license

26KB
448 lines

mcfn - An mcfunction preprocessor

[!NOTE] This preprocessor is primarily designed for Bedrock Edition and may not work for Java Edition due to its different command syntax.

Overview

Philosophy

Instead of inventing an entire new programming language like mcscript for Java Edition, mcfn extends the mcfunction format to save repetitive tasks in a more readable way.

mcfn makes use of the execute and scoreboard commands to simulate a runtime within Minecraft. This allows dynamic conditional programming with several macros.

Installtion

Cargo

cargo install -f mcfn

Usage

To transpile all *.mcfn files within the functions directory, the following command can be used.

mcfn functions/**/*.mcfn

Reference

Indention

Leading spaces from each line in the program is removed in the built file. This allows nesting code in blocks for a better visual view.

#!proc greet
say Hello
say World
#!end

#!! can be written like below as well

#!proc greet
  say Hello
  say World
#!end

Comments

There are two different kinds of comments. A normal comment starts with a hash sign (#) as usual but must not be followed by a bang (!). They are included in the built file and can be used wherever full commands can be used. In fact, they can be used at the end of a command. The other kind of comment starts with a hash sign and two bangs (#!!). They must be used as a whole line meaning they cannot be appended to a macro or a command.

with

The with macro preprends each line of the block with its argument.

#!with execute as @a
  say Hello
  say World
#!end

#!! same as

executa as @a say Hello
execute as @a say World

proc and call

The proc macro creates a procedure. Its content can be called at a later point in the program any amount of times by using the call macro.

#!proc greet
  say Hello
  say World
#!end

#!call greet
#!call greet

Unlike most programming languages, procedures are always global, meaning a procedure defined within a different procedure can be called from outside as long as the call is present after the procedure has been defined.

#!call foo
#^^^^^^^^^ error

#!proc foo
  #!proc bar
    say Hello
    say World
  #!end
#!end

#!call bar

Procedures cannot have parameters. However you can implements such behaviour as shown below:

scoreboard objectes add _internal dummy

#!proc fizzbuzz
  #!if scores @s _arg_a matches 3
  #!then
    say Hi
  #!else
    say Hello
  #!end
#!end

scoreboard players set @s _arg_a 3
#!call fizzbuzz
scoreboard players reset @s _arg_a

scoreboard players set @s _arg_a 20
#!call fizzbuzz
scoreboard players reset @s _arg_a

log

The log macro prints its value to the console at transpile time whenever it is reached. This may be useful for debugging.

#!log hi

if, ifn and else

You can use if to verify a condition is met and ifn to verify the opposite. If multiple if or ifn macros are chained, then the block is executed when one of the conditions is met.

#!if score @s foo matches 3
#!ifn score @s bar matches 5
#!then
  say Hello
  say World
#!end

You can also add an else-block which will run if none of the conditions were met.

#!if score @s foo matches 3
#!ifn score @s bar matches 5
#!then
  say Hello
  say World
#!else
  say Bye
  say World
#!end

include

You can include another file relative to the current one by using the include macro.

#!include path/to/file.in

Note that including mcfn files will not be rendered so you may only use this with mcfunction programs that do not need to be transpiled initially (in case that step happens after including the file).

This macro can also be used to include some sort of header for example by creating a mcfunction program consisting of only comments.

declare and delete

A quick way of creating scores is by using the declare macro. Removing a score is possible with the delete macro.

#!declare x
#!delete x

The result would be:

scoreboard objectives add x dummy
scoreboard objectives remove x

when and else

If you have used programming languages like C or Rust before you might be familar with conditional compilation. Conditional compilation allows you to build variable programs depending on configuration settings or the OS for instance.

if cfg!(any(target_os = "windows", target_os = "linux")) {
    // do performant stuff
} else if cfg!(any(target_os = "android", target_os = "macos")) {
    // do less performant stuff
}

This can be implemented in mcfn in a way as well by using the when macro.

#!when MCFN_TARGET windows
#!when MCFN_TARGET linux
#!then
  say Hello
#!else
  say Hi
#!end

Such conditions compare a specified environment variable (such as MCFN_TARGET) with a string (such as windows). The block is rendered when the condition is met and ignored otherwise (also if the environment variable is not defined). The structure matches normal conditions with the exception that when is used instead of if and negated when macros do not exist. Note that when branches still run log and include macros and validate the blocks. The rendered block will just be ignored during the tranpilation step.

You can then control the transpilation with bash for example by using this syntax:

MCFN_TARGET="windows" mcfn script.mcfn

Good To Know

Highlighting

The preprocessor language is designed to look good with the mcfunction highlighter as well so you can use it in your repositories. Both .mcfn and .mcfn.mcfunction are therefore supported as file extensions.

Magic variables

The score __mcfn_internal_n where n may be any natural number and the "fake player" name __mcfn_internal are used by mcfn and should not be used within a program or else it might result in unexpected behaviour.

Usage in Allay

Add the following ruby script to your script directory and refer to it in allay.toml.

# TODO

Dependencies

~1.2–1.8MB
~34K SLoC