Compare commits

...

2 Commits

Author SHA1 Message Date
5f316cf17d add new features to readme 2022-02-26 12:14:31 +01:00
e608d3bb4b implement const 2022-02-26 11:46:38 +01:00
8 changed files with 274 additions and 91 deletions

View File

@@ -56,6 +56,14 @@ 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:
@@ -138,6 +146,18 @@ 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:
@@ -296,15 +316,65 @@ non-zero integer.
#### Memory load/store
To read from memory you specify a memory location as `base?offset` or `base!offset`. `?` reads a byte and `!` reads a 32bit word.
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 = expressoin` and `base!offset = expression`.
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`, `i32.store8` and `i32.store`. Other WASM load/store instructions will be implemented as intrinsics, but aren't yet.
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

View File

@@ -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,

13
examples/microw8/uw8.cwa Normal file
View 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;

View File

@@ -8,7 +8,8 @@ pub struct Script {
pub global_vars: Vec<GlobalVar>,
pub functions: Vec<Function>,
pub data: Vec<Data>,
pub includes: Vec<Include>
pub includes: Vec<Include>,
pub consts: Vec<GlobalConst>,
}
impl Script {
@@ -17,6 +18,7 @@ impl Script {
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());
}
}
@@ -27,7 +29,8 @@ pub enum TopLevelItem {
GlobalVar(GlobalVar),
Function(Function),
Data(Data),
Include(Include)
Include(Include),
Const(GlobalConst),
}
#[derive(Debug)]
@@ -67,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,
@@ -170,7 +181,7 @@ pub enum DataType {
F64,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct MemoryLocation {
pub span: Span,
pub size: MemSize,
@@ -178,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,
@@ -213,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>,
@@ -355,7 +373,7 @@ pub enum BinOp {
pub enum MemSize {
Byte,
Word,
Float
Float,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]

View File

@@ -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))
@@ -90,8 +158,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 +305,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 +341,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!(),
}

View File

@@ -1,7 +1,7 @@
use anyhow::{bail, Result};
use parser::Sources;
use std::ffi::OsStr;
use std::path::Path;
use parser::Sources;
mod ast;
mod constfold;
@@ -36,15 +36,18 @@ pub fn compile_file<P: AsRef<Path>>(path: P, options: Options) -> Result<Vec<u8>
Ok(script) => script,
Err(_) => bail!("Parse failed"),
};
includes::resolve_includes(&mut new_script, &path)?;
for include in std::mem::take(&mut new_script.includes) {
let mut path = path.parent().expect("Script path has no parent").to_path_buf();
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
@@ -59,8 +62,9 @@ pub fn compile_file<P: AsRef<Path>>(path: P, options: Options) -> Result<Vec<u8>
}
}
constfold::fold_script(&mut script);
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");
}

View File

@@ -925,6 +925,21 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = ScriptError> + Clo
})
.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))
@@ -975,17 +990,17 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = ScriptError> + Clo
|path, span| ast::TopLevelItem::Include(ast::Include { span, path }),
));
import.or(function).or(global).or(data).or(include).boxed()
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(),
includes: Vec::new(),
};
let mut script = ast::Script::default();
for item in items {
match item {
ast::TopLevelItem::Import(i) => script.imports.push(i),
@@ -993,6 +1008,7 @@ fn script_parser() -> impl Parser<Token, ast::Script, Error = ScriptError> + Clo
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

View File

@@ -104,6 +104,17 @@ pub fn tc_script(script: &mut ast::Script, sources: &Sources) -> 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) {
@@ -298,7 +309,7 @@ impl LocalVars {
}
}
fn report_duplicate_definition(
pub fn report_duplicate_definition(
msg: &str,
span: &Span,
prev_span: &Span,