mirror of
https://github.com/exoticorn/curlywas.git
synced 2026-01-20 11:46:43 +01:00
Compare commits
25 Commits
7e4764bca8
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e7ea508cd | |||
| 0a0d90c801 | |||
| c22297ea82 | |||
| c59b35f9c6 | |||
| 2cf47085c1 | |||
| 01d64baaab | |||
| a52fe53a01 | |||
| 1e746be750 | |||
| 4cfc7ae8a8 | |||
| aac7bbd878 | |||
| ebc701e2f2 | |||
| 557c3a8426 | |||
| 896385654a | |||
| cda3eb868b | |||
| 5f316cf17d | |||
| e608d3bb4b | |||
| b41b7f250c | |||
| 71de622634 | |||
| f433948d4e | |||
| 132aea3996 | |||
| 1b434f6b30 | |||
| ce8435e3dc | |||
| e58b13c8ee | |||
| ac9b6cd4ca | |||
| 0e403ed82e |
24
Cargo.lock
generated
24
Cargo.lock
generated
@@ -13,9 +13,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.44"
|
||||
version = "1.0.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1"
|
||||
checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd"
|
||||
|
||||
[[package]]
|
||||
name = "ariadne"
|
||||
@@ -34,9 +34,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chumsky"
|
||||
version = "0.5.0"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2d3efff85e8572b1c3fa0127706af58c4fff8458f8d9436d54b1e97573c7a3f"
|
||||
checksum = "8d02796e4586c6c41aeb68eae9bfb4558a522c35f1430c14b40136c3706e09e4"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
@@ -83,9 +83,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.3"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||
checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
@@ -106,9 +106,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.105"
|
||||
version = "0.2.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "869d572136620d55835903746bcb5cdc54cb2851fd0aeec53220b4bb65ef3013"
|
||||
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
|
||||
|
||||
[[package]]
|
||||
name = "pico-args"
|
||||
@@ -139,18 +139,18 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.8.0"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db0c351632e46cc06a58a696a6c11e4cf90cad4b9f8f07a0b59128d616c29bb0"
|
||||
checksum = "aa9d9bf45fc46f71c407837c9b30b1e874197f2dc357588430b21e5017d290ab"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.81.0"
|
||||
version = "0.83.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98930446519f63d00a836efdc22f67766ceae8dbcc1571379f2bcabc6b2b9abc"
|
||||
checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a"
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
|
||||
@@ -7,9 +7,9 @@ license = "MIT"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
wasmparser = "0.81"
|
||||
wasm-encoder = "0.8"
|
||||
wasmparser = "0.83"
|
||||
wasm-encoder = "0.10"
|
||||
anyhow = "1"
|
||||
chumsky = "0.5"
|
||||
chumsky = "0.8"
|
||||
ariadne = "0.1"
|
||||
pico-args = "0.4"
|
||||
|
||||
243
README.md
243
README.md
@@ -56,9 +56,17 @@ Then run it on [MicroW8](https://exoticorn.github.io/microw8/v0.1pre2)
|
||||
*/
|
||||
```
|
||||
|
||||
### Include
|
||||
|
||||
Other sourcefiles can be included with the `include` top level statement:
|
||||
|
||||
```
|
||||
include "platform_imports.cwa"
|
||||
```
|
||||
|
||||
### Types
|
||||
|
||||
There are four types in WebAssembly and therefore Curlywas:
|
||||
There are four types in WebAssembly and therefore CurlyWas:
|
||||
|
||||
* `i32`: 32bit integer
|
||||
* `i64`: 64bit integer
|
||||
@@ -81,19 +89,28 @@ For floating point numbers, only the most basic decimal format is currently impl
|
||||
0.464, 3.141, -10.0
|
||||
```
|
||||
|
||||
String literals exist in a very basic form. No escape character implemented yet.
|
||||
String literals are used for include paths, import names and as literal strings in the data section. The following escapes are supported:
|
||||
| Escape | Result | Comment |
|
||||
| `\"` | `"` | |
|
||||
| `\'` | `'` | |
|
||||
| `\t` | 8 | |
|
||||
| `\n` | 10 | |
|
||||
| `\r` | 13 | |
|
||||
| `\N` | 0x0N | (Can't be followed by a hex digit) |
|
||||
| `\NN` | 0xNN | |
|
||||
|
||||
```
|
||||
"env.memory", "Hello World!"
|
||||
|
||||
this does not work, yet:
|
||||
|
||||
"one line\nsecond line", "They said: \"Enough!\""
|
||||
```
|
||||
|
||||
Character literals are enclosed in single quotes `'` and support the same escapes as strings. They can contain up to 4 characters and evaluate to the
|
||||
little-endian representation of these characters. For examples: `'A'` evaluates to `0x41`, `'hi'` evaluates to 0x6968, and `'Crly'` to 0x7a6c7243.
|
||||
|
||||
### Imports
|
||||
|
||||
WebAssembly imports are specified with a module and a name. In Curlywas you give them inside a single string literal, seperated by a dot. So a module `env` and name `printString` would be written `"env.printString"`.
|
||||
WebAssembly imports are specified with a module and a name. In CurlyWas you give them inside a single string literal, seperated by a dot. So a module `env` and name `printString` would be written `"env.printString"`.
|
||||
|
||||
Linear memory can be imported like this:
|
||||
|
||||
@@ -124,6 +141,32 @@ import "env.random" rand() -> i32; // no params
|
||||
import "env.atan2" atan2(f32, f32) -> f32;
|
||||
```
|
||||
|
||||
### Global variables
|
||||
|
||||
Global variables are declare like this:
|
||||
|
||||
```
|
||||
global name[: type] = value; // immutable global value
|
||||
global mut name[: type] = initial_value; // mutable variable
|
||||
```
|
||||
|
||||
An immutable global is probably of very limited use, as usually you'd most often use it by exporting it so that some other module
|
||||
can use it. However, exporting global variable is not yet supported in CurlyWas.
|
||||
|
||||
The type is optional, if missing it is inferred from the init value.
|
||||
|
||||
### Constants
|
||||
|
||||
Constants can be declared in the global scope:
|
||||
|
||||
```
|
||||
const name[: type] = value;
|
||||
```
|
||||
|
||||
`value` has to be an expression evaluating to a constant value. It may reference other constants.
|
||||
|
||||
The type is optional, but if given has to match the type of `value`.
|
||||
|
||||
### Functions
|
||||
|
||||
Functions look like this:
|
||||
@@ -144,6 +187,8 @@ export fn upd() {
|
||||
}
|
||||
```
|
||||
|
||||
The body of a function is a block (see below), meaning a sequence of statements followed by an optional expression which gives the return value of the function.
|
||||
|
||||
#### Local variables
|
||||
|
||||
Variables are defined using `let`:
|
||||
@@ -158,9 +203,11 @@ They can also be initialized to a value at the same time, in this case the type
|
||||
let name = expression;
|
||||
```
|
||||
|
||||
There are two modifiers that change when the initializer expression is actually evaluated.
|
||||
Local variables are lexically scoped and shadowing variables declared earlier is explicitely allowed.
|
||||
|
||||
They both can reduce the size of the resulting code, but it is up to the coder to make sure that the delayed evaluation doesn't change the semantics of the code.
|
||||
`name = value;` assigns a new value to a (non-inline) variable.
|
||||
|
||||
There are two modifiers that change when the initializer expression is actually evaluated. They both can reduce the size of the resulting code (when used appropriately), but it is up to the coder to make sure that the delayed evaluation doesn't change the semantics of the code.
|
||||
|
||||
```
|
||||
let inline name = expression;
|
||||
@@ -205,3 +252,185 @@ if rand() & 1 {
|
||||
printNumber(foo / 2 + 2); // foo is never initialized in the else branch
|
||||
}
|
||||
```
|
||||
|
||||
#### Expressions
|
||||
|
||||
Expressions are written in familiar infix operator and function call syntax.
|
||||
|
||||
The avaliable operators are:
|
||||
|
||||
| Precedence | Symbol | WASM instruction(s) | Description |
|
||||
| ---------- | ---------------- | -------------------------------------- | ----------------------------- |
|
||||
| 1 | - | fxx.neg, ixx.sub | Unary negate |
|
||||
| | ! | i32.eqz | Unary not / equal to zero |
|
||||
| 2 | as | default signed casts | Type cast |
|
||||
| 3 | ?, ! | i32.load_u8_u, i32.load | load byte/word |
|
||||
| 4 | * | ixx.mul, fxx.mul | Multiplication |
|
||||
| | /, % | ixx.div_s, fxx.div, ixx.rem_s | signed division / remainder |
|
||||
| | #/, #% | ixx.div_u, ixx.rem_u | unsigned division / remainder |
|
||||
| 5 | +, - | xxx.add, xxx.sub | Addition, substraction |
|
||||
| 6 | <<, >>, #>> | ixx.shl, ixx.shr_s, ixx.shr_u | Shifts |
|
||||
| 7 | ==, != | xxx.eq, xxx.ne | Equal, not equal |
|
||||
| | <, <=, >, >= | ixx.lt_s, ixx.le_s, ixx.gt_s, ixx.ge_s | signed comparison |
|
||||
| | | fxx.lt, fxx.le, fxx.gt, fxx.ge | |
|
||||
| | #<, #<=, #>, #>= | ixx.lt_u, ixx.le_u, ixx.gt_u, ixx.ge_u | unsigned comparison |
|
||||
| 8 | &, \|, ^ | ixx.and, ixx.or, ixx.xor | Bitwise logic |
|
||||
| 9 | <\| | n/a | take first, see sequencing |
|
||||
|
||||
You can obviously group sub-expression using brackets `( )`.
|
||||
|
||||
Functions can be called using familiar `function_name(parameters)` syntax.
|
||||
|
||||
There are intrinsic functions for all WASM instructions that simply take a number of parameters and return a value. So you can, for example, do something like `i32.clz(value)` to use instructions that don't map to their own operator.
|
||||
|
||||
Some common float instructions have shortcuts, ie. they can be used without the `f32.` / `.f64.` prefix:
|
||||
|
||||
`sqrt, min, max, ceil, floor, trunc, nearest, abs, copysign`
|
||||
|
||||
`name := value` both assigns the value to the variable `name` and returns the value (using the `local.tee` WASM instruction).
|
||||
|
||||
Blocks are delimited by curly braces `{ }`. They contain zero or more statements, optionally followed by an expression. They evaluate to the
|
||||
value of that final expression if it is there.
|
||||
|
||||
So for example this block evaluates to 12:
|
||||
|
||||
```
|
||||
{
|
||||
let a = 5;
|
||||
let b = 7;
|
||||
a + b
|
||||
}
|
||||
```
|
||||
|
||||
Blocks are used as function bodies and in flow control (`if`, `block`, `loop`), but can also used at any point inside an expression.
|
||||
|
||||
Variable re-assignments of the form `name = name <op> expression` can be shortened to `name <op>= expression`, for example `x += 1` to increment `x` by one. This works for all arithmetic, bit and shift operators.
|
||||
The same is allowed for `name := name <op> expression`, ie. `x +:= 1` increments `x` and returns the new value.
|
||||
|
||||
#### Flow control
|
||||
|
||||
`if condition_expression { if_true_block } [else {if_false_block}]` executes the `if_true_block` if the condition evaluates to a non-zero integer and
|
||||
the `if_false_block` otherwise (if it exists). It can also be used as an expression, for example:
|
||||
|
||||
```
|
||||
let a = if 0 { 2 } else { 3 }; // assigns 3 to a
|
||||
```
|
||||
|
||||
If the `if_false_block` contains exactly one `if` expression or statement you may omit the curly braces, writing `else if` chains like:
|
||||
```
|
||||
if x == 0 {
|
||||
doOneThing()
|
||||
} else if x == 1 {
|
||||
doThatOtherThing()
|
||||
} else {
|
||||
keepWaiting()
|
||||
}
|
||||
```
|
||||
|
||||
`block name { ... }` opens a named block scope. A branch statement can be used to jump to the end of the block. Currently, `block` can only be used
|
||||
as a statement, returning a value from the block is not yet supported.
|
||||
|
||||
`loop name { ... }` opens a named loop scope. A branch statement can be used to jump back to the beginning of the loop.
|
||||
|
||||
`branch name` jumps to the end/start of the named `block` or `loop` scope. `branch_if condition: name` does the same if the condition evaluates to a
|
||||
non-zero integer.
|
||||
|
||||
`return [expression]` returns from the current function with the value of the optional expression.
|
||||
|
||||
#### Memory load/store
|
||||
|
||||
To read from memory you specify a memory location as `base?offset`, `base!offset` or `base$offset`. `?` reads a byte, `!` reads a 32bit word
|
||||
and `$` reads a 32bit float.
|
||||
|
||||
`base` can be any expression that evaluates to an `i32` while `offset` has to be a constant `i32` value. The effective memory address is the sum of both.
|
||||
|
||||
Writing to memory looks just like an assignment to a memory location: `base?offset = expression`, `base!offset = expression` and `base$offset = expression`.
|
||||
|
||||
When reading/writing 32bit words you need to make sure the address is 4-byte aligned.
|
||||
|
||||
These compile to `i32.load8_u`, `i32.load`, `f32.load`, `i32.store8`, `i32.store` and `f32.store`.
|
||||
|
||||
In addition, all wasm memory instructions are available as intrinsics:
|
||||
|
||||
```
|
||||
<load-ins>(<base-address>[, <offset>, [<align>]])
|
||||
|
||||
offset defaults to 0, align to the natural alignment: 0 for 8bit loads, 1 for 16bit, 2 for 32 bit and 3 for 64bit.
|
||||
```
|
||||
|
||||
with `<load-ins>` being one of `i32.load`, `i32.load8_u`, `i32.load8_s`, `i32.load16_u`, `i32.load16_s`,
|
||||
`i64.load`, `i64.load8_u`, `i64.load8_s`, `i64.load16_u`, `i64.load16_s`, `i32.load32_u`, `i32.load32_s`,
|
||||
`f32.load` and `f64.load`.
|
||||
|
||||
```
|
||||
<store-ins>(<value>, <base-address>[, <offset>, [<align>]])
|
||||
|
||||
offset and align defaults are the same as the load intrinsics.
|
||||
```
|
||||
with `<store-ins>` being one of `i32.store`, `i32.store8`, `i32.store16`, `i64.store`, `i64.store8`,
|
||||
`i64.store16`, `i64.store32`, `f32.store` and `f64.store`.
|
||||
|
||||
#### Data
|
||||
|
||||
Data sections are written in `data` blocks:
|
||||
|
||||
```
|
||||
data <address> {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The content of such a block is loaded at the given address at module start.
|
||||
|
||||
Inside the data block you can include 8, 16, 32, 64, f32 or f64 values:
|
||||
|
||||
```
|
||||
i8(1, 255) i16(655350) i32(0x12345678) i64(0x1234567890abcdefi64) f32(1.0, 3.141) f64(0.5f64)
|
||||
```
|
||||
|
||||
Strings:
|
||||
```
|
||||
"First line" i8(13, 10) "Second line"
|
||||
```
|
||||
|
||||
And binary files:
|
||||
|
||||
```
|
||||
file("font.bin")
|
||||
```
|
||||
|
||||
#### Advanced sequencing
|
||||
|
||||
Sometimes when sizeoptimizing it helps to be able to execute some side-effecty code in the middle an expression.
|
||||
Using a block scope, we can execute any number of statements before evaluating a final expression to an actual value. For example:
|
||||
|
||||
```
|
||||
let x = { randomSeed(time); random() }; // set the random seed right before obtaining a random value
|
||||
```
|
||||
|
||||
To execute something after evaluating the value we want to return, we can use the `<|` operator. Here is an example from the Wasm4 version of
|
||||
Skip Ahead (see the example folder for the full source):
|
||||
|
||||
```
|
||||
text(8000, set_color(c) <| rect(rx, y, rw, 1), set_color(4));
|
||||
```
|
||||
|
||||
Here, we first set the color to `c`. `set_color` also happens to return the constant `6` which we want to use for the text x-position but only
|
||||
after drawing a rectangle with color `c` and setting the color for the text to `4`. This line compiles to the following sequence:
|
||||
|
||||
* Push `8000` onto the stack
|
||||
* Call `set_color(c)` which sets the drawing color and pushes 6 on the stack
|
||||
* Call `rect` which draws a rectangle with the set color. This call doesn't affect the stack.
|
||||
* Call `set_color(4)` which sets the drawing color to `4` and pushes another 6 on the stack.
|
||||
* Call `text` with the parameters (`8000`, `6`, `6`) pushed on the stack.
|
||||
|
||||
## Limitations
|
||||
|
||||
The idea of CurlyWas is to be able to hand-craft any valid WASM program, ie. having the same amount of control over the instruction sequence as if you would write in the web assembly text format (`.wat`) just with better ergonomics.
|
||||
|
||||
This goal is not yet fully reached, with the following being the main limitations:
|
||||
|
||||
* CurlyWas currently only targets MVP web assembly + non-trapping float-to-int conversions. No other post-MVP features are currently supported. Especially "Multi-value" will be problematic as this allows programs that don't map cleanly to an expression tree.
|
||||
* Memory intrinsics are still missing, so only (unsigned) 8 and 32 bit integer reads and writes are possible.
|
||||
* `block`s cannot return values, as the branch instructions are missing syntax to pass along a value.
|
||||
* `br_table` and `call_indirect` are not yet implemented.
|
||||
16
examples/microw8/mem_intrinsics.cwa
Normal file
16
examples/microw8/mem_intrinsics.cwa
Normal file
@@ -0,0 +1,16 @@
|
||||
import "env.memory" memory(4);
|
||||
import "env.random" fn random() -> i32;
|
||||
|
||||
export fn upd() {
|
||||
let i: i32;
|
||||
loop pixels {
|
||||
let inline left = i32.load8_u((i + (320*240 - 319)) % (320*240), 120);
|
||||
let inline top = i32.load8_u((i + 320*239) % (320*240), 120, 0);
|
||||
let inline here = i32.load16_u(i, 119);
|
||||
let inline all = (left << 24) | (top << 16) | here;
|
||||
let lazy r = random();
|
||||
let inline new = (all #>> ((r & 3) * 8)) ^ ((r & 31) * !(r #>> 22));
|
||||
i32.store8(new, i, 120);
|
||||
branch_if (i := i + 1) < 320*240: pixels;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,8 @@
|
||||
import "env.memory" memory(4);
|
||||
include "uw8.cwa"
|
||||
|
||||
import "env.pow" fn pow(f32, f32) -> f32;
|
||||
import "env.sin" fn sin(f32) -> f32;
|
||||
import "env.cos" fn cos(f32) -> f32;
|
||||
import "env.atan2" fn atan2(f32, f32) -> f32;
|
||||
import "env.tan" fn tan(f32) -> f32;
|
||||
import "env.atan" fn atan(f32) -> f32;
|
||||
import "env.rectangle" fn rect(f32, f32, f32, f32, i32);
|
||||
const SWEETY = PALETTE + 192 * 4;
|
||||
|
||||
//export fn tic(time: i32) {
|
||||
// let i: i32;
|
||||
// loop pixels {
|
||||
// let lazy x = (i % 320) as f32 - 160.5;
|
||||
// let lazy y = (i / 320 - 120) as f32;
|
||||
//
|
||||
// let lazy dist = 4000 as f32 / sqrt(x*x + y*y + 10 as f32);
|
||||
// let lazy angle = atan2(x, y) * (64.0 / 3.141);
|
||||
//
|
||||
// i?120 = ((((dist + time as f32 / 63 as f32) as i32 ^ angle as i32) #% 32 + 32) >> ((dist as i32 - i % 7 * 3) / 40)) + 192;
|
||||
//
|
||||
// branch_if (i := i + 1) < 320*240: pixels;
|
||||
// }
|
||||
//}
|
||||
|
||||
export fn tic(time: i32) {
|
||||
export fn upd() {
|
||||
let i: i32;
|
||||
loop colors {
|
||||
rect((i % 16 * 15) as f32, (i / 16 * 15) as f32, 15 as f32, 15 as f32, i);
|
||||
@@ -43,7 +22,7 @@ start fn gen_palette() {
|
||||
let lazy a = max(llimit, min(ulimit, c)) * (scale + 0.05);
|
||||
let lazy b = scale * scale * 0.8;
|
||||
let inline v = (select(i < 11*16*3, max(0 as f32, min(a + b - a * b, 1 as f32)), scale) * 255 as f32) as i32;
|
||||
(i%3 + i/3*4)?(120+320*240) = v;
|
||||
(i%3 + i/3*4)?PALETTE = v;
|
||||
avg = (avg + c) * 0.5;
|
||||
|
||||
branch_if i := i - 1: gradients;
|
||||
@@ -56,15 +35,15 @@ start fn gen_palette() {
|
||||
let lazy first_step = index >= 32;
|
||||
let inline src1 = select(first_step, index % 32 / 2, index * 2);
|
||||
let inline src2 = select(first_step, (index + 1) % 32 / 2, index * 2 + 1);
|
||||
let inline c1 = (src1 * 4 + channel)?(120+320*240+192*4);
|
||||
let inline c2 = (src2 * 4 + channel)?(120+320*240+192*4);
|
||||
i?(120+320*240+192*4) = (c1 + c2) * (3 + first_step) / 8;
|
||||
let inline c1 = (src1 * 4 + channel)?SWEETY;
|
||||
let inline c2 = (src2 * 4 + channel)?SWEETY;
|
||||
i?SWEETY = (c1 + c2) * (3 + first_step) / 8;
|
||||
|
||||
branch_if (i := i - 1) >= 0: expand_sweetie;
|
||||
}
|
||||
}
|
||||
|
||||
data 120+320*240+192*4 {
|
||||
data SWEETY {
|
||||
i32(
|
||||
0x2c1c1a,
|
||||
0x5d275d,
|
||||
|
||||
24
examples/microw8/print.cwa
Normal file
24
examples/microw8/print.cwa
Normal file
@@ -0,0 +1,24 @@
|
||||
import "env.memory" memory(4);
|
||||
import "env.printString" fn printString(i32);
|
||||
import "env.printChar" fn printChar(i32);
|
||||
|
||||
export fn upd() {
|
||||
printChar(12);
|
||||
printChar('Test');
|
||||
printChar('\1f\10\10');
|
||||
printChar('abc\n');
|
||||
printString(0);
|
||||
|
||||
let t = 32!32 / 1000 #% 3;
|
||||
if t == 0 {
|
||||
printChar('one');
|
||||
} else if t == 1 {
|
||||
printChar('two');
|
||||
} else {
|
||||
printChar('many');
|
||||
}
|
||||
}
|
||||
|
||||
data 0 {
|
||||
"\0e\64\"Colors!!!\"\0e\1\r\n\0"
|
||||
}
|
||||
@@ -1,24 +1,29 @@
|
||||
import "env.memory" memory(4);
|
||||
import "env.sin" fn sin(f32) -> f32;
|
||||
import "env.time" fn time() -> f32;
|
||||
import "env.setPixel" fn setPixel(i32, i32, i32);
|
||||
|
||||
export fn tic(time: i32) {
|
||||
let i: i32;
|
||||
export fn upd() {
|
||||
let x: i32;
|
||||
let y: i32;
|
||||
loop screen {
|
||||
let lazy t = time as f32 / 2000 as f32;
|
||||
let lazy o = sin(t) * 0.8;
|
||||
let lazy q = (i % 320) as f32 - 160.1;
|
||||
let lazy w = (i / 320 - 120) as f32;
|
||||
let inline t = time() / 2 as f32;
|
||||
let lazy o = sin(t) * 0.75;
|
||||
let inline q = x as f32 - 160.5;
|
||||
let inline w = (y - 120) as f32;
|
||||
let lazy r = sqrt(q*q + w*w);
|
||||
let lazy z = q / r;
|
||||
let lazy s = z * o + sqrt(z * z * o * o + 1 as f32 - o * o);
|
||||
let lazy q2 = (z * s - o) * 10 as f32 + t;
|
||||
let lazy w2 = w / r * s * 10 as f32 + t;
|
||||
let lazy s2 = s * 50 as f32 / r;
|
||||
i?120 = max(
|
||||
let inline q2 = (z * s - o) * 10 as f32 + t;
|
||||
let inline w2 = w / r * s * 10 as f32 + t;
|
||||
let inline s2 = s * 100 as f32 / r;
|
||||
let inline color = max(
|
||||
0 as f32,
|
||||
((q2 as i32 ^ w2 as i32 & ((s2 + t) * 20 as f32) as i32) & 5) as f32 *
|
||||
(2 as f32 - s2) * 22 as f32
|
||||
) as i32;
|
||||
branch_if (i := i + 1) < 320*240: screen
|
||||
((q2 as i32 ^ w2 as i32 & ((s2 + time()) * 10 as f32) as i32) & 5) as f32 *
|
||||
(4 as f32 - s2) as f32
|
||||
) as i32 - 32;
|
||||
setPixel(x, y, color);
|
||||
branch_if x := (x + 1) % 320: screen;
|
||||
branch_if y := (y + 1) % 320: screen;
|
||||
}
|
||||
}
|
||||
13
examples/microw8/uw8.cwa
Normal file
13
examples/microw8/uw8.cwa
Normal file
@@ -0,0 +1,13 @@
|
||||
import "env.memory" memory(4);
|
||||
|
||||
import "env.pow" fn pow(f32, f32) -> f32;
|
||||
import "env.sin" fn sin(f32) -> f32;
|
||||
import "env.cos" fn cos(f32) -> f32;
|
||||
import "env.atan2" fn atan2(f32, f32) -> f32;
|
||||
import "env.tan" fn tan(f32) -> f32;
|
||||
import "env.atan" fn atan(f32) -> f32;
|
||||
import "env.rectangle" fn rect(f32, f32, f32, f32, i32);
|
||||
|
||||
const FRAMEBUFFER = 120;
|
||||
const PALETTE = 0x13000;
|
||||
const FONT = 0x13400;
|
||||
@@ -1,15 +1,16 @@
|
||||
import "env.memory" memory(2);
|
||||
import "env.time" fn time() -> f32;
|
||||
|
||||
export fn tic(time: i32) {
|
||||
export fn upd() {
|
||||
let i: i32;
|
||||
loop pixels {
|
||||
let lazy x = (i % 320) as f32 - 160.1;
|
||||
let lazy y = (i / 320 - 120) as f32;
|
||||
let lazy dist = 10000.0 / (x*x + y*y);
|
||||
let lazy t = time as f32 / 20 as f32;
|
||||
let lazy dist = 1024_f / (x*x + y*y);
|
||||
let inline t = time() * 4_f;
|
||||
|
||||
i?120 = (x * dist + t) as i32 ^ (y * dist + t) as i32;
|
||||
i?120 = (x * dist + t) as i32 ^ (y * dist + t) as i32 | -32;
|
||||
|
||||
branch_if (i := i + 1) < 320*240: pixels
|
||||
branch_if (i +:= 1) < 320*240: pixels
|
||||
}
|
||||
}
|
||||
@@ -18,14 +18,14 @@ fn rng(state: i32) -> i32 {
|
||||
}
|
||||
|
||||
fn set_color(color: i32) -> i32 {
|
||||
?20 = color;
|
||||
0?20 = color;
|
||||
6
|
||||
}
|
||||
|
||||
export fn update() {
|
||||
let y: i32;
|
||||
let score = pz;
|
||||
let lazy pad = ?22;
|
||||
let lazy pad = 0?22;
|
||||
let lazy zero = 0.0;
|
||||
|
||||
let lazy control_speed = 0.03;
|
||||
@@ -33,7 +33,7 @@ export fn update() {
|
||||
f = f * 0.7;
|
||||
|
||||
loop lines {
|
||||
?(8003-y) = (score := score / 10) % 10 + 48;
|
||||
(8003-y)?0 = (score := score / 10) % 10 + 48;
|
||||
let lazy z = (4000 / (y := y + 1) + pz) / 20;
|
||||
let lazy x = (rng(rng(rng(rng(z)))) >> 30) as f32 - px;
|
||||
let lazy w = 9 as f32 / sqrt(z as f32);
|
||||
|
||||
49
src/ast.rs
49
src/ast.rs
@@ -1,13 +1,26 @@
|
||||
use std::{fmt, path::PathBuf};
|
||||
|
||||
use crate::Span;
|
||||
use crate::parser::Span;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Script {
|
||||
pub imports: Vec<Import>,
|
||||
pub global_vars: Vec<GlobalVar>,
|
||||
pub functions: Vec<Function>,
|
||||
pub data: Vec<Data>,
|
||||
pub includes: Vec<Include>,
|
||||
pub consts: Vec<GlobalConst>,
|
||||
}
|
||||
|
||||
impl Script {
|
||||
pub fn merge(&mut self, mut other: Script) {
|
||||
self.imports.append(&mut other.imports);
|
||||
self.global_vars.append(&mut other.global_vars);
|
||||
self.functions.append(&mut other.functions);
|
||||
self.data.append(&mut other.data);
|
||||
self.consts.append(&mut other.consts);
|
||||
assert!(other.includes.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -16,6 +29,14 @@ pub enum TopLevelItem {
|
||||
GlobalVar(GlobalVar),
|
||||
Function(Function),
|
||||
Data(Data),
|
||||
Include(Include),
|
||||
Const(GlobalConst),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Include {
|
||||
pub span: Span,
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -49,6 +70,14 @@ pub struct GlobalVar {
|
||||
pub mutable: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GlobalConst {
|
||||
pub span: Span,
|
||||
pub name: String,
|
||||
pub value: Expression,
|
||||
pub type_: Option<Type>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Function {
|
||||
pub span: Span,
|
||||
@@ -152,7 +181,7 @@ pub enum DataType {
|
||||
F64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MemoryLocation {
|
||||
pub span: Span,
|
||||
pub size: MemSize,
|
||||
@@ -160,7 +189,7 @@ pub struct MemoryLocation {
|
||||
pub right: Box<Expression>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Expression {
|
||||
pub type_: Option<Type>,
|
||||
pub expr: Expr,
|
||||
@@ -195,9 +224,16 @@ impl Expression {
|
||||
_ => panic!("Expected F64Const"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_const(&self) -> bool {
|
||||
match self.expr {
|
||||
Expr::I32Const(_) | Expr::I64Const(_) | Expr::F32Const(_) | Expr::F64Const(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Expr {
|
||||
Block {
|
||||
statements: Vec<Expression>,
|
||||
@@ -288,7 +324,7 @@ impl Expr {
|
||||
Expression {
|
||||
type_: None,
|
||||
expr: self,
|
||||
span: span,
|
||||
span,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -337,6 +373,7 @@ pub enum BinOp {
|
||||
pub enum MemSize {
|
||||
Byte,
|
||||
Word,
|
||||
Float,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
||||
|
||||
160
src/constfold.rs
160
src/constfold.rs
@@ -1,35 +1,103 @@
|
||||
use crate::ast;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
ast,
|
||||
parser::{Sources, Span},
|
||||
typecheck::{report_duplicate_definition, report_error},
|
||||
};
|
||||
|
||||
type Result<T> = std::result::Result<T, ()>;
|
||||
|
||||
pub fn fold_script(script: &mut ast::Script, sources: &Sources) -> Result<()> {
|
||||
let mut context = Context {
|
||||
consts: HashMap::new(),
|
||||
sources,
|
||||
};
|
||||
fold_consts(&mut context, &mut script.consts)?;
|
||||
|
||||
pub fn fold_script(script: &mut ast::Script) {
|
||||
for var in &mut script.global_vars {
|
||||
fold_expr(&mut var.value);
|
||||
fold_expr(&context, &mut var.value);
|
||||
}
|
||||
|
||||
for func in &mut script.functions {
|
||||
fold_expr(&mut func.body);
|
||||
fold_expr(&context, &mut func.body);
|
||||
}
|
||||
|
||||
for data in &mut script.data {
|
||||
fold_expr(&mut data.offset);
|
||||
fold_expr(&context, &mut data.offset);
|
||||
for values in &mut data.data {
|
||||
match values {
|
||||
ast::DataValues::Array { values, .. } => {
|
||||
for value in values {
|
||||
fold_expr(value);
|
||||
fold_expr(&context, value);
|
||||
}
|
||||
}
|
||||
ast::DataValues::String(_) | ast::DataValues::File { .. } => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fold_mem_location(mem_location: &mut ast::MemoryLocation) {
|
||||
fold_expr(&mut mem_location.left);
|
||||
fold_expr(&mut mem_location.right);
|
||||
struct Context<'a> {
|
||||
consts: HashMap<String, ast::Expr>,
|
||||
sources: &'a Sources,
|
||||
}
|
||||
|
||||
fn fold_expr(expr: &mut ast::Expression) {
|
||||
fn fold_consts(context: &mut Context, consts: &mut [ast::GlobalConst]) -> Result<()> {
|
||||
let mut spans: HashMap<&str, Span> = HashMap::new();
|
||||
|
||||
for cnst in consts.iter_mut() {
|
||||
if let Some(prev_span) = spans.insert(&cnst.name, cnst.span.clone()) {
|
||||
report_duplicate_definition(
|
||||
"Const already defined",
|
||||
&cnst.span,
|
||||
&prev_span,
|
||||
context.sources,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
while context.consts.len() < consts.len() {
|
||||
let mut making_progress = false;
|
||||
for cnst in consts.iter_mut() {
|
||||
if !context.consts.contains_key(&cnst.name) {
|
||||
fold_expr(context, &mut cnst.value);
|
||||
if cnst.value.is_const() {
|
||||
context
|
||||
.consts
|
||||
.insert(cnst.name.clone(), cnst.value.expr.clone());
|
||||
making_progress = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !making_progress {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut result = Ok(());
|
||||
for cnst in consts {
|
||||
if !context.consts.contains_key(&cnst.name) {
|
||||
result = report_error(
|
||||
&format!("Failed to fold const '{}'", cnst.name),
|
||||
&cnst.span,
|
||||
context.sources,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn fold_mem_location(context: &Context, mem_location: &mut ast::MemoryLocation) {
|
||||
fold_expr(context, &mut mem_location.left);
|
||||
fold_expr(context, &mut mem_location.right);
|
||||
}
|
||||
|
||||
fn fold_expr(context: &Context, expr: &mut ast::Expression) {
|
||||
use ast::BinOp::*;
|
||||
match expr.expr {
|
||||
ast::Expr::Block {
|
||||
@@ -37,15 +105,15 @@ fn fold_expr(expr: &mut ast::Expression) {
|
||||
ref mut final_expression,
|
||||
} => {
|
||||
for stmt in statements {
|
||||
fold_expr(stmt);
|
||||
fold_expr(context, stmt);
|
||||
}
|
||||
if let Some(ref mut expr) = final_expression {
|
||||
fold_expr(expr);
|
||||
fold_expr(context, expr);
|
||||
}
|
||||
}
|
||||
ast::Expr::Let { ref mut value, .. } => {
|
||||
if let Some(ref mut expr) = value {
|
||||
fold_expr(expr);
|
||||
fold_expr(context, expr);
|
||||
}
|
||||
}
|
||||
ast::Expr::Poke {
|
||||
@@ -53,12 +121,12 @@ fn fold_expr(expr: &mut ast::Expression) {
|
||||
ref mut value,
|
||||
..
|
||||
} => {
|
||||
fold_mem_location(mem_location);
|
||||
fold_expr(value);
|
||||
fold_mem_location(context, mem_location);
|
||||
fold_expr(context, value);
|
||||
}
|
||||
ast::Expr::Peek(ref mut mem_location) => fold_mem_location(mem_location),
|
||||
ast::Expr::Peek(ref mut mem_location) => fold_mem_location(context, mem_location),
|
||||
ast::Expr::UnaryOp { op, ref mut value } => {
|
||||
fold_expr(value);
|
||||
fold_expr(context, value);
|
||||
let result = match (op, &value.expr) {
|
||||
(ast::UnaryOp::Negate, ast::Expr::I32Const(value)) => {
|
||||
Some(ast::Expr::I32Const(-*value))
|
||||
@@ -72,6 +140,20 @@ fn fold_expr(expr: &mut ast::Expression) {
|
||||
(ast::UnaryOp::Negate, ast::Expr::F64Const(value)) => {
|
||||
Some(ast::Expr::F64Const(-*value))
|
||||
}
|
||||
(ast::UnaryOp::Negate, ast::Expr::Cast { value, type_ }) => {
|
||||
if let ast::Expr::I32Const(v) = value.expr {
|
||||
Some(ast::Expr::Cast {
|
||||
value: Box::new(ast::Expression {
|
||||
expr: ast::Expr::I32Const(-v),
|
||||
span: value.span.clone(),
|
||||
type_: value.type_,
|
||||
}),
|
||||
type_: *type_,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
(ast::UnaryOp::Not, ast::Expr::I32Const(value)) => {
|
||||
Some(ast::Expr::I32Const((*value == 0) as i32))
|
||||
}
|
||||
@@ -90,8 +172,8 @@ fn fold_expr(expr: &mut ast::Expression) {
|
||||
ref mut right,
|
||||
..
|
||||
} => {
|
||||
fold_expr(left);
|
||||
fold_expr(right);
|
||||
fold_expr(context, left);
|
||||
fold_expr(context, right);
|
||||
match (&left.expr, &right.expr) {
|
||||
(&ast::Expr::I32Const(left), &ast::Expr::I32Const(right)) => {
|
||||
let result = match op {
|
||||
@@ -237,24 +319,28 @@ fn fold_expr(expr: &mut ast::Expression) {
|
||||
ast::Expr::I32Const(_)
|
||||
| ast::Expr::I64Const(_)
|
||||
| ast::Expr::F32Const(_)
|
||||
| ast::Expr::F64Const(_)
|
||||
| ast::Expr::Variable { .. } => (),
|
||||
ast::Expr::Assign { ref mut value, .. } => fold_expr(value),
|
||||
ast::Expr::LocalTee { ref mut value, .. } => fold_expr(value),
|
||||
ast::Expr::Loop { ref mut block, .. } => fold_expr(block),
|
||||
ast::Expr::LabelBlock { ref mut block, .. } => fold_expr(block),
|
||||
| ast::Expr::F64Const(_) => (),
|
||||
ast::Expr::Variable { ref name, .. } => {
|
||||
if let Some(value) = context.consts.get(name) {
|
||||
expr.expr = value.clone();
|
||||
}
|
||||
}
|
||||
ast::Expr::Assign { ref mut value, .. } => fold_expr(context, value),
|
||||
ast::Expr::LocalTee { ref mut value, .. } => fold_expr(context, value),
|
||||
ast::Expr::Loop { ref mut block, .. } => fold_expr(context, block),
|
||||
ast::Expr::LabelBlock { ref mut block, .. } => fold_expr(context, block),
|
||||
ast::Expr::Branch(_) => (),
|
||||
ast::Expr::BranchIf {
|
||||
ref mut condition, ..
|
||||
} => fold_expr(condition),
|
||||
ast::Expr::Cast { ref mut value, .. } => fold_expr(value),
|
||||
} => fold_expr(context, condition),
|
||||
ast::Expr::Cast { ref mut value, .. } => fold_expr(context, value),
|
||||
ast::Expr::FuncCall {
|
||||
ref name,
|
||||
ref mut params,
|
||||
..
|
||||
} => {
|
||||
for param in params.iter_mut() {
|
||||
fold_expr(param);
|
||||
fold_expr(context, param);
|
||||
}
|
||||
use ast::Expr::*;
|
||||
let params: Vec<_> = params.iter().map(|e| &e.expr).collect();
|
||||
@@ -269,31 +355,31 @@ fn fold_expr(expr: &mut ast::Expression) {
|
||||
ref mut if_false,
|
||||
..
|
||||
} => {
|
||||
fold_expr(condition);
|
||||
fold_expr(if_true);
|
||||
fold_expr(if_false);
|
||||
fold_expr(context, condition);
|
||||
fold_expr(context, if_true);
|
||||
fold_expr(context, if_false);
|
||||
}
|
||||
ast::Expr::If {
|
||||
ref mut condition,
|
||||
ref mut if_true,
|
||||
ref mut if_false,
|
||||
} => {
|
||||
fold_expr(condition);
|
||||
fold_expr(if_true);
|
||||
fold_expr(context, condition);
|
||||
fold_expr(context, if_true);
|
||||
if let Some(ref mut if_false) = if_false {
|
||||
fold_expr(if_false);
|
||||
fold_expr(context, if_false);
|
||||
}
|
||||
}
|
||||
ast::Expr::Return {
|
||||
value: Some(ref mut value),
|
||||
} => fold_expr(value),
|
||||
} => fold_expr(context, value),
|
||||
ast::Expr::Return { value: None } => (),
|
||||
ast::Expr::First {
|
||||
ref mut value,
|
||||
ref mut drop,
|
||||
} => {
|
||||
fold_expr(value);
|
||||
fold_expr(drop);
|
||||
fold_expr(context, value);
|
||||
fold_expr(context, drop);
|
||||
}
|
||||
ast::Expr::Error => unreachable!(),
|
||||
}
|
||||
|
||||
68
src/emit.rs
68
src/emit.rs
@@ -6,7 +6,11 @@ use wasm_encoder::{
|
||||
MemArg, MemoryType, Module, NameMap, NameSection, StartSection, TypeSection, ValType,
|
||||
};
|
||||
|
||||
use crate::{ast, intrinsics::Intrinsics, Options};
|
||||
use crate::{
|
||||
ast,
|
||||
intrinsics::{Intrinsics, MemInstruction},
|
||||
Options,
|
||||
};
|
||||
|
||||
pub fn emit(script: &ast::Script, module_name: &str, options: &Options) -> Vec<u8> {
|
||||
let mut module = Module::new();
|
||||
@@ -65,9 +69,7 @@ pub fn emit(script: &ast::Script, module_name: &str, options: &Options) -> Vec<u
|
||||
} => {
|
||||
function_map.insert(name.clone(), function_map.len() as u32);
|
||||
EntityType::Function(
|
||||
*function_types
|
||||
.get(&(params.clone(), result.clone()))
|
||||
.unwrap() as u32,
|
||||
*function_types.get(&(params.clone(), *result)).unwrap() as u32
|
||||
)
|
||||
}
|
||||
};
|
||||
@@ -254,9 +256,7 @@ fn collect_function_types(script: &ast::Script) -> HashMap<FunctionTypeKey, usiz
|
||||
} = import.type_
|
||||
{
|
||||
let index = types.len();
|
||||
types
|
||||
.entry((params.clone(), result.clone()))
|
||||
.or_insert(index);
|
||||
types.entry((params.clone(), *result)).or_insert(index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,7 +289,7 @@ struct FunctionContext<'a> {
|
||||
functions: &'a HashMap<String, u32>,
|
||||
locals: &'a ast::Locals,
|
||||
labels: Vec<String>,
|
||||
let_values: HashMap<u32, (&'a ast::Expression, ast::LetType)>,
|
||||
let_values: HashMap<u32, Vec<(&'a ast::Expression, ast::LetType)>>,
|
||||
intrinsics: &'a Intrinsics,
|
||||
}
|
||||
|
||||
@@ -346,6 +346,11 @@ fn mem_arg_for_location(mem_location: &ast::MemoryLocation) -> MemArg {
|
||||
memory_index: 0,
|
||||
offset,
|
||||
},
|
||||
ast::MemSize::Float => MemArg {
|
||||
align: 2,
|
||||
memory_index: 0,
|
||||
offset,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,7 +385,10 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
|
||||
.instruction(&Instruction::LocalSet(local.index.unwrap()));
|
||||
}
|
||||
ast::LetType::Lazy | ast::LetType::Inline => {
|
||||
ctx.let_values.insert(local_id.unwrap(), (value, *let_type));
|
||||
ctx.let_values
|
||||
.entry(local_id.unwrap())
|
||||
.or_default()
|
||||
.push((value, *let_type));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -391,6 +399,7 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
|
||||
ctx.function.instruction(&match mem_location.size {
|
||||
ast::MemSize::Byte => Instruction::I32Load8_U(mem_arg),
|
||||
ast::MemSize::Word => Instruction::I32Load(mem_arg),
|
||||
ast::MemSize::Float => Instruction::F32Load(mem_arg),
|
||||
});
|
||||
}
|
||||
ast::Expr::Poke {
|
||||
@@ -403,6 +412,7 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
|
||||
ctx.function.instruction(&match mem_location.size {
|
||||
ast::MemSize::Byte => Instruction::I32Store8(mem_arg),
|
||||
ast::MemSize::Word => Instruction::I32Store(mem_arg),
|
||||
ast::MemSize::Float => Instruction::F32Store(mem_arg),
|
||||
});
|
||||
}
|
||||
ast::Expr::UnaryOp { op, value } => {
|
||||
@@ -457,7 +467,7 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
|
||||
(I32, Or) => Instruction::I32Or,
|
||||
(I32, Xor) => Instruction::I32Xor,
|
||||
(I32, Eq) => Instruction::I32Eq,
|
||||
(I32, Ne) => Instruction::I32Neq,
|
||||
(I32, Ne) => Instruction::I32Ne,
|
||||
(I32, Lt) => Instruction::I32LtS,
|
||||
(I32, LtU) => Instruction::I32LtU,
|
||||
(I32, Le) => Instruction::I32LeS,
|
||||
@@ -481,7 +491,7 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
|
||||
(I64, Or) => Instruction::I64Or,
|
||||
(I64, Xor) => Instruction::I64Xor,
|
||||
(I64, Eq) => Instruction::I64Eq,
|
||||
(I64, Ne) => Instruction::I64Neq,
|
||||
(I64, Ne) => Instruction::I64Ne,
|
||||
(I64, Lt) => Instruction::I64LtS,
|
||||
(I64, LtU) => Instruction::I64LtU,
|
||||
(I64, Le) => Instruction::I64LeS,
|
||||
@@ -503,7 +513,7 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
|
||||
DivU | Rem | RemU | And | Or | Xor | Shl | ShrU | ShrS | LtU | LeU | GtU | GeU,
|
||||
) => unreachable!(),
|
||||
(F32, Eq) => Instruction::F32Eq,
|
||||
(F32, Ne) => Instruction::F32Neq,
|
||||
(F32, Ne) => Instruction::F32Ne,
|
||||
(F32, Lt) => Instruction::F32Lt,
|
||||
(F32, Le) => Instruction::F32Le,
|
||||
(F32, Gt) => Instruction::F32Gt,
|
||||
@@ -518,7 +528,7 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
|
||||
DivU | Rem | RemU | And | Or | Xor | Shl | ShrU | ShrS | LtU | LeU | GtU | GeU,
|
||||
) => unreachable!(),
|
||||
(F64, Eq) => Instruction::F64Eq,
|
||||
(F64, Ne) => Instruction::F64Neq,
|
||||
(F64, Ne) => Instruction::F64Ne,
|
||||
(F64, Lt) => Instruction::F64Lt,
|
||||
(F64, Le) => Instruction::F64Le,
|
||||
(F64, Gt) => Instruction::F64Gt,
|
||||
@@ -605,17 +615,17 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
|
||||
}
|
||||
ast::Expr::Variable { name, local_id } => {
|
||||
if let &Some(id) = local_id {
|
||||
if let Some((expr, let_type)) = ctx.let_values.get(&id) {
|
||||
if let Some((expr, let_type)) = ctx.let_values.get_mut(&id).and_then(|s| s.pop()) {
|
||||
match let_type {
|
||||
ast::LetType::Lazy => {
|
||||
let expr = ctx.let_values.remove(&id).unwrap().0;
|
||||
emit_expression(ctx, expr);
|
||||
ctx.let_values.get_mut(&id).unwrap().clear();
|
||||
ctx.function
|
||||
.instruction(&Instruction::LocalTee(ctx.locals[id].index.unwrap()));
|
||||
}
|
||||
ast::LetType::Inline => {
|
||||
let expr = *expr;
|
||||
emit_expression(ctx, expr);
|
||||
ctx.let_values.get_mut(&id).unwrap().push((expr, let_type));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
@@ -654,6 +664,31 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
|
||||
}
|
||||
}
|
||||
ast::Expr::FuncCall { name, params, .. } => {
|
||||
fn mem_instruction(
|
||||
inst: MemInstruction,
|
||||
params: &[ast::Expression],
|
||||
) -> Instruction<'static> {
|
||||
let offset = params
|
||||
.get(0)
|
||||
.map(|e| e.const_i32() as u32 as u64)
|
||||
.unwrap_or(0);
|
||||
let alignment = params.get(1).map(|e| e.const_i32() as u32);
|
||||
(inst.instruction)(MemArg {
|
||||
offset,
|
||||
align: alignment.unwrap_or(inst.natural_alignment),
|
||||
memory_index: 0,
|
||||
})
|
||||
}
|
||||
if let Some(load) = ctx.intrinsics.find_load(name) {
|
||||
emit_expression(ctx, ¶ms[0]);
|
||||
ctx.function
|
||||
.instruction(&mem_instruction(load, ¶ms[1..]));
|
||||
} else if let Some(store) = ctx.intrinsics.find_store(name) {
|
||||
emit_expression(ctx, ¶ms[1]);
|
||||
emit_expression(ctx, ¶ms[0]);
|
||||
ctx.function
|
||||
.instruction(&mem_instruction(store, ¶ms[2..]));
|
||||
} else {
|
||||
for param in params {
|
||||
emit_expression(ctx, param);
|
||||
}
|
||||
@@ -669,6 +704,7 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
|
||||
.instruction(&ctx.intrinsics.get_instr(name, &types).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::Expr::Select {
|
||||
condition,
|
||||
if_true,
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{collections::HashSet, fs::File};
|
||||
|
||||
use crate::ast;
|
||||
use anyhow::{anyhow, Result};
|
||||
|
||||
pub fn resolve_includes(script: &mut ast::Script, path: &Path) -> Result<()> {
|
||||
pub fn resolve_includes(
|
||||
script: &mut ast::Script,
|
||||
dependencies: &mut HashSet<PathBuf>,
|
||||
path: &Path,
|
||||
) -> Result<()> {
|
||||
let script_dir = path.parent().expect("Script path has no parent");
|
||||
for data in &mut script.data {
|
||||
for values in &mut data.data {
|
||||
match values {
|
||||
ast::DataValues::File {
|
||||
if let ast::DataValues::File {
|
||||
ref path,
|
||||
ref mut data,
|
||||
} => {
|
||||
} = values
|
||||
{
|
||||
let mut full_path = script_dir.to_path_buf();
|
||||
full_path.push(path);
|
||||
File::open(&full_path)
|
||||
@@ -21,8 +25,7 @@ pub fn resolve_includes(script: &mut ast::Script, path: &Path) -> Result<()> {
|
||||
anyhow!("Failed to load data from {}: {}", full_path.display(), e)
|
||||
})?
|
||||
.read_to_end(data)?;
|
||||
}
|
||||
_ => (),
|
||||
dependencies.insert(full_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
use crate::ast::Type;
|
||||
use enc::MemArg;
|
||||
use std::collections::HashMap;
|
||||
use wasm_encoder as enc;
|
||||
|
||||
pub struct Intrinsics(HashMap<String, HashMap<Vec<Type>, (Type, enc::Instruction<'static>)>>);
|
||||
pub struct Intrinsics(
|
||||
HashMap<String, HashMap<Vec<Type>, (Option<Type>, enc::Instruction<'static>)>>,
|
||||
);
|
||||
|
||||
impl Intrinsics {
|
||||
pub fn new() -> Intrinsics {
|
||||
@@ -11,14 +14,11 @@ impl Intrinsics {
|
||||
i
|
||||
}
|
||||
|
||||
pub fn find_types(
|
||||
&self,
|
||||
name: &str,
|
||||
) -> Option<HashMap<Vec<Type>, Option<Type>>> {
|
||||
pub fn find_types(&self, name: &str) -> Option<HashMap<Vec<Type>, Option<Type>>> {
|
||||
self.0.get(name).map(|types| {
|
||||
types
|
||||
.iter()
|
||||
.map(|(params, (ret, _))| (params.clone(), Some(*ret)))
|
||||
.map(|(params, (ret, _))| (params.clone(), *ret))
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
@@ -33,87 +33,121 @@ impl Intrinsics {
|
||||
fn add_instructions(&mut self) {
|
||||
use enc::Instruction as I;
|
||||
use Type::*;
|
||||
self.inst("i32.rotl", &[I32, I32], I32, I::I32Rotl);
|
||||
self.inst("i32.rotr", &[I32, I32], I32, I::I32Rotr);
|
||||
self.inst("i32.clz", &[I32], I32, I::I32Clz);
|
||||
self.inst("i32.ctz", &[I32], I32, I::I32Ctz);
|
||||
self.inst("i32.popcnt", &[I32], I32, I::I32Popcnt);
|
||||
self.inst("i32.rotl", &[I32, I32], Some(I32), I::I32Rotl);
|
||||
self.inst("i32.rotr", &[I32, I32], Some(I32), I::I32Rotr);
|
||||
self.inst("i32.clz", &[I32], Some(I32), I::I32Clz);
|
||||
self.inst("i32.ctz", &[I32], Some(I32), I::I32Ctz);
|
||||
self.inst("i32.popcnt", &[I32], Some(I32), I::I32Popcnt);
|
||||
|
||||
self.inst("i64.rotl", &[I64, I64], I64, I::I64Rotl);
|
||||
self.inst("i64.rotr", &[I64, I64], I64, I::I64Rotr);
|
||||
self.inst("i64.clz", &[I64], I64, I::I64Clz);
|
||||
self.inst("i64.ctz", &[I64], I64, I::I64Ctz);
|
||||
self.inst("i64.popcnt", &[I64], I64, I::I64Popcnt);
|
||||
self.inst("i64.rotl", &[I64, I64], Some(I64), I::I64Rotl);
|
||||
self.inst("i64.rotr", &[I64, I64], Some(I64), I::I64Rotr);
|
||||
self.inst("i64.clz", &[I64], Some(I64), I::I64Clz);
|
||||
self.inst("i64.ctz", &[I64], Some(I64), I::I64Ctz);
|
||||
self.inst("i64.popcnt", &[I64], Some(I64), I::I64Popcnt);
|
||||
|
||||
self.inst("f32/sqrt", &[F32], F32, I::F32Sqrt);
|
||||
self.inst("f32/min", &[F32, F32], F32, I::F32Min);
|
||||
self.inst("f32/max", &[F32, F32], F32, I::F32Max);
|
||||
self.inst("f32/ceil", &[F32], F32, I::F32Ceil);
|
||||
self.inst("f32/floor", &[F32], F32, I::F32Floor);
|
||||
self.inst("f32/trunc", &[F32], F32, I::F32Trunc);
|
||||
self.inst("f32/nearest", &[F32], F32, I::F32Nearest);
|
||||
self.inst("f32/abs", &[F32], F32, I::F32Abs);
|
||||
self.inst("f32.copysign", &[F32, F32], F32, I::F32Copysign);
|
||||
self.inst("f32/sqrt", &[F32], Some(F32), I::F32Sqrt);
|
||||
self.inst("f32/min", &[F32, F32], Some(F32), I::F32Min);
|
||||
self.inst("f32/max", &[F32, F32], Some(F32), I::F32Max);
|
||||
self.inst("f32/ceil", &[F32], Some(F32), I::F32Ceil);
|
||||
self.inst("f32/floor", &[F32], Some(F32), I::F32Floor);
|
||||
self.inst("f32/trunc", &[F32], Some(F32), I::F32Trunc);
|
||||
self.inst("f32/nearest", &[F32], Some(F32), I::F32Nearest);
|
||||
self.inst("f32/abs", &[F32], Some(F32), I::F32Abs);
|
||||
self.inst("f32.copysign", &[F32, F32], Some(F32), I::F32Copysign);
|
||||
|
||||
self.inst("f64/sqrt", &[F64], F64, I::F64Sqrt);
|
||||
self.inst("f64/min", &[F64, F64], F64, I::F64Min);
|
||||
self.inst("f64/max", &[F64, F64], F64, I::F64Max);
|
||||
self.inst("f64/ceil", &[F64], F64, I::F64Ceil);
|
||||
self.inst("f64/floor", &[F64], F64, I::F64Floor);
|
||||
self.inst("f64/trunc", &[F64], F64, I::F64Trunc);
|
||||
self.inst("f64/nearest", &[F64], F64, I::F64Nearest);
|
||||
self.inst("f64/abs", &[F64], F64, I::F64Abs);
|
||||
self.inst("f64.copysign", &[F64, F64], F64, I::F64Copysign);
|
||||
self.inst("f64/sqrt", &[F64], Some(F64), I::F64Sqrt);
|
||||
self.inst("f64/min", &[F64, F64], Some(F64), I::F64Min);
|
||||
self.inst("f64/max", &[F64, F64], Some(F64), I::F64Max);
|
||||
self.inst("f64/ceil", &[F64], Some(F64), I::F64Ceil);
|
||||
self.inst("f64/floor", &[F64], Some(F64), I::F64Floor);
|
||||
self.inst("f64/trunc", &[F64], Some(F64), I::F64Trunc);
|
||||
self.inst("f64/nearest", &[F64], Some(F64), I::F64Nearest);
|
||||
self.inst("f64/abs", &[F64], Some(F64), I::F64Abs);
|
||||
self.inst("f64.copysign", &[F64, F64], Some(F64), I::F64Copysign);
|
||||
|
||||
self.inst("i32.wrap_i64", &[I64], I32, I::I32WrapI64);
|
||||
self.inst("i64.extend_i32_s", &[I32], I64, I::I64ExtendI32S);
|
||||
self.inst("i64.extend_i32_u", &[I32], I64, I::I64ExtendI32U);
|
||||
self.inst("i32.wrap_i64", &[I64], Some(I32), I::I32WrapI64);
|
||||
self.inst("i64.extend_i32_s", &[I32], Some(I64), I::I64ExtendI32S);
|
||||
self.inst("i64.extend_i32_u", &[I32], Some(I64), I::I64ExtendI32U);
|
||||
|
||||
self.inst("i32.trunc_f32_s", &[F32], I32, I::I32TruncF32S);
|
||||
self.inst("i32.trunc_f64_s", &[F64], I32, I::I32TruncF64S);
|
||||
self.inst("i64.trunc_f32_s", &[F32], I64, I::I64TruncF32S);
|
||||
self.inst("i64.trunc_f64_s", &[F64], I64, I::I64TruncF64S);
|
||||
self.inst("i32.trunc_f32_s", &[F32], Some(I32), I::I32TruncF32S);
|
||||
self.inst("i32.trunc_f64_s", &[F64], Some(I32), I::I32TruncF64S);
|
||||
self.inst("i64.trunc_f32_s", &[F32], Some(I64), I::I64TruncF32S);
|
||||
self.inst("i64.trunc_f64_s", &[F64], Some(I64), I::I64TruncF64S);
|
||||
|
||||
self.inst("i32.trunc_f32_u", &[F32], I32, I::I32TruncF32U);
|
||||
self.inst("i32.trunc_f64_u", &[F64], I32, I::I32TruncF64U);
|
||||
self.inst("i64.trunc_f32_u", &[F32], I64, I::I64TruncF32U);
|
||||
self.inst("i64.trunc_f64_u", &[F64], I64, I::I64TruncF64U);
|
||||
self.inst("i32.trunc_f32_u", &[F32], Some(I32), I::I32TruncF32U);
|
||||
self.inst("i32.trunc_f64_u", &[F64], Some(I32), I::I32TruncF64U);
|
||||
self.inst("i64.trunc_f32_u", &[F32], Some(I64), I::I64TruncF32U);
|
||||
self.inst("i64.trunc_f64_u", &[F64], Some(I64), I::I64TruncF64U);
|
||||
|
||||
self.inst("f32.demote_f64", &[F64], F32, I::F32DemoteF64);
|
||||
self.inst("f64.promote_f32", &[F32], F64, I::F64PromoteF32);
|
||||
self.inst("f32.demote_f64", &[F64], Some(F32), I::F32DemoteF64);
|
||||
self.inst("f64.promote_f32", &[F32], Some(F64), I::F64PromoteF32);
|
||||
|
||||
self.inst("f32.convert_i32_s", &[I32], F32, I::F32ConvertI32S);
|
||||
self.inst("f32.convert_i64_s", &[I64], F32, I::F32ConvertI32S);
|
||||
self.inst("f64.convert_i32_s", &[I32], F64, I::F32ConvertI32S);
|
||||
self.inst("f64.convert_i64_s", &[I64], F64, I::F32ConvertI32S);
|
||||
self.inst("f32.convert_i32_s", &[I32], Some(F32), I::F32ConvertI32S);
|
||||
self.inst("f32.convert_i64_s", &[I64], Some(F32), I::F32ConvertI32S);
|
||||
self.inst("f64.convert_i32_s", &[I32], Some(F64), I::F32ConvertI32S);
|
||||
self.inst("f64.convert_i64_s", &[I64], Some(F64), I::F32ConvertI32S);
|
||||
|
||||
self.inst("f32.convert_i32_u", &[I32], F32, I::F32ConvertI32U);
|
||||
self.inst("f32.convert_i64_u", &[I64], F32, I::F32ConvertI32U);
|
||||
self.inst("f64.convert_i32_u", &[I32], F64, I::F32ConvertI32U);
|
||||
self.inst("f64.convert_i64_u", &[I64], F64, I::F32ConvertI32U);
|
||||
self.inst("f32.convert_i32_u", &[I32], Some(F32), I::F32ConvertI32U);
|
||||
self.inst("f32.convert_i64_u", &[I64], Some(F32), I::F32ConvertI32U);
|
||||
self.inst("f64.convert_i32_u", &[I32], Some(F64), I::F32ConvertI32U);
|
||||
self.inst("f64.convert_i64_u", &[I64], Some(F64), I::F32ConvertI32U);
|
||||
|
||||
self.inst("i32.reinterpret_f32", &[F32], I32, I::I32ReinterpretF32);
|
||||
self.inst("i64.reinterpret_f64", &[F64], I64, I::I64ReinterpretF64);
|
||||
self.inst("f32.reinterpret_i32", &[I32], F32, I::F32ReinterpretI32);
|
||||
self.inst("f64.reinterpret_i64", &[I64], F64, I::F64ReinterpretI64);
|
||||
self.inst(
|
||||
"i32.reinterpret_f32",
|
||||
&[F32],
|
||||
Some(I32),
|
||||
I::I32ReinterpretF32,
|
||||
);
|
||||
self.inst(
|
||||
"i64.reinterpret_f64",
|
||||
&[F64],
|
||||
Some(I64),
|
||||
I::I64ReinterpretF64,
|
||||
);
|
||||
self.inst(
|
||||
"f32.reinterpret_i32",
|
||||
&[I32],
|
||||
Some(F32),
|
||||
I::F32ReinterpretI32,
|
||||
);
|
||||
self.inst(
|
||||
"f64.reinterpret_i64",
|
||||
&[I64],
|
||||
Some(F64),
|
||||
I::F64ReinterpretI64,
|
||||
);
|
||||
|
||||
self.inst("i32.extend8_s", &[I32], I32, I::I32Extend8S);
|
||||
self.inst("i32.extend16_s", &[I32], I32, I::I32Extend16S);
|
||||
self.inst("i64.extend8_s", &[I64], I64, I::I64Extend8S);
|
||||
self.inst("i64.extend16_s", &[I64], I64, I::I64Extend16S);
|
||||
self.inst("i64.extend32_s", &[I64], I64, I::I64Extend32S);
|
||||
self.inst("i32.extend8_s", &[I32], Some(I32), I::I32Extend8S);
|
||||
self.inst("i32.extend16_s", &[I32], Some(I32), I::I32Extend16S);
|
||||
self.inst("i64.extend8_s", &[I64], Some(I64), I::I64Extend8S);
|
||||
self.inst("i64.extend16_s", &[I64], Some(I64), I::I64Extend16S);
|
||||
self.inst("i64.extend32_s", &[I64], Some(I64), I::I64Extend32S);
|
||||
|
||||
self.inst("i32.trunc_sat_f32_s", &[F32], I32, I::I32TruncSatF32S);
|
||||
self.inst("i32.trunc_sat_f32_u", &[F32], I32, I::I32TruncSatF32U);
|
||||
self.inst("i32.trunc_sat_f64_s", &[F64], I32, I::I32TruncSatF64S);
|
||||
self.inst("i32.trunc_sat_f64_u", &[F64], I32, I::I32TruncSatF64U);
|
||||
self.inst("i64.trunc_sat_f32_s", &[F32], I64, I::I64TruncSatF32S);
|
||||
self.inst("i64.trunc_sat_f32_u", &[F32], I64, I::I64TruncSatF32U);
|
||||
self.inst("i64.trunc_sat_f64_s", &[F64], I64, I::I64TruncSatF64S);
|
||||
self.inst("i64.trunc_sat_f64_u", &[F64], I64, I::I64TruncSatF64U);
|
||||
self.inst("i32.trunc_sat_f32_s", &[F32], Some(I32), I::I32TruncSatF32S);
|
||||
self.inst("i32.trunc_sat_f32_u", &[F32], Some(I32), I::I32TruncSatF32U);
|
||||
self.inst("i32.trunc_sat_f64_s", &[F64], Some(I32), I::I32TruncSatF64S);
|
||||
self.inst("i32.trunc_sat_f64_u", &[F64], Some(I32), I::I32TruncSatF64U);
|
||||
self.inst("i64.trunc_sat_f32_s", &[F32], Some(I64), I::I64TruncSatF32S);
|
||||
self.inst("i64.trunc_sat_f32_u", &[F32], Some(I64), I::I64TruncSatF32U);
|
||||
self.inst("i64.trunc_sat_f64_s", &[F64], Some(I64), I::I64TruncSatF64S);
|
||||
self.inst("i64.trunc_sat_f64_u", &[F64], Some(I64), I::I64TruncSatF64U);
|
||||
|
||||
self.inst(
|
||||
"memory.copy",
|
||||
&[I32, I32, I32],
|
||||
None,
|
||||
I::MemoryCopy { src: 0, dst: 0 },
|
||||
);
|
||||
self.inst("memory.fill", &[I32, I32, I32], None, I::MemoryFill(0));
|
||||
}
|
||||
|
||||
fn inst(&mut self, name: &str, params: &[Type], ret: Type, ins: enc::Instruction<'static>) {
|
||||
fn inst(
|
||||
&mut self,
|
||||
name: &str,
|
||||
params: &[Type],
|
||||
ret: Option<Type>,
|
||||
ins: enc::Instruction<'static>,
|
||||
) {
|
||||
if let Some(slash_idx) = name.find('/') {
|
||||
self.insert(name[(slash_idx + 1)..].to_string(), params, ret, &ins);
|
||||
let mut full_name = name[..slash_idx].to_string();
|
||||
@@ -129,7 +163,7 @@ impl Intrinsics {
|
||||
&mut self,
|
||||
name: String,
|
||||
params: &[Type],
|
||||
ret: Type,
|
||||
ret: Option<Type>,
|
||||
ins: &enc::Instruction<'static>,
|
||||
) {
|
||||
self.0
|
||||
@@ -137,4 +171,65 @@ impl Intrinsics {
|
||||
.or_default()
|
||||
.insert(params.to_vec(), (ret, ins.clone()));
|
||||
}
|
||||
|
||||
pub fn find_load(&self, name: &str) -> Option<MemInstruction> {
|
||||
use enc::Instruction as I;
|
||||
use Type::*;
|
||||
let ins = match name {
|
||||
"i32.load" => MemInstruction::new(I32, I::I32Load, 2),
|
||||
"i32.load8_s" => MemInstruction::new(I32, I::I32Load8_S, 0),
|
||||
"i32.load8_u" => MemInstruction::new(I32, I::I32Load8_U, 0),
|
||||
"i32.load16_s" => MemInstruction::new(I32, I::I32Load16_S, 1),
|
||||
"i32.load16_u" => MemInstruction::new(I32, I::I32Load16_U, 1),
|
||||
"i64.load" => MemInstruction::new(I64, I::I64Load, 3),
|
||||
"i64.load8_s" => MemInstruction::new(I64, I::I64Load8_S, 0),
|
||||
"i64.load8_u" => MemInstruction::new(I64, I::I64Load8_U, 0),
|
||||
"i64.load16_s" => MemInstruction::new(I64, I::I64Load16_S, 1),
|
||||
"i64.load16_u" => MemInstruction::new(I64, I::I64Load16_U, 1),
|
||||
"i64.load32_s" => MemInstruction::new(I64, I::I64Load32_S, 2),
|
||||
"i64.load32_u" => MemInstruction::new(I64, I::I64Load32_U, 2),
|
||||
"f32.load" => MemInstruction::new(F32, I::F32Load, 2),
|
||||
"f64.load" => MemInstruction::new(F64, I::F64Load, 3),
|
||||
_ => return None,
|
||||
};
|
||||
return Some(ins);
|
||||
}
|
||||
|
||||
pub fn find_store(&self, name: &str) -> Option<MemInstruction> {
|
||||
use enc::Instruction as I;
|
||||
use Type::*;
|
||||
let ins = match name {
|
||||
"i32.store" => MemInstruction::new(I32, I::I32Store, 2),
|
||||
"i32.store8" => MemInstruction::new(I32, I::I32Store8, 0),
|
||||
"i32.store16" => MemInstruction::new(I32, I::I32Store16, 1),
|
||||
"i64.store" => MemInstruction::new(I64, I::I64Store, 3),
|
||||
"i64.store8" => MemInstruction::new(I64, I::I64Store8, 0),
|
||||
"i64.store16" => MemInstruction::new(I64, I::I64Store16, 1),
|
||||
"i64.store32" => MemInstruction::new(I64, I::I64Store32, 2),
|
||||
"f32.store" => MemInstruction::new(F32, I::F32Store, 2),
|
||||
"f64.store" => MemInstruction::new(F64, I::F64Store, 3),
|
||||
_ => return None,
|
||||
};
|
||||
return Some(ins);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MemInstruction {
|
||||
pub type_: Type,
|
||||
pub instruction: fn(MemArg) -> enc::Instruction<'static>,
|
||||
pub natural_alignment: u32,
|
||||
}
|
||||
|
||||
impl MemInstruction {
|
||||
fn new(
|
||||
type_: Type,
|
||||
instruction: fn(MemArg) -> enc::Instruction<'static>,
|
||||
natural_alignment: u32,
|
||||
) -> MemInstruction {
|
||||
MemInstruction {
|
||||
type_,
|
||||
instruction,
|
||||
natural_alignment,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
77
src/lib.rs
77
src/lib.rs
@@ -1,7 +1,8 @@
|
||||
use anyhow::{bail, Result};
|
||||
use parser::Sources;
|
||||
use std::collections::HashSet;
|
||||
use std::ffi::OsStr;
|
||||
use std::io::prelude::*;
|
||||
use std::{fs::File, path::Path};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
mod ast;
|
||||
mod constfold;
|
||||
@@ -11,8 +12,6 @@ mod intrinsics;
|
||||
mod parser;
|
||||
mod typecheck;
|
||||
|
||||
type Span = std::ops::Range<usize>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Options {
|
||||
pub(crate) debug: bool,
|
||||
@@ -20,31 +19,64 @@ pub struct Options {
|
||||
|
||||
impl Options {
|
||||
pub fn with_debug(self) -> Self {
|
||||
Options {
|
||||
debug: true,
|
||||
..self
|
||||
}
|
||||
Options { debug: true }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile_file<P: AsRef<Path>>(path: P, options: Options) -> Result<Vec<u8>> {
|
||||
let path = path.as_ref();
|
||||
let mut input = String::new();
|
||||
File::open(path)?.read_to_string(&mut input)?;
|
||||
|
||||
compile_str(&input, path, options)
|
||||
pub struct CompiledModule {
|
||||
pub wasm: Vec<u8>,
|
||||
pub dependencies: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
pub fn compile_str(input: &str, path: &Path, options: Options) -> Result<Vec<u8>> {
|
||||
let mut script = match parser::parse(&input) {
|
||||
pub fn compile_file<P: AsRef<Path>>(path: P, options: Options) -> (Result<Vec<u8>>, Vec<PathBuf>) {
|
||||
fn compile_file_inner(
|
||||
path: &Path,
|
||||
options: Options,
|
||||
dependencies: &mut HashSet<PathBuf>,
|
||||
) -> Result<Vec<u8>> {
|
||||
let mut script = ast::Script::default();
|
||||
|
||||
let mut sources = Sources::new();
|
||||
|
||||
let mut pending_files = vec![(path.to_path_buf(), None)];
|
||||
while let Some((path, span)) = pending_files.pop() {
|
||||
match sources.add(&path) {
|
||||
Ok((id, true)) => {
|
||||
dependencies.insert(path.clone());
|
||||
let mut new_script = match parser::parse(&sources, id) {
|
||||
Ok(script) => script,
|
||||
Err(_) => bail!("Parse failed"),
|
||||
};
|
||||
|
||||
includes::resolve_includes(&mut script, path)?;
|
||||
includes::resolve_includes(&mut new_script, dependencies, &path)?;
|
||||
|
||||
constfold::fold_script(&mut script);
|
||||
if let Err(_) = typecheck::tc_script(&mut script, &input) {
|
||||
for include in std::mem::take(&mut new_script.includes) {
|
||||
let mut path = path
|
||||
.parent()
|
||||
.expect("Script path has no parent")
|
||||
.to_path_buf();
|
||||
path.push(include.path);
|
||||
pending_files.push((path, Some(include.span)));
|
||||
}
|
||||
|
||||
script.merge(new_script);
|
||||
}
|
||||
Ok((_, false)) => (), // already parsed this include
|
||||
Err(err) => {
|
||||
if let Some(span) = span {
|
||||
let _ = typecheck::report_error(&err.to_string(), &span, &sources);
|
||||
} else {
|
||||
eprintln!("Failed to load script {}: {}", path.display(), err);
|
||||
}
|
||||
bail!("Parse failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if constfold::fold_script(&mut script, &sources).is_err() {
|
||||
bail!("Constant folding failed");
|
||||
}
|
||||
if typecheck::tc_script(&mut script, &sources).is_err() {
|
||||
bail!("Type check failed");
|
||||
}
|
||||
let wasm = emit::emit(
|
||||
@@ -57,3 +89,10 @@ pub fn compile_str(input: &str, path: &Path, options: Options) -> Result<Vec<u8>
|
||||
);
|
||||
Ok(wasm)
|
||||
}
|
||||
|
||||
let mut dependencies = HashSet::new();
|
||||
|
||||
let result = compile_file_inner(path.as_ref(), options, &mut dependencies);
|
||||
|
||||
(result, dependencies.into_iter().collect())
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ fn main() -> Result<()> {
|
||||
|
||||
let mut filename = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
||||
|
||||
let wasm = compile_file(&filename, options)?;
|
||||
let wasm = compile_file(&filename, options).0?;
|
||||
|
||||
wasmparser::validate(&wasm)?;
|
||||
|
||||
|
||||
589
src/parser.rs
589
src/parser.rs
@@ -1,8 +1,62 @@
|
||||
use crate::ast;
|
||||
use crate::Span;
|
||||
use ariadne::{Color, Fmt, Label, Report, ReportKind, Source};
|
||||
use chumsky::{prelude::*, stream::Stream};
|
||||
use std::fmt;
|
||||
use anyhow::Result;
|
||||
use ariadne::{Color, Fmt, Label, Report, ReportKind};
|
||||
use chumsky::prelude::*;
|
||||
use std::{
|
||||
fmt,
|
||||
fs::File,
|
||||
io::Read,
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
pub type Span = (usize, Range<usize>);
|
||||
|
||||
pub struct SourceFile {
|
||||
source: ariadne::Source,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
pub struct Sources(Vec<SourceFile>);
|
||||
|
||||
impl Sources {
|
||||
pub fn new() -> Sources {
|
||||
Sources(Vec::new())
|
||||
}
|
||||
|
||||
pub fn add(&mut self, path: &Path) -> Result<(usize, bool)> {
|
||||
let canonical = path.canonicalize()?;
|
||||
for (index, source) in self.0.iter().enumerate() {
|
||||
if source.path.canonicalize()? == canonical {
|
||||
return Ok((index, false));
|
||||
}
|
||||
}
|
||||
let mut source = String::new();
|
||||
File::open(path)?.read_to_string(&mut source)?;
|
||||
self.0.push(SourceFile {
|
||||
source: ariadne::Source::from(source),
|
||||
path: path.to_path_buf(),
|
||||
});
|
||||
Ok((self.0.len() - 1, true))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Index<usize> for Sources {
|
||||
type Output = SourceFile;
|
||||
fn index(&self, idx: usize) -> &SourceFile {
|
||||
&self.0[idx]
|
||||
}
|
||||
}
|
||||
|
||||
impl ariadne::Cache<usize> for &Sources {
|
||||
fn fetch(&mut self, id: &usize) -> Result<&ariadne::Source, Box<dyn std::fmt::Debug + '_>> {
|
||||
Ok(&self.0[*id].source)
|
||||
}
|
||||
|
||||
fn display<'a>(&self, id: &'a usize) -> Option<Box<dyn std::fmt::Display + 'a>> {
|
||||
Some(Box::new(self.0[*id].path.clone().display().to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
enum Token {
|
||||
@@ -25,6 +79,7 @@ enum Token {
|
||||
Str(String),
|
||||
Int(i32),
|
||||
Int64(i64),
|
||||
IntFloat(i32),
|
||||
Float(String),
|
||||
Float64(String),
|
||||
Op(String),
|
||||
@@ -53,6 +108,7 @@ impl fmt::Display for Token {
|
||||
Token::Str(s) => write!(f, "{:?}", s),
|
||||
Token::Int(v) => write!(f, "{}", v),
|
||||
Token::Int64(v) => write!(f, "{}", v),
|
||||
Token::IntFloat(v) => write!(f, "{}_f", v),
|
||||
Token::Float(v) => write!(f, "{}", v),
|
||||
Token::Float64(v) => write!(f, "{}", v),
|
||||
Token::Op(s) => write!(f, "{}", s),
|
||||
@@ -61,8 +117,36 @@ impl fmt::Display for Token {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(source: &str) -> Result<ast::Script, ()> {
|
||||
let tokens = match lexer().parse(source) {
|
||||
type SourceStream<It> = chumsky::Stream<'static, char, Span, It>;
|
||||
type TokenStream<It> = chumsky::Stream<'static, Token, Span, It>;
|
||||
|
||||
pub fn parse(sources: &Sources, source_id: usize) -> Result<ast::Script, ()> {
|
||||
let source = &sources[source_id].source;
|
||||
let source_stream = SourceStream::from_iter(
|
||||
(source_id, source.len()..source.len() + 1),
|
||||
Box::new(
|
||||
// this is a bit of an ugly hack
|
||||
// once the next version of ariadne is released
|
||||
// use the source string chars as input
|
||||
// and only create the ariadne::Source lazily when printing errors
|
||||
source
|
||||
.lines()
|
||||
.flat_map(|l| {
|
||||
let mut chars: Vec<char> = l.chars().collect();
|
||||
while chars.len() + 1 < l.len() {
|
||||
chars.push(' ');
|
||||
}
|
||||
if chars.len() < l.len() {
|
||||
chars.push('\n');
|
||||
}
|
||||
chars.into_iter()
|
||||
})
|
||||
.enumerate()
|
||||
.map(|(index, char)| (char, (source_id, index..(index + 1)))),
|
||||
),
|
||||
);
|
||||
|
||||
let tokens = match lexer().parse(source_stream) {
|
||||
Ok(tokens) => tokens,
|
||||
Err(errors) => {
|
||||
report_errors(
|
||||
@@ -70,15 +154,14 @@ pub fn parse(source: &str) -> Result<ast::Script, ()> {
|
||||
.into_iter()
|
||||
.map(|e| e.map(|c| c.to_string()))
|
||||
.collect(),
|
||||
source,
|
||||
sources,
|
||||
);
|
||||
return Err(());
|
||||
}
|
||||
};
|
||||
|
||||
let source_len = source.chars().count();
|
||||
let script = match script_parser().parse(Stream::from_iter(
|
||||
source_len..source_len + 1,
|
||||
let script = match script_parser().parse(TokenStream::from_iter(
|
||||
(source_id, source.len()..source.len() + 1),
|
||||
tokens.into_iter(),
|
||||
)) {
|
||||
Ok(script) => script,
|
||||
@@ -88,7 +171,7 @@ pub fn parse(source: &str) -> Result<ast::Script, ()> {
|
||||
.into_iter()
|
||||
.map(|e| e.map(|t| t.to_string()))
|
||||
.collect(),
|
||||
source,
|
||||
sources,
|
||||
);
|
||||
return Err(());
|
||||
}
|
||||
@@ -96,9 +179,9 @@ pub fn parse(source: &str) -> Result<ast::Script, ()> {
|
||||
Ok(script)
|
||||
}
|
||||
|
||||
fn report_errors(errors: Vec<Simple<String>>, source: &str) {
|
||||
fn report_errors(errors: Vec<Simple<String, Span>>, sources: &Sources) {
|
||||
for error in errors {
|
||||
let report = Report::build(ReportKind::Error, (), error.span().start());
|
||||
let report = Report::build(ReportKind::Error, error.span().0, error.span().1.start());
|
||||
|
||||
let report = match error.reason() {
|
||||
chumsky::error::SimpleReason::Unclosed { span, delimiter } => report
|
||||
@@ -138,7 +221,7 @@ fn report_errors(errors: Vec<Simple<String>>, source: &str) {
|
||||
} else {
|
||||
error
|
||||
.expected()
|
||||
.map(|x| x.to_string())
|
||||
.map(|x| x.as_deref().unwrap_or("EOF"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
}
|
||||
@@ -161,59 +244,106 @@ fn report_errors(errors: Vec<Simple<String>>, source: &str) {
|
||||
),
|
||||
};
|
||||
|
||||
report.finish().eprint(Source::from(source)).unwrap();
|
||||
report.finish().eprint(sources).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = Simple<char>> {
|
||||
let float64 = text::int(10)
|
||||
type LexerError = Simple<char, Span>;
|
||||
fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = LexerError> {
|
||||
let float64 = text::digits(10)
|
||||
.chain::<char, _, _>(just('.').chain(text::digits(10)))
|
||||
.then_ignore(seq::<_, _, Simple<char>>("f64".chars()))
|
||||
.then_ignore(just("f64"))
|
||||
.collect::<String>()
|
||||
.map(Token::Float64);
|
||||
|
||||
let float = text::int(10)
|
||||
let float = text::digits(10)
|
||||
.chain::<char, _, _>(just('.').chain(text::digits(10)))
|
||||
.collect::<String>()
|
||||
.map(Token::Float);
|
||||
|
||||
let integer = seq::<_, _, Simple<char>>("0x".chars())
|
||||
.ignore_then(text::int(16))
|
||||
let integer = just::<_, _, LexerError>("0x")
|
||||
.ignore_then(text::digits(16))
|
||||
.try_map(|n, span| {
|
||||
u64::from_str_radix(&n, 16).map_err(|err| Simple::custom(span, err.to_string()))
|
||||
u64::from_str_radix(&n, 16).map_err(|err| LexerError::custom(span, err.to_string()))
|
||||
})
|
||||
.or(text::int(10).try_map(|n: String, span: Span| {
|
||||
u64::from_str_radix(&n, 10).map_err(|err| Simple::custom(span, err.to_string()))
|
||||
.or(text::digits(10).try_map(|n: String, span: Span| {
|
||||
n.parse::<u64>()
|
||||
.map_err(|err| LexerError::custom(span, err.to_string()))
|
||||
}))
|
||||
.boxed();
|
||||
|
||||
let int64 = integer
|
||||
.clone()
|
||||
.then_ignore(seq::<_, _, Simple<char>>("i64".chars()))
|
||||
.then_ignore(just("i64"))
|
||||
.map(|n| Token::Int64(n as i64));
|
||||
|
||||
let int_float = integer
|
||||
.clone()
|
||||
.then_ignore(just("_f"))
|
||||
.map(|n| Token::IntFloat(n as i32));
|
||||
|
||||
let int = integer.try_map(|n, span| {
|
||||
u32::try_from(n)
|
||||
.map(|n| Token::Int(n as i32))
|
||||
.map_err(|err| Simple::custom(span, err.to_string()))
|
||||
.map_err(|err| LexerError::custom(span, err.to_string()))
|
||||
});
|
||||
|
||||
let str_ = just('"')
|
||||
.ignore_then(filter(|c| *c != '"').repeated())
|
||||
.then_ignore(just('"'))
|
||||
.collect::<String>()
|
||||
.map(Token::Str);
|
||||
|
||||
let op = one_of("+-*/%&^|<=>#".chars())
|
||||
let str_ = just('\\')
|
||||
.then(any())
|
||||
.map(|t| vec![t.0, t.1])
|
||||
.or(none_of("\"").map(|c| vec![c]))
|
||||
.repeated()
|
||||
.at_least(1)
|
||||
.or(just(':').chain(just('=')))
|
||||
.flatten()
|
||||
.delimited_by(just('"'), just('"'))
|
||||
.collect::<String>()
|
||||
.map(Token::Op);
|
||||
.map(|s| Token::Str(parse_string_escapes(s)));
|
||||
|
||||
let ctrl = one_of("(){};,:?!".chars()).map(Token::Ctrl);
|
||||
let char_ = just('\\')
|
||||
.then(any())
|
||||
.map(|t| vec![t.0, t.1])
|
||||
.or(none_of("\'").map(|c| vec![c]))
|
||||
.repeated()
|
||||
.flatten()
|
||||
.delimited_by(just('\''), just('\''))
|
||||
.collect::<String>()
|
||||
.map(|s| {
|
||||
let s = parse_string_escapes(s);
|
||||
let mut value = 0;
|
||||
for (i, c) in s.chars().enumerate() {
|
||||
// TODO: generate error on overflow
|
||||
if i < 4 {
|
||||
value |= (c as u32) << (i * 8);
|
||||
}
|
||||
}
|
||||
Token::Int(value as i32)
|
||||
});
|
||||
|
||||
fn ident() -> impl Parser<char, String, Error = Simple<char>> + Copy + Clone {
|
||||
let op = choice((
|
||||
just("#/"),
|
||||
just("#%"),
|
||||
just("<<"),
|
||||
just(">>"),
|
||||
just("#>>"),
|
||||
just(">="),
|
||||
just("<="),
|
||||
just("=="),
|
||||
just("!="),
|
||||
just("#>="),
|
||||
just("#<="),
|
||||
just("#<"),
|
||||
just("#>"),
|
||||
just("->"),
|
||||
just(":="),
|
||||
just("<|"),
|
||||
))
|
||||
.map(|s| s.to_string())
|
||||
.or(one_of("+-*/%&^|<=>").map(|s: char| s.to_string()))
|
||||
.map(Token::Op)
|
||||
.boxed();
|
||||
|
||||
let ctrl = one_of("(){};,:?!$").map(Token::Ctrl);
|
||||
|
||||
fn ident() -> impl Parser<char, String, Error = LexerError> + Copy + Clone {
|
||||
filter(|c: &char| c.is_ascii_alphabetic() || *c == '_')
|
||||
.map(Some)
|
||||
.chain::<char, Vec<_>, _>(
|
||||
@@ -241,22 +371,15 @@ fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = Simple<char>> {
|
||||
_ => Token::Ident(ident),
|
||||
});
|
||||
|
||||
let single_line =
|
||||
seq::<_, _, Simple<char>>("//".chars()).then_ignore(take_until(text::newline()));
|
||||
let single_line = just("//").then_ignore(take_until(text::newline()));
|
||||
|
||||
let multi_line =
|
||||
seq::<_, _, Simple<char>>("/*".chars()).then_ignore(take_until(seq("*/".chars())));
|
||||
let multi_line = just("/*").then_ignore(take_until(just("*/")));
|
||||
|
||||
let comment = single_line.or(multi_line);
|
||||
|
||||
let token = float
|
||||
.or(float64)
|
||||
.or(int64)
|
||||
.or(int)
|
||||
.or(str_)
|
||||
.or(op)
|
||||
.or(ctrl)
|
||||
.or(ident)
|
||||
let token = choice((
|
||||
float, float64, int64, int_float, int, str_, char_, op, ctrl, ident,
|
||||
))
|
||||
.recover_with(skip_then_retry_until([]));
|
||||
|
||||
token
|
||||
@@ -264,44 +387,107 @@ fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = Simple<char>> {
|
||||
.padded()
|
||||
.padded_by(comment.padded().repeated())
|
||||
.repeated()
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn map_token<O>(
|
||||
f: impl Fn(&Token) -> Option<O> + 'static + Clone,
|
||||
) -> impl Parser<Token, O, Error = Simple<Token>> + Clone {
|
||||
f: impl Fn(&Token, &Span) -> Option<O> + 'static + Clone,
|
||||
) -> impl Parser<Token, O, Error = ScriptError> + Clone {
|
||||
filter_map(move |span, tok: Token| {
|
||||
if let Some(output) = f(&tok) {
|
||||
if let Some(output) = f(&tok, &span) {
|
||||
Ok(output)
|
||||
} else {
|
||||
Err(Simple::expected_input_found(span, Vec::new(), Some(tok)))
|
||||
Err(ScriptError::expected_input_found(
|
||||
span,
|
||||
Vec::new(),
|
||||
Some(tok),
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + Clone {
|
||||
fn parse_string_escapes(s: String) -> String {
|
||||
let mut result = String::new();
|
||||
let mut chars = s.chars().peekable();
|
||||
while let Some(c) = chars.next() {
|
||||
if c != '\\' {
|
||||
result.push(c);
|
||||
} else if let Some(c) = chars.next() {
|
||||
match c {
|
||||
'0'..='9' | 'a'..='f' | 'A'..='F' => {
|
||||
let mut number = c.to_string();
|
||||
if let Some('0'..='9' | 'a'..='f' | 'A'..='F') = chars.peek() {
|
||||
number.push(chars.next().unwrap());
|
||||
}
|
||||
result.push(u8::from_str_radix(&number, 16).unwrap() as char);
|
||||
}
|
||||
'n' => result.push('\n'),
|
||||
'r' => result.push('\r'),
|
||||
't' => result.push('\t'),
|
||||
other => result.push(other),
|
||||
}
|
||||
} else {
|
||||
result.push('\\');
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
type ScriptError = Simple<Token, Span>;
|
||||
fn script_parser() -> impl Parser<Token, ast::Script, Error = ScriptError> + Clone {
|
||||
let identifier = filter_map(|span, tok| match tok {
|
||||
Token::Ident(id) => Ok(id),
|
||||
_ => Err(Simple::expected_input_found(span, Vec::new(), Some(tok))),
|
||||
_ => Err(ScriptError::expected_input_found(
|
||||
span,
|
||||
Vec::new(),
|
||||
Some(tok),
|
||||
)),
|
||||
})
|
||||
.labelled("identifier");
|
||||
|
||||
let integer = map_token(|tok| match tok {
|
||||
let integer = map_token(|tok, _| match tok {
|
||||
Token::Int(v) => Some(*v),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let string = map_token(|tok| match tok {
|
||||
let string = map_token(|tok, _| match tok {
|
||||
Token::Str(s) => Some(s.clone()),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let product_op = just(Token::Op("*".to_string()))
|
||||
.to(ast::BinOp::Mul)
|
||||
.or(just(Token::Op("/".to_string())).to(ast::BinOp::Div))
|
||||
.or(just(Token::Op("#/".to_string())).to(ast::BinOp::DivU))
|
||||
.or(just(Token::Op("%".to_string())).to(ast::BinOp::Rem))
|
||||
.or(just(Token::Op("#%".to_string())).to(ast::BinOp::RemU))
|
||||
.boxed();
|
||||
let sum_op = just(Token::Op("+".to_string()))
|
||||
.to(ast::BinOp::Add)
|
||||
.or(just(Token::Op("-".to_string())).to(ast::BinOp::Sub))
|
||||
.boxed();
|
||||
let shift_op = just(Token::Op("<<".to_string()))
|
||||
.to(ast::BinOp::Shl)
|
||||
.or(just(Token::Op("#>>".to_string())).to(ast::BinOp::ShrU))
|
||||
.or(just(Token::Op(">>".to_string())).to(ast::BinOp::ShrS))
|
||||
.boxed();
|
||||
let bit_op = just(Token::Op("&".to_string()))
|
||||
.to(ast::BinOp::And)
|
||||
.or(just(Token::Op("|".to_string())).to(ast::BinOp::Or))
|
||||
.or(just(Token::Op("^".to_string())).to(ast::BinOp::Xor))
|
||||
.boxed();
|
||||
|
||||
let mut expression_out = None;
|
||||
let block = recursive(|block| {
|
||||
let mut block_expression = None;
|
||||
let expression = recursive(|expression| {
|
||||
let val = map_token(|tok| match tok {
|
||||
let val = map_token(|tok, span| match tok {
|
||||
Token::Int(v) => Some(ast::Expr::I32Const(*v)),
|
||||
Token::Int64(v) => Some(ast::Expr::I64Const(*v)),
|
||||
Token::IntFloat(v) => Some(ast::Expr::Cast {
|
||||
value: Box::new(ast::Expr::I32Const(*v).with_span(span.clone())),
|
||||
type_: ast::Type::F32,
|
||||
}),
|
||||
Token::Float(v) => Some(ast::Expr::F32Const(v.parse().unwrap())),
|
||||
Token::Float64(v) => Some(ast::Expr::F64Const(v.parse().unwrap())),
|
||||
_ => None,
|
||||
@@ -313,7 +499,11 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
name: id,
|
||||
local_id: None,
|
||||
}),
|
||||
_ => Err(Simple::expected_input_found(span, Vec::new(), Some(tok))),
|
||||
_ => Err(ScriptError::expected_input_found(
|
||||
span,
|
||||
Vec::new(),
|
||||
Some(tok),
|
||||
)),
|
||||
})
|
||||
.labelled("variable");
|
||||
|
||||
@@ -326,6 +516,36 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
})
|
||||
.boxed();
|
||||
|
||||
let local_tee_op = identifier
|
||||
.then(
|
||||
product_op
|
||||
.clone()
|
||||
.or(sum_op.clone())
|
||||
.or(shift_op.clone())
|
||||
.or(bit_op.clone()),
|
||||
)
|
||||
.then_ignore(just(Token::Op(":=".to_string())))
|
||||
.then(expression.clone())
|
||||
.map_with_span(|((name, op), expr), span| ast::Expr::LocalTee {
|
||||
name: name.clone(),
|
||||
value: Box::new(
|
||||
ast::Expr::BinOp {
|
||||
left: Box::new(
|
||||
ast::Expr::Variable {
|
||||
name,
|
||||
local_id: None,
|
||||
}
|
||||
.with_span(span.clone()),
|
||||
),
|
||||
right: Box::new(expr),
|
||||
op,
|
||||
}
|
||||
.with_span(span),
|
||||
),
|
||||
local_id: None,
|
||||
})
|
||||
.boxed();
|
||||
|
||||
let loop_expr = just(Token::Loop)
|
||||
.ignore_then(identifier)
|
||||
.then(block.clone())
|
||||
@@ -342,14 +562,24 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
block: Box::new(block),
|
||||
});
|
||||
|
||||
let if_expr = just(Token::If)
|
||||
let if_expr = recursive::<_, ast::Expr, _, _, _>(|if_expr| {
|
||||
just(Token::If)
|
||||
.ignore_then(expression.clone())
|
||||
.then(block.clone())
|
||||
.then(just(Token::Else).ignore_then(block.clone()).or_not())
|
||||
.then(
|
||||
just(Token::Else)
|
||||
.ignore_then(
|
||||
block
|
||||
.clone()
|
||||
.or(if_expr.map_with_span(|expr, span| expr.with_span(span))),
|
||||
)
|
||||
.or_not(),
|
||||
)
|
||||
.map(|((condition, if_true), if_false)| ast::Expr::If {
|
||||
condition: Box::new(condition),
|
||||
if_true: Box::new(if_true),
|
||||
if_false: if_false.map(Box::new),
|
||||
})
|
||||
});
|
||||
|
||||
let block_expr = loop_expr.or(label_block_expr).or(if_expr).boxed();
|
||||
@@ -358,7 +588,7 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
|
||||
let branch = just(Token::Branch)
|
||||
.ignore_then(identifier)
|
||||
.map(|label| ast::Expr::Branch(label));
|
||||
.map(ast::Expr::Branch);
|
||||
|
||||
let branch_if = just(Token::BranchIf)
|
||||
.ignore_then(expression.clone())
|
||||
@@ -377,7 +607,7 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
.or(just(Token::Inline).to(ast::LetType::Inline)))
|
||||
.or_not(),
|
||||
)
|
||||
.then(identifier.clone())
|
||||
.then(identifier)
|
||||
.then(just(Token::Ctrl(':')).ignore_then(type_parser()).or_not())
|
||||
.then(
|
||||
just(Token::Op("=".to_string()))
|
||||
@@ -393,17 +623,6 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
})
|
||||
.boxed();
|
||||
|
||||
let assign = identifier
|
||||
.clone()
|
||||
.then_ignore(just(Token::Op("=".to_string())))
|
||||
.then(expression.clone())
|
||||
.map(|(name, value)| ast::Expr::Assign {
|
||||
name,
|
||||
value: Box::new(value),
|
||||
local_id: None,
|
||||
})
|
||||
.boxed();
|
||||
|
||||
let select = just(Token::Select)
|
||||
.ignore_then(
|
||||
expression
|
||||
@@ -412,7 +631,7 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
.then(expression.clone())
|
||||
.then_ignore(just(Token::Ctrl(',')))
|
||||
.then(expression.clone())
|
||||
.delimited_by(Token::Ctrl('('), Token::Ctrl(')')),
|
||||
.delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))),
|
||||
)
|
||||
.map(|((condition, if_true), if_false)| ast::Expr::Select {
|
||||
condition: Box::new(condition),
|
||||
@@ -422,12 +641,11 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
.boxed();
|
||||
|
||||
let function_call = identifier
|
||||
.clone()
|
||||
.then(
|
||||
expression
|
||||
.clone()
|
||||
.separated_by(just(Token::Ctrl(',')))
|
||||
.delimited_by(Token::Ctrl('('), Token::Ctrl(')')),
|
||||
.delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))),
|
||||
)
|
||||
.map(|(name, params)| ast::Expr::FuncCall { name, params })
|
||||
.boxed();
|
||||
@@ -438,21 +656,23 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
value: value.map(Box::new),
|
||||
});
|
||||
|
||||
let atom = val
|
||||
.or(function_call)
|
||||
.or(assign)
|
||||
.or(local_tee)
|
||||
.or(variable)
|
||||
.or(block_expr)
|
||||
.or(branch)
|
||||
.or(branch_if)
|
||||
.or(let_)
|
||||
.or(select)
|
||||
.or(return_)
|
||||
let atom = choice((
|
||||
val,
|
||||
function_call,
|
||||
local_tee,
|
||||
local_tee_op,
|
||||
variable,
|
||||
block_expr,
|
||||
branch,
|
||||
branch_if,
|
||||
let_,
|
||||
select,
|
||||
return_,
|
||||
))
|
||||
.map_with_span(|expr, span| expr.with_span(span))
|
||||
.or(expression
|
||||
.clone()
|
||||
.delimited_by(Token::Ctrl('('), Token::Ctrl(')')))
|
||||
.delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))))
|
||||
.or(block)
|
||||
.recover_with(nested_delimiters(
|
||||
Token::Ctrl('('),
|
||||
@@ -470,7 +690,7 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
.then(atom)
|
||||
.map(|(ops, value)| {
|
||||
ops.into_iter().rev().fold(value, |acc, (op, span)| {
|
||||
let span = span.start..acc.span.end;
|
||||
let span = (span.0, span.1.start..acc.span.1.end);
|
||||
ast::Expr::UnaryOp {
|
||||
op,
|
||||
value: Box::new(acc),
|
||||
@@ -499,7 +719,8 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
|
||||
let mem_size = just(Token::Ctrl('?'))
|
||||
.to(ast::MemSize::Byte)
|
||||
.or(just(Token::Ctrl('!')).to(ast::MemSize::Word));
|
||||
.or(just(Token::Ctrl('!')).to(ast::MemSize::Word))
|
||||
.or(just(Token::Ctrl('$')).to(ast::MemSize::Float));
|
||||
|
||||
let mem_op = mem_size.then(op_cast.clone());
|
||||
|
||||
@@ -509,7 +730,7 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
poke_op: Option<((ast::MemSize, ast::Expression), ast::Expression)>,
|
||||
) -> ast::Expression {
|
||||
let left = peek_ops.into_iter().fold(left, |left, (size, right)| {
|
||||
let span = left.span.start..right.span.end;
|
||||
let span = (left.span.0, left.span.1.start..right.span.1.end);
|
||||
ast::Expr::Peek(ast::MemoryLocation {
|
||||
span: span.clone(),
|
||||
left: Box::new(left),
|
||||
@@ -519,7 +740,7 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
.with_span(span)
|
||||
});
|
||||
if let Some(((size, right), value)) = poke_op {
|
||||
let span = left.span.start..value.span.end;
|
||||
let span = (left.span.0, left.span.1.start..value.span.1.end);
|
||||
ast::Expr::Poke {
|
||||
mem_location: ast::MemoryLocation {
|
||||
span: span.clone(),
|
||||
@@ -565,18 +786,9 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
|
||||
let op_product = memory_op
|
||||
.clone()
|
||||
.then(
|
||||
just(Token::Op("*".to_string()))
|
||||
.to(ast::BinOp::Mul)
|
||||
.or(just(Token::Op("/".to_string())).to(ast::BinOp::Div))
|
||||
.or(just(Token::Op("#/".to_string())).to(ast::BinOp::DivU))
|
||||
.or(just(Token::Op("%".to_string())).to(ast::BinOp::Rem))
|
||||
.or(just(Token::Op("#%".to_string())).to(ast::BinOp::RemU))
|
||||
.then(memory_op.clone())
|
||||
.repeated(),
|
||||
)
|
||||
.then(product_op.clone().then(memory_op.clone()).repeated())
|
||||
.foldl(|left, (op, right)| {
|
||||
let span = left.span.start..right.span.end;
|
||||
let span = (left.span.0, left.span.1.start..right.span.1.end);
|
||||
ast::Expr::BinOp {
|
||||
op,
|
||||
left: Box::new(left),
|
||||
@@ -588,15 +800,9 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
|
||||
let op_sum = op_product
|
||||
.clone()
|
||||
.then(
|
||||
just(Token::Op("+".to_string()))
|
||||
.to(ast::BinOp::Add)
|
||||
.or(just(Token::Op("-".to_string())).to(ast::BinOp::Sub))
|
||||
.then(op_product.clone())
|
||||
.repeated(),
|
||||
)
|
||||
.then(sum_op.clone().then(op_product.clone()).repeated())
|
||||
.foldl(|left, (op, right)| {
|
||||
let span = left.span.start..right.span.end;
|
||||
let span = (left.span.0, left.span.1.start..right.span.1.end);
|
||||
ast::Expr::BinOp {
|
||||
op,
|
||||
left: Box::new(left),
|
||||
@@ -608,16 +814,9 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
|
||||
let op_shift = op_sum
|
||||
.clone()
|
||||
.then(
|
||||
just(Token::Op("<<".to_string()))
|
||||
.to(ast::BinOp::Shl)
|
||||
.or(just(Token::Op("#>>".to_string())).to(ast::BinOp::ShrU))
|
||||
.or(just(Token::Op(">>".to_string())).to(ast::BinOp::ShrS))
|
||||
.then(op_sum.clone())
|
||||
.repeated(),
|
||||
)
|
||||
.then(shift_op.clone().then(op_sum.clone()).repeated())
|
||||
.foldl(|left, (op, right)| {
|
||||
let span = left.span.start..right.span.end;
|
||||
let span = (left.span.0, left.span.1.start..right.span.1.end);
|
||||
ast::Expr::BinOp {
|
||||
op,
|
||||
left: Box::new(left),
|
||||
@@ -645,7 +844,7 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
.repeated(),
|
||||
)
|
||||
.foldl(|left, (op, right)| {
|
||||
let span = left.span.start..right.span.end;
|
||||
let span = (left.span.0, left.span.1.start..right.span.1.end);
|
||||
ast::Expr::BinOp {
|
||||
op,
|
||||
left: Box::new(left),
|
||||
@@ -657,16 +856,9 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
|
||||
let op_bit = op_cmp
|
||||
.clone()
|
||||
.then(
|
||||
just(Token::Op("&".to_string()))
|
||||
.to(ast::BinOp::And)
|
||||
.or(just(Token::Op("|".to_string())).to(ast::BinOp::Or))
|
||||
.or(just(Token::Op("^".to_string())).to(ast::BinOp::Xor))
|
||||
.then(op_cmp.clone())
|
||||
.repeated(),
|
||||
)
|
||||
.then(bit_op.clone().then(op_cmp.clone()).repeated())
|
||||
.foldl(|left, (op, right)| {
|
||||
let span = left.span.start..right.span.end;
|
||||
let span = (left.span.0, left.span.1.start..right.span.1.end);
|
||||
ast::Expr::BinOp {
|
||||
op,
|
||||
left: Box::new(left),
|
||||
@@ -676,7 +868,7 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
})
|
||||
.boxed();
|
||||
|
||||
let op_first = op_bit
|
||||
op_bit
|
||||
.clone()
|
||||
.then(
|
||||
just(Token::Op("<|".to_string()))
|
||||
@@ -684,27 +876,72 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
.repeated(),
|
||||
)
|
||||
.foldl(|left, right| {
|
||||
let span = left.span.start..right.span.end;
|
||||
let span = (left.span.0, left.span.1.start..right.span.1.end);
|
||||
ast::Expr::First {
|
||||
value: Box::new(left),
|
||||
drop: Box::new(right),
|
||||
}
|
||||
.with_span(span)
|
||||
})
|
||||
.boxed();
|
||||
|
||||
op_first
|
||||
.boxed()
|
||||
});
|
||||
|
||||
expression_out = Some(expression.clone());
|
||||
|
||||
let block_expression = block_expression.unwrap();
|
||||
|
||||
let assign = identifier
|
||||
.then_ignore(just(Token::Op("=".to_string())))
|
||||
.then(expression.clone())
|
||||
.map(|(name, value)| ast::Expr::Assign {
|
||||
name,
|
||||
value: Box::new(value),
|
||||
local_id: None,
|
||||
})
|
||||
.map_with_span(|expr, span| expr.with_span(span))
|
||||
.boxed();
|
||||
|
||||
let assign_op = identifier
|
||||
.then(
|
||||
product_op
|
||||
.clone()
|
||||
.or(sum_op.clone())
|
||||
.or(shift_op.clone())
|
||||
.or(bit_op.clone()),
|
||||
)
|
||||
.then_ignore(just(Token::Op("=".to_string())))
|
||||
.then(expression.clone())
|
||||
.map_with_span(|((name, op), value), span| {
|
||||
ast::Expr::Assign {
|
||||
name: name.clone(),
|
||||
value: Box::new(
|
||||
ast::Expr::BinOp {
|
||||
left: Box::new(
|
||||
ast::Expr::Variable {
|
||||
name,
|
||||
local_id: None,
|
||||
}
|
||||
.with_span(span.clone()),
|
||||
),
|
||||
right: Box::new(value),
|
||||
op,
|
||||
}
|
||||
.with_span(span.clone()),
|
||||
),
|
||||
local_id: None,
|
||||
}
|
||||
.with_span(span)
|
||||
})
|
||||
.boxed();
|
||||
|
||||
block_expression
|
||||
.clone()
|
||||
.then(just(Token::Ctrl(';')).or_not())
|
||||
.map_with_span(|(expr, semi), span| (expr.with_span(span), semi.is_none()))
|
||||
.or(expression.clone().then(just(Token::Ctrl(';')).to(false)))
|
||||
.or(assign
|
||||
.or(assign_op)
|
||||
.or(expression.clone())
|
||||
.then(just(Token::Ctrl(';')).to(false)))
|
||||
.repeated()
|
||||
.then(expression.clone().or_not())
|
||||
.map_with_span(|(mut statements, mut final_expression), span| {
|
||||
@@ -718,11 +955,11 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
}
|
||||
ast::Expr::Block {
|
||||
statements: statements.into_iter().map(|(expr, _)| expr).collect(),
|
||||
final_expression: final_expression.map(|e| Box::new(e)),
|
||||
final_expression: final_expression.map(Box::new),
|
||||
}
|
||||
.with_span(span)
|
||||
})
|
||||
.delimited_by(Token::Ctrl('{'), Token::Ctrl('}'))
|
||||
.delimited_by(just(Token::Ctrl('{')), just(Token::Ctrl('}')))
|
||||
.boxed()
|
||||
});
|
||||
|
||||
@@ -733,14 +970,14 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
.ignore_then(
|
||||
integer
|
||||
.clone()
|
||||
.delimited_by(Token::Ctrl('('), Token::Ctrl(')')),
|
||||
.delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))),
|
||||
)
|
||||
.map(|min_size| ast::ImportType::Memory(min_size as u32))
|
||||
.boxed();
|
||||
|
||||
let import_global = just(Token::Global)
|
||||
.ignore_then(just(Token::Mut).or_not())
|
||||
.then(identifier.clone())
|
||||
.then(identifier)
|
||||
.then_ignore(just(Token::Ctrl(':')))
|
||||
.then(type_parser())
|
||||
.map(|((mut_opt, name), type_)| ast::ImportType::Variable {
|
||||
@@ -751,11 +988,11 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
.boxed();
|
||||
|
||||
let import_function = just(Token::Fn)
|
||||
.ignore_then(identifier.clone())
|
||||
.ignore_then(identifier)
|
||||
.then(
|
||||
type_parser()
|
||||
.separated_by(just(Token::Ctrl(',')))
|
||||
.delimited_by(Token::Ctrl('('), Token::Ctrl(')')),
|
||||
.delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))),
|
||||
)
|
||||
.then(
|
||||
just(Token::Op("->".to_string()))
|
||||
@@ -783,7 +1020,6 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
.boxed();
|
||||
|
||||
let parameter = identifier
|
||||
.clone()
|
||||
.then_ignore(just(Token::Ctrl(':')))
|
||||
.then(type_parser())
|
||||
.boxed();
|
||||
@@ -792,11 +1028,11 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
.or_not()
|
||||
.then(just(Token::Ident("start".to_string())).or_not())
|
||||
.then_ignore(just(Token::Fn))
|
||||
.then(identifier.clone())
|
||||
.then(identifier)
|
||||
.then(
|
||||
parameter
|
||||
.separated_by(just(Token::Ctrl(',')))
|
||||
.delimited_by(Token::Ctrl('('), Token::Ctrl(')')),
|
||||
.delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))),
|
||||
)
|
||||
.then(
|
||||
just(Token::Op("->".to_string()))
|
||||
@@ -820,7 +1056,7 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
|
||||
let global = just(Token::Global)
|
||||
.ignore_then(just(Token::Mut).or_not())
|
||||
.then(identifier.clone())
|
||||
.then(identifier)
|
||||
.then(just(Token::Ctrl(':')).ignore_then(type_parser()).or_not())
|
||||
.then(just(Token::Op("=".to_string())).ignore_then(expression.clone()))
|
||||
.then_ignore(just(Token::Ctrl(';')))
|
||||
@@ -835,6 +1071,21 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
})
|
||||
.boxed();
|
||||
|
||||
let global_const = just(Token::Ident("const".to_string()))
|
||||
.ignore_then(identifier)
|
||||
.then(just(Token::Ctrl(':')).ignore_then(type_parser()).or_not())
|
||||
.then(just(Token::Op("=".to_string())).ignore_then(expression.clone()))
|
||||
.then_ignore(just(Token::Ctrl(';')))
|
||||
.map_with_span(|((name, type_), value), span| {
|
||||
ast::TopLevelItem::Const(ast::GlobalConst {
|
||||
name,
|
||||
type_,
|
||||
value,
|
||||
span,
|
||||
})
|
||||
})
|
||||
.boxed();
|
||||
|
||||
let data_i8 = just(Token::Ident("i8".to_string()))
|
||||
.to(ast::DataType::I8)
|
||||
.or(just(Token::Ident("i16".to_string())).to(ast::DataType::I16))
|
||||
@@ -846,17 +1097,17 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
expression
|
||||
.clone()
|
||||
.separated_by(just(Token::Ctrl(',')))
|
||||
.delimited_by(Token::Ctrl('('), Token::Ctrl(')')),
|
||||
.delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))),
|
||||
)
|
||||
.map(|(type_, values)| ast::DataValues::Array { type_, values });
|
||||
|
||||
let data_string = string.clone().map(|s| ast::DataValues::String(s));
|
||||
let data_string = string.clone().map(ast::DataValues::String);
|
||||
|
||||
let data_file = just(Token::Ident("file".to_string()))
|
||||
.ignore_then(
|
||||
string
|
||||
.clone()
|
||||
.delimited_by(Token::Ctrl('('), Token::Ctrl(')')),
|
||||
.delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))),
|
||||
)
|
||||
.map(|s| ast::DataValues::File {
|
||||
path: s.into(),
|
||||
@@ -870,7 +1121,7 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
.or(data_string)
|
||||
.or(data_file)
|
||||
.repeated()
|
||||
.delimited_by(Token::Ctrl('{'), Token::Ctrl('}')),
|
||||
.delimited_by(just(Token::Ctrl('{')), just(Token::Ctrl('}'))),
|
||||
)
|
||||
.map(|(offset, data)| {
|
||||
ast::TopLevelItem::Data(ast::Data {
|
||||
@@ -880,41 +1131,49 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = Simple<Token>> + C
|
||||
})
|
||||
.boxed();
|
||||
|
||||
import.or(function).or(global).or(data).boxed()
|
||||
let include =
|
||||
just(Token::Ident("include".to_string())).ignore_then(string.clone().map_with_span(
|
||||
|path, span| ast::TopLevelItem::Include(ast::Include { span, path }),
|
||||
));
|
||||
|
||||
import
|
||||
.or(function)
|
||||
.or(global)
|
||||
.or(data)
|
||||
.or(include)
|
||||
.or(global_const)
|
||||
.boxed()
|
||||
};
|
||||
|
||||
top_level_item.repeated().then_ignore(end()).map(|items| {
|
||||
let mut script = ast::Script {
|
||||
imports: Vec::new(),
|
||||
global_vars: Vec::new(),
|
||||
functions: Vec::new(),
|
||||
data: Vec::new(),
|
||||
};
|
||||
let mut script = ast::Script::default();
|
||||
for item in items {
|
||||
match item {
|
||||
ast::TopLevelItem::Import(i) => script.imports.push(i),
|
||||
ast::TopLevelItem::GlobalVar(v) => script.global_vars.push(v),
|
||||
ast::TopLevelItem::Function(f) => script.functions.push(f),
|
||||
ast::TopLevelItem::Data(d) => script.data.push(d),
|
||||
ast::TopLevelItem::Include(i) => script.includes.push(i),
|
||||
ast::TopLevelItem::Const(c) => script.consts.push(c),
|
||||
}
|
||||
}
|
||||
script
|
||||
})
|
||||
}
|
||||
|
||||
fn type_parser() -> impl Parser<Token, ast::Type, Error = Simple<Token>> + Clone {
|
||||
fn type_parser() -> impl Parser<Token, ast::Type, Error = ScriptError> + Clone {
|
||||
filter_map(|span, tok| match tok {
|
||||
Token::Ident(id) if id == "i32" => Ok(ast::Type::I32),
|
||||
Token::Ident(id) if id == "i64" => Ok(ast::Type::I64),
|
||||
Token::Ident(id) if id == "f32" => Ok(ast::Type::F32),
|
||||
Token::Ident(id) if id == "f64" => Ok(ast::Type::F64),
|
||||
_ => Err(Simple::expected_input_found(
|
||||
_ => Err(ScriptError::expected_input_found(
|
||||
span,
|
||||
vec![
|
||||
Token::Ident("i32".into()),
|
||||
Token::Ident("i64".into()),
|
||||
Token::Ident("f32".into()),
|
||||
Token::Ident("f64".into()),
|
||||
Some(Token::Ident("i32".into())),
|
||||
Some(Token::Ident("i64".into())),
|
||||
Some(Token::Ident("f32".into())),
|
||||
Some(Token::Ident("f64".into())),
|
||||
],
|
||||
Some(tok),
|
||||
)),
|
||||
|
||||
320
src/typecheck.rs
320
src/typecheck.rs
@@ -1,9 +1,9 @@
|
||||
use ariadne::{Color, Label, Report, ReportKind, Source};
|
||||
use ariadne::{Color, Label, Report, ReportKind};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::ast;
|
||||
use crate::ast::{self, MemSize};
|
||||
use crate::intrinsics::Intrinsics;
|
||||
use crate::Span;
|
||||
use crate::parser::{Sources, Span};
|
||||
use ast::Type::*;
|
||||
|
||||
type Result<T> = std::result::Result<T, ()>;
|
||||
@@ -15,9 +15,9 @@ struct Var {
|
||||
}
|
||||
type Vars = HashMap<String, Var>;
|
||||
|
||||
pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> {
|
||||
pub fn tc_script(script: &mut ast::Script, sources: &Sources) -> Result<()> {
|
||||
let mut context = Context {
|
||||
source,
|
||||
sources,
|
||||
global_vars: HashMap::new(),
|
||||
functions: HashMap::new(),
|
||||
locals: ast::Locals::default(),
|
||||
@@ -41,7 +41,7 @@ pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> {
|
||||
"Global already defined",
|
||||
&import.span,
|
||||
span,
|
||||
source,
|
||||
sources,
|
||||
);
|
||||
} else {
|
||||
context.global_vars.insert(
|
||||
@@ -64,7 +64,7 @@ pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> {
|
||||
"Function already defined",
|
||||
&import.span,
|
||||
&fnc.span,
|
||||
source,
|
||||
sources,
|
||||
);
|
||||
} else {
|
||||
context.functions.insert(
|
||||
@@ -83,12 +83,12 @@ pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> {
|
||||
|
||||
for v in &mut script.global_vars {
|
||||
if let Some(Var { span, .. }) = context.global_vars.get(&v.name) {
|
||||
result = report_duplicate_definition("Global already defined", &v.span, span, source);
|
||||
result = report_duplicate_definition("Global already defined", &v.span, span, sources);
|
||||
} else {
|
||||
tc_const(&mut v.value, source)?;
|
||||
tc_const(&mut v.value, sources)?;
|
||||
if v.type_ != v.value.type_ {
|
||||
if v.type_.is_some() {
|
||||
result = type_mismatch(v.type_, &v.span, v.value.type_, &v.value.span, source);
|
||||
result = type_mismatch(v.type_, &v.span, v.value.type_, &v.value.span, sources);
|
||||
} else {
|
||||
v.type_ = v.value.type_;
|
||||
}
|
||||
@@ -104,11 +104,26 @@ pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
for c in &mut script.consts {
|
||||
tc_const(&mut c.value, sources)?;
|
||||
if c.value.type_ != c.type_ {
|
||||
if c.type_.is_some() {
|
||||
result = type_mismatch(c.type_, &c.span, c.value.type_, &c.value.span, sources);
|
||||
} else {
|
||||
c.type_ = c.value.type_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for f in &script.functions {
|
||||
let params = f.params.iter().map(|(_, t)| *t).collect();
|
||||
if let Some(fnc) = context.functions.get(&f.name) {
|
||||
result =
|
||||
report_duplicate_definition("Function already defined", &f.span, &fnc.span, source);
|
||||
result = report_duplicate_definition(
|
||||
"Function already defined",
|
||||
&f.span,
|
||||
&fnc.span,
|
||||
sources,
|
||||
);
|
||||
} else {
|
||||
context.functions.insert(
|
||||
f.name.clone(),
|
||||
@@ -132,7 +147,7 @@ pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> {
|
||||
.or_else(|| context.global_vars.get(name).map(|v| &v.span))
|
||||
{
|
||||
result =
|
||||
report_duplicate_definition("Variable already defined", &f.span, span, source);
|
||||
report_duplicate_definition("Variable already defined", &f.span, span, sources);
|
||||
} else {
|
||||
context.local_vars.insert(
|
||||
name.clone(),
|
||||
@@ -160,10 +175,10 @@ pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> {
|
||||
context.locals.locals[index].index = Some((locals_start + id) as u32);
|
||||
}
|
||||
|
||||
f.locals = std::mem::replace(&mut context.locals, ast::Locals::default());
|
||||
f.locals = std::mem::take(&mut context.locals);
|
||||
|
||||
if f.body.type_ != f.type_ {
|
||||
result = type_mismatch(f.type_, &f.span, f.body.type_, &f.body.span, source);
|
||||
result = type_mismatch(f.type_, &f.span, f.body.type_, &f.body.span, sources);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +186,7 @@ pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> {
|
||||
for f in &script.functions {
|
||||
if f.start {
|
||||
if !f.params.is_empty() || f.type_.is_some() {
|
||||
Report::build(ReportKind::Error, (), f.span.start)
|
||||
Report::build(ReportKind::Error, f.span.0, f.span.1.start)
|
||||
.with_message("Start function can't have params or a return value")
|
||||
.with_label(
|
||||
Label::new(f.span.clone())
|
||||
@@ -179,7 +194,7 @@ pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> {
|
||||
.with_color(Color::Red),
|
||||
)
|
||||
.finish()
|
||||
.eprint(Source::from(source))
|
||||
.eprint(sources)
|
||||
.unwrap();
|
||||
|
||||
result = Err(());
|
||||
@@ -189,7 +204,7 @@ pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> {
|
||||
"Start function already defined",
|
||||
&f.span,
|
||||
&prev.span,
|
||||
source,
|
||||
sources,
|
||||
);
|
||||
} else {
|
||||
start_function = Some(f);
|
||||
@@ -198,14 +213,14 @@ pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> {
|
||||
}
|
||||
|
||||
for data in &mut script.data {
|
||||
tc_const(&mut data.offset, source)?;
|
||||
tc_const(&mut data.offset, sources)?;
|
||||
if data.offset.type_ != Some(I32) {
|
||||
result = type_mismatch(
|
||||
Some(I32),
|
||||
&data.offset.span,
|
||||
data.offset.type_,
|
||||
&data.offset.span,
|
||||
source,
|
||||
sources,
|
||||
);
|
||||
}
|
||||
for values in &mut data.data {
|
||||
@@ -220,14 +235,14 @@ pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> {
|
||||
ast::DataType::F64 => ast::Type::F64,
|
||||
};
|
||||
for value in values {
|
||||
tc_const(value, source)?;
|
||||
tc_const(value, sources)?;
|
||||
if value.type_ != Some(needed_type) {
|
||||
result = type_mismatch(
|
||||
Some(needed_type),
|
||||
&value.span,
|
||||
value.type_,
|
||||
&value.span,
|
||||
source,
|
||||
sources,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -247,7 +262,7 @@ struct FunctionType {
|
||||
}
|
||||
|
||||
struct Context<'a> {
|
||||
source: &'a str,
|
||||
sources: &'a Sources,
|
||||
global_vars: Vars,
|
||||
functions: HashMap<String, FunctionType>,
|
||||
locals: ast::Locals,
|
||||
@@ -294,13 +309,13 @@ impl LocalVars {
|
||||
}
|
||||
}
|
||||
|
||||
fn report_duplicate_definition(
|
||||
pub fn report_duplicate_definition(
|
||||
msg: &str,
|
||||
span: &Span,
|
||||
prev_span: &Span,
|
||||
source: &str,
|
||||
sources: &Sources,
|
||||
) -> Result<()> {
|
||||
Report::build(ReportKind::Error, (), span.start)
|
||||
Report::build(ReportKind::Error, span.0, span.1.start)
|
||||
.with_message(msg)
|
||||
.with_label(
|
||||
Label::new(span.clone())
|
||||
@@ -313,7 +328,7 @@ fn report_duplicate_definition(
|
||||
.with_color(Color::Yellow),
|
||||
)
|
||||
.finish()
|
||||
.eprint(Source::from(source))
|
||||
.eprint(sources)
|
||||
.unwrap();
|
||||
Err(())
|
||||
}
|
||||
@@ -323,9 +338,9 @@ fn type_mismatch(
|
||||
span1: &Span,
|
||||
type2: Option<ast::Type>,
|
||||
span2: &Span,
|
||||
source: &str,
|
||||
sources: &Sources,
|
||||
) -> Result<()> {
|
||||
Report::build(ReportKind::Error, (), span2.start)
|
||||
Report::build(ReportKind::Error, span2.0, span2.1.start)
|
||||
.with_message("Type mismatch")
|
||||
.with_label(
|
||||
Label::new(span1.clone())
|
||||
@@ -333,7 +348,7 @@ fn type_mismatch(
|
||||
"Expected type {:?}...",
|
||||
type1
|
||||
.map(|t| format!("{:?}", t))
|
||||
.unwrap_or("void".to_string())
|
||||
.unwrap_or_else(|| "void".to_string())
|
||||
))
|
||||
.with_color(Color::Yellow),
|
||||
)
|
||||
@@ -343,70 +358,48 @@ fn type_mismatch(
|
||||
"...but found type {}",
|
||||
type2
|
||||
.map(|t| format!("{:?}", t))
|
||||
.unwrap_or("void".to_string())
|
||||
.unwrap_or_else(|| "void".to_string())
|
||||
))
|
||||
.with_color(Color::Red),
|
||||
)
|
||||
.finish()
|
||||
.eprint(Source::from(source))
|
||||
.eprint(sources)
|
||||
.unwrap();
|
||||
Err(())
|
||||
}
|
||||
|
||||
fn expected_type(span: &Span, source: &str) -> Result<()> {
|
||||
Report::build(ReportKind::Error, (), span.start)
|
||||
.with_message("Expected value but found expression of type void")
|
||||
pub fn report_error(msg: &str, span: &Span, sources: &Sources) -> Result<()> {
|
||||
Report::build(ReportKind::Error, span.0, span.1.start)
|
||||
.with_message(msg)
|
||||
.with_label(
|
||||
Label::new(span.clone())
|
||||
.with_message("Expected value but found expression of type void")
|
||||
.with_message(msg)
|
||||
.with_color(Color::Red),
|
||||
)
|
||||
.finish()
|
||||
.eprint(Source::from(source))
|
||||
.eprint(sources)
|
||||
.unwrap();
|
||||
Err(())
|
||||
}
|
||||
|
||||
fn unknown_variable(span: &Span, source: &str) -> Result<()> {
|
||||
Report::build(ReportKind::Error, (), span.start)
|
||||
.with_message("Unknown variable")
|
||||
.with_label(
|
||||
Label::new(span.clone())
|
||||
.with_message("Unknown variable")
|
||||
.with_color(Color::Red),
|
||||
fn expected_type(span: &Span, sources: &Sources) -> Result<()> {
|
||||
report_error(
|
||||
"Expected value but found expression of type void",
|
||||
span,
|
||||
sources,
|
||||
)
|
||||
.finish()
|
||||
.eprint(Source::from(source))
|
||||
.unwrap();
|
||||
Err(())
|
||||
}
|
||||
|
||||
fn immutable_assign(span: &Span, source: &str) -> Result<()> {
|
||||
Report::build(ReportKind::Error, (), span.start)
|
||||
.with_message("Trying to assign to immutable variable")
|
||||
.with_label(
|
||||
Label::new(span.clone())
|
||||
.with_message("Trying to assign to immutable variable")
|
||||
.with_color(Color::Red),
|
||||
)
|
||||
.finish()
|
||||
.eprint(Source::from(source))
|
||||
.unwrap();
|
||||
Err(())
|
||||
fn unknown_variable(span: &Span, sources: &Sources) -> Result<()> {
|
||||
report_error("Unknown variable", span, sources)
|
||||
}
|
||||
|
||||
fn missing_label(span: &Span, source: &str) -> Result<()> {
|
||||
Report::build(ReportKind::Error, (), span.start)
|
||||
.with_message("Label not found")
|
||||
.with_label(
|
||||
Label::new(span.clone())
|
||||
.with_message("Label not found")
|
||||
.with_color(Color::Red),
|
||||
)
|
||||
.finish()
|
||||
.eprint(Source::from(source))
|
||||
.unwrap();
|
||||
return Err(());
|
||||
fn immutable_assign(span: &Span, sources: &Sources) -> Result<()> {
|
||||
report_error("Trying to assign to immutable variable", span, sources)
|
||||
}
|
||||
|
||||
fn missing_label(span: &Span, sources: &Sources) -> Result<()> {
|
||||
report_error("Label not found", span, sources)
|
||||
}
|
||||
|
||||
fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()> {
|
||||
@@ -445,11 +438,11 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
|
||||
&expr.span,
|
||||
value.type_,
|
||||
&value.span,
|
||||
context.source,
|
||||
context.sources,
|
||||
);
|
||||
}
|
||||
} else if value.type_.is_none() {
|
||||
return expected_type(&value.span, context.source);
|
||||
return expected_type(&value.span, context.sources);
|
||||
} else {
|
||||
*type_ = value.type_;
|
||||
}
|
||||
@@ -471,23 +464,17 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
|
||||
*local_id = Some(id);
|
||||
context.local_vars.insert(name.clone(), id);
|
||||
} else {
|
||||
Report::build(ReportKind::Error, (), expr.span.start)
|
||||
.with_message("Type missing")
|
||||
.with_label(
|
||||
Label::new(expr.span.clone())
|
||||
.with_message("Type missing")
|
||||
.with_color(Color::Red),
|
||||
)
|
||||
.finish()
|
||||
.eprint(Source::from(context.source))
|
||||
.unwrap();
|
||||
return Err(());
|
||||
return report_error("Type missing", &expr.span, context.sources);
|
||||
}
|
||||
None
|
||||
}
|
||||
ast::Expr::Peek(ref mut mem_location) => {
|
||||
tc_mem_location(context, mem_location)?;
|
||||
Some(I32)
|
||||
let ty = match mem_location.size {
|
||||
MemSize::Float => F32,
|
||||
_ => I32,
|
||||
};
|
||||
Some(ty)
|
||||
}
|
||||
ast::Expr::Poke {
|
||||
ref mut mem_location,
|
||||
@@ -495,13 +482,17 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
|
||||
} => {
|
||||
tc_mem_location(context, mem_location)?;
|
||||
tc_expression(context, value)?;
|
||||
if value.type_ != Some(I32) {
|
||||
let ty = match mem_location.size {
|
||||
MemSize::Float => F32,
|
||||
_ => I32,
|
||||
};
|
||||
if value.type_ != Some(ty) {
|
||||
return type_mismatch(
|
||||
Some(I32),
|
||||
Some(ty),
|
||||
&expr.span,
|
||||
value.type_,
|
||||
&value.span,
|
||||
context.source,
|
||||
context.sources,
|
||||
);
|
||||
}
|
||||
None
|
||||
@@ -513,7 +504,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
|
||||
ast::Expr::UnaryOp { op, ref mut value } => {
|
||||
tc_expression(context, value)?;
|
||||
if value.type_.is_none() {
|
||||
return expected_type(&value.span, context.source);
|
||||
return expected_type(&value.span, context.sources);
|
||||
}
|
||||
use ast::Type::*;
|
||||
use ast::UnaryOp::*;
|
||||
@@ -526,7 +517,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
|
||||
&expr.span,
|
||||
value.type_,
|
||||
&value.span,
|
||||
context.source,
|
||||
context.sources,
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -545,11 +536,11 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
|
||||
&left.span,
|
||||
right.type_,
|
||||
&right.span,
|
||||
context.source,
|
||||
context.sources,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return expected_type(&left.span, context.source);
|
||||
return expected_type(&left.span, context.sources);
|
||||
}
|
||||
use ast::BinOp::*;
|
||||
match op {
|
||||
@@ -561,7 +552,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
|
||||
&left.span,
|
||||
left.type_,
|
||||
&left.span,
|
||||
context.source,
|
||||
context.sources,
|
||||
);
|
||||
} else {
|
||||
left.type_
|
||||
@@ -575,7 +566,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
|
||||
&left.span,
|
||||
left.type_,
|
||||
&left.span,
|
||||
context.source,
|
||||
context.sources,
|
||||
);
|
||||
} else {
|
||||
Some(I32)
|
||||
@@ -593,7 +584,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
|
||||
} else if let Some(&Var { type_, .. }) = context.global_vars.get(name) {
|
||||
Some(type_)
|
||||
} else {
|
||||
return unknown_variable(&expr.span, context.source);
|
||||
return unknown_variable(&expr.span, context.sources);
|
||||
}
|
||||
}
|
||||
ast::Expr::Assign {
|
||||
@@ -607,7 +598,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
|
||||
*local_id = Some(id);
|
||||
let local = &context.locals[id];
|
||||
if local.index.is_none() {
|
||||
return immutable_assign(&expr.span, context.source);
|
||||
return immutable_assign(&expr.span, context.sources);
|
||||
}
|
||||
(local.type_, &local.span)
|
||||
} else if let Some(&Var {
|
||||
@@ -617,15 +608,15 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
|
||||
}) = context.global_vars.get(name)
|
||||
{
|
||||
if !mutable {
|
||||
return immutable_assign(&expr.span, context.source);
|
||||
return immutable_assign(&expr.span, context.sources);
|
||||
}
|
||||
(type_, span)
|
||||
} else {
|
||||
return unknown_variable(&expr.span, context.source);
|
||||
return unknown_variable(&expr.span, context.sources);
|
||||
};
|
||||
|
||||
if value.type_ != Some(type_) {
|
||||
return type_mismatch(Some(type_), span, value.type_, &value.span, context.source);
|
||||
return type_mismatch(Some(type_), span, value.type_, &value.span, context.sources);
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -640,7 +631,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
|
||||
let local = &context.locals[id];
|
||||
|
||||
if local.index.is_none() {
|
||||
return immutable_assign(&expr.span, context.source);
|
||||
return immutable_assign(&expr.span, context.sources);
|
||||
}
|
||||
|
||||
if value.type_ != Some(local.type_) {
|
||||
@@ -649,13 +640,13 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
|
||||
&local.span,
|
||||
value.type_,
|
||||
&value.span,
|
||||
context.source,
|
||||
context.sources,
|
||||
);
|
||||
}
|
||||
|
||||
Some(local.type_)
|
||||
} else {
|
||||
return unknown_variable(&expr.span, context.source);
|
||||
return unknown_variable(&expr.span, context.sources);
|
||||
}
|
||||
}
|
||||
ast::Expr::Loop {
|
||||
@@ -676,13 +667,13 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
|
||||
context.block_stack.pop();
|
||||
if block.type_ != None {
|
||||
// TODO: implement, requires branches to optionally provide values
|
||||
return type_mismatch(None, &expr.span, block.type_, &block.span, context.source);
|
||||
return type_mismatch(None, &expr.span, block.type_, &block.span, context.sources);
|
||||
}
|
||||
None
|
||||
}
|
||||
ast::Expr::Branch(ref label) => {
|
||||
if !context.block_stack.contains(label) {
|
||||
return missing_label(&expr.span, context.source);
|
||||
return missing_label(&expr.span, context.sources);
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -697,11 +688,11 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
|
||||
&expr.span,
|
||||
condition.type_,
|
||||
&condition.span,
|
||||
context.source,
|
||||
context.sources,
|
||||
);
|
||||
}
|
||||
if !context.block_stack.contains(label) {
|
||||
return missing_label(&expr.span, context.source);
|
||||
return missing_label(&expr.span, context.sources);
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -711,7 +702,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
|
||||
} => {
|
||||
tc_expression(context, value)?;
|
||||
if value.type_.is_none() {
|
||||
return expected_type(&expr.span, context.source);
|
||||
return expected_type(&expr.span, context.sources);
|
||||
}
|
||||
Some(type_)
|
||||
}
|
||||
@@ -722,10 +713,30 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
|
||||
for param in params.iter_mut() {
|
||||
tc_expression(context, param)?;
|
||||
if param.type_.is_none() {
|
||||
return expected_type(¶m.span, context.source);
|
||||
return expected_type(¶m.span, context.sources);
|
||||
}
|
||||
}
|
||||
if let Some(type_map) = context
|
||||
if let Some(load) = context.intrinsics.find_load(name) {
|
||||
tc_memarg(context, params.as_mut_slice(), &expr.span)?;
|
||||
Some(load.type_)
|
||||
} else if let Some(store) = context.intrinsics.find_store(name) {
|
||||
if let Some(value) = params.first_mut() {
|
||||
tc_expression(context, value)?;
|
||||
if value.type_ != Some(store.type_) {
|
||||
type_mismatch(
|
||||
Some(store.type_),
|
||||
&expr.span,
|
||||
value.type_,
|
||||
&value.span,
|
||||
context.sources,
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
return report_error("Missing parameters", &expr.span, context.sources);
|
||||
}
|
||||
tc_memarg(context, &mut params[1..], &expr.span)?;
|
||||
None
|
||||
} else if let Some(type_map) = context
|
||||
.functions
|
||||
.get(name)
|
||||
.map(|fnc| HashMap::from_iter([(fnc.params.clone(), fnc.type_)]))
|
||||
@@ -736,7 +747,8 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
|
||||
{
|
||||
*rtype
|
||||
} else {
|
||||
let mut report = Report::build(ReportKind::Error, (), expr.span.start)
|
||||
let mut report =
|
||||
Report::build(ReportKind::Error, expr.span.0, expr.span.1.start)
|
||||
.with_message("No matching function found");
|
||||
for (params, rtype) in type_map {
|
||||
let param_str: Vec<_> = params.into_iter().map(|t| t.to_string()).collect();
|
||||
@@ -752,24 +764,15 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
|
||||
);
|
||||
report = report.with_label(Label::new(expr.span.clone()).with_message(msg));
|
||||
}
|
||||
report
|
||||
.finish()
|
||||
.eprint(Source::from(context.source))
|
||||
.unwrap();
|
||||
report.finish().eprint(context.sources).unwrap();
|
||||
return Err(());
|
||||
}
|
||||
} else {
|
||||
Report::build(ReportKind::Error, (), expr.span.start)
|
||||
.with_message(format!("Unknown function {}", name))
|
||||
.with_label(
|
||||
Label::new(expr.span.clone())
|
||||
.with_message(format!("Unknown function {}", name))
|
||||
.with_color(Color::Red),
|
||||
)
|
||||
.finish()
|
||||
.eprint(Source::from(context.source))
|
||||
.unwrap();
|
||||
return Err(());
|
||||
return report_error(
|
||||
&format!("Unknown function {}", name),
|
||||
&expr.span,
|
||||
context.sources,
|
||||
);
|
||||
}
|
||||
}
|
||||
ast::Expr::Select {
|
||||
@@ -786,7 +789,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
|
||||
&condition.span,
|
||||
condition.type_,
|
||||
&condition.span,
|
||||
context.source,
|
||||
context.sources,
|
||||
);
|
||||
}
|
||||
if if_true.type_.is_some() {
|
||||
@@ -796,11 +799,11 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
|
||||
&if_true.span,
|
||||
if_false.type_,
|
||||
&if_false.span,
|
||||
context.source,
|
||||
context.sources,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return expected_type(&if_true.span, context.source);
|
||||
return expected_type(&if_true.span, context.sources);
|
||||
}
|
||||
if_true.type_
|
||||
}
|
||||
@@ -819,7 +822,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
|
||||
&if_true.span,
|
||||
if_false.type_,
|
||||
&if_false.span,
|
||||
context.source,
|
||||
context.sources,
|
||||
);
|
||||
} else {
|
||||
if_true.type_
|
||||
@@ -837,7 +840,7 @@ fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()
|
||||
&expr.span,
|
||||
value.type_,
|
||||
&value.span,
|
||||
context.source,
|
||||
context.sources,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -861,14 +864,14 @@ fn tc_mem_location<'a>(
|
||||
mem_location: &mut ast::MemoryLocation,
|
||||
) -> Result<()> {
|
||||
tc_expression(context, &mut mem_location.left)?;
|
||||
tc_const(&mut mem_location.right, context.source)?;
|
||||
tc_const(&mut mem_location.right, context.sources)?;
|
||||
if mem_location.left.type_ != Some(I32) {
|
||||
return type_mismatch(
|
||||
Some(I32),
|
||||
&mem_location.left.span,
|
||||
mem_location.left.type_,
|
||||
&mem_location.left.span,
|
||||
context.source,
|
||||
context.sources,
|
||||
);
|
||||
}
|
||||
if mem_location.right.type_ != Some(I32) {
|
||||
@@ -877,32 +880,53 @@ fn tc_mem_location<'a>(
|
||||
&mem_location.right.span,
|
||||
mem_location.right.type_,
|
||||
&mem_location.right.span,
|
||||
context.source,
|
||||
context.sources,
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn tc_const(expr: &mut ast::Expression, source: &str) -> Result<()> {
|
||||
fn tc_const(expr: &mut ast::Expression, sources: &Sources) -> Result<()> {
|
||||
use ast::Expr::*;
|
||||
expr.type_ = Some(match expr.expr {
|
||||
I32Const(_) => I32,
|
||||
I64Const(_) => I64,
|
||||
F32Const(_) => F32,
|
||||
F64Const(_) => F64,
|
||||
_ => {
|
||||
Report::build(ReportKind::Error, (), expr.span.start)
|
||||
.with_message("Expected constant value")
|
||||
.with_label(
|
||||
Label::new(expr.span.clone())
|
||||
.with_message("Expected constant value")
|
||||
.with_color(Color::Red),
|
||||
)
|
||||
.finish()
|
||||
.eprint(Source::from(source))
|
||||
.unwrap();
|
||||
return Err(());
|
||||
}
|
||||
_ => return report_error("Expected constant value", &expr.span, sources),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn tc_memarg(context: &mut Context, params: &mut [ast::Expression], span: &Span) -> Result<()> {
|
||||
if params.is_empty() || params.len() > 3 {
|
||||
let msg = if params.is_empty() {
|
||||
"Missing base address parameter"
|
||||
} else {
|
||||
"Too many MemArg parameters"
|
||||
};
|
||||
return report_error(msg, span, context.sources);
|
||||
}
|
||||
|
||||
for (index, param) in params.iter_mut().enumerate() {
|
||||
tc_expression(context, param)?;
|
||||
if param.type_ != Some(I32) {
|
||||
return type_mismatch(Some(I32), &span, param.type_, ¶m.span, context.sources);
|
||||
}
|
||||
if index > 0 {
|
||||
tc_const(param, context.sources)?;
|
||||
}
|
||||
if index == 2 {
|
||||
let align = param.const_i32();
|
||||
if align < 0 || align > 4 {
|
||||
return report_error(
|
||||
&format!("Alignment {} out of range (0-4)", align),
|
||||
¶m.span,
|
||||
context.sources,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
15
test/xorshift.cwa
Normal file
15
test/xorshift.cwa
Normal file
@@ -0,0 +1,15 @@
|
||||
// simple test to see whether lazy/inline chains with the same variable compile correctly
|
||||
|
||||
fn xorshift(x: i32) -> i32 {
|
||||
let lazy x = x ^ (x << 13);
|
||||
let lazy x = x ^ (x #>> 17);
|
||||
let inline x = x ^ (x << 5);
|
||||
x
|
||||
}
|
||||
|
||||
fn xorshift2(x: i32) -> i32 {
|
||||
x ^= x << 13;
|
||||
x ^= x #>> 17;
|
||||
x ^= x << 5;
|
||||
x
|
||||
}
|
||||
Reference in New Issue
Block a user