Add initial WASM compiler framework (compiler incomplete)

This commit is contained in:
NGnius (Graham) 2024-09-06 11:14:31 -04:00
parent f477673ebb
commit f7be1ed33f
23 changed files with 1191 additions and 10 deletions

219
Cargo.lock generated
View file

@ -2,12 +2,107 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "anstream"
version = "0.6.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
[[package]]
name = "anstyle-parse"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
dependencies = [
"anstyle",
"windows-sys",
]
[[package]]
name = "beef"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
[[package]]
name = "clap"
version = "4.5.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
[[package]]
name = "colorchoice"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
[[package]]
name = "diff"
version = "0.1.13"
@ -20,12 +115,30 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "leb128"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
[[package]]
name = "logos"
version = "0.14.0"
@ -62,6 +175,10 @@ dependencies = [
[[package]]
name = "muss2"
version = "2.0.0-alpha0"
dependencies = [
"clap",
"muss2-wasm",
]
[[package]]
name = "muss2-lang"
@ -71,6 +188,14 @@ dependencies = [
"pretty_assertions",
]
[[package]]
name = "muss2-wasm"
version = "2.0.0-alpha0"
dependencies = [
"muss2-lang",
"wasm-encoder",
]
[[package]]
name = "pretty_assertions"
version = "1.4.0"
@ -105,6 +230,12 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.55"
@ -122,6 +253,94 @@ version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "wasm-encoder"
version = "0.212.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501940df4418b8929eb6d52f1aade1fdd15a5b86c92453cb696e3c906bd3fc33"
dependencies = [
"leb128",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "yansi"
version = "0.5.1"

View file

@ -11,8 +11,11 @@ readme = "README.md"
[dependencies]
clap = { version = "4", features = [ "derive" ] }
muss2-wasm = { version = "2.0.0-alpha0", path = "./crates/wasm" }
[workspace]
members = [
"crates/lang"
"crates/lang",
"crates/wasm"
]

View file

@ -10,7 +10,7 @@ mod test {
use pretty_assertions::assert_eq;
const ALL_TOKENS_STR: &str = "u n + - * / && || => x> ~> = . n_u_ :: is_a_ :: VaR1AbLe ( ) { } : ; -12345 12345.6789 \"char[]\" /* long\ncomment */ // short comment \n \n <>";
const ALL_TOKENS_STR: &str = "u n + - * / && || => x> ~> = . n_u_ :: is_a_ :: VaR1AbLe ( ) { } : ; -12345 12345.6789 \"char[]\" /* long\ncomment */ // short comment \n \n <> ! | & << >> yield use as";
#[test]
fn parse_everything() {
@ -46,6 +46,14 @@ mod test {
Token::ShortComment((" short comment ".into(), TokenInfo { line: 1, column: 11..29, index: 115..133 })),
Token::Newline(TokenInfo { line: 2, column: 1..2, index: 134..135 }),
Token::Generate(TokenInfo { line: 3, column: 1..3, index: 136..138 }),
Token::Not(TokenInfo { line: 3, column: 4..5, index: 139..140 }),
Token::BitwiseOr(TokenInfo { line: 3, column: 6..7, index: 141..142 }),
Token::BitwiseAnd(TokenInfo { line: 3, column: 8..9, index: 143..144 }),
Token::BitwiseShiftLeft(TokenInfo { line: 3, column: 10..12, index: 145..147 }),
Token::BitwiseShiftRight(TokenInfo { line: 3, column: 13..15, index: 148..150 }),
Token::Yield(TokenInfo { line: 3, column: 16..21, index: 151..156 }),
Token::Use(TokenInfo { line: 3, column: 23..26, index: 158..161 }),
Token::As(TokenInfo { line: 3, column: 28..30, index: 163..165 }),
];
let mut actual = Vec::new();

View file

@ -64,6 +64,20 @@ pub enum Token {
And(TokenInfo),
#[token("||", callback = all_cb)]
Or(TokenInfo),
#[token("!", callback = all_cb)]
Not(TokenInfo),
// Bitwise operations
#[token("|", callback = all_cb)]
BitwiseOr(TokenInfo),
#[token("&", callback = all_cb)]
BitwiseAnd(TokenInfo),
#[token("<<", callback = all_cb)]
BitwiseShiftLeft(TokenInfo),
#[token(">>", callback = all_cb)]
BitwiseShiftRight(TokenInfo),
// State operations
#[token("yield", priority = 99, callback = all_cb)]
Yield(TokenInfo),
// Functional
#[token("=>", callback = all_cb)]
@ -77,7 +91,6 @@ pub enum Token {
// Declarations
// Basics
#[token("=", callback = all_cb)]
Equal(TokenInfo),
@ -114,6 +127,12 @@ pub enum Token {
#[regex("\\/\\/[^\n]*\n", priority = 1, callback = oneline_comment_cb)]
ShortComment((String, TokenInfo)),
// Imports
#[token("use", callback = all_cb)]
Use(TokenInfo),
#[token("as", callback = all_cb)]
As(TokenInfo),
/// Ignore
#[regex(r"\n", newline_cb)]
Newline(TokenInfo),
@ -211,6 +230,12 @@ impl Token {
Self::Divide(_) => write!(result, "/"),
Self::And(_) => write!(result, "&&"),
Self::Or(_) => write!(result, "||"),
Self::Not(_) => write!(result, "!"),
Self::BitwiseOr(_) => write!(result, "|"),
Self::BitwiseAnd(_) => write!(result, "&"),
Self::BitwiseShiftLeft(_) => write!(result, "<<"),
Self::BitwiseShiftRight(_) => write!(result, ">>"),
Self::Yield(_) => write!(result, "yield"),
Self::Map(_) => write!(result, "=>"),
Self::Filter(_) => write!(result, "x>"),
Self::Sort(_) => write!(result, "~>"),
@ -230,6 +255,8 @@ impl Token {
Self::String((s, _)) => write!(result, "\"{}\"", s),
Self::LongComment((c, _)) => write!(result, "/*{}*/", c),
Self::ShortComment((c, _)) => write!(result, "//{}\n", c),
Self::Use(_) => write!(result, "use"),
Self::As(_) => write!(result, "as"),
Self::Newline(_) => write!(result, "\n"),
}
}

View file

@ -110,6 +110,8 @@ pub struct Module {
/// SetOp -> n
/// SetOp -> u
/// UnaryOp -> -
/// UnaryOp -> !
/// UnaryOp -> yield
/// Literal -> "Name"
/// Literal -> Integer
/// Literal -> Float

View file

@ -6,7 +6,7 @@ mod parser;
pub(crate) use parser::TokenParser;
mod tokens;
pub use tokens::{SyntaxToken, Token, Literal, Op, Functional, Field, Path, Comment};
pub use tokens::{SyntaxToken, Token, Literal, Op, Functional, Field, Path, Comment, Import};
#[cfg(test)]
mod test {
@ -14,7 +14,7 @@ mod test {
use pretty_assertions::assert_eq;
const ALL_TOKENS_STR: &str = "u n + - * / && || => x> ~> <> = n_u_::is_a_::VaR1AbLe .th1s.is_A_.f13Ld ( ) { } ; -404 -1234.5 \"\" /* block comment */ // line comment \n";
const ALL_TOKENS_STR: &str = "u n + - * / && || ! & | >> << yield => x> ~> <> = n_u_::is_a_::VaR1AbLe .th1s.is_A_.f13Ld ( ) { } ; -404 -1234.5 \"\" /* block comment */ // line comment \n use as";
#[test]
fn parse_everything() {
@ -27,6 +27,12 @@ mod test {
Token::Operation(Op::Divide),
Token::Operation(Op::And),
Token::Operation(Op::Or),
Token::Operation(Op::Not),
Token::Operation(Op::BitwiseAnd),
Token::Operation(Op::BitwiseOr),
Token::Operation(Op::ShiftRight),
Token::Operation(Op::ShiftLeft),
Token::Operation(Op::Yield),
Token::Functional(Functional::Map),
Token::Functional(Functional::Filter),
Token::Functional(Functional::Sort),

View file

@ -1,4 +1,4 @@
pub(crate) struct TokenParser<'a, I: core::iter::Iterator<Item=Result<crate::lexer::Token, crate::lexer::LexError>> + 'a> {
pub struct TokenParser<'a, I: core::iter::Iterator<Item=Result<crate::lexer::Token, crate::lexer::LexError>> + 'a> {
_idc: core::marker::PhantomData<&'a ()>,
iter: I,
lookahead: Option<crate::lexer::Token>,
@ -64,6 +64,12 @@ impl <'a, I: core::iter::Iterator<Item=Result<crate::lexer::Token, crate::lexer:
crate::lexer::Token::Divide(info) => super::Token::Operation(super::Op::Divide).with(info),
crate::lexer::Token::And(info) => super::Token::Operation(super::Op::And).with(info),
crate::lexer::Token::Or(info) => super::Token::Operation(super::Op::Or).with(info),
crate::lexer::Token::Not(info) => super::Token::Operation(super::Op::Not).with(info),
crate::lexer::Token::BitwiseAnd(info) => super::Token::Operation(super::Op::BitwiseAnd).with(info),
crate::lexer::Token::BitwiseOr(info) => super::Token::Operation(super::Op::BitwiseOr).with(info),
crate::lexer::Token::BitwiseShiftLeft(info) => super::Token::Operation(super::Op::ShiftLeft).with(info),
crate::lexer::Token::BitwiseShiftRight(info) => super::Token::Operation(super::Op::ShiftRight).with(info),
crate::lexer::Token::Yield(info) => super::Token::Operation(super::Op::Yield).with(info),
crate::lexer::Token::Map(info) => super::Token::Functional(super::Functional::Map).with(info),
crate::lexer::Token::Filter(info) => super::Token::Functional(super::Functional::Filter).with(info),
crate::lexer::Token::Sort(info) => super::Token::Functional(super::Functional::Sort).with(info),
@ -174,6 +180,8 @@ impl <'a, I: core::iter::Iterator<Item=Result<crate::lexer::Token, crate::lexer:
crate::lexer::Token::String((s, info)) => super::Token::Literal(super::Literal::String(s)).with(info),
crate::lexer::Token::LongComment((c, info)) => super::Token::Comment(super::tokens::Comment::Block(c)).with(info),
crate::lexer::Token::ShortComment((c, info)) => super::Token::Comment(super::tokens::Comment::Line(c)).with(info),
crate::lexer::Token::Use(info) => super::Token::Import(super::tokens::Import::Use).with(info),
crate::lexer::Token::As(info) => super::Token::Import(super::tokens::Import::As).with(info),
crate::lexer::Token::Newline(_) => panic!("Got non-ignored newline"),
};
Some(Ok(translated))

View file

@ -22,6 +22,7 @@ pub enum Token {
Literal(Literal),
Comment(Comment),
Import(Import),
}
impl Token {
@ -30,12 +31,24 @@ impl Token {
}
}
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Field(pub Vec<String>);
#[derive(Debug, PartialEq, Clone)]
impl Field {
pub fn as_str(&self) -> String {
format!(".{}", self.0.join("."))
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Path(pub Vec<String>);
impl Path {
pub fn as_str(&self) -> String {
self.0.join("::")
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum Op {
// Set operations
@ -49,6 +62,14 @@ pub enum Op {
// Logical operations
And,
Or,
Not,
// Bitwise operations
BitwiseAnd,
BitwiseOr,
ShiftLeft,
ShiftRight,
// State operations
Yield,
}
impl Op {
@ -62,6 +83,12 @@ impl Op {
Self::Divide => "/",
Self::And => "&&",
Self::Or => "||",
Self::Not => "!",
Self::BitwiseAnd => "&",
Self::BitwiseOr => "|",
Self::ShiftLeft => "<<",
Self::ShiftRight => ">>",
Self::Yield => "yield",
}
}
}
@ -117,6 +144,21 @@ impl Comment {
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum Import {
Use,
As,
}
impl Import {
pub fn as_str(&self) -> &'static str {
match self {
Self::Use => "use",
Self::As => "as",
}
}
}
impl Token {
pub fn write_str(&self, result: &mut String) -> std::fmt::Result {
use core::fmt::Write;
@ -147,6 +189,7 @@ impl Token {
Self::Semicolon => write!(result, ";"),
Self::Literal(l) => write!(result, "{}", l.as_str()),
Self::Comment(c) => write!(result, "{}", c.as_str()),
Self::Import(i) => write!(result, "{}", i.as_str()),
}
}
}

9
crates/wasm/Cargo.toml Normal file
View file

@ -0,0 +1,9 @@
[package]
name = "muss2-wasm"
version = "2.0.0-alpha0"
edition = "2021"
[dependencies]
wasm-encoder = { version = "0.212" }
muss2-lang = { version = "2.0.0-alpha0", path = "../lang" }

View file

@ -0,0 +1,89 @@
use crate::symbols::{Symbols, FunctionType, Types, Locals};
pub fn generate_function(func: &FunctionType, symbols: &Symbols, types: &Types) -> Result<(wasm_encoder::FuncType, wasm_encoder::Function), Vec<super::CodegenError>> {
match func.decl().type_ {
muss2_lang::syntax::Functional::Map => generate_map_function(func, symbols, types),
muss2_lang::syntax::Functional::Filter => todo!(),
muss2_lang::syntax::Functional::Sort => todo!(),
muss2_lang::syntax::Functional::Generate => todo!(),
}
}
pub fn generate_map_function(func: &FunctionType, symbols: &Symbols, types: &Types) -> Result<(wasm_encoder::FuncType, wasm_encoder::Function), Vec<super::CodegenError>> {
let mut errors = Vec::new();
// transform parameters to wasm representation
let mut params_ty = Vec::new();
for (i, param) in func.decl().params.iter().enumerate() {
if let Some(param_ty) = &param.type_ {
if let Some(struct_index) = symbols.structs().nmap().get(&param.name) {
let param = wasm_encoder::ValType::Ref(wasm_encoder::RefType {
nullable: false,
heap_type: wasm_encoder::HeapType::Concrete(*struct_index as u32),
});
params_ty.push(param.clone());
} else {
errors.push(super::CodegenError::ParamTypeNotFound {
param_i: i,
name: param.name.clone(),
type_: param_ty.clone(),
func: func.decl().name.clone(),
})
}
} else {
errors.push(super::CodegenError::ParamTypeNotKnowable {
param_i: i,
name: param.name.clone(),
func: func.decl().name.clone(),
});
}
}
if !errors.is_empty() {
return Err(errors)
}
// TODO transform ops to wasm representation
let mut results_ty: Option<(muss2_lang::syntax::Path, wasm_encoder::ValType)> = None;
let mut locals = Locals::new(func.decl().name.clone());
let mut ops_meta = Vec::new();
for (i, op) in func.decl().ops.iter().enumerate() {
let result = super::ops::generate_op(op, symbols, &mut locals, types);
match result {
Ok(instr) => {
if instr.is_return {
if let Some(return_type_exp) = &results_ty {
if !(&return_type_exp.0 == instr.type_.code_ty() && &return_type_exp.1 == instr.type_.wasm_ty()) {
errors.push(super::CodegenError::ReturnTypeNotConsistent {
return_i: i,
func: func.decl().name.clone(),
type_exp: return_type_exp.0.clone(),
type_real: instr.type_.code_ty().to_owned(),
})
}
} else {
results_ty = Some((instr.type_.code_ty().to_owned(), instr.type_.wasm_ty().to_owned()));
}
}
ops_meta.push(instr);
},
Err(mut e) => errors.append(&mut e),
}
}
if !errors.is_empty() {
return Err(errors);
}
if let Some(results_ty) = results_ty {
let functype = wasm_encoder::FuncType::new(params_ty, [results_ty.1]);
let mut func = wasm_encoder::Function::new_with_locals_types(
ops_meta.iter()
.map(|m| m.type_.wasm_ty().to_owned())
);
for instrs in ops_meta.iter() {
for instr in instrs.instr.iter() {
func.instruction(&instr);
}
}
Ok((functype, func))
} else {
errors.push(super::CodegenError::ReturnTypeNotKnowable { func: func.decl().name.clone() });
Err(errors)
}
}

View file

@ -0,0 +1,94 @@
mod function;
mod ops;
mod structs;
#[derive(Debug, PartialEq, Clone)]
pub enum CodegenError {
ParamTypeNotFound {
param_i: usize,
name: muss2_lang::syntax::Path,
type_: muss2_lang::syntax::Path,
func: muss2_lang::syntax::Path,
},
ParamTypeNotKnowable {
param_i: usize,
name: muss2_lang::syntax::Path,
func: muss2_lang::syntax::Path,
},
ReturnTypeNotConsistent {
return_i: usize,
func: muss2_lang::syntax::Path,
type_exp: muss2_lang::syntax::Path,
type_real: muss2_lang::syntax::Path,
},
ReturnTypeNotKnowable {
func: muss2_lang::syntax::Path,
},
AssignUndeclared {
func: muss2_lang::syntax::Path,
name: muss2_lang::syntax::Path,
type_: muss2_lang::syntax::Path,
},
DeclareUntyped {
func: muss2_lang::syntax::Path,
name: muss2_lang::syntax::Path,
},
FieldTypeNotKnowable {
param_i: usize,
name: muss2_lang::syntax::Path,
struc: muss2_lang::syntax::Path,
},
UnknownType {
func: muss2_lang::syntax::Path,
type_: muss2_lang::syntax::Path,
}
}
impl core::fmt::Display for CodegenError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ParamTypeNotFound { param_i, name, type_, func } => write!(f, "function `{}` param `{}`#{} type `{}` does not exist", func.as_str(), name.as_str(), param_i, type_.as_str()),
Self::ParamTypeNotKnowable { param_i, name, func } => write!(f, "function `{}` param `{}`#{} type is unknown", func.as_str(), name.as_str(), param_i),
Self::ReturnTypeNotConsistent { return_i, func, type_exp, type_real } => write!(f, "function `{}` returned `{}`#{} but expected `{}`", func.as_str(), type_real.as_str(), return_i, type_exp.as_str()),
Self::ReturnTypeNotKnowable { func } => write!(f, "function `{}` return type is unknown", func.as_str()),
Self::AssignUndeclared { func, name, type_ } => write!(f, "function `{}` assigns `{}` but `{}` was never declared", func.as_str(), type_.as_str(), name.as_str()),
Self::DeclareUntyped { func, name } => write!(f, "function `{}` declares `{}` but type is unknown", func.as_str(), name.as_str()),
Self::FieldTypeNotKnowable { param_i, name, struc } => write!(f, "struc `{}` field `{}`#{} type is unknown", struc.as_str(), name.as_str(), param_i),
Self::UnknownType { func, type_ } => write!(f, "function `{}` uses unknown type `{}`", func.as_str(), type_.as_str()),
}
}
}
pub fn generate(symbols: &crate::symbols::Symbols, module: &mut wasm_encoder::Module) -> Result<(), Vec<CodegenError>> {
let mut errors = Vec::new();
let mut types = crate::symbols::Types::new();
let mut struct_table = wasm_encoder::TypeSection::new();
for structure in symbols.structs().symbols() {
let result = structs::generate_struct(structure, symbols, &types);
match result {
Ok((fields, tys)) => {
struct_table.struct_(fields);
tys.into_iter().for_each(|ty| types.push(ty));
},
Err(mut e) => {
types.push(crate::symbols::TypeDecl::new_lazy_declared(structure.decl().name.clone(), types.symbols().len()));
errors.append(&mut e);
}
}
}
let mut fn_table = wasm_encoder::TypeSection::new();
for function in symbols.functions().symbols() {
let result = function::generate_function(function, symbols, &types);
match result {
Ok(func) => {
fn_table.func_type(&func.0);
},
Err(mut e) => {
fn_table.func_type(&wasm_encoder::FuncType::new([], []));
errors.append(&mut e);
}
}
}
module.section(&fn_table);
todo!()
}

View file

@ -0,0 +1,63 @@
use muss2_lang::statement::Op;
use crate::symbols::{LocalDecl, Locals, Symbols, Types};
// Assumption: the 'result' of an instruction is the first/latest item on the stack
pub struct OpInstruction<'a> {
pub instr: Vec<wasm_encoder::Instruction<'a>>,
pub type_: LocalDecl,
pub is_return: bool,
pub is_assign: bool,
}
pub fn generate_op<'a>(op: &Op, symbols: &Symbols, locals: &mut Locals, types: &Types) -> Result<OpInstruction<'a>, Vec<super::CodegenError>> {
match op {
Op::DeclareAssign(da) => {
let mut op_instr = generate_op(&da.op, symbols, locals, types)?;
locals.push(LocalDecl::new(op_instr.type_.wasm_ty().clone(), da.var.clone(), op_instr.type_.code_ty().clone()));
op_instr.instr.push(wasm_encoder::Instruction::LocalSet(locals.by_name(&da.var).unwrap() as _));
op_instr.is_assign = true;
Ok(op_instr)
},
Op::Declare(d) => {
if let Some(type_) = &d.type_ {
let type_i = types.by_name(type_).ok_or_else(|| vec![
super::CodegenError::UnknownType {
func: locals.func().clone(),
type_: type_.clone(),
}
])?;
let type_decl = &types.symbols()[type_i];
let decl = LocalDecl::new(type_decl.wasm_ty().to_owned(), d.var.clone(), type_.to_owned());
locals.push(decl.clone());
Ok(OpInstruction {
instr: Vec::with_capacity(0),
type_: decl,
is_return: false,
is_assign: false,
})
} else {
Err(vec![
super::CodegenError::DeclareUntyped {
func: locals.func().clone(),
name: d.var.clone(),
}
])
}
},
Op::Assign(a) => {
let mut op_instr = generate_op(&a.op, symbols, locals, types)?;
let local_index = locals.by_name(&a.var).ok_or_else(|| vec![
super::CodegenError::AssignUndeclared {
func: locals.func().clone(),
name: a.var.clone(),
type_: op_instr.type_.code_ty().clone(),
}
])?;
op_instr.instr.push(wasm_encoder::Instruction::LocalSet(local_index as _));
op_instr.is_assign = true;
Ok(op_instr)
},
_ => todo!("finish compiler op generation")
}
}

View file

@ -0,0 +1,50 @@
use crate::symbols::{Symbols, StructType, TypeDecl, Types, TypeVariant};
pub fn generate_struct(strct: &StructType, _symbols: &Symbols, types: &Types) -> Result<(Vec<wasm_encoder::FieldType>, Vec<TypeDecl>), Vec<super::CodegenError>> {
let mut errors = Vec::new();
let mut wasm_fields = Vec::new();
let mut code_fields = std::collections::HashMap::new();
let mut undeclared_tys = Vec::new();
for (i, field) in strct.decl().params.iter().enumerate() {
if let Some(field_ty) = &field.type_ {
if let Some(field_ty_decl_i) = types.by_name(field_ty) {
// field type is already declared, no need to re-declare it
let field_decl = &types.symbols()[field_ty_decl_i];
wasm_fields.push(wasm_encoder::FieldType {
element_type: wasm_encoder::StorageType::Val(field_decl.wasm_ty().to_owned()),
mutable: true,
});
code_fields.insert(field.name.clone(), field_decl.to_owned());
} else {
// field type is still undeclared, create a lazy version of it
let lazy_i = types.symbols().len() + undeclared_tys.len() - 1;
let lazy_ty = TypeDecl::new_lazy_declared(field_ty.clone(), lazy_i);
wasm_fields.push(wasm_encoder::FieldType {
element_type: wasm_encoder::StorageType::Val(lazy_ty.wasm_ty().to_owned()),
mutable: true,
});
code_fields.insert(field.name.clone(), lazy_ty.clone());
undeclared_tys.push(lazy_ty);
}
} else {
errors.push(super::CodegenError::FieldTypeNotKnowable {
param_i: i, name: field.name.clone(), struc: strct.decl().name.clone() })
}
}
if !errors.is_empty() {
// this struct will be invalid since it has missing fields
return Err(errors);
}
let mut all_new_tys = undeclared_tys; // rename because we're now using it for a fully declared type
let val_ty = wasm_encoder::ValType::Ref(wasm_encoder::RefType {
nullable: false,
heap_type: wasm_encoder::HeapType::Concrete((types.symbols().len() + all_new_tys.len() - 1) as _),
});
let struct_ty = TypeDecl::new(strct.decl().name.clone(), TypeVariant::Structure {
fields: code_fields,
wasm_fields: wasm_fields.clone(),
}, val_ty);
// put all new types in one vector to return
all_new_tys.push(struct_ty);
Ok((wasm_fields, all_new_tys))
}

16
crates/wasm/src/errors.rs Normal file
View file

@ -0,0 +1,16 @@
#[derive(Debug, PartialEq, Clone)]
pub enum CompileError {
Language(muss2_lang::statement::LanguageError),
Symbol(crate::symbols::SymbolError),
Codegen(crate::codegen::CodegenError),
}
impl core::fmt::Display for CompileError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Language(lang) => write!(f, "{}", lang),
Self::Symbol(sym) => write!(f, "{}", sym),
Self::Codegen(cg) => write!(f, "{}", cg),
}
}
}

36
crates/wasm/src/lib.rs Normal file
View file

@ -0,0 +1,36 @@
mod errors;
pub use errors::CompileError;
mod codegen;
mod symbols;
pub fn compile(s: &str) -> Result<wasm_encoder::Module, Vec<CompileError>> {
let mut errors = Vec::new();
let lang: Vec<muss2_lang::statement::Statement> = muss2_lang::statement::LanguageParser::lex(s).filter_map(|res| match res {
Ok(statement) => Some(statement),
Err(e) => {
errors.push(CompileError::Language(e));
None
}
}).collect();
if !errors.is_empty() {
return Err(errors);
}
let symbol_tbl = match symbols::Symbols::build_table(&mut lang.iter()) {
Ok(symbols) => symbols,
Err(e) => {
e.into_iter().for_each(|e| errors.push(CompileError::Symbol(e)));
return Err(errors);
}
};
let mut module = wasm_encoder::Module::new();
// compile
if let Err(e) = codegen::generate(&symbol_tbl, &mut module) {
e.into_iter().for_each(|e| errors.push(CompileError::Codegen(e)));
}
if errors.is_empty() {
Ok(module)
} else {
Err(errors)
}
}

View file

@ -0,0 +1,52 @@
use std::collections::HashMap;
pub struct FunctionType {
decl: muss2_lang::statement::DeclareFun,
module: muss2_lang::syntax::Path,
}
impl FunctionType {
pub fn with_decl(decl: muss2_lang::statement::DeclareFun, module: &super::modules::ModuleDecl) -> Self {
Self {
decl,
module: module.as_root_path().to_owned(),
}
}
pub fn decl(&self) -> &'_ muss2_lang::statement::DeclareFun {
&self.decl
}
pub fn module(&self) -> &'_ muss2_lang::syntax::Path {
&self.module
}
}
pub struct Functions {
symbols: Vec<FunctionType>,
name_map: HashMap<muss2_lang::syntax::Path, usize>,
}
impl Functions {
pub fn with_decls(iter: &mut impl core::iter::Iterator<Item=FunctionType>) -> Self {
let symbols: Vec<_> = iter.collect();
let name_map = symbols.iter().enumerate().map(|(i, item)| {
let mut name = item.module.clone();
name.0.append(&mut item.decl.name.0.clone());
(name, i)
}).collect();
Self {
symbols,
name_map,
}
}
pub fn symbols(&self) -> &'_ Vec<FunctionType> {
&self.symbols
}
pub fn nmap(&self) -> &'_ HashMap<muss2_lang::syntax::Path, usize> {
&self.name_map
}
}

View file

@ -0,0 +1,75 @@
use std::collections::HashMap;
#[derive(Clone)]
pub struct LocalDecl {
wasm_type: wasm_encoder::ValType,
name: muss2_lang::syntax::Path,
type_: muss2_lang::syntax::Path,
}
impl LocalDecl {
pub fn new(wasm_ty: wasm_encoder::ValType, name: muss2_lang::syntax::Path, code_ty: muss2_lang::syntax::Path) -> Self {
Self {
wasm_type: wasm_ty,
name,
type_: code_ty,
}
}
pub fn code_ty(&self) -> &'_ muss2_lang::syntax::Path {
&self.type_
}
pub fn code_name(&self) -> &'_ muss2_lang::syntax::Path {
&self.name
}
pub fn wasm_ty(&self) -> &'_ wasm_encoder::ValType {
&self.wasm_type
}
}
pub struct Locals {
symbols: Vec<LocalDecl>,
name_map: HashMap<muss2_lang::syntax::Path, usize>,
function: muss2_lang::syntax::Path,
}
impl Locals {
pub fn new(func: muss2_lang::syntax::Path) -> Self {
Self {
symbols: Vec::new(),
name_map: HashMap::new(),
function: func,
}
}
pub fn symbols(&self) -> &'_ Vec<LocalDecl> {
&self.symbols
}
pub fn nmap(&self) -> &'_ HashMap<muss2_lang::syntax::Path, usize> {
&self.name_map
}
pub fn func(&self) -> &'_ muss2_lang::syntax::Path {
&self.function
}
pub fn push(&mut self, decl: LocalDecl) {
if let Some(&existing_entry) = self.name_map.get(&decl.name) {
let existing_decl = &self.symbols[existing_entry];
if existing_decl.wasm_type == decl.wasm_type {
// an existing variable is getting re-declared with the same type and name
// -- no need to create another local variable for it
return;
}
}
self.name_map.insert(decl.name.clone(), self.symbols.len());
self.symbols.push(decl);
}
pub fn by_name(&self, name: &muss2_lang::syntax::Path) -> Option<usize> {
self.name_map.get(name).map(|x| *x)
}
}

