Compare commits

...

14 Commits

14 changed files with 559 additions and 266 deletions

View File

@@ -89,16 +89,25 @@ For floating point numbers, only the most basic decimal format is currently impl
0.464, 3.141, -10.0 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!" "env.memory", "Hello World!"
this does not work, yet:
"one line\nsecond line", "They said: \"Enough!\"" "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 ### 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"`.
@@ -295,6 +304,9 @@ So for example this block evaluates to 12:
Blocks are used as function bodies and in flow control (`if`, `block`, `loop`), but can also used at any point inside an expression. 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 #### 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 `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
@@ -304,6 +316,17 @@ the `if_false_block` otherwise (if it exists). It can also be used as an express
let a = if 0 { 2 } else { 3 }; // assigns 3 to a 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 `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. as a statement, returning a value from the block is not yet supported.

View File

@@ -22,7 +22,7 @@ start fn gen_palette() {
let lazy a = max(llimit, min(ulimit, c)) * (scale + 0.05); let lazy a = max(llimit, min(ulimit, c)) * (scale + 0.05);
let lazy b = scale * scale * 0.8; 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; 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)?(PALETTE) = v; (i%3 + i/3*4)?PALETTE = v;
avg = (avg + c) * 0.5; avg = (avg + c) * 0.5;
branch_if i := i - 1: gradients; branch_if i := i - 1: gradients;
@@ -37,7 +37,7 @@ start fn gen_palette() {
let inline src2 = select(first_step, (index + 1) % 32 / 2, index * 2 + 1); let inline src2 = select(first_step, (index + 1) % 32 / 2, index * 2 + 1);
let inline c1 = (src1 * 4 + channel)?SWEETY; let inline c1 = (src1 * 4 + channel)?SWEETY;
let inline c2 = (src2 * 4 + channel)?SWEETY; let inline c2 = (src2 * 4 + channel)?SWEETY;
i?(SWEETY) = (c1 + c2) * (3 + first_step) / 8; i?SWEETY = (c1 + c2) * (3 + first_step) / 8;
branch_if (i := i - 1) >= 0: expand_sweetie; branch_if (i := i - 1) >= 0: expand_sweetie;
} }

View 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"
}

View File

@@ -1,24 +1,29 @@
import "env.memory" memory(4); import "env.memory" memory(4);
import "env.sin" fn sin(f32) -> f32; 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) { export fn upd() {
let i: i32; let x: i32;
let y: i32;
loop screen { loop screen {
let lazy t = time as f32 / 2000 as f32; let inline t = time() / 2 as f32;
let lazy o = sin(t) * 0.8; let lazy o = sin(t) * 0.75;
let lazy q = (i % 320) as f32 - 160.1; let inline q = x as f32 - 160.5;
let lazy w = (i / 320 - 120) as f32; let inline w = (y - 120) as f32;
let lazy r = sqrt(q*q + w*w); let lazy r = sqrt(q*q + w*w);
let lazy z = q / r; let lazy z = q / r;
let lazy s = z * o + sqrt(z * z * o * o + 1 as f32 - o * o); 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 inline q2 = (z * s - o) * 10 as f32 + t;
let lazy w2 = w / r * s * 10 as f32 + t; let inline w2 = w / r * s * 10 as f32 + t;
let lazy s2 = s * 50 as f32 / r; let inline s2 = s * 100 as f32 / r;
i?120 = max( let inline color = max(
0 as f32, 0 as f32,
((q2 as i32 ^ w2 as i32 & ((s2 + t) * 20 as f32) as i32) & 5) as f32 * ((q2 as i32 ^ w2 as i32 & ((s2 + time()) * 10 as f32) as i32) & 5) as f32 *
(2 as f32 - s2) * 22 as f32 (4 as f32 - s2) as f32
) as i32; ) as i32 - 32;
branch_if (i := i + 1) < 320*240: screen setPixel(x, y, color);
branch_if x := (x + 1) % 320: screen;
branch_if y := (y + 1) % 320: screen;
} }
} }

View File

@@ -1,15 +1,16 @@
import "env.memory" memory(2); import "env.memory" memory(2);
import "env.time" fn time() -> f32;
export fn tic(time: i32) { export fn upd() {
let i: i32; let i: i32;
loop pixels { loop pixels {
let lazy x = (i % 320) as f32 - 160.1; let lazy x = (i % 320) as f32 - 160.1;
let lazy y = (i / 320 - 120) as f32; let lazy y = (i / 320 - 120) as f32;
let lazy dist = 10000.0 / (x*x + y*y); let lazy dist = 1024_f / (x*x + y*y);
let lazy t = time as f32 / 20 as f32; 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
} }
} }

View File

