JoustExt is a simple language compiling to BF Joust. It adds a function call system (evaluated at compile time) and a continuation system for constructs like if/else.
To compile the compiler, you need sbt installed.
Run scripts/make
or sbt package
.
To compile a JoustExt program as follows, run scripts/jxc foo.jx foo.bf
.
To compile the example programs in examples/*, run scripts/jxc
.
All BFJoust commands and constructs except (a{b}c)*n
can be directly embedded in JoustExt code.
Unlike BF Joust, however, comments have to be marked with //
like in C.
++++ // This is a comment
[+] // If we didn't have the '//', it would be a syntax error!
(-)*30(---)*-1
If needed for some reason, any command or block can be suffixed with an ;
to disambiguate.
Functions names are prefixed with @
and variable names are prefixed with $
.
Variables and function names all must match the pattern [a-zA-Z_][a-zA-Z0-9_]
.
// Declare a function named fn, with two parameters a and b.
@fn($a, $b) {
$c = ($a + $b) * $a + $b
++++
}
@fn(1, 2)
@fn(2, 3)
Function calls are compiled by splicing.
Thus, the function call @fn(30, 40)
would be the same as the following code:
local { // local creates a new scope.
$a = 30
$b = 40
$c = ($a + $b) * $a + $b
++++
}
In BF Joust expressions, only five operators exist:
addition (+
),
subtraction/unary negation (-
),
multiplication (*
),
division (/
),
and modulo (%
).
Unary negation has the highest precedence,
multiplication, division, and modulo share the next lowest precedence,
and addition and subtraction share the lowest precedence.
You may use expressions in the (a)*n
construct directly, such as in (+)*$a
or (+)*($a + $b)
.
The parentheses are required for any expression more complicated than a constant value or a single variable.
For ease of implementation, variables and functions in JoustExt are dynamically scoped However, as changes to variables are limited to the block they are modified in, variables are effectively immutable despite the dynamic scoping.
@fn() {
// Despite not being available in lexical scope, $a can still be accessed here.
(+)*$a
@fn2() // And since @fn2 will be defined by the time @fn is called, it is accessible here.
}
@fn2() {
(-)*$a
}
$a = 1
local {
$a = 2
// $a is 2 here.
@fn() // and this evaluates to (+)*2 (-)*2
}
// but $a is 1 here!
@fn() // so this evaluates to (+)*1 (-)*1
Conditionals are evaluated at compile time, just like functions. If predicate evaluates to true, the contents are included, otherwise the else block is included if it exists.
if($a > 0) {
(+)*$a
} else {
(-)*(-$a)
}
if ($b == 0 || $c == 0) {
(<)*1000 // byeeee~
}
Valid comparison operators are
less than (<
),
greater than (>
),
less than or equal to (<=
),
greater than or equal to (>=
),
equals (==
),
and not equals (!=
).
Valid boolean operators are
and (&
),
or (|
),
and not (!
).
They have no particular order of precedence, so use parentheses liberally.
You may include raw text in a JoustExt program by writing raw
followed by a quoted string.
These quoted strings are much simpler than in most languages.
There is no escape mechanism, and new lines/etc can be directly embedded.
However, this means that there is absolutely no way to output a raw "
character.
The raw
may be followed by +margins
token to cause the JoustExt compiler to strip all text up to the first |
on each line.
In other words, Scala's String.stripMargins is called on the contents of the block.
The abort
command has the same syntax as the raw
command, with the exception that raw
doesn't support +margins
.
abort
will output a message to the output file, and then execute (.)*-1
.
//
comments in raw blocks are not output in the final program, due to a lazy implementation of comments.
raw "foo"
raw "
foo"
raw "\\..\"
raw +margins "| foo
| bar"
raw " foo // baz
bar" // baz will NOT appear in the final output.
abort "We can't do anything anymore. :("
There is a for
block construct that allows you to count iterations unlike (a)*n
.
for($i in $a + $b to $c) {
+++
}
for($i in 9 to 30) {
[-] (<)*$i (+)*100 (>)*$i
}
Finally, you may use the terminate
instruction at any time to stop execution at the linearize
phase.
Sometime in the future, it'll work in the exprs
phase too.
For some advanced control flow, JoustExt supports an continuation system.
callcc(@a) {
++
[ @a() ]
}
callcc(@a)
creates a special function named @a
, which when called, jumps to whatever is after the callcc
block.
This is done by code rewriting which is very prone to generating very long code, however, so, use this with care.
To prevent that, reset
blocks may be used to contain the scope of a continuation.
When a continuation is generated, and a reset
block is encountered, the program will halt.