View file

@ -0,0 +1,88 @@
mod functions;
pub use functions::{FunctionType, Functions};
mod modules;
pub use modules::{ModuleDecl, Modules};
mod structs;
pub use structs::{StructType, Structs};
mod locals;
pub use locals::{LocalDecl, Locals};
mod types;
pub use types::{TypeDecl, TypeVariant, Types};
#[derive(Debug, PartialEq, Clone)]
pub enum SymbolError {
DuplicateType {
name: muss2_lang::syntax::Path,
module: muss2_lang::syntax::Path,
}
}
impl core::fmt::Display for SymbolError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::DuplicateType { name, module } => write!(f, "duplicate type `{}` in module `{}`", name.as_str(), module.as_str())
}
}
}
pub struct Symbols {
functions: functions::Functions,
structs: structs::Structs,
modules: modules::Modules,
}
impl Symbols {
pub fn build_table<'a>(iter: &mut impl core::iter::Iterator<Item=&'a muss2_lang::statement::Statement>) -> Result<Self, Vec<SymbolError>> {
let module = modules::ModuleDecl::root();
Self::build_submodule(iter, &module)
}
fn build_submodule<'a>(iter: &mut impl core::iter::Iterator<Item=&'a muss2_lang::statement::Statement>, module: &modules::ModuleDecl) -> Result<Self, Vec<SymbolError>> {
let mut function_decls = Vec::new();
let mut struct_decls = Vec::new();
let mut module_decls = Vec::new();
let mut errors = Vec::new();
for statement in iter {
match statement {
muss2_lang::statement::Statement::Declare(muss2_lang::statement::Declare::Function(fn_decl)) => {
function_decls.push(functions::FunctionType::with_decl(fn_decl.to_owned(), module));
},
muss2_lang::statement::Statement::Declare(muss2_lang::statement::Declare::Type(ty_decl)) => {
struct_decls.push(structs::StructType::with_decl(ty_decl.to_owned(), module));
},
muss2_lang::statement::Statement::Module(submodule) => {
let submodule_decl = modules::ModuleDecl::from_decl(submodule.to_owned(), module);
let subsymbols_res = Self::build_submodule(&mut submodule.inner.iter(), module);
match subsymbols_res {
Ok(subsymbols) => module_decls.push((submodule_decl, subsymbols)),
Err(mut e) => errors.append(&mut e),
}
}
_ => {},
}
}
if errors.is_empty() {
Err(errors)
} else {
Ok(Self {
functions: functions::Functions::with_decls(&mut function_decls.into_iter()),
structs: structs::Structs::with_decls(&mut struct_decls.into_iter()),
modules: modules::Modules::with_decls(&mut module_decls.into_iter()),
})
}
}
pub fn functions(&self) -> &'_ Functions {
&self.functions
}
pub fn modules(&self) -> &'_ Modules {
&self.modules
}
pub fn structs(&self) -> &'_ Structs {
&self.structs
}
}

