diff --git a/depell/wasm-fmt/src/lib.rs b/depell/wasm-fmt/src/lib.rs index 7af6d40..76a25b8 100644 --- a/depell/wasm-fmt/src/lib.rs +++ b/depell/wasm-fmt/src/lib.rs @@ -20,7 +20,7 @@ unsafe extern "C" fn fmt() { let arena = parser::Arena::with_capacity(code.len() * parser::SOURCE_TO_AST_FACTOR); let mut ctx = parser::ParserCtx::default(); - let exprs = parser::Parser::parse(&mut ctx, code, "source.hb", &mut |_, _| Ok(0), &arena); + let exprs = parser::Parser::parse(&mut ctx, code, "source.hb", &mut parser::no_loader, &arena); let mut f = wasm_rt::Write(&mut OUTPUT[..]); fmt::fmt_file(exprs, code, &mut f).unwrap(); diff --git a/depell/wasm-hbc/src/lib.rs b/depell/wasm-hbc/src/lib.rs index c42d4b7..5788f5e 100644 --- a/depell/wasm-hbc/src/lib.rs +++ b/depell/wasm-hbc/src/lib.rs @@ -55,7 +55,10 @@ unsafe fn compile_and_run(mut fuel: usize) { let files = { let mut ctx = hblang::parser::ParserCtx::default(); let paths = files.iter().map(|f| f.path).collect::>(); - let mut loader = |path: &str, _: &str| Ok(paths.binary_search(&path).unwrap() as FileId); + let mut loader = |path: &str, _: &str, kind| match kind { + hblang::parser::FileKind::Module => Ok(paths.binary_search(&path).unwrap() as FileId), + hblang::parser::FileKind::Embed => Err("embeds are not supported".into()), + }; files .into_iter() .map(|f| { diff --git a/lang/README.md b/lang/README.md index aa4743e..0718933 100644 --- a/lang/README.md +++ b/lang/README.md @@ -131,6 +131,12 @@ fib := fn(n: int): int { main := fn(): int { a := 1 b := &a + + boundary := 1000 + + b = b + boundary - 2 + b = b - (boundary - 2) + modify(b) drop(a) return *b - 2 diff --git a/lang/src/codegen.rs b/lang/src/codegen.rs index 0d8c0ac..81ec90b 100644 --- a/lang/src/codegen.rs +++ b/lang/src/codegen.rs @@ -727,6 +727,7 @@ mod trap { #[derive(Default)] pub struct Codegen { pub files: Vec, + pub embeds: Vec>, tasks: Vec>, tys: Types, @@ -1615,6 +1616,7 @@ impl Codegen { E::BinOp { left, op, right } if op != T::Decl => 'ops: { let left = self.expr_ctx(left, Ctx { ty: ctx.ty.filter(|_| op.is_homogenous()), + check: ctx.check, ..Default::default() })?; @@ -2743,6 +2745,7 @@ impl Codegen { } else { let dty = self.ty_display(ty); let dexpected = self.ty_display(expected); + log::info!("mode: {:?}", kind); self.report(pos, format_args!("expected {hint} of type {dexpected}, got {dty}",)); } } @@ -2814,7 +2817,7 @@ mod tests { fn generate(ident: &'static str, input: &'static str, output: &mut String) { _ = log::set_logger(&crate::fs::Logger); - log::set_max_level(log::LevelFilter::Error); + log::set_max_level(log::LevelFilter::Debug); let mut codegen = super::Codegen { files: crate::test_parse_files(ident, input), ..Default::default() }; diff --git a/lang/src/fmt.rs b/lang/src/fmt.rs index 749690f..05ade5c 100644 --- a/lang/src/fmt.rs +++ b/lang/src/fmt.rs @@ -210,6 +210,7 @@ impl<'a> Formatter<'a> { Expr::String { literal, .. } => f.write_str(literal), Expr::Comment { literal, .. } => f.write_str(literal), Expr::Mod { path, .. } => write!(f, "@use(\"{path}\")"), + Expr::Embed { path, .. } => write!(f, "@embed(\"{path}\")"), Expr::Field { target, name: field, .. } => { self.fmt_paren(target, f, postfix)?; f.write_str(".")?; @@ -366,13 +367,20 @@ impl<'a> Formatter<'a> { self.fmt(right, f) } Expr::BinOp { right, op, left } => { - let pec_miss = |e: &Expr| { + let prec_miss_left = |e: &Expr| { matches!( e, Expr::BinOp { op: lop, .. } if op.precedence() > lop.precedence() ) }; + let prec_miss_right = |e: &Expr| { + matches!( + e, Expr::BinOp { op: lop, .. } + if (op.precedence() == lop.precedence() && !op.is_comutative()) + || op.precedence() > lop.precedence() + ) + }; - self.fmt_paren(left, f, pec_miss)?; + self.fmt_paren(left, f, prec_miss_left)?; if let Some(mut prev) = self.source.get(..right.pos() as usize) { prev = prev.trim_end(); let estimate_bound = @@ -396,7 +404,7 @@ impl<'a> Formatter<'a> { f.write_str(op.name())?; f.write_str(" ")?; } - self.fmt_paren(right, f, pec_miss) + self.fmt_paren(right, f, prec_miss_right) } } } @@ -452,7 +460,8 @@ pub mod test { let len = crate::fmt::minify(&mut minned); minned.truncate(len); - let ast = parser::Ast::new(ident, minned, &mut ParserCtx::default(), &mut |_, _| Ok(0)); + let ast = + parser::Ast::new(ident, minned, &mut ParserCtx::default(), &mut parser::no_loader); //log::error!( // "{} / {} = {} | {} / {} = {}", // ast.mem.size(), diff --git a/lang/src/fs.rs b/lang/src/fs.rs index ca19088..05dac21 100644 --- a/lang/src/fs.rs +++ b/lang/src/fs.rs @@ -1,7 +1,7 @@ use { crate::{ codegen, - parser::{self, Ast, ParserCtx}, + parser::{self, Ast, FileKind, ParserCtx}, }, alloc::{string::String, vec::Vec}, core::{fmt::Write, num::NonZeroUsize}, @@ -10,7 +10,9 @@ use { collections::VecDeque, eprintln, ffi::OsStr, - io::{self, Write as _}, + io::{ + Write as _, {self}, + }, path::{Path, PathBuf}, string::ToString, sync::Mutex, @@ -79,15 +81,16 @@ pub fn run_compiler(root_file: &str, options: Options, out: &mut Vec) -> std } if options.fmt { - for parsed in parsed { + for parsed in parsed.ast { format_ast(parsed)?; } } else if options.fmt_stdout { - let ast = parsed.into_iter().next().unwrap(); + let ast = parsed.ast.into_iter().next().unwrap(); write!(out, "{ast}").unwrap(); } else { let mut codegen = codegen::Codegen::default(); - codegen.files = parsed; + codegen.files = parsed.ast; + codegen.embeds = parsed.embeds; codegen.generate(0); if options.dump_asm { @@ -187,7 +190,12 @@ impl TaskQueueInner { } } -pub fn parse_from_fs(extra_threads: usize, root: &str) -> io::Result> { +pub struct Loaded { + ast: Vec, + embeds: Vec>, +} + +pub fn parse_from_fs(extra_threads: usize, root: &str) -> io::Result { fn resolve(path: &str, from: &str, tmp: &mut PathBuf) -> Result { tmp.clear(); match Path::new(from).parent() { @@ -224,44 +232,73 @@ pub fn parse_from_fs(extra_threads: usize, root: &str) -> io::Result> { type Task = (u32, PathBuf); - let seen = Mutex::new(crate::HashMap::::default()); + let seen_modules = Mutex::new(crate::HashMap::::default()); + let seen_embeds = Mutex::new(crate::HashMap::::default()); let tasks = TaskQueue::::new(extra_threads + 1); let ast = Mutex::new(Vec::>::new()); + let embeds = Mutex::new(Vec::>::new()); - let loader = |path: &str, from: &str, tmp: &mut _| { - if path.starts_with("rel:") { - return Err(io::Error::new( - io::ErrorKind::Other, - "`rel:` prefix was removed and is now equivalent to no prefix (remove it)" - .to_string(), - )); - } - + let loader = |path: &str, from: &str, kind: FileKind, tmp: &mut _| { let mut physiscal_path = resolve(path, from, tmp)?; - let id = { - let mut seen = seen.lock().unwrap(); - let len = seen.len(); - match seen.entry(physiscal_path) { - hash_map::Entry::Occupied(entry) => { - return Ok(*entry.get()); - } - hash_map::Entry::Vacant(entry) => { - physiscal_path = entry.insert_entry(len as _).key().clone(); - len as u32 + match kind { + FileKind::Module => { + let id = { + let mut seen = seen_modules.lock().unwrap(); + let len = seen.len(); + match seen.entry(physiscal_path) { + hash_map::Entry::Occupied(entry) => { + return Ok(*entry.get()); + } + hash_map::Entry::Vacant(entry) => { + physiscal_path = entry.insert_entry(len as _).key().clone(); + len as u32 + } + } + }; + + if !physiscal_path.exists() { + return Err(io::Error::new( + io::ErrorKind::NotFound, + format!("can't find file: {}", display_rel_path(&physiscal_path)), + )); } + + tasks.push((id, physiscal_path)); + Ok(id) } - }; + FileKind::Embed => { + let id = { + let mut seen = seen_embeds.lock().unwrap(); + let len = seen.len(); + match seen.entry(physiscal_path) { + hash_map::Entry::Occupied(entry) => { + return Ok(*entry.get()); + } + hash_map::Entry::Vacant(entry) => { + physiscal_path = entry.insert_entry(len as _).key().clone(); + len as u32 + } + } + }; - if !physiscal_path.exists() { - return Err(io::Error::new( - io::ErrorKind::NotFound, - format!("can't find file: {}", display_rel_path(&physiscal_path)), - )); + let content = std::fs::read(&physiscal_path).map_err(|e| { + io::Error::new( + e.kind(), + format!( + "can't load embed file: {}: {e}", + display_rel_path(&physiscal_path) + ), + ) + })?; + let mut embeds = embeds.lock().unwrap(); + if id as usize >= embeds.len() { + embeds.resize(id as usize + 1, Default::default()); + } + embeds[id as usize] = content; + Ok(id) + } } - - tasks.push((id, physiscal_path)); - Ok(id) }; let execute_task = |ctx: &mut _, (_, path): Task, tmp: &mut _| { @@ -271,8 +308,8 @@ pub fn parse_from_fs(extra_threads: usize, root: &str) -> io::Result> { format!("path contains invalid characters: {}", display_rel_path(&path)), ) })?; - Ok(Ast::new(path, std::fs::read_to_string(path)?, ctx, &mut |path, from| { - loader(path, from, tmp).map_err(|e| e.to_string()) + Ok(Ast::new(path, std::fs::read_to_string(path)?, ctx, &mut |path, from, kind| { + loader(path, from, kind, tmp).map_err(|e| e.to_string()) })) }; @@ -291,7 +328,7 @@ pub fn parse_from_fs(extra_threads: usize, root: &str) -> io::Result> { let path = Path::new(root).canonicalize().map_err(|e| { io::Error::new(e.kind(), format!("can't canonicalize root file path ({root})")) })?; - seen.lock().unwrap().insert(path.clone(), 0); + seen_modules.lock().unwrap().insert(path.clone(), 0); tasks.push((0, path)); if extra_threads == 0 { @@ -300,7 +337,10 @@ pub fn parse_from_fs(extra_threads: usize, root: &str) -> io::Result> { std::thread::scope(|s| (0..extra_threads + 1).for_each(|_| _ = s.spawn(thread))); } - ast.into_inner().unwrap().into_iter().collect::>>() + Ok(Loaded { + ast: ast.into_inner().unwrap().into_iter().collect::>>()?, + embeds: embeds.into_inner().unwrap(), + }) } pub fn display_rel_path(path: &(impl AsRef + ?Sized)) -> std::path::Display { diff --git a/lang/src/lib.rs b/lang/src/lib.rs index 220c686..29d2967 100644 --- a/lang/src/lib.rs +++ b/lang/src/lib.rs @@ -249,7 +249,7 @@ mod ty { lexer::TokenKind, parser::{self, Pos}, }, - core::{default, num::NonZeroU32, ops::Range}, + core::{num::NonZeroU32, ops::Range}, }; pub type ArrayLen = u32; @@ -384,7 +384,7 @@ mod ty { } } - #[derive(PartialEq, Eq, Default, Debug)] + #[derive(PartialEq, Eq, Default, Debug, Clone, Copy)] pub enum TyCheck { BinOp, #[default] @@ -1345,7 +1345,10 @@ pub fn run_test( #[cfg(test)] fn test_parse_files(ident: &'static str, input: &'static str) -> Vec { - use std::{borrow::ToOwned, string::ToString}; + use { + self::parser::FileKind, + std::{borrow::ToOwned, string::ToString}, + }; fn find_block(mut input: &'static str, test_name: &'static str) -> &'static str { const CASE_PREFIX: &str = "#### "; @@ -1385,7 +1388,8 @@ fn test_parse_files(ident: &'static str, input: &'static str) -> Vec; pub type FileId = u32; pub type IdentIndex = u16; pub type LoaderError = String; -pub type Loader<'a> = &'a mut (dyn FnMut(&str, &str) -> Result + 'a); +pub type Loader<'a> = &'a mut (dyn FnMut(&str, &str, FileKind) -> Result + 'a); + +#[derive(PartialEq, Eq, Debug)] +pub enum FileKind { + Module, + Embed, +} pub const SOURCE_TO_AST_FACTOR: usize = 7 * (core::mem::size_of::() / 4) + 1; @@ -44,8 +50,8 @@ pub mod idfl { } } -pub fn no_loader(_: &str, _: &str) -> Result { - Err(String::new()) +pub fn no_loader(_: &str, _: &str, _: FileKind) -> Result { + Ok(0) } #[derive(Debug)] @@ -276,7 +282,7 @@ impl<'a, 'b> Parser<'a, 'b> { E::Mod { pos, path, - id: match (self.loader)(path, self.path) { + id: match (self.loader)(path, self.path, FileKind::Module) { Ok(id) => id, Err(e) => { self.report(str.start, format_args!("error loading dependency: {e:#}")) @@ -284,6 +290,23 @@ impl<'a, 'b> Parser<'a, 'b> { }, } } + T::Directive if self.lexer.slice(token.range()) == "embed" => { + self.expect_advance(TokenKind::LParen); + let str = self.expect_advance(TokenKind::DQuote); + self.expect_advance(TokenKind::RParen); + let path = self.lexer.slice(str.range()); + let path = &path[1..path.len() - 1]; + + E::Embed { + pos, + path, + id: match (self.loader)(path, self.path, FileKind::Embed) { + Ok(id) => id, + Err(e) => self + .report(str.start, format_args!("error loading embedded file: {e:#}")), + }, + } + } T::Directive => E::Directive { pos: pos - 1, // need to undo the directive shift name: self.tok_str(token), @@ -816,6 +839,12 @@ generate_expr! { id: FileId, path: &'a str, }, + /// `'@use' '(' String ')'` + Embed { + pos: Pos, + id: FileId, + path: &'a str, + }, } }