https://github.com/qucumbah/programming-language
Statically typed programming language that compiles to WebAssembly text format
https://github.com/qucumbah/programming-language
compiler programming-language webassembly
Last synced: 5 months ago
JSON representation
Statically typed programming language that compiles to WebAssembly text format
- Host: GitHub
- URL: https://github.com/qucumbah/programming-language
- Owner: qucumbah
- Created: 2022-05-02T09:44:50.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2022-07-19T06:04:03.000Z (over 3 years ago)
- Last Synced: 2025-03-20T03:40:00.131Z (10 months ago)
- Topics: compiler, programming-language, webassembly
- Language: TypeScript
- Homepage:
- Size: 356 KB
- Stars: 2
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: readme.md
Awesome Lists containing this project
README
# Language That Compiles To WebAssembly
- [Language That Compiles To WebAssembly](#language-that-compiles-to-webassembly)
- [About](#about)
- [Try it out](#try-it-out)
- [Usage](#usage)
- [Language overview](#language-overview)
- [Getting started](#getting-started)
- [Type system](#type-system)
- [Statements](#statements)
- [Expressions](#expressions)
- [Numeric literals](#numeric-literals)
- [Comments](#comments)
- [Explicit type conversions](#explicit-type-conversions)
- [Functions](#functions)
- [Function parameters](#function-parameters)
- [Variables and constants](#variables-and-constants)
- [Variable scoping and re-declarations](#variable-scoping-and-re-declarations)
- [Operators](#operators)
- [Control flow](#control-flow)
- [Pointers usage](#pointers-usage)
- [Memory declarations](#memory-declarations)
## About
LTCTWA is a low-level statically-typed C-like language that compiles to
WebAssembly text format.
## Try it out
There is an interactive demo available on
[GitHub Pages](https://qucumbah.github.io/programming-language/).
## Usage
The language is written in typescript and has to be run with Deno.
Compiling a source file into WAST:
`deno run --allow-read ./src/index.ts ./source-file.ltctwa > ./result-file.wast`
You will need a separate compiler to convert the result into WASM binary.
[WABT](https://github.com/WebAssembly/wabt) is a good tool for this task.
Running unit tests:
`deno test --allow-read`
## Language overview
### Getting started
Create a file named `main.ltctwa` and paste the following contents into it:
```
func export inc(arg: i32): i32 {
return arg + 1;
}
```
Use `deno run --allow-read ./src/index.ts ./main.ltctwa > ./output.wast` to
compile to WebAssembly text format.
This will produce the following output:
```
(module
(func
$inc
(export "inc")
(param i32)
(result i32)
local.get 0
i32.const 1
i32.add
return
)
)
```
The next step is to compile the result into WebAssembly binary, which can be
done with `wat2wasm ./dist/compiled.wast -o ./dist/main.wasm`.
Now, the resulting code may be imported and executed from JavaScript:
```js
async function main() {
const wasm = await getWasm();
const result = wasm.exports.inc(15);
console.log(result);
}
async function getWasm() {
const bytes = await fetch("main.wasm").then((res) => res.arrayBuffer());
const module = await WebAssembly.compile(bytes);
return WebAssembly.instantiate(module);
}
```
### Type system
There are 7 basic types:
- Signed integers: `i32`, `i64`
- Unsigned integers: `i32`, `i64`;
- Floats: `f32`, `f64`;
- Void (only available for function returns): `void`
There are also pointer types, which can only point to sections of memory, not to
other variables.
Pointer type is prepended with a `$` symbol: `$i32`, `$$u64` (pointer to
pointer) etc.
There is no void pointer for generic sections of memory; a pointer to `i32` can
be used instead.
### Statements
A program consists of a series of statements that are terminated by the
semicolon (`;`).
There are 5 kinds of statements:
- Conditional Statement (`if`)
- Loop Statement (`while`)
- Return Statement (`return`)
- VariableDeclaration Statement (operator `=`)
- Expression Statement (any expression terminated with a semicolon)
### Expressions
There are 7 kinds of expressions:
- Identifier Expression (variables/constants/parameters access)
- Numeric Expression (numeric literals)
- Function Call Expression (functionName(arg1value, arg2value))
- Unary Operator Expression (-val, !val, ...)
- Binary Operator Expression (val1 + val2, val1 << val2, ...)
- Type Conversion Expression (a special kind of binary operator expression: val
-> type)
- Composite Expression (any expression inside parentheses)
### Numeric literals
Values of non-void types can be created with numeric literals:
```
var signed: i32 = -1;
var unsigned: u32 = 1u;
var long: i32 = 1l;
var unsignedLong: i32 = 1ul;
var float: f32 = 1.;
var double: f64 = 1.l;
```
### Comments
Only single-line comments are supported. Use `//` for comments:
```
// This is a comment
var someVar: i32 = 15; // Also a comment
```
### Explicit type conversions
Implicit type conversions are not allowed. All type changes have to be performed
explicitly using the type conversion operator `->`:
```
const float: f32 = 5. + 1 -> f32;
const float: f32 = 5. + 1; // This would fail
```
Type conversions can also be chained. This is useful for converting negative
floats to unsigned integer, as this will cause a runtime error in WASM:
```
const unsignedLong: u64 = -1. -> i64 -> u64;
```
### Functions
There are three kinds of functions: plain (internal, not available in JS),
export (available both in module and JS), import (imported from JS).
Each of them has to have an explicit result type.
Examples:
```
func plainFunction(arg1: i32, arg2: i32): i32 {
return arg1 & arg2;
}
func export exportFunction(arg1: i32, arg2: i32): i32 {
return arg1 & arg2;
}
func import(console::log) console_log(arg: i32): void;
```
Import object is used to pass import function's body from JS.
For example, to use import function from above it has to be passed like this:
```js
WebAssembly.instantiate(module, {
console: {
log: console.log,
},
});
```
### Function parameters
Each function parameter has to have an explicit non-void type.
All parameters are constant.
### Variables and constants
All variables and constants have to be initialized from the start, and have to
have an explicit type:
```
var someVariable: i32 = 3;
const someConstant: f32 = someFloatArgument;
```
### Variable scoping and re-declarations
Variables, constants and parameters can be re-declared with a different type:
```
func redeclarationFunc(arg: i32): void {
const arg: f32 = arg -> f32;
const arg: u64 = 15ul;
var someVar: i32 = 3;
var someVar: i64 = someVar -> i64;
}
```
Variables are block-scoped:
```
const someValue: i32 = 1;
if (1) {
const someValue: f32 = 2.;
console_log(someValue); // The value is now 2.0
}
```
If a variable is not declared in the current scope, its value is taken from the
outer one:
```
var someVar: i32 = 1;
if (1) {
console_log(someVar); // The value is still 1
// If we change it here, the outer variable will be affected
someVar = 2;
const someVar: i32 = 3;
console_log(someVar); // Inside this block of code, someVar's value is now 3
}
console_log(someVar); // But the outer value is still 2
```
### Operators
Operators precedence table with explanation:
| Precedence | Operator(s) | Description | Accepted value(s) | Return value |
| ---------- | ----------------------------- | ------------------------------------------------------------------------------ | ------------------------------------ | ---------------- |
| 1 | `!`
`@`
`-` | Logical NOT
Pointer dereference
Unary minus | Integers
Pointers
Non-void
| Same as operands |
| 2 | `*`
`/` | Multiplication
Division | Non-void | Same as operands |
| 3 | `+`
`-` | Addition
Subtraction | Non-void | Same as operands |
| 4 | `>`
`<`
`>=`
`<=` | Greater than
Less than
Greater than or equal to
Less than or equal to | Non-void | `i32` |
| 5 | `==`
`!=` | Equal to
Not equal to | Non-void | `i32` |
| 6 | `<<`
`>>` | Left shift
Right shift | Integers | Same as operands |
| 7 | `&` | Logical AND | Integers | Same as operands |
| 8 | `^` | Logical XOR | Integers | Same as operands |
| 9 | `\|` | Logical OR | Integers | Same as operands |
| 10 | `=` | Assignment | Identifier `=` Non-void | `void` |
| 11 | `->` | Type conversion | Non-void `->` `type` | `type` |
Pointer type is also included in the non-void values category.
Just as with variable assignment, there is no implicit conversion, so both
operands have to be the same type. The only two exceptions to this rule are
assignment and type conversion operators.
### Control flow
There are three control flow statements: conditional, loop, and return
statements.
Examples:
```
func loopTest(arg: u32): i32 {
var someVar: i32 = 1;
// Conditional and loop statements accept a value of type `i32`
while (someVar < arg) {
if (someVar == 256) {
return 256;
}
someVar = someVar * 2;
}
return someVar;
}
```
### Pointers usage
Pointers are represented as `i32` internally.
Pointer arithmetic is available, but all adresses are counted in bytes. Example:
```
// All values have to be converted into pointers
var pointerToInt: $i32 = 0 -> $i32;
var pointerToNextInt: $i32 = pointerToInt + 4 -> $i32;
```
In C, second variable's value would have been calculated as `pointerToInt + 1`.
The expression would be the same as
`(byte*)pointerToInt + (1 * sizeof(*pointerToInt))`.
In LTCTWA, such implicit calculation is absent.
To manipulate a value that is pointed to, dereference operator "`@`" is used:
```
var somePointer: $i32 = 100 -> $i32;
@somePointer = 1;
@(somePointer + 4) = 2;
const two: i32 = @(104 -> $i32);
var pointerToPointer: $$i32 = 200;
@pointerToPointer = somePointer;
const one = @@pointerToPointer;
const two = @(@pointerToPointer + 4 -> $i32);
```
There is no way to get an adress of a variable on the stack, only adresses of
memory can be used.
### Memory declarations
Just like functions, there are three ways to declare memory: plain (only used
within a module), export (may be accessed from JS), and import (imported from
JS).
There may only be one memory declaration per module.
Syntax examples:
```
memory(1u);
// This can be accessed in JS as `instance.exports.exportMem`
memory(1u) export(exportMem);
// For this to work an instance of `WebAssembly.Memory` has to be passed to the import object
memory(1u) import(memoryNamespace::memoryName);
```