View file

@ -0,0 +1,36 @@
use std::collections::HashMap;
pub struct ModuleDecl(muss2_lang::syntax::Path);
impl ModuleDecl {
pub fn from_decl(decl: muss2_lang::statement::Module, module: &Self) -> Self {
let mut path = module.0.clone();
path.0.append(&mut decl.name.0.clone());
Self(path)
}
pub fn root() -> Self {
Self(muss2_lang::syntax::Path(Vec::new()))
}
pub fn as_root_path(&self) -> &'_ muss2_lang::syntax::Path {
&self.0
}
}
pub struct Modules {
symbols: Vec<(ModuleDecl, super::Symbols)>,
name_map: HashMap<muss2_lang::syntax::Path, usize>,
}
impl Modules {
pub fn with_decls(iter: &mut impl core::iter::Iterator<Item=(ModuleDecl, super::Symbols)>) -> Self {
let symbols: Vec<_> = iter.collect();
let name_map = symbols.iter().enumerate().map(|(i, item)| (item.0.as_root_path().to_owned(), i)).collect();
Self {
symbols,
name_map,
}
}
}

View file

@ -0,0 +1,52 @@
use std::collections::HashMap;
pub struct StructType {
decl: muss2_lang::statement::DeclareType,
module: muss2_lang::syntax::Path,
}
impl StructType {
pub fn with_decl(decl: muss2_lang::statement::DeclareType, module: &super::modules::ModuleDecl) -> Self {
Self {
decl,
module: module.as_root_path().to_owned(),
}
}
pub fn decl(&self) -> &'_ muss2_lang::statement::DeclareType {
&self.decl
}
pub fn module(&self) -> &'_ muss2_lang::syntax::Path {
&self.module
}
}
pub struct Structs {
symbols: Vec<StructType>,
name_map: HashMap<muss2_lang::syntax::Path, usize>,
}
impl Structs {
pub fn with_decls(iter: &mut impl core::iter::Iterator<Item=StructType>) -> Self {
let symbols: Vec<_> = iter.collect();
let name_map = symbols.iter().enumerate().map(|(i, item)| {
let mut name = item.module.clone();
name.0.append(&mut item.decl.name.0.clone());
(name, i)
}).collect();
Self {
symbols,
name_map,
}
}
pub fn symbols(&self) -> &'_ Vec<StructType> {
&self.symbols
}
pub fn nmap(&self) -> &'_ HashMap<muss2_lang::syntax::Path, usize> {
&self.name_map
}
}