@@ -18,14 +18,14 @@ fn rng(state: i32) -> i32 {
} }
fn set_color(color: i32) -> i32 { fn set_color(color: i32) -> i32 {
?20 = color; 0?20 = color;
6 6
} }
export fn update() { export fn update() {
let y: i32; let y: i32;
let score = pz; let score = pz;
let lazy pad = ?22; let lazy pad = 0?22;
let lazy zero = 0.0; let lazy zero = 0.0;
let lazy control_speed = 0.03; let lazy control_speed = 0.03;
@@ -33,7 +33,7 @@ export fn update() {
f = f * 0.7; f = f * 0.7;
loop lines { 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 z = (4000 / (y := y + 1) + pz) / 20;
let lazy x = (rng(rng(rng(rng(z)))) >> 30) as f32 - px; let lazy x = (rng(rng(rng(rng(z)))) >> 30) as f32 - px;
let lazy w = 9 as f32 / sqrt(z as f32); let lazy w = 9 as f32 / sqrt(z as f32);

View File

@@ -140,6 +140,20 @@ fn fold_expr(context: &Context, expr: &mut ast::Expression) {
(ast::UnaryOp::Negate, ast::Expr::F64Const(value)) => { (ast::UnaryOp::Negate, ast::Expr::F64Const(value)) => {
Some(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)) => { (ast::UnaryOp::Not, ast::Expr::I32Const(value)) => {
Some(ast::Expr::I32Const((*value == 0) as i32)) Some(ast::Expr::I32Const((*value == 0) as i32))
} }

View File

@@ -289,7 +289,7 @@ struct FunctionContext<'a> {
functions: &'a HashMap<String, u32>, functions: &'a HashMap<String, u32>,
locals: &'a ast::Locals, locals: &'a ast::Locals,
labels: Vec<String>, 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, intrinsics: &'a Intrinsics,
} }
@@ -385,7 +385,10 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
.instruction(&Instruction::LocalSet(local.index.unwrap())); .instruction(&Instruction::LocalSet(local.index.unwrap()));
} }
ast::LetType::Lazy | ast::LetType::Inline => { 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));
} }
} }
} }
@@ -612,17 +615,17 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression)
} }
ast::Expr::Variable { name, local_id } => { ast::Expr::Variable { name, local_id } => {
if let &Some(id) = 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 { match let_type {
ast::LetType::Lazy => { ast::LetType::Lazy => {
let expr = ctx.let_values.remove(&id).unwrap().0;
emit_expression(ctx, expr); emit_expression(ctx, expr);
ctx.let_values.get_mut(&id).unwrap().clear();
ctx.function ctx.function
.instruction(&Instruction::LocalTee(ctx.locals[id].index.unwrap())); .instruction(&Instruction::LocalTee(ctx.locals[id].index.unwrap()));
} }
ast::LetType::Inline => { ast::LetType::Inline => {
let expr = *expr;
emit_expression(ctx, expr); emit_expression(ctx, expr);
ctx.let_values.get_mut(&id).unwrap().push((expr, let_type));
} }
_ => unreachable!(), _ => unreachable!(),
} }

View File

@@ -1,11 +1,15 @@
use std::fs::File;
use std::io::prelude::*; use std::io::prelude::*;
use std::path::Path; use std::path::{Path, PathBuf};
use std::{collections::HashSet, fs::File};
use crate::ast; use crate::ast;
use anyhow::{anyhow, Result}; 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"); let script_dir = path.parent().expect("Script path has no parent");
for data in &mut script.data { for data in &mut script.data {
for values in &mut data.data { for values in &mut data.data {
@@ -21,6 +25,7 @@ pub fn resolve_includes(script: &mut ast::Script, path: &Path) -> Result<()> {
anyhow!("Failed to load data from {}: {}", full_path.display(), e) anyhow!("Failed to load data from {}: {}", full_path.display(), e)
})? })?
.read_to_end(data)?; .read_to_end(data)?;
dependencies.insert(full_path);
} }
} }
} }

View File

@@ -1,9 +1,11 @@
use crate::ast::Type; use crate::ast::Type;
use std::collections::HashMap;
use enc::MemArg; use enc::MemArg;
use std::collections::HashMap;
use wasm_encoder as enc; 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 { impl Intrinsics {
pub fn new() -> Intrinsics { pub fn new() -> Intrinsics {
@@ -12,14 +14,11 @@ impl Intrinsics {
i i
} }
pub fn find_types( pub fn find_types(&self, name: &str) -> Option<HashMap<Vec<Type>, Option<Type>>> {
&self,
name: &str,
) -> Option<HashMap<Vec<Type>, Option<Type>>> {
self.0.get(name).map(|types| { self.0.get(name).map(|types| {
types types
.iter() .iter()
.map(|(params, (ret, _))| (params.clone(), Some(*ret))) .map(|(params, (ret, _))| (params.clone(), *ret))
.collect() .collect()
}) })
} }
@@ -34,87 +33,121 @@ impl Intrinsics {
fn add_instructions(&mut self) { fn add_instructions(&mut self) {
use enc::Instruction as I; use enc::Instruction as I;
use Type::*; use Type::*;
self.inst("i32.rotl", &[I32, I32], I32, I::I32Rotl); self.inst("i32.rotl", &[I32, I32], Some(I32), I::I32Rotl);
self.inst("i32.rotr", &[I32, I32], I32, I::I32Rotr); self.inst("i32.rotr", &[I32, I32], Some(I32), I::I32Rotr);
self.inst("i32.clz", &[I32], I32, I::I32Clz); self.inst("i32.clz", &[I32], Some(I32), I::I32Clz);
self.inst("i32.ctz", &[I32], I32, I::I32Ctz); self.inst("i32.ctz", &[I32], Some(I32), I::I32Ctz);
self.inst("i32.popcnt", &[I32], I32, I::I32Popcnt); self.inst("i32.popcnt", &[I32], Some(I32), I::I32Popcnt);
self.inst("i64.rotl", &[I64, I64], I64, I::I64Rotl); self.inst("i64.rotl", &[I64, I64], Some(I64), I::I64Rotl);
self.inst("i64.rotr", &[I64, I64], I64, I::I64Rotr); self.inst("i64.rotr", &[I64, I64], Some(I64), I::I64Rotr);
self.inst("i64.clz", &[I64], I64, I::I64Clz); self.inst("i64.clz", &[I64], Some(I64), I::I64Clz);
self.inst("i64.ctz", &[I64], I64, I::I64Ctz); self.inst("i64.ctz", &[I64], Some(I64), I::I64Ctz);
self.inst("i64.popcnt", &[I64], I64, I::I64Popcnt); self.inst("i64.popcnt", &[I64], Some(I64), I::I64Popcnt);
self.inst("f32/sqrt", &[F32], F32, I::F32Sqrt); self.inst("f32/sqrt", &[F32], Some(F32), I::F32Sqrt);
self.inst("f32/min", &[F32, F32], F32, I::F32Min); self.inst("f32/min", &[F32, F32], Some(F32), I::F32Min);
self.inst("f32/max", &[F32, F32], F32, I::F32Max); self.inst("f32/max", &[F32, F32], Some(F32), I::F32Max);
self.inst("f32/ceil", &[F32], F32, I::F32Ceil); self.inst("f32/ceil", &[F32], Some(F32), I::F32Ceil);
self.inst("f32/floor", &[F32], F32, I::F32Floor); self.inst("f32/floor", &[F32], Some(F32), I::F32Floor);
self.inst("f32/trunc", &[F32], F32, I::F32Trunc); self.inst("f32/trunc", &[F32], Some(F32), I::F32Trunc);
self.inst("f32/nearest", &[F32], F32, I::F32Nearest); self.inst("f32/nearest", &[F32], Some(F32), I::F32Nearest);
self.inst("f32/abs", &[F32], F32, I::F32Abs); self.inst("f32/abs", &[F32], Some(F32), I::F32Abs);
self.inst("f32.copysign", &[F32, F32], F32, I::F32Copysign); self.inst("f32.copysign", &[F32, F32], Some(F32), I::F32Copysign);
self.inst("f64/sqrt", &[F64], F64, I::F64Sqrt); self.inst("f64/sqrt", &[F64], Some(F64), I::F64Sqrt);
self.inst("f64/min", &[F64, F64], F64, I::F64Min); self.inst("f64/min", &[F64, F64], Some(F64), I::F64Min);
self.inst("f64/max", &[F64, F64], F64, I::F64Max); self.inst("f64/max", &[F64, F64], Some(F64), I::F64Max);
self.inst("f64/ceil", &[F64], F64, I::F64Ceil); self.inst("f64/ceil", &[F64], Some(F64), I::F64Ceil);
self.inst("f64/floor", &[F64], F64, I::F64Floor); self.inst("f64/floor", &[F64], Some(F64), I::F64Floor);
self.inst("f64/trunc", &[F64], F64, I::F64Trunc); self.inst("f64/trunc", &[F64], Some(F64), I::F64Trunc);
self.inst("f64/nearest", &[F64], F64, I::F64Nearest); self.inst("f64/nearest", &[F64], Some(F64), I::F64Nearest);
self.inst("f64/abs", &[F64], F64, I::F64Abs); self.inst("f64/abs", &[F64], Some(F64), I::F64Abs);
self.inst("f64.copysign", &[F64, F64], F64, I::F64Copysign); self.inst("f64.copysign", &[F64, F64], Some(F64), I::F64Copysign);
self.inst("i32.wrap_i64", &[I64], I32, I::I32WrapI64); self.inst("i32.wrap_i64", &[I64], Some(I32), I::I32WrapI64);
self.inst("i64.extend_i32_s", &[I32], I64, I::I64ExtendI32S); self.inst("i64.extend_i32_s", &[I32], Some(I64), I::I64ExtendI32S);
self.inst("i64.extend_i32_u", &[I32], I64, I::I64ExtendI32U); 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_f32_s", &[F32], Some(I32), I::I32TruncF32S);
self.inst("i32.trunc_f64_s", &[F64], I32, I::I32TruncF64S); self.inst("i32.trunc_f64_s", &[F64], Some(I32), I::I32TruncF64S);
self.inst("i64.trunc_f32_s", &[F32], I64, I::I64TruncF32S); self.inst("i64.trunc_f32_s", &[F32], Some(I64), I::I64TruncF32S);
self.inst("i64.trunc_f64_s", &[F64], I64, I::I64TruncF64S); 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_f32_u", &[F32], Some(I32), I::I32TruncF32U);
self.inst("i32.trunc_f64_u", &[F64], I32, I::I32TruncF64U); self.inst("i32.trunc_f64_u", &[F64], Some(I32), I::I32TruncF64U);
self.inst("i64.trunc_f32_u", &[F32], I64, I::I64TruncF32U); self.inst("i64.trunc_f32_u", &[F32], Some(I64), I::I64TruncF32U);
self.inst("i64.trunc_f64_u", &[F64], I64, I::I64TruncF64U); self.inst("i64.trunc_f64_u", &[F64], Some(I64), I::I64TruncF64U);
self.inst("f32.demote_f64", &[F64], F32, I::F32DemoteF64); self.inst("f32.demote_f64", &[F64], Some(F32), I::F32DemoteF64);
self.inst("f64.promote_f32", &[F32], F64, I::F64PromoteF32); self.inst("f64.promote_f32", &[F32], Some(F64), I::F64PromoteF32);
self.inst("f32.convert_i32_s", &[I32], F32, I::F32ConvertI32S); self.inst("f32.convert_i32_s", &[I32], Some(F32), I::F32ConvertI32S);
self.inst("f32.convert_i64_s", &[I64], F32, I::F32ConvertI32S); self.inst("f32.convert_i64_s", &[I64], Some(F32), I::F32ConvertI32S);
self.inst("f64.convert_i32_s", &[I32], F64, I::F32ConvertI32S); self.inst("f64.convert_i32_s", &[I32], Some(F64), I::F32ConvertI32S);
self.inst("f64.convert_i64_s", &[I64], 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_i32_u", &[I32], Some(F32), I::F32ConvertI32U);
self.inst("f32.convert_i64_u", &[I64], F32, I::F32ConvertI32U); self.inst("f32.convert_i64_u", &[I64], Some(F32), I::F32ConvertI32U);
self.inst("f64.convert_i32_u", &[I32], F64, I::F32ConvertI32U); self.inst("f64.convert_i32_u", &[I32], Some(F64), I::F32ConvertI32U);
self.inst("f64.convert_i64_u", &[I64], 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(
self.inst("i64.reinterpret_f64", &[F64], I64, I::I64ReinterpretF64); "i32.reinterpret_f32",
self.inst("f32.reinterpret_i32", &[I32], F32, I::F32ReinterpretI32); &[F32],
self.inst("f64.reinterpret_i64", &[I64], F64, I::F64ReinterpretI64); 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.extend8_s", &[I32], Some(I32), I::I32Extend8S);
self.inst("i32.extend16_s", &[I32], I32, I::I32Extend16S); self.inst("i32.extend16_s", &[I32], Some(I32), I::I32Extend16S);
self.inst("i64.extend8_s", &[I64], I64, I::I64Extend8S); self.inst("i64.extend8_s", &[I64], Some(I64), I::I64Extend8S);
self.inst("i64.extend16_s", &[I64], I64, I::I64Extend16S); self.inst("i64.extend16_s", &[I64], Some(I64), I::I64Extend16S);
self.inst("i64.extend32_s", &[I64], I64, I::I64Extend32S); 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_s", &[F32], Some(I32), I::I32TruncSatF32S);
self.inst("i32.trunc_sat_f32_u", &[F32], I32, I::I32TruncSatF32U); self.inst("i32.trunc_sat_f32_u", &[F32], Some(I32), I::I32TruncSatF32U);
self.inst("i32.trunc_sat_f64_s", &[F64], I32, I::I32TruncSatF64S); self.inst("i32.trunc_sat_f64_s", &[F64], Some(I32), I::I32TruncSatF64S);
self.inst("i32.trunc_sat_f64_u", &[F64], I32, I::I32TruncSatF64U); self.inst("i32.trunc_sat_f64_u", &[F64], Some(I32), I::I32TruncSatF64U);
self.inst("i64.trunc_sat_f32_s", &[F32], I64, I::I64TruncSatF32S); self.inst("i64.trunc_sat_f32_s", &[F32], Some(I64), I::I64TruncSatF32S);
self.inst("i64.trunc_sat_f32_u", &[F32], I64, I::I64TruncSatF32U); self.inst("i64.trunc_sat_f32_u", &[F32], Some(I64), I::I64TruncSatF32U);
self.inst("i64.trunc_sat_f64_s", &[F64], I64, I::I64TruncSatF64S); self.inst("i64.trunc_sat_f64_s", &[F64], Some(I64), I::I64TruncSatF64S);
self.inst("i64.trunc_sat_f64_u", &[F64], I64, I::I64TruncSatF64U); 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('/') { if let Some(slash_idx) = name.find('/') {
self.insert(name[(slash_idx + 1)..].to_string(), params, ret, &ins); self.insert(name[(slash_idx + 1)..].to_string(), params, ret, &ins);
let mut full_name = name[..slash_idx].to_string(); let mut full_name = name[..slash_idx].to_string();
@@ -130,7 +163,7 @@ impl Intrinsics {
&mut self, &mut self,
name: String, name: String,
params: &[Type], params: &[Type],
ret: Type, ret: Option<Type>,
ins: &enc::Instruction<'static>, ins: &enc::Instruction<'static>,
) { ) {
self.0 self.0
@@ -157,7 +190,7 @@ impl Intrinsics {
"i64.load32_u" => MemInstruction::new(I64, I::I64Load32_U, 2), "i64.load32_u" => MemInstruction::new(I64, I::I64Load32_U, 2),
"f32.load" => MemInstruction::new(F32, I::F32Load, 2), "f32.load" => MemInstruction::new(F32, I::F32Load, 2),
"f64.load" => MemInstruction::new(F64, I::F64Load, 3), "f64.load" => MemInstruction::new(F64, I::F64Load, 3),
_ => return None _ => return None,
}; };
return Some(ins); return Some(ins);
} }
@@ -175,7 +208,7 @@ impl Intrinsics {
"i64.store32" => MemInstruction::new(I64, I::I64Store32, 2), "i64.store32" => MemInstruction::new(I64, I::I64Store32, 2),
"f32.store" => MemInstruction::new(F32, I::F32Store, 2), "f32.store" => MemInstruction::new(F32, I::F32Store, 2),
"f64.store" => MemInstruction::new(F64, I::F64Store, 3), "f64.store" => MemInstruction::new(F64, I::F64Store, 3),
_ => return None _ => return None,
}; };
return Some(ins); return Some(ins);
} }
@@ -184,13 +217,19 @@ impl Intrinsics {
pub struct MemInstruction { pub struct MemInstruction {
pub type_: Type, pub type_: Type,
pub instruction: fn(MemArg) -> enc::Instruction<'static>, pub instruction: fn(MemArg) -> enc::Instruction<'static>,
pub natural_alignment: u32 pub natural_alignment: u32,
} }
impl MemInstruction { impl MemInstruction {
fn new(type_: Type, instruction: fn(MemArg) -> enc::Instruction<'static>, natural_alignment: u32) -> MemInstruction { fn new(
type_: Type,
instruction: fn(MemArg) -> enc::Instruction<'static>,
natural_alignment: u32,
) -> MemInstruction {
MemInstruction { MemInstruction {
type_, instruction, natural_alignment type_,
instruction,
natural_alignment,
} }
} }
} }

View File

@@ -1,7 +1,8 @@
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use parser::Sources; use parser::Sources;
use std::collections::HashSet;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::path::Path; use std::path::{Path, PathBuf};
mod ast; mod ast;
mod constfold; mod constfold;
@@ -22,59 +23,76 @@ impl Options {
} }
} }
pub fn compile_file<P: AsRef<Path>>(path: P, options: Options) -> Result<Vec<u8>> { pub struct CompiledModule {
let path = path.as_ref(); pub wasm: Vec<u8>,
let mut script = ast::Script::default(); pub dependencies: Vec<PathBuf>,
}
let mut sources = Sources::new(); 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 pending_files = vec![(path.to_path_buf(), None)]; let mut sources = Sources::new();
while let Some((path, span)) = pending_files.pop() {
match sources.add(&path) {
Ok((id, true)) => {
let mut new_script = match parser::parse(&sources, id) {
Ok(script) => script,
Err(_) => bail!("Parse failed"),
};
includes::resolve_includes(&mut new_script, &path)?; 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"),
};
for include in std::mem::take(&mut new_script.includes) { includes::resolve_includes(&mut new_script, dependencies, &path)?;
let mut path = path
.parent() for include in std::mem::take(&mut new_script.includes) {
.expect("Script path has no parent") let mut path = path
.to_path_buf(); .parent()
path.push(include.path); .expect("Script path has no parent")
pending_files.push((path, Some(include.span))); .to_path_buf();
path.push(include.path);
pending_files.push((path, Some(include.span)));
}
script.merge(new_script);
} }
Ok((_, false)) => (), // already parsed this include
script.merge(new_script); Err(err) => {
} if let Some(span) = span {
Ok((_, false)) => (), // already parsed this include let _ = typecheck::report_error(&err.to_string(), &span, &sources);
Err(err) => { } else {
if let Some(span) = span { eprintln!("Failed to load script {}: {}", path.display(), err);
let _ = typecheck::report_error(&err.to_string(), &span, &sources); }
} else { bail!("Parse failed");
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(
&script,
&path
.file_stem()
.unwrap_or_else(|| OsStr::new("unknown"))
.to_string_lossy(),
&options,
);
Ok(wasm)
} }
if constfold::fold_script(&mut script, &sources).is_err() { let mut dependencies = HashSet::new();
bail!("Constant folding failed");
} let result = compile_file_inner(path.as_ref(), options, &mut dependencies);
if typecheck::tc_script(&mut script, &sources).is_err() {
bail!("Type check failed"); (result, dependencies.into_iter().collect())
}
let wasm = emit::emit(
&script,
&path
.file_stem()
.unwrap_or_else(|| OsStr::new("unknown"))
.to_string_lossy(),
&options,
);
Ok(wasm)
} }

View File

@@ -15,7 +15,7 @@ fn main() -> Result<()> {
let mut filename = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?; 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)?; wasmparser::validate(&wasm)?;

View File

@@ -79,6 +79,7 @@ enum Token {
Str(String), Str(String),
Int(i32), Int(i32),
Int64(i64), Int64(i64),
IntFloat(i32),
Float(String), Float(String),
Float64(String), Float64(String),
Op(String), Op(String),
@@ -107,6 +108,7 @@ impl fmt::Display for Token {
Token::Str(s) => write!(f, "{:?}", s), Token::Str(s) => write!(f, "{:?}", s),
Token::Int(v) => write!(f, "{}", v), Token::Int(v) => write!(f, "{}", v),
Token::Int64(v) => write!(f, "{}", v), Token::Int64(v) => write!(f, "{}", v),
Token::IntFloat(v) => write!(f, "{}_f", v),
Token::Float(v) => write!(f, "{}", v), Token::Float(v) => write!(f, "{}", v),
Token::Float64(v) => write!(f, "{}", v), Token::Float64(v) => write!(f, "{}", v),
Token::Op(s) => write!(f, "{}", s), Token::Op(s) => write!(f, "{}", s),
@@ -248,23 +250,23 @@ fn report_errors(errors: Vec<Simple<String, Span>>, sources: &Sources) {
type LexerError = Simple<char, Span>; type LexerError = Simple<char, Span>;
fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = LexerError> { fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = LexerError> {
let float64 = text::int(10) let float64 = text::digits(10)
.chain::<char, _, _>(just('.').chain(text::digits(10))) .chain::<char, _, _>(just('.').chain(text::digits(10)))
.then_ignore(just("f64")) .then_ignore(just("f64"))
.collect::<String>() .collect::<String>()
.map(Token::Float64); .map(Token::Float64);
let float = text::int(10) let float = text::digits(10)
.chain::<char, _, _>(just('.').chain(text::digits(10))) .chain::<char, _, _>(just('.').chain(text::digits(10)))
.collect::<String>() .collect::<String>()
.map(Token::Float); .map(Token::Float);
let integer = just::<_, _, LexerError>("0x") let integer = just::<_, _, LexerError>("0x")
.ignore_then(text::int(16)) .ignore_then(text::digits(16))
.try_map(|n, span| { .try_map(|n, span| {
u64::from_str_radix(&n, 16).map_err(|err| LexerError::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| { .or(text::digits(10).try_map(|n: String, span: Span| {
n.parse::<u64>() n.parse::<u64>()
.map_err(|err| LexerError::custom(span, err.to_string())) .map_err(|err| LexerError::custom(span, err.to_string()))
})) }))
@@ -275,24 +277,69 @@ fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = LexerError> {
.then_ignore(just("i64")) .then_ignore(just("i64"))
.map(|n| Token::Int64(n as 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| { let int = integer.try_map(|n, span| {
u32::try_from(n) u32::try_from(n)
.map(|n| Token::Int(n as i32)) .map(|n| Token::Int(n as i32))
.map_err(|err| LexerError::custom(span, err.to_string())) .map_err(|err| LexerError::custom(span, err.to_string()))
}); });
let str_ = just('"') let str_ = just('\\')
.ignore_then(filter(|c| *c != '"').repeated()) .then(any())
.then_ignore(just('"')) .map(|t| vec![t.0, t.1])
.collect::<String>() .or(none_of("\"").map(|c| vec![c]))
.map(Token::Str);
let op = one_of("+-*/%&^|<=>#")
.repeated() .repeated()
.at_least(1) .flatten()
.or(just(':').chain(just('='))) .delimited_by(just('"'), just('"'))
.collect::<String>() .collect::<String>()
.map(Token::Op); .map(|s| Token::Str(parse_string_escapes(s)));
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)
});
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); let ctrl = one_of("(){};,:?!$").map(Token::Ctrl);
@@ -330,28 +377,24 @@ fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = LexerError> {
let comment = single_line.or(multi_line); let comment = single_line.or(multi_line);
let token = float let token = choice((
.or(float64) float, float64, int64, int_float, int, str_, char_, op, ctrl, ident,
.or(int64) ))
.or(int) .recover_with(skip_then_retry_until([]));
.or(str_)
.or(op)
.or(ctrl)
.or(ident)
.recover_with(skip_then_retry_until([]));
token token
.map_with_span(|tok, span| (tok, span)) .map_with_span(|tok, span| (tok, span))
.padded() .padded()
.padded_by(comment.padded().repeated()) .padded_by(comment.padded().repeated())
.repeated() .repeated()
.boxed()
} }
fn map_token<O>( fn map_token<O>(
f: impl Fn(&Token) -> Option<O> + 'static + Clone, f: impl Fn(&Token, &Span) -> Option<O> + 'static + Clone,
) -> impl Parser<Token, O, Error = ScriptError> + Clone { ) -> impl Parser<Token, O, Error = ScriptError> + Clone {
filter_map(move |span, tok: Token| { filter_map(move |span, tok: Token| {
if let Some(output) = f(&tok) { if let Some(output) = f(&tok, &span) {
Ok(output) Ok(output)
} else { } else {
Err(ScriptError::expected_input_found( Err(ScriptError::expected_input_found(
@@ -363,6 +406,33 @@ fn map_token<O>(
}) })
} }
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>; type ScriptError = Simple<Token, Span>;
fn script_parser() -> impl Parser<Token, ast::Script, Error = ScriptError> + Clone { fn script_parser() -> impl Parser<Token, ast::Script, Error = ScriptError> + Clone {
let identifier = filter_map(|span, tok| match tok { let identifier = filter_map(|span, tok| match tok {
@@ -375,23 +445,49 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = ScriptError> + Clo
}) })
.labelled("identifier"); .labelled("identifier");
let integer = map_token(|tok| match tok { let integer = map_token(|tok, _| match tok {
Token::Int(v) => Some(*v), Token::Int(v) => Some(*v),
_ => None, _ => None,
}); });
let string = map_token(|tok| match tok { let string = map_token(|tok, _| match tok {
Token::Str(s) => Some(s.clone()), Token::Str(s) => Some(s.clone()),
_ => None, _ => 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 mut expression_out = None;
let block = recursive(|block| { let block = recursive(|block| {
let mut block_expression = None; let mut block_expression = None;
let expression = recursive(|expression| { 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::Int(v) => Some(ast::Expr::I32Const(*v)),
Token::Int64(v) => Some(ast::Expr::I64Const(*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::Float(v) => Some(ast::Expr::F32Const(v.parse().unwrap())),
Token::Float64(v) => Some(ast::Expr::F64Const(v.parse().unwrap())), Token::Float64(v) => Some(ast::Expr::F64Const(v.parse().unwrap())),
_ => None, _ => None,
@@ -420,6 +516,36 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = ScriptError> + Clo
}) })
.boxed(); .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) let loop_expr = just(Token::Loop)
.ignore_then(identifier) .ignore_then(identifier)
.then(block.clone()) .then(block.clone())
@@ -436,15 +562,25 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = ScriptError> + Clo
block: Box::new(block), block: Box::new(block),
}); });
let if_expr = just(Token::If) let if_expr = recursive::<_, ast::Expr, _, _, _>(|if_expr| {
.ignore_then(expression.clone()) just(Token::If)
.then(block.clone()) .ignore_then(expression.clone())
.then(just(Token::Else).ignore_then(block.clone()).or_not()) .then(block.clone())
.map(|((condition, if_true), if_false)| ast::Expr::If { .then(
condition: Box::new(condition), just(Token::Else)
if_true: Box::new(if_true), .ignore_then(
if_false: if_false.map(Box::new), 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(); let block_expr = loop_expr.or(label_block_expr).or(if_expr).boxed();
@@ -487,16 +623,6 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = ScriptError> + Clo
}) })
.boxed(); .boxed();
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,
})
.boxed();
let select = just(Token::Select) let select = just(Token::Select)
.ignore_then( .ignore_then(
expression expression
@@ -530,29 +656,31 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = ScriptError> + Clo
value: value.map(Box::new), value: value.map(Box::new),
}); });
let atom = val let atom = choice((
.or(function_call) val,
.or(assign) function_call,
.or(local_tee) local_tee,
.or(variable) local_tee_op,
.or(block_expr) variable,
.or(branch) block_expr,
.or(branch_if) branch,
.or(let_) branch_if,
.or(select) let_,
.or(return_) select,
.map_with_span(|expr, span| expr.with_span(span)) return_,
.or(expression ))
.clone() .map_with_span(|expr, span| expr.with_span(span))
.delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')')))) .or(expression
.or(block) .clone()
.recover_with(nested_delimiters( .delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))))
Token::Ctrl('('), .or(block)
Token::Ctrl(')'), .recover_with(nested_delimiters(
[(Token::Ctrl('{'), Token::Ctrl('}'))], Token::Ctrl('('),
|span| ast::Expr::Error.with_span(span), Token::Ctrl(')'),
)) [(Token::Ctrl('{'), Token::Ctrl('}'))],
.boxed(); |span| ast::Expr::Error.with_span(span),
))
.boxed();
let unary_op = just(Token::Op("-".to_string())) let unary_op = just(Token::Op("-".to_string()))
.to(ast::UnaryOp::Negate) .to(ast::UnaryOp::Negate)
@@ -658,16 +786,7 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = ScriptError> + Clo
let op_product = memory_op let op_product = memory_op
.clone() .clone()
.then( .then(product_op.clone().then(memory_op.clone()).repeated())
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(),
)
.foldl(|left, (op, right)| { .foldl(|left, (op, right)| {
let span = (left.span.0, left.span.1.start..right.span.1.end); let span = (left.span.0, left.span.1.start..right.span.1.end);
ast::Expr::BinOp { ast::Expr::BinOp {
@@ -681,13 +800,7 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = ScriptError> + Clo
let op_sum = op_product let op_sum = op_product
.clone() .clone()
.then( .then(sum_op.clone().then(op_product.clone()).repeated())
just(Token::Op("+".to_string()))
.to(ast::BinOp::Add)
.or(just(Token::Op("-".to_string())).to(ast::BinOp::Sub))
.then(op_product.clone())
.repeated(),
)
.foldl(|left, (op, right)| { .foldl(|left, (op, right)| {
let span = (left.span.0, left.span.1.start..right.span.1.end); let span = (left.span.0, left.span.1.start..right.span.1.end);
ast::Expr::BinOp { ast::Expr::BinOp {
@@ -701,14 +814,7 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = ScriptError> + Clo
let op_shift = op_sum let op_shift = op_sum
.clone() .clone()
.then( .then(shift_op.clone().then(op_sum.clone()).repeated())
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(),
)
.foldl(|left, (op, right)| { .foldl(|left, (op, right)| {
let span = (left.span.0, left.span.1.start..right.span.1.end); let span = (left.span.0, left.span.1.start..right.span.1.end);
ast::Expr::BinOp { ast::Expr::BinOp {
@@ -750,14 +856,7 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = ScriptError> + Clo
let op_bit = op_cmp let op_bit = op_cmp
.clone() .clone()
.then( .then(bit_op.clone().then(op_cmp.clone()).repeated())
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(),
)
.foldl(|left, (op, right)| { .foldl(|left, (op, right)| {
let span = (left.span.0, left.span.1.start..right.span.1.end); let span = (left.span.0, left.span.1.start..right.span.1.end);
ast::Expr::BinOp { ast::Expr::BinOp {
@@ -791,11 +890,58 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = ScriptError> + Clo
let block_expression = block_expression.unwrap(); 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 block_expression
.clone() .clone()
.then(just(Token::Ctrl(';')).or_not()) .then(just(Token::Ctrl(';')).or_not())
.map_with_span(|(expr, semi), span| (expr.with_span(span), semi.is_none())) .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() .repeated()
.then(expression.clone().or_not()) .then(expression.clone().or_not())
.map_with_span(|(mut statements, mut final_expression), span| { .map_with_span(|(mut statements, mut final_expression), span| {

15
test/xorshift.cwa Normal file
View 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
}