WauxLang - A language that compiles to WASM bytecode.
This is currently an experimental language/compiler targeting WASM as it's runtime. Compiler is written in F#.
Should I use this? Probably not.
This is currently alpha software. You'll run into bugs and issues, potentially for no unexplained reason. Here be dragons.
Why Waux? What does that word mean? How do you pronounce it?
- Pronounced like the English work "walk" 1
- "A Scots word for wake"
- Why Waux?
- I googled "words that start with wa" and I liked this one the best.
The fastest way to get started is to install via nuget tool.
- Install a flavor of .NET 8 from microsoft
- Run the following command:
dotnet tool install --global Waux.Lang.Cli
- To update if already installed:
dotnet tool update -g Waux.Lang.Cli
- To update if already installed:
- You now have access to the
waux
cli tool
How to compile and use.
- Write a valid
.waux
program- See examples to get started
- Every waux file requires a 0 parameter
main
function - All code must be in a single
.waux
file, waux does not currently support importing across files.
- Run
waux compile <.waux file>
to generate a wasm file - Run
waux run <.wasm file>
to run the generated wasm- Can also run in any other wasm compliant runtime.
- This leverages the wasmtime nuget package.
Please see the examples folder for valid programs
Waux currently supports the following language concepts:
- Functions
- While loops
- If/else blocks
- Most boolean comparisons
- Most integer operations
- With clarification below
- Support operator precedence
WebAssembly stores numbers in memory in variable width. Right now Waux works with int32s that can be stored in one byte.
This will be fixed in an upcoming release.
Lets start with the simpliest program.
func main() {
10
}
A main function that returns a hard coded value.
You can declare and define a variable to hold values using the let
binding.
func main() {
let val = 23;
val
}
Once a variable has been declared, you change change the value by using the :=
operator.
func main() {
let val = 23;
val := val - 11;
val
}
Status:
- Currently variables and function names can be made of the following values:
- Alpha characters: [a-zA-Z]
- Digit characters: [0-9]
- Underscore: [_]
- CombinedRegex: [a-zA-Z_0-9]+
- There is currently no way to define an empty/null variable
let x;
Semicolons separate expressions, the last semicolon is unneeded to indicate a return type. Future work may drop the need for semicolons.
General rule of thumb:
- Add semicolon to all normal lines of code
- Blocks of code
{}
do not need a semicolon after the closing block}
. - Return values do not need a semicolon
- But last line in a block that does not return a value (currently only
while
loop), need a semicolon.- See the euler1 example below.
if/else
is currently an expression, so last line is assumed the return value. Again, see euler1 example below.
There is still some undefined behavior around semicolons. When I'm debugging sometimes I'll add semicolons to see if that fixes anything. Alpha code, am I right???
You can create more complicated mathematical expressions.
func main() {
let a = 5;
let b = 10;
a * 2 + b
}
and another
func main() {
let a = 5;
let b = 10;
a * (2 + b)
}
Status:
- All four primary mathematical operations (+, -, *, /)
- Plus modulo (%)
- Operator precedence (* before +, etc)
- Use of parentheses to denotate precedence
Currently Waux does not contain a true boolean value true/false
. WebAssembly also does not contain a true boolean value.
For comparison, it currently uses 0 meaning false and 1 meaning true.
So it supports all the following boolean operators but just on integer values instead of true/false
==
- equal!=
- not equal<
- less than<=
- less than equal>
- greater than>=
- greater than equaland
- only true if both expressions are trueor
- true if either expressions are true
All these operators are binary expressions in the form of: <expression1> <operator> <expression2>
.
You can utilize boolean logic with both of the following concepts.
You can also add if/else expressions in waux:
func main() {
let p = 10;
let result = if (p > 20) {
10
} else {
0
};
result
}
Status:
- Since
if/else
is an expression (returns a value), no semicolon is needed on the last lines of each branch.- In the example above,
result
is set as the value of theif/else
expressions.
- In the example above,
- Currently
else if
statement is not supported - Right now the
else
is optional, but will be changing in a future release
You can leverage a while loop like below.
func countTo(n) {
let x = 0;
while (x < n) {
x := x + 1;
}
x
}
func main() {
countTo(10)
}
Status:
break
orcontinue
keywords are currently supported- Since
while
loop is a statement instead of an expression, a semicolon is needed on the last line in the while loop.
Putting this all together, we have a solution for euler1:
func main() {
let count = 0;
let n = 1;
while (n < 1000) {
let num = if (((n % 3 == 0) or ((n % 5) == 0))) {
n
} else {
0
};
count := count + num;
n := n + 1;
}
count
}
In my day job I'm a web developer. This language is a compilation of two books that I've worked my way through.
- Writing an Interpreter in Go
- This helped me understand lexing and parsing.
- I created an implementation in F#.
- WebAssembly from the Group Up
- This details how to compile code to WebAssembly.
- It's implemented in JavaScript using the Ohm Parsing Library.
- I used it as the basis for Waux's Web Assembly Compiler.
Both of these books are very approachable introductions to language design and compilers.
I would recommend these books if you are interested in learning about langauges and compilers.
This repo currently uses fantomas in order to format the compiler.
- To install the tool
dotnet tool install fantomas
dotnet tool restore
- To run and format everything
./format.bat
- Or
dotnet fantomas -r .