View file

@ -0,0 +1,115 @@
use std::collections::HashMap;
#[derive(Clone)]
pub enum TypeVariant {
Function {
params: Vec<TypeDecl>,
wasm_type: wasm_encoder::FuncType,
wasm: wasm_encoder::Function,
},
Structure {
fields: HashMap<muss2_lang::syntax::Path, TypeDecl>,
wasm_fields: Vec<wasm_encoder::FieldType>,
},
Primitive, // assumption: primitives are defined before type building is started
Undeclared,
}
#[derive(Clone)]
pub struct TypeDecl {
name: muss2_lang::syntax::Path,
wasm: wasm_encoder::ValType,
variant: TypeVariant,
}
impl TypeDecl {
pub fn new(code_ty: muss2_lang::syntax::Path, variant_ty: TypeVariant, wasm_ty: wasm_encoder::ValType) -> Self {
Self {
name: code_ty,
wasm: wasm_ty,
variant: variant_ty,
}
}
pub fn new_lazy_declared(code_ty: muss2_lang::syntax::Path, index: usize) -> Self {
Self {
name: code_ty,
wasm: wasm_encoder::ValType::Ref(
wasm_encoder::RefType {
nullable: false,
heap_type: wasm_encoder::HeapType::Concrete(index as _),
}
),
variant: TypeVariant::Undeclared,
}
}
pub fn code_ty(&self) -> &'_ muss2_lang::syntax::Path {
&self.name
}
pub fn wasm_ty(&self) -> &'_ wasm_encoder::ValType {
&self.wasm
}
pub fn variant(&self) -> &'_ TypeVariant {
&self.variant
}
pub fn is_struct(&self) -> bool {
matches!(self.variant, TypeVariant::Structure { .. })
}
pub fn is_func(&self) -> bool {
matches!(self.variant, TypeVariant::Function { .. })
}
pub fn is_primi(&self) -> bool {
matches!(self.variant, TypeVariant::Primitive)
}
pub fn is_fully_declared(&self) -> bool {
!matches!(self.variant, TypeVariant::Undeclared)
}
}
pub struct Types {
symbols: Vec<TypeDecl>,
name_map: HashMap<muss2_lang::syntax::Path, usize>,
}
impl Types {
pub fn new() -> Self {
Self {
symbols: Vec::new(),
name_map: HashMap::new(),
}
}
pub fn symbols(&self) -> &'_ Vec<TypeDecl> {
&self.symbols
}
pub fn nmap(&self) -> &'_ HashMap<muss2_lang::syntax::Path, usize> {
&self.name_map
}
pub fn push(&mut self, decl: TypeDecl) {
if let Some(&existing_entry) = self.name_map.get(&decl.name) {
let existing_decl = &self.symbols[existing_entry];
if !existing_decl.is_fully_declared() {
// an existing type is encountered again because it wasn't fully declared the first time
self.symbols[existing_entry] = decl;
} else {
panic!("existing type got re-declared when it was already declared")
}
} else {
self.name_map.insert(decl.name.clone(), self.symbols.len());
self.symbols.push(decl);
}
}
pub fn by_name(&self, name: &muss2_lang::syntax::Path) -> Option<usize> {
self.name_map.get(name).map(|x| *x)
}
}

41
src/cli.rs Normal file
View file

@ -0,0 +1,41 @@
use clap::{Parser, Subcommand, Args};
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
pub struct Cli {
/// Turn debugging information on
#[arg(short = 'v', action = clap::ArgAction::Count)]
pub debug: u8,
#[command(subcommand)]
pub mode: CommandMode,
}
impl Cli {
pub fn get() -> Self {
Cli::parse()
}
}
#[derive(Subcommand, Debug)]
pub enum CommandMode {
Wasm(WasmCommand),
}
/// WebAssembly operations
#[derive(Args, Debug)]
pub struct WasmCommand {
#[command(subcommand)]
pub mode: WasmMode,
}
#[derive(Subcommand, Debug)]
pub enum WasmMode {
/// Compile to a WebAssembly module binary
Compile {
#[arg(default_value = "./in.muss")]
r#in: std::path::PathBuf,
#[arg(default_value = "./out.wasm")]
out: std::path::PathBuf,
}
}

View file

@ -10,6 +10,55 @@
//! Create a novel but powerful language which is also great for manipulating a large music library.
//!
fn main() {
println!("Hello, world!");
mod cli;
#[repr(u8)]
enum LogLevel {
Critical = 0,
Info = 1,
Debug = 2,
Verbose = 3,
}
struct CliLogLevel(u8);
fn main() {
let args = cli::Cli::get();
let cli_level = CliLogLevel(args.debug);
do_if_level(|| println!("cli args: {:?}", args), LogLevel::Debug, &cli_level);
match args.mode {
cli::CommandMode::Wasm(wasm) => handle_wasm(wasm, &cli_level),
}
}
#[inline(always)]
fn do_if_level<F: FnOnce()>(todo: F, level: LogLevel, cli_level: &CliLogLevel) {
if cli_level.0 >= (level as u8) {
todo()
}
}
fn handle_wasm(wasm: cli::WasmCommand, cli_level: &CliLogLevel) {
match wasm.mode {
cli::WasmMode::Compile { r#in, out } => {
let to_compile = std::fs::read_to_string(r#in).expect("Failed to read in file");
let result = muss2_wasm::compile(&to_compile);
match result {
Ok(module) => {
std::fs::write(out, module.finish()).expect("Failed to write to out file");
},
Err(errs) => {
do_if_level(|| println!("got {} errors: ", errs.len()), LogLevel::Debug, cli_level);
do_if_level(|| println!("errors: {:?}", errs), LogLevel::Verbose, cli_level);
for (i, error) in errs.iter().enumerate() {
do_if_level(|| println!("error {} of {}", i, errs.len()), LogLevel::Info, cli_level);
do_if_level(|| println!("error {}: {:?}", i, error), LogLevel::Verbose, cli_level);
do_if_level(|| println!("{}", error), LogLevel::Critical, cli_level);
}
}
}
}
}
}