diff --git a/Cargo.lock b/Cargo.lock index c8d363b..0ecc10b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,39 @@ dependencies = [ "generic-array", ] +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0061da739915fae12ea00e16397555ed4371a6bb285431aab930f61b0aa4ba" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "cargo_metadata" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -78,6 +111,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" name = "mist-lang" version = "0.0.2-alpha0" dependencies = [ + "cargo_metadata", "mist-parser", "serde", "serde_json", @@ -154,6 +188,16 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde" version = "1.0.228" @@ -219,6 +263,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "typenum" version = "1.20.0" diff --git a/Cargo.toml b/Cargo.toml index 5b958f4..9fbe48d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ categories = ["compilers"] serde = { version = "1.0.228", features = ["serde_derive"] } serde_json = "1.0.149" mist-parser = { path = "./parser", version = "0.1.1"} +cargo_metadata = "0.23.1" [[bin]] name = "mist" diff --git a/mist-test/src/logger.mist b/mist-test/src/logger.mist index 7854ab9..ab194cd 100644 --- a/mist-test/src/logger.mist +++ b/mist-test/src/logger.mist @@ -2,7 +2,7 @@ use ; use ; pub trait TestLogging: fmt::Display { - String get(); + String get(self*); } pub enum LogLevel { @@ -29,6 +29,12 @@ impl fmt::Display for LogLevel { } } +impl TestLogging for LogLevel { + String get(self*) { + return format!("{self}"); + } +} + pub class Logger { String prefix; @@ -55,7 +61,7 @@ pub class Logger { } LogLevel::Warning => { - println!("{level} {} {}", self.prefix, message); + println!("{} {} {}", level.get(), self.prefix, message); } LogLevel::Error => { diff --git a/parser/src/ast/mod.rs b/parser/src/ast/mod.rs index ad0b287..f1d03f3 100644 --- a/parser/src/ast/mod.rs +++ b/parser/src/ast/mod.rs @@ -17,6 +17,9 @@ pub struct Identifier(pub String); #[derive(Debug, Clone, Serialize, Default)] pub struct ParamList(pub Vec); +#[derive(Debug, Clone, Serialize)] +pub struct TypeExpr(pub TypeExprKind, pub Vec); + #[derive(Debug, Clone, Serialize)] pub enum TypePostfix { Ref, @@ -34,7 +37,11 @@ pub enum TypeExprKind { } #[derive(Debug, Clone, Serialize)] -pub struct TypeExpr(pub TypeExprKind, pub Vec); +pub struct Spanned { + pub line: usize, + pub column: usize, + pub item: T, +} impl TypeExpr { pub fn no_px(kind: TypeExprKind) -> Self { @@ -52,3 +59,9 @@ impl From for Generic { } } } + +impl Spanned { + pub fn get_comment(&self) -> String { + format!("/* {}:{} */", self.line, self.column) + } +} diff --git a/parser/src/ast/statement.rs b/parser/src/ast/statement.rs index 8ae23e9..22375f4 100644 --- a/parser/src/ast/statement.rs +++ b/parser/src/ast/statement.rs @@ -3,7 +3,7 @@ use serde::Serialize; use super::*; #[derive(Debug, Clone, Serialize, Default)] -pub struct Block(pub Vec); +pub struct Block(pub Vec>); #[derive(Debug, Clone, Serialize)] pub enum Statement { diff --git a/parser/src/ast/top_level.rs b/parser/src/ast/top_level.rs index 04644ca..8de8531 100644 --- a/parser/src/ast/top_level.rs +++ b/parser/src/ast/top_level.rs @@ -22,7 +22,7 @@ pub enum Attribute { } #[derive(Debug, Clone, Serialize)] -pub struct TopLevel(pub TopLevelKind, pub Vec); +pub struct TopLevel(pub Spanned, pub Vec); #[derive(Debug, Clone, Serialize)] pub enum TopLevelKind { @@ -35,35 +35,35 @@ pub enum TopLevelKind { name: Identifier, generics: GenericsDecl, requirements: Vec, - items: Vec, + items: Vec>, }, EnumDecl { visibility: Visibility, name: Identifier, generics: GenericsDecl, - fields: Vec, + fields: Vec>, }, StructDecl { visibility: Visibility, name: Identifier, generics: GenericsDecl, - fields: Vec, + fields: Vec>, }, FunctionDecl(FunctionDecl), ClassDecl { visibility: Visibility, name: Identifier, generics: GenericsDecl, - fields: Vec, - constructor: ClassConstructor, + fields: Vec>, + constructor: Spanned, items: Vec, }, } #[derive(Debug, Clone, Serialize)] pub enum ClassItem { - Method(FunctionDecl), - ImplDecl(ImplDecl), + Method(Spanned), + ImplDecl(Spanned), } #[derive(Debug, Clone, Serialize, Default)] @@ -105,7 +105,7 @@ pub struct ImplDecl { pub generics: GenericsDecl, pub target: TypeExpr, pub trait_: Option, - pub methods: Vec, + pub methods: Vec>, } #[derive(Debug, Clone, Serialize)] diff --git a/parser/src/lib.rs b/parser/src/lib.rs index 867c2ef..0a4824c 100644 --- a/parser/src/lib.rs +++ b/parser/src/lib.rs @@ -4,6 +4,7 @@ use pest_derive::Parser; pub mod ast; pub mod error; pub mod parser; +pub mod rev_mapper; use ast::*; diff --git a/parser/src/parser/items/mod.rs b/parser/src/parser/items/mod.rs index 229ea88..fe45403 100644 --- a/parser/src/parser/items/mod.rs +++ b/parser/src/parser/items/mod.rs @@ -8,7 +8,7 @@ use crate::{ Rule, ast::*, ast_expr, - error::{AstError, AstResult, IntoErr, collect_recovered}, + error::{AstError, IntoErr, collect_recovered}, parser::consume_rule, }; @@ -21,10 +21,7 @@ impl<'a> TryFrom> for TopLevel { let attributes = collect_recovered(inner.next().unwrap().into_inner()); ast_expr!(TopLevel( - inner - .next() - .map(TopLevelKind::try_from) - .unwrap_or(Ok(TopLevelKind::ModAttribute)), + inner.next().map(Spanned::try_from).unwrap(), attributes, )) } @@ -57,7 +54,7 @@ impl<'a> TryFrom> for TopLevelKind { fields: inner .next() - .map(|pair| collect_recovered::(pair.into_inner())) + .map(|pair| collect_recovered(pair.into_inner())) .transpose() .map(|v| v.unwrap_or_default()), }), @@ -116,7 +113,7 @@ impl<'a> TryFrom> for TopLevelKind { .transpose() .map(|v| v.unwrap_or_default()), - items: Ok(Vec::new()) as AstResult<'_, Vec<_>>, + items: collect_recovered(&mut inner), }), _ => AstError::bug_unimplemented(pair), diff --git a/parser/src/parser/mod.rs b/parser/src/parser/mod.rs index d7dad09..2e73885 100644 --- a/parser/src/parser/mod.rs +++ b/parser/src/parser/mod.rs @@ -1,7 +1,7 @@ pub mod common; pub mod items; -use crate::Rule; +use crate::{Rule, ast::Spanned, error::AstError}; pub fn listen_rule(pairs: &mut pest::iterators::Pairs<'_, Rule>, rule: Rule) -> bool { let consumed = pairs @@ -27,3 +27,28 @@ pub fn consume_rule<'a>( if consumed { pairs.next() } else { None } } + +impl<'a, T: TryFrom, Error = AstError<'a, T>>> + TryFrom> for Spanned +{ + type Error = AstError<'a, Self>; + + fn try_from(pair: pest::iterators::Pair<'a, Rule>) -> Result { + let span = pair.as_span().start_pos().line_col(); + + Ok(Self { + line: span.0, + column: span.1, + item: pair.try_into().map_err(|err: AstError<'_, T>| AstError { + span: err.span, + error_code: err.error_code, + error_message: err.error_message, + recovered: err.recovered.map(|v| Spanned { + line: span.0, + column: span.1, + item: v, + }), + })?, + }) + } +} diff --git a/parser/src/rev_mapper/grammar.pest b/parser/src/rev_mapper/grammar.pest new file mode 100644 index 0000000..7ef6582 --- /dev/null +++ b/parser/src/rev_mapper/grammar.pest @@ -0,0 +1,11 @@ +WHITESPACE = _{ " " | "\t" | "\r" | "\n" } + +integer = @{ ASCII_DIGIT+ } + +next_line = { "+" } + +map = { "/*" ~ integer ~ ":" ~ integer ~ next_line? ~ "*/" } + +non_map = { (!map ~ ANY)+ } + +mapping = { SOI ~ (map | non_map)* ~ EOI } diff --git a/parser/src/rev_mapper/mod.rs b/parser/src/rev_mapper/mod.rs new file mode 100644 index 0000000..ff9fbcf --- /dev/null +++ b/parser/src/rev_mapper/mod.rs @@ -0,0 +1,61 @@ +use std::collections::HashSet; + +use pest::Parser; +use pest_derive::Parser; + +#[derive(Parser)] +#[grammar = "./src/rev_mapper/grammar.pest"] +pub struct MistMapperParser; + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct MistMap(pub usize, pub usize); + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct RustMap(pub usize, pub usize); + +pub fn get_mapping(input: &str) -> HashSet<(RustMap, MistMap)> { + let mut mapping = HashSet::new(); + + let pairs = MistMapperParser::parse(Rule::mapping, input) + .unwrap() + .next() + .unwrap() + .into_inner(); + + for pair in pairs { + if pair.as_rule() == Rule::map { + let rs = pair.as_span().start_pos().line_col(); + let mut inner = pair.into_inner(); + + let (line, col) = ( + inner.next().unwrap().as_str().parse().unwrap(), + inner.next().unwrap().as_str().parse().unwrap(), + ); + + mapping.insert(( + RustMap(rs.0, rs.1), + MistMap( + if inner.next().is_some() { + line + } else { + line - 1 + }, + col, + ), + )); + } + } + + mapping +} + +pub fn find_mapping( + mapping: &HashSet<(RustMap, MistMap)>, + target: &RustMap, +) -> Option<(RustMap, MistMap)> { + mapping + .iter() + .copied() + .filter(|(rust, _)| rust <= target) + .max_by_key(|(rust, _)| *rust) +} diff --git a/src/builder.rs b/src/builder.rs new file mode 100644 index 0000000..57e5a89 --- /dev/null +++ b/src/builder.rs @@ -0,0 +1,174 @@ +use std::{ + collections::HashMap, + env, fs, + path::PathBuf, + process::{Command, Stdio}, +}; + +use cargo_metadata::{CompilerMessage, Message}; +use mist_parser::rev_mapper::{RustMap, find_mapping, get_mapping}; + +use crate::transpiler::Config; + +#[derive(Debug, Clone)] +pub struct MistDiagnosticMessage { + pub message: String, + pub file_path: PathBuf, + pub file_name: String, + pub line: usize, + pub column: usize, +} + +#[derive(Debug, Clone)] +pub enum MistDiagnostic { + Error(MistDiagnosticMessage), + Warning(MistDiagnosticMessage), + Rust(CompilerMessage), +} + +pub fn build(mut args: Vec, config: Config, root: PathBuf) -> bool { + args.remove(0); + args.insert(1, "--message-format=json".to_string()); + + let is_root = root == env::current_dir().unwrap(); + + let mut command = Command::new("cargo") + .args(args) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .stdin(Stdio::inherit()) + .spawn() + .unwrap(); + + let mut reader = std::io::BufReader::new(command.stdout.take().unwrap()); + + let mut diagnostics = Vec::new(); + + let mut mapping = HashMap::new(); + + for message in cargo_metadata::Message::parse_stream(&mut reader) { + match message { + Ok(Message::CompilerMessage(msg)) => { + for span in &msg.message.spans { + if span.is_primary { + let rust_path = root.join(&span.file_name); + + let mist_file = span + .file_name + .replacen(&config.output, &config.src, 1) + .replace(".rs", ".mist"); + + let mist_path = root.join(&mist_file); + + if !fs::exists(&mist_path).unwrap() { + diagnostics.push(MistDiagnostic::Rust(msg)); + break; + } + + let map = mapping.entry(rust_path.clone()).or_insert_with(|| { + get_mapping(&fs::read_to_string(rust_path).unwrap()) + }); + + let mist_span = + find_mapping(&map, &RustMap(span.line_end, span.column_start)).unwrap(); + + let mist_msg = MistDiagnosticMessage { + message: span.label.clone().unwrap_or(msg.message.message.clone()), + file_name: if is_root { + mist_file + } else { + mist_path.to_string_lossy().to_string() + }, + file_path: mist_path, + line: mist_span.1.0, + column: mist_span.1.1, + }; + + match msg.message.level { + cargo_metadata::diagnostic::DiagnosticLevel::Error => { + diagnostics.push(MistDiagnostic::Error(mist_msg)) + } + + cargo_metadata::diagnostic::DiagnosticLevel::Warning => { + diagnostics.push(MistDiagnostic::Warning(mist_msg)) + } + + _ => {} + } + } + } + } + + Ok(Message::BuildFinished(finish)) => { + print_diagnostics(&diagnostics); + + let mut raw_reader = reader.into_inner(); + let mut stdout = std::io::stdout(); + + std::io::copy(&mut raw_reader, &mut stdout).unwrap(); + + command.wait().unwrap(); + + return finish.success; + } + + Ok(Message::TextLine(text)) => println!("{text}"), + _ => {} + } + } + + false +} + +pub fn print_diagnostics(diagnostics: &Vec) { + let mut files = HashMap::new(); + + for diag in diagnostics { + match diag { + MistDiagnostic::Error(msg) => { + let line = get_line(&mut files, &msg); + + println!( + "\n{}:{}:{}\n \x1b[31mError\x1b[0m: {}\n\t{}", + msg.file_name, + msg.line + 1, + msg.column, + msg.message, + line.unwrap_or_default(), + ) + } + + MistDiagnostic::Warning(msg) => { + let line = get_line(&mut files, &msg); + + println!( + "\n{}:{}:{}\n \x1b[33mWarning\x1b[0m: {}\n\t{}", + msg.file_name, + msg.line + 1, + msg.column, + msg.message, + line.unwrap_or_default(), + ) + } + MistDiagnostic::Rust(rs) => println!("{rs}"), + } + } +} + +pub fn get_line( + files: &mut HashMap>, + msg: &MistDiagnosticMessage, +) -> Option { + let src_path = msg.file_path.clone(); + + let lines = files.entry(src_path.clone()).or_insert_with(|| { + fs::read_to_string(src_path) + .unwrap() + .lines() + .into_iter() + .map(String::from) + .collect() + }); + + lines.get(msg.line).map(|v| v.trim().to_string()) +} diff --git a/src/codegen.rs b/src/codegen.rs index 2b44796..6fdd3c9 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -1,8 +1,8 @@ use mist_parser::ast::{ Attribute, BinaryOp, Block, ClassItem, EnumItem, ExprPath, ExprPathSegment, Expression, FieldDecl, FunctionDecl, Generic, GenericDecl, Generics, GenericsDecl, Identifier, ImplDecl, - Literal, Path, Pattern, Postfix, Prefix, Statement, StatementBranch, TopLevel, TopLevelKind, - TypeExpr, TypeExprKind, TypePostfix, VarDecl, VarDeclStmt, Visibility, + Literal, Path, Pattern, Postfix, Prefix, Spanned, Statement, StatementBranch, TopLevel, + TopLevelKind, TypeExpr, TypeExprKind, TypePostfix, VarDecl, VarDeclStmt, Visibility, }; // --------------------------------------------------------------------------- @@ -88,6 +88,17 @@ impl Default for RustCodegen { // GetRust — pure string production (expressions, types) // --------------------------------------------------------------------------- +impl GetRust for Spanned { + fn get_rust(self) -> String { + format!( + "/* {}:{} */ {}", + self.line, + self.column, + self.item.get_rust() + ) + } +} + impl GetRust for TypeExpr { fn get_rust(self) -> String { get_type_postfixes(self.1) + &self.0.get_rust() @@ -315,6 +326,13 @@ impl GetRust for Vec { // ToRust — output-writing (top-level, statements, blocks) // --------------------------------------------------------------------------- +impl ToRust for Spanned { + fn to_rust(self, cg: &mut RustCodegen) { + cg.add_indentedln(&format!("/* {}:{} */", self.line, self.column)); + self.item.to_rust(cg); + } +} + impl ToRust for Block { fn to_rust(self, cg: &mut RustCodegen) { for stmt in self.0 { @@ -325,16 +343,13 @@ impl ToRust for Block { impl ToRust for TopLevel { fn to_rust(self, cg: &mut RustCodegen) { - match self.0 { - TopLevelKind::ModAttribute => { - for attr in self.1 { - cg.addln(&format!("#![{}]", attr.get_rust())); - } + if let TopLevelKind::ModAttribute = self.0.item { + for attr in self.1 { + cg.addln(&format!("#![{}]", attr.get_rust())); } - _ => { - for attr in self.1 { - cg.addln(&format!("#[{}]", attr.get_rust())); - } + } else { + for attr in self.1 { + cg.addln(&format!("#[{}]", attr.get_rust())); } } @@ -369,9 +384,9 @@ impl ToRust for TopLevelKind { match self { Self::ModAttribute => {} Self::Import(vis, path) => { - cg.addln(&format!("{} use {};", vis.get_rust(), path.get_rust())) + cg.addln(&format!("{}use {};", vis.get_rust(), path.get_rust())) } - Self::Mod(vis, id) => cg.addln(&format!("{} mod {};", vis.get_rust(), id.get_rust())), + Self::Mod(vis, id) => cg.addln(&format!("{}mod {};", vis.get_rust(), id.get_rust())), Self::FunctionDecl(decl) => decl.to_rust(cg), Self::ImplDecl(impl_) => impl_.to_rust(cg), Self::StructDecl { @@ -410,7 +425,7 @@ impl ToRust for TopLevelKind { cg.indent += 1; for field in fields { - cg.add_indentedln(&(format!("{}", field.get_rust()) + ",")); + cg.add_indentedln(&(field.get_rust() + ",")); } cg.indent -= 1; @@ -466,7 +481,8 @@ impl ToRust for TopLevelKind { cg.indent += 1; for field in fields.clone() { - cg.add_indentedln(&field.decl.get_rust()); + cg.add_indentedln(&field.get_comment()); + cg.add_indentedln(&field.item.decl.get_rust()); } cg.indent -= 1; @@ -490,6 +506,10 @@ impl ToRust for TopLevelKind { )); cg.indent += 1; + let constructor_comment = constructor.get_comment(); + + let constructor = constructor.item; + let params_str = constructor .params .0 @@ -500,6 +520,7 @@ impl ToRust for TopLevelKind { .join(", "); cg.add_indentedln("#[allow(invalid_value)]"); + cg.add_indentedln(&constructor_comment); cg.add_indentedln(&format!( "{}fn new{}({}) -> Self {{", constructor.visibility.clone().get_rust(), @@ -511,10 +532,14 @@ impl ToRust for TopLevelKind { cg.add_indentedln("let mut this: Self = unsafe { std::mem::MaybeUninit::::zeroed().assume_init() };"); for field in fields { - if let Some(init) = field.init { + let comment = field.get_comment(); + + if let Some(init) = field.item.init { + cg.add_indentedln(&comment); + cg.add_indentedln(&format!( "this.{} = {};", - field.decl.name.get_rust(), + field.item.decl.name.get_rust(), init.get_rust() )); } @@ -537,6 +562,7 @@ impl ToRust for TopLevelKind { cg.add_indentedln("}\n"); // Constructor function + cg.add_indentedln(&constructor_comment); cg.add_indentedln(&format!( "{}fn construct_class{}(&mut self, {}) {{", constructor.visibility.get_rust(), @@ -565,8 +591,8 @@ impl ToRust for TopLevelKind { ClassItem::ImplDecl(impl_) => { let mut impl_ = impl_.clone(); - impl_.trait_ = Some(impl_.target); - impl_.target = + impl_.item.trait_ = Some(impl_.item.target); + impl_.item.target = TypeExpr(TypeExprKind::Path(Path(vec![name.clone()])), Vec::new()); impl_.to_rust(cg); diff --git a/src/main.rs b/src/main.rs index a3a0abe..d99e832 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ -use std::process::{self, Command}; +use std::process::{self}; +pub mod builder; pub mod codegen; -pub mod compiler; +pub mod transpiler; fn main() { let args: Vec = std::env::args().collect(); @@ -12,11 +13,18 @@ fn main() { } match args[1].as_str() { - "run" | "build" | "r" | "b" => { - compiler::build(); - Command::new("cargo").args(&args[1..]).status().unwrap(); + "run" | "build" | "check" | "r" | "b" | "c" => { + let (config, root) = transpiler::build(); + println!(""); + if builder::build(args, config, root) { + println!("\x1b[32m\nBuild successful\x1b[0m"); + } else { + println!("\x1b[31m\nBuild failed\x1b[0m"); + } + } + "transpile" | "t" => { + transpiler::build(); } - "transpile" | "t" => compiler::build(), "version" | "--version" | "-v" => { println!("mist {}", env!("CARGO_PKG_VERSION")); } @@ -36,12 +44,15 @@ fn print_usage() { println!("󰖟 View our documentation! \x1b[35mhttps://mist.selimaj.dev\x1b[0m"); println!("îȘ„ Follow us on github! \x1b[35mhttps://github.com/mist-go\x1b[0m\n"); println!("usage:"); - println!(" \x1b[36m mist run\x1b[0m run the project in the current directory\n"); + println!(" \x1b[36m mist run\x1b[0m,\x1b[36m r\x1b[0m run the project in the current directory\n"); println!( - " \x1b[36m mist build\x1b[0m build the project in the current directory\n" + " \x1b[36m mist build\x1b[0m,\x1b[36m b\x1b[0m build the project in the current directory\n" ); println!( - " \x1b[36m mist transpile\x1b[0m transpile the project in the current directory\n" + " \x1b[36m mist transpile\x1b[0m,\x1b[36m t\x1b[0m transpile the project in the current directory\n" + ); + println!( + " \x1b[36m mist check\x1b[0m,\x1b[36m c\x1b[0m check the project in the current directory\n" ); println!(" \x1b[36m mist version \x1b[0m print the compiler version\n"); println!(" \x1b[36m mist help\x1b[0m print this message\n"); diff --git a/src/compiler.rs b/src/transpiler.rs similarity index 78% rename from src/compiler.rs rename to src/transpiler.rs index e1dd733..ec282c2 100644 --- a/src/compiler.rs +++ b/src/transpiler.rs @@ -9,22 +9,18 @@ use mist_parser::error::ParseError; use serde::Deserialize; #[derive(Deserialize)] -struct Config { - src: String, - output: String, +pub struct Config { + pub src: String, + pub output: String, } -pub fn build() { +pub fn build() -> (Config, PathBuf) { let start = Instant::now(); - // 1. find project root let root = find_project_root().unwrap_or_else(|| { panic!("error: could not find project root (mist.json)"); }); - println!("mistc build ({})", root.display()); - - // 2. load config let config = load_config(&root); let src_dir = root.join(&config.src); @@ -34,7 +30,12 @@ pub fn build() { let elapsed = start.elapsed(); - println!("build finished in {:.2?}", elapsed); + println!( + "\x1b[32m\nTranspile successful\x1b[0m in \x1b[34m{:.2?}\x1b[0m", + elapsed + ); + + (config, root) } fn build_dir(root: &Path, base_src: &Path, current_dir: &Path, out_dir: &Path) { @@ -71,13 +72,30 @@ fn build_dir(root: &Path, base_src: &Path, current_dir: &Path, out_dir: &Path) { let relative = path.strip_prefix(base_src).unwrap(); + // Handle non-mist files with a cache check if path.extension().and_then(|e| e.to_str()) != Some("mist") { - fs::copy(&path, out_dir.join(relative)).expect("Failed to copy non-mist file"); + let dest_path = out_dir.join(relative); + + // Create parent directories for static assets if needed + if let Some(parent) = dest_path.parent() { + let _ = fs::create_dir_all(parent); + } + + if should_skip(&path, &dest_path) { + continue; + } + + fs::copy(&path, dest_path).expect("Failed to copy non-mist file"); continue; } let output_path = out_dir.join(relative).with_extension("rs"); + // Cache layer: Skip if the generated .rs file is newer than the .mist source + if should_skip(&path, &output_path) { + continue; + } + // create parent directories if let Some(parent) = output_path.parent() { if let Err(e) = fs::create_dir_all(parent) { @@ -147,6 +165,15 @@ fn build_dir(root: &Path, base_src: &Path, current_dir: &Path, out_dir: &Path) { } } +fn should_skip(source: &Path, output: &Path) -> bool { + if let (Ok(src_meta), Ok(out_meta)) = (fs::metadata(source), fs::metadata(output)) { + if let (Ok(src_time), Ok(out_time)) = (src_meta.modified(), out_meta.modified()) { + return out_time >= src_time; + } + } + false +} + pub fn find_project_root() -> Option { let mut dir = std::env::current_dir().ok()?;