From d1101b0b7160a6cbd0ae2e3446d3561dcb95a7f0 Mon Sep 17 00:00:00 2001 From: Priyanshu Dangare Date: Sat, 31 Jan 2026 20:44:33 +0530 Subject: [PATCH 1/4] feat: relative list imports --- src/ast/sprite.rs | 3 +++ src/codegen/datalists.rs | 28 ++++++++++++++++++++++++++-- src/codegen/sb3.rs | 14 ++++++++------ src/frontend/build.rs | 11 +++++++---- 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/ast/sprite.rs b/src/ast/sprite.rs index 558a8e1..d458a9a 100644 --- a/src/ast/sprite.rs +++ b/src/ast/sprite.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use fxhash::{ FxHashMap, FxHashSet, @@ -19,6 +21,7 @@ use crate::{ #[derive(Debug, Default, Serialize, Deserialize)] pub struct Sprite { + pub path: Option, pub costumes: Vec, pub sounds: Vec, pub procs: FxHashMap, diff --git a/src/codegen/datalists.rs b/src/codegen/datalists.rs index cd80606..3a07440 100644 --- a/src/codegen/datalists.rs +++ b/src/codegen/datalists.rs @@ -5,7 +5,10 @@ use std::{ BufRead, BufReader, }, - path::Path, + path::{ + Path, + PathBuf, + }, rc::Rc, }; @@ -15,15 +18,36 @@ use crate::{ vfs::VFS, }; +fn resolve_path(base_path: Option<&Path>, path: &str, input: &Path) -> PathBuf { + if let Some(stripped) = path.strip_prefix("./") { + if let Some(parent) = base_path.and_then(|p| p.parent()) { + return parent.join(stripped); + } + return input.join(stripped); + } else if let Some(stripped) = path.strip_prefix("../") { + if let Some(parent) = base_path.and_then(|p| p.parent()).and_then(|p| p.parent()) { + return parent.join(stripped); + } + return input.join(stripped); + } + // No prefix: relative to sprite directory (or input for stage) + if let Some(parent) = base_path.and_then(|p| p.parent()) { + return parent.join(path); + } + input.join(path) +} + pub fn read_list( fs: Rc>, input: &Path, path: &SmolStr, + base_path: Option<&Path>, ) -> Result, DiagnosticKind> { let (_, ext) = path.rsplit_once('.').unwrap_or_default(); + let resolved_path = resolve_path(base_path, path, input); let mut fs = fs.borrow_mut(); let mut file = fs - .read_file(&input.join(&**path)) + .read_file(&resolved_path) .map_err(|err| DiagnosticKind::IOError(err.to_string().into()))?; match ext { _ => read_list_text(&mut file), diff --git a/src/codegen/sb3.rs b/src/codegen/sb3.rs index eee7e0c..e270c80 100644 --- a/src/codegen/sb3.rs +++ b/src/codegen/sb3.rs @@ -869,13 +869,15 @@ where T: Write + Seek .into_iter() .map(|v| s.evaluate_const_expr(d, v).to_string()) .collect(), - Some(ListDefault::File { path, span }) => match read_list(fs, input, path) { - Ok(data) => data, - Err(error) => { - d.report(error, span); - vec![] + Some(ListDefault::File { path, span }) => { + match read_list(fs, input, path, s.sprite.path.as_deref()) { + Ok(data) => data, + Err(error) => { + d.report(error, span); + vec![] + } } - }, + } None => vec![], }; match &list.type_ { diff --git a/src/frontend/build.rs b/src/frontend/build.rs index d793077..bc5362d 100644 --- a/src/frontend/build.rs +++ b/src/frontend/build.rs @@ -101,8 +101,9 @@ pub fn build_impl( if !fs.borrow_mut().is_file(&stage_path) { return Err(anyhow!("{} not found", stage_path.display())); } - let mut stage_diagnostics = SpriteDiagnostics::new(fs.clone(), stage_path, &stdlib); - let (stage, parse_diagnostics) = parser::parse(&stage_diagnostics.translation_unit); + let mut stage_diagnostics = SpriteDiagnostics::new(fs.clone(), stage_path.clone(), &stdlib); + let (mut stage, parse_diagnostics) = parser::parse(&stage_diagnostics.translation_unit); + stage.path = Some(stage_path.clone()); stage_diagnostics.diagnostics.extend(parse_diagnostics); let mut sprites_diagnostics: FxHashMap = Default::default(); let mut sprites: FxHashMap = Default::default(); @@ -126,8 +127,10 @@ pub fn build_impl( .to_str() .unwrap() .into(); - let mut sprite_diagnostics = SpriteDiagnostics::new(fs.clone(), sprite_path, &stdlib); - let (sprite, parse_diagnostics) = parser::parse(&sprite_diagnostics.translation_unit); + let mut sprite_diagnostics = + SpriteDiagnostics::new(fs.clone(), sprite_path.clone(), &stdlib); + let (mut sprite, parse_diagnostics) = parser::parse(&sprite_diagnostics.translation_unit); + sprite.path = Some(sprite_path.clone()); sprite_diagnostics.diagnostics.extend(parse_diagnostics); sprites_diagnostics.insert(sprite_name.clone(), sprite_diagnostics); sprites.insert(sprite_name, sprite); From bfce5163caa42f71886d4b006a4accfbcacefdff Mon Sep 17 00:00:00 2001 From: Priyanshu Dangare Date: Thu, 5 Feb 2026 05:24:44 +0530 Subject: [PATCH 2/4] fix --- src/codegen/datalists.rs | 142 +++++++++++++++++++++++++++++++++++---- 1 file changed, 129 insertions(+), 13 deletions(-) diff --git a/src/codegen/datalists.rs b/src/codegen/datalists.rs index 3a07440..316294e 100644 --- a/src/codegen/datalists.rs +++ b/src/codegen/datalists.rs @@ -19,22 +19,63 @@ use crate::{ }; fn resolve_path(base_path: Option<&Path>, path: &str, input: &Path) -> PathBuf { + // Handle absolute paths + if Path::new(path).is_absolute() { + return PathBuf::from(path); + } + + // "./" prefix: relative to sprite directory (or input for stage) if let Some(stripped) = path.strip_prefix("./") { - if let Some(parent) = base_path.and_then(|p| p.parent()) { - return parent.join(stripped); - } - return input.join(stripped); - } else if let Some(stripped) = path.strip_prefix("../") { - if let Some(parent) = base_path.and_then(|p| p.parent()).and_then(|p| p.parent()) { - return parent.join(stripped); - } - return input.join(stripped); + let base_dir = if let Some(base_path) = base_path { + base_path.parent().unwrap_or_else(|| Path::new(".")) + } else { + input.parent().unwrap_or(input) + }; + return normalize_path(base_dir.join(stripped)); } - // No prefix: relative to sprite directory (or input for stage) - if let Some(parent) = base_path.and_then(|p| p.parent()) { - return parent.join(path); + + // For sprites: any path starting with "../" should also be relative to sprite directory + if path.starts_with("../") && base_path.is_some() { + let base_dir = base_path + .unwrap() + .parent() + .unwrap_or_else(|| Path::new(".")); + return normalize_path(base_dir.join(path)); + } + + // No prefix: relative to project root (input's parent directory or input itself) + let project_root = input.parent().unwrap_or(input); + normalize_path(project_root.join(path)) +} + +fn normalize_path(path: PathBuf) -> PathBuf { + // Try to normalize manually first for more predictable behavior in tests + let mut result = PathBuf::new(); + + for component in path.components() { + match component { + std::path::Component::ParentDir => { + // Only pop if we have something to pop and it's not already at root + if !result.as_os_str().is_empty() + && result + .components() + .last() + .map(|c| !matches!(c, std::path::Component::ParentDir)) + .unwrap_or(false) + { + result.pop(); + } else { + // If we can't go up further, keep the parent dir + result.push(".."); + } + } + std::path::Component::CurDir => { + // Skip current directory components + } + _ => result.push(component), + } } - input.join(path) + result } pub fn read_list( @@ -59,3 +100,78 @@ fn read_list_text(file: &mut Box) -> Result, io: let file = BufReader::new(file); Ok(file.lines().into_iter().flatten().map(Into::into).collect()) } + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use super::*; + + #[test] + fn test_resolve_path_relative_to_project_root() { + let base_path = PathBuf::from("/project/sprites/sprite.gbo"); + let input = PathBuf::from("/project/stage.gbo"); + + // Test no prefix: relative to project root + let result = resolve_path(Some(&base_path), "data.txt", &input); + assert_eq!(result, PathBuf::from("/project/data.txt")); + } + + #[test] + fn test_resolve_path_current_dir() { + let base_path = PathBuf::from("/project/sprites/sprite.gbo"); + let input = PathBuf::from("/project/stage.gbo"); + + // Test ./ prefix: relative to sprite directory + let result = resolve_path(Some(&base_path), "./data.txt", &input); + assert_eq!(result, PathBuf::from("/project/sprites/data.txt")); + } + + #[test] + fn test_resolve_path_parent_dir() { + let base_path = PathBuf::from("/project/sprites/sprite.gbo"); + let input = PathBuf::from("/project/stage.gbo"); + + // Test ../ prefix: relative to sprite directory, go up one level + let result = resolve_path(Some(&base_path), "../data.txt", &input); + assert_eq!(result, PathBuf::from("/project/data.txt")); + } + + #[test] + fn test_resolve_path_multiple_parents() { + let base_path = PathBuf::from("/project/sprites/subdir/sprite.gbo"); + let input = PathBuf::from("/project/stage.gbo"); + + // Test multiple ../: relative to sprite directory, go up multiple levels + let result = resolve_path(Some(&base_path), "../../data.txt", &input); + assert_eq!(result, PathBuf::from("/project/data.txt")); + } + + #[test] + fn test_resolve_path_no_base() { + let input = PathBuf::from("/project/stage.gbo"); + + // Test with no base_path (stage case): relative to project root + let result = resolve_path(None, "data.txt", &input); + assert_eq!(result, PathBuf::from("/project/data.txt")); + } + + #[test] + fn test_resolve_path_no_base_current_dir() { + let input = PathBuf::from("/project/stage.gbo"); + + // Test ./ prefix with no base_path: relative to stage directory + let result = resolve_path(None, "./data.txt", &input); + assert_eq!(result, PathBuf::from("/project/data.txt")); + } + + #[test] + fn test_resolve_path_absolute() { + let base_path = PathBuf::from("/project/sprites/sprite.gbo"); + let input = PathBuf::from("/project/stage.gbo"); + + // Test absolute path + let result = resolve_path(Some(&base_path), "/absolute/data.txt", &input); + assert_eq!(result, PathBuf::from("/absolute/data.txt")); + } +} From 022eb85898fba6f35cf6ae7dc80923c76b36f053 Mon Sep 17 00:00:00 2001 From: Priyanshu Dangare Date: Thu, 5 Feb 2026 05:51:07 +0530 Subject: [PATCH 3/4] fix --- src/ast/sprite.rs | 189 +++++++++++---------- src/codegen/datalists.rs | 4 +- src/codegen/sb3.rs | 10 +- src/frontend/build.rs | 348 +++++++++++++++++++-------------------- 4 files changed, 277 insertions(+), 274 deletions(-) diff --git a/src/ast/sprite.rs b/src/ast/sprite.rs index d458a9a..8734dab 100644 --- a/src/ast/sprite.rs +++ b/src/ast/sprite.rs @@ -1,96 +1,93 @@ -use std::path::PathBuf; - -use fxhash::{ - FxHashMap, - FxHashSet, -}; -use logos::Span; -use serde::{ - Deserialize, - Serialize, -}; - -use super::*; -use crate::{ - diagnostic::{ - Diagnostic, - DiagnosticKind, - }, - misc::SmolStr, -}; - -#[derive(Debug, Default, Serialize, Deserialize)] -pub struct Sprite { - pub path: Option, - pub costumes: Vec, - pub sounds: Vec, - pub procs: FxHashMap, - pub proc_definitions: FxHashMap>, - pub proc_references: FxHashMap, - pub proc_args: FxHashMap>, - pub funcs: FxHashMap, - pub func_definitions: FxHashMap>, - pub func_references: FxHashMap, - pub func_args: FxHashMap>, - pub enums: FxHashMap, - pub structs: FxHashMap, - pub vars: FxHashMap, - pub proc_locals: FxHashMap>, - pub func_locals: FxHashMap>, - pub lists: FxHashMap, - pub events: Vec, - pub used_procs: FxHashSet, - pub used_funcs: FxHashSet, - pub volume: Option<(Value, Span)>, - pub layer_order: Option<(Value, Span)>, - pub x_position: Option<(Value, Span)>, - pub y_position: Option<(Value, Span)>, - pub size: Option<(Value, Span)>, - pub direction: Option<(Value, Span)>, - pub rotation_style: RotationStyle, - pub hidden: bool, -} - -impl Sprite { - pub fn add_proc( - &mut self, - proc: Proc, - args: Vec, - stmts: Vec, - diagnostics: &mut Vec, - ) { - let name = proc.name.clone(); - if self.procs.contains_key(&name) { - diagnostics.push(Diagnostic { - kind: DiagnosticKind::ProcedureRedefinition(name), - span: proc.span.clone(), - }); - return; - } - self.procs.insert(name.clone(), proc); - self.proc_args.insert(name.clone(), args); - self.proc_definitions.insert(name.clone(), stmts); - self.proc_references.insert(name, Default::default()); - } - - pub fn add_func( - &mut self, - func: Func, - args: Vec, - stmts: Vec, - diagnostics: &mut Vec, - ) { - let name = func.name.clone(); - if self.funcs.contains_key(&name) { - diagnostics.push(Diagnostic { - kind: DiagnosticKind::FunctionRedefinition(name), - span: func.span.clone(), - }); - return; - } - self.funcs.insert(name.clone(), func); - self.func_args.insert(name.clone(), args); - self.func_definitions.insert(name.clone(), stmts); - self.func_references.insert(name, Default::default()); - } -} +use fxhash::{ + FxHashMap, + FxHashSet, +}; +use logos::Span; +use serde::{ + Deserialize, + Serialize, +}; + +use super::*; +use crate::{ + diagnostic::{ + Diagnostic, + DiagnosticKind, + }, + misc::SmolStr, +}; + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct Sprite { + pub costumes: Vec, + pub sounds: Vec, + pub procs: FxHashMap, + pub proc_definitions: FxHashMap>, + pub proc_references: FxHashMap, + pub proc_args: FxHashMap>, + pub funcs: FxHashMap, + pub func_definitions: FxHashMap>, + pub func_references: FxHashMap, + pub func_args: FxHashMap>, + pub enums: FxHashMap, + pub structs: FxHashMap, + pub vars: FxHashMap, + pub proc_locals: FxHashMap>, + pub func_locals: FxHashMap>, + pub lists: FxHashMap, + pub events: Vec, + pub used_procs: FxHashSet, + pub used_funcs: FxHashSet, + pub volume: Option<(Value, Span)>, + pub layer_order: Option<(Value, Span)>, + pub x_position: Option<(Value, Span)>, + pub y_position: Option<(Value, Span)>, + pub size: Option<(Value, Span)>, + pub direction: Option<(Value, Span)>, + pub rotation_style: RotationStyle, + pub hidden: bool, +} + +impl Sprite { + pub fn add_proc( + &mut self, + proc: Proc, + args: Vec, + stmts: Vec, + diagnostics: &mut Vec, + ) { + let name = proc.name.clone(); + if self.procs.contains_key(&name) { + diagnostics.push(Diagnostic { + kind: DiagnosticKind::ProcedureRedefinition(name), + span: proc.span.clone(), + }); + return; + } + self.procs.insert(name.clone(), proc); + self.proc_args.insert(name.clone(), args); + self.proc_definitions.insert(name.clone(), stmts); + self.proc_references.insert(name, Default::default()); + } + + pub fn add_func( + &mut self, + func: Func, + args: Vec, + stmts: Vec, + diagnostics: &mut Vec, + ) { + let name = func.name.clone(); + if self.funcs.contains_key(&name) { + diagnostics.push(Diagnostic { + kind: DiagnosticKind::FunctionRedefinition(name), + span: func.span.clone(), + }); + return; + } + self.funcs.insert(name.clone(), func); + self.func_args.insert(name.clone(), args); + self.func_definitions.insert(name.clone(), stmts); + self.func_references.insert(name, Default::default()); + } +} diff --git a/src/codegen/datalists.rs b/src/codegen/datalists.rs index 316294e..57bf434 100644 --- a/src/codegen/datalists.rs +++ b/src/codegen/datalists.rs @@ -82,10 +82,10 @@ pub fn read_list( fs: Rc>, input: &Path, path: &SmolStr, - base_path: Option<&Path>, + current_file_path: &Path, ) -> Result, DiagnosticKind> { let (_, ext) = path.rsplit_once('.').unwrap_or_default(); - let resolved_path = resolve_path(base_path, path, input); + let resolved_path = resolve_path(Some(current_file_path), path, input); let mut fs = fs.borrow_mut(); let mut file = fs .read_file(&resolved_path) diff --git a/src/codegen/sb3.rs b/src/codegen/sb3.rs index e270c80..0d7b394 100644 --- a/src/codegen/sb3.rs +++ b/src/codegen/sb3.rs @@ -870,7 +870,15 @@ where T: Write + Seek .map(|v| s.evaluate_const_expr(d, v).to_string()) .collect(), Some(ListDefault::File { path, span }) => { - match read_list(fs, input, path, s.sprite.path.as_deref()) { + // Get the current file path from translation unit based on span + let current_file_path = if d.translation_unit.get_text().len() > span.start { + let (_, include) = d.translation_unit.translate_position(span.start); + &include.path + } else { + // Fallback to input path if span is invalid + input + }; + match read_list(fs, input, path, ¤t_file_path) { Ok(data) => data, Err(error) => { d.report(error, span); diff --git a/src/frontend/build.rs b/src/frontend/build.rs index bc5362d..7266bec 100644 --- a/src/frontend/build.rs +++ b/src/frontend/build.rs @@ -1,175 +1,173 @@ -use std::{ - cell::RefCell, - env, - fs::File, - io::{ - BufWriter, - Seek, - Write, - }, - path::PathBuf, - rc::Rc, -}; - -use anyhow::{ - anyhow, - Context, -}; -use directories::ProjectDirs; -use fxhash::FxHashMap; - -use crate::{ - ast::{ - Project, - Sprite, - }, - codegen::sb3::Sb3, - config::Config, - diagnostic::{ - Artifact, - SpriteDiagnostics, - }, - misc::SmolStr, - parser, - standard_library::{ - fetch_standard_library, - new_standard_library, - standard_library_from_latest, - StandardLibrary, - }, - vfs::{ - RealFS, - VFS, - }, - visitor, -}; - -fn assign_layer_orders(project: &mut Project, config: &Config) { - let mut layer_order: usize = 1; - if let Some(layers) = &config.layers { - for layer in layers { - if let Some(sprite) = project.sprites.get_mut(&**layer) { - sprite.layer_order = Some((layer_order.into(), 0..0)); - layer_order += 1; - } - } - } -} - -pub fn build(input: Option, output: Option) -> anyhow::Result { - let input = input.unwrap_or_else(|| env::current_dir().unwrap()); - let canonical_input = input.canonicalize()?; - let project_name = canonical_input.file_name().unwrap().to_str().unwrap(); - let output = output.unwrap_or_else(|| input.join(format!("{project_name}.sb3"))); - let sb3 = Sb3::new(BufWriter::new(File::create(&output)?)); - let fs = Rc::new(RefCell::new(RealFS::new())); - build_impl(fs, canonical_input, sb3, None) -} - -pub fn build_impl( - fs: Rc>, - input: PathBuf, - mut sb3: Sb3, - stdlib: Option, -) -> anyhow::Result { - let config_path = input.join("goboscript.toml"); - let config_src = fs - .borrow_mut() - .read_to_string(&config_path) - .unwrap_or_default(); - let config: Config = toml::from_str(&config_src) - .with_context(|| format!("failed to parse {}", config_path.display()))?; - let stdlib = if let Some(stdlib) = stdlib { - stdlib - } else if let Some(std) = &config.std { - let dirs = ProjectDirs::from("com", "aspizu", "goboscript").unwrap(); - let std = std - .strip_prefix('v') - .unwrap_or(std) - .parse() - .with_context(|| format!("std version `{}` is not a valid semver version", std))?; - new_standard_library(std, &dirs.config_dir().join("std")) - } else { - let dirs = ProjectDirs::from("com", "aspizu", "goboscript").unwrap(); - standard_library_from_latest(&dirs.config_dir().join("std"))? - }; - // v0.0.0 means stdlib is from wasm - if stdlib.version.major != 0 { - fetch_standard_library(&stdlib)?; - } - let stage_path = input.join("stage.gs"); - if !fs.borrow_mut().is_file(&stage_path) { - return Err(anyhow!("{} not found", stage_path.display())); - } - let mut stage_diagnostics = SpriteDiagnostics::new(fs.clone(), stage_path.clone(), &stdlib); - let (mut stage, parse_diagnostics) = parser::parse(&stage_diagnostics.translation_unit); - stage.path = Some(stage_path.clone()); - stage_diagnostics.diagnostics.extend(parse_diagnostics); - let mut sprites_diagnostics: FxHashMap = Default::default(); - let mut sprites: FxHashMap = Default::default(); - let files = fs.borrow_mut().read_dir(&input)?; - for sprite_path in files { - if sprite_path.file_stem().is_some_and(|stem| stem == "stage") { - continue; - } - if sprite_path - .extension() - .is_none_or(|extension| extension != "gs") - { - continue; - } - if fs.borrow_mut().is_dir(&sprite_path) { - continue; - } - let sprite_name: SmolStr = sprite_path - .file_stem() - .unwrap_or_default() - .to_str() - .unwrap() - .into(); - let mut sprite_diagnostics = - SpriteDiagnostics::new(fs.clone(), sprite_path.clone(), &stdlib); - let (mut sprite, parse_diagnostics) = parser::parse(&sprite_diagnostics.translation_unit); - sprite.path = Some(sprite_path.clone()); - sprite_diagnostics.diagnostics.extend(parse_diagnostics); - sprites_diagnostics.insert(sprite_name.clone(), sprite_diagnostics); - sprites.insert(sprite_name, sprite); - } - let mut project = Project { stage, sprites }; - if !(stage_diagnostics.diagnostics.is_empty() - && sprites_diagnostics - .values() - .all(|sprite_diagnostics| sprite_diagnostics.diagnostics.is_empty())) - { - return Ok(Artifact { - project, - stage_diagnostics, - sprites_diagnostics, - }); - } - visitor::pass0::visit_project(&input, &mut project); - visitor::pass1::visit_project(&mut project); - visitor::pass2::visit_project( - &mut project, - &mut stage_diagnostics, - &mut sprites_diagnostics, - ); - visitor::pass3::visit_project(&mut project); - visitor::pass4::visit_project(&mut project); - log::info!("{:#?}", project); - assign_layer_orders(&mut project, &config); - sb3.project( - fs.clone(), - &input, - &project, - &config, - &mut stage_diagnostics, - &mut sprites_diagnostics, - )?; - drop(sb3); - Ok(Artifact { - project, - stage_diagnostics, - sprites_diagnostics, - }) -} +use std::{ + cell::RefCell, + env, + fs::File, + io::{ + BufWriter, + Seek, + Write, + }, + path::PathBuf, + rc::Rc, +}; + +use anyhow::{ + anyhow, + Context, +}; +use directories::ProjectDirs; +use fxhash::FxHashMap; + +use crate::{ + ast::{ + Project, + Sprite, + }, + codegen::sb3::Sb3, + config::Config, + diagnostic::{ + Artifact, + SpriteDiagnostics, + }, + misc::SmolStr, + parser, + standard_library::{ + fetch_standard_library, + new_standard_library, + standard_library_from_latest, + StandardLibrary, + }, + vfs::{ + RealFS, + VFS, + }, + visitor, +}; + +fn assign_layer_orders(project: &mut Project, config: &Config) { + let mut layer_order: usize = 1; + if let Some(layers) = &config.layers { + for layer in layers { + if let Some(sprite) = project.sprites.get_mut(&**layer) { + sprite.layer_order = Some((layer_order.into(), 0..0)); + layer_order += 1; + } + } + } +} + +pub fn build(input: Option, output: Option) -> anyhow::Result { + let input = input.unwrap_or_else(|| env::current_dir().unwrap()); + let canonical_input = input.canonicalize()?; + let project_name = canonical_input.file_name().unwrap().to_str().unwrap(); + let output = output.unwrap_or_else(|| input.join(format!("{project_name}.sb3"))); + let sb3 = Sb3::new(BufWriter::new(File::create(&output)?)); + let fs = Rc::new(RefCell::new(RealFS::new())); + build_impl(fs, canonical_input, sb3, None) +} + +pub fn build_impl( + fs: Rc>, + input: PathBuf, + mut sb3: Sb3, + stdlib: Option, +) -> anyhow::Result { + let config_path = input.join("goboscript.toml"); + let config_src = fs + .borrow_mut() + .read_to_string(&config_path) + .unwrap_or_default(); + let config: Config = toml::from_str(&config_src) + .with_context(|| format!("failed to parse {}", config_path.display()))?; + let stdlib = if let Some(stdlib) = stdlib { + stdlib + } else if let Some(std) = &config.std { + let dirs = ProjectDirs::from("com", "aspizu", "goboscript").unwrap(); + let std = std + .strip_prefix('v') + .unwrap_or(std) + .parse() + .with_context(|| format!("std version `{}` is not a valid semver version", std))?; + new_standard_library(std, &dirs.config_dir().join("std")) + } else { + let dirs = ProjectDirs::from("com", "aspizu", "goboscript").unwrap(); + standard_library_from_latest(&dirs.config_dir().join("std"))? + }; + // v0.0.0 means stdlib is from wasm + if stdlib.version.major != 0 { + fetch_standard_library(&stdlib)?; + } + let stage_path = input.join("stage.gs"); + if !fs.borrow_mut().is_file(&stage_path) { + return Err(anyhow!("{} not found", stage_path.display())); + } + let mut stage_diagnostics = SpriteDiagnostics::new(fs.clone(), stage_path.clone(), &stdlib); + let (stage, parse_diagnostics) = parser::parse(&stage_diagnostics.translation_unit); + stage_diagnostics.diagnostics.extend(parse_diagnostics); + let mut sprites_diagnostics: FxHashMap = Default::default(); + let mut sprites: FxHashMap = Default::default(); + let files = fs.borrow_mut().read_dir(&input)?; + for sprite_path in files { + if sprite_path.file_stem().is_some_and(|stem| stem == "stage") { + continue; + } + if sprite_path + .extension() + .is_none_or(|extension| extension != "gs") + { + continue; + } + if fs.borrow_mut().is_dir(&sprite_path) { + continue; + } + let sprite_name: SmolStr = sprite_path + .file_stem() + .unwrap_or_default() + .to_str() + .unwrap() + .into(); + let mut sprite_diagnostics = + SpriteDiagnostics::new(fs.clone(), sprite_path.clone(), &stdlib); + let (sprite, parse_diagnostics) = parser::parse(&sprite_diagnostics.translation_unit); + sprite_diagnostics.diagnostics.extend(parse_diagnostics); + sprites_diagnostics.insert(sprite_name.clone(), sprite_diagnostics); + sprites.insert(sprite_name, sprite); + } + let mut project = Project { stage, sprites }; + if !(stage_diagnostics.diagnostics.is_empty() + && sprites_diagnostics + .values() + .all(|sprite_diagnostics| sprite_diagnostics.diagnostics.is_empty())) + { + return Ok(Artifact { + project, + stage_diagnostics, + sprites_diagnostics, + }); + } + visitor::pass0::visit_project(&input, &mut project); + visitor::pass1::visit_project(&mut project); + visitor::pass2::visit_project( + &mut project, + &mut stage_diagnostics, + &mut sprites_diagnostics, + ); + visitor::pass3::visit_project(&mut project); + visitor::pass4::visit_project(&mut project); + log::info!("{:#?}", project); + assign_layer_orders(&mut project, &config); + sb3.project( + fs.clone(), + &input, + &project, + &config, + &mut stage_diagnostics, + &mut sprites_diagnostics, + )?; + drop(sb3); + Ok(Artifact { + project, + stage_diagnostics, + sprites_diagnostics, + }) +} From edf0a20159c3742f2dbf8dcce6f5ab620b2c9eba Mon Sep 17 00:00:00 2001 From: Priyanshu Dangare Date: Thu, 5 Feb 2026 05:53:18 +0530 Subject: [PATCH 4/4] reformat --- src/codegen/stmt.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/codegen/stmt.rs b/src/codegen/stmt.rs index ae6cedb..e9a32fa 100644 --- a/src/codegen/stmt.rs +++ b/src/codegen/stmt.rs @@ -348,10 +348,7 @@ where T: Write + Seek if let Block::SwitchCostume = block { let costume_name = value.to_string(); if !s.sprite.costumes.iter().any(|c| c.name == costume_name) { - d.report( - DiagnosticKind::InvalidCostumeName(costume_name), - arg_span, - ); + d.report(DiagnosticKind::InvalidCostumeName(costume_name), arg_span); } } // Validate backdrop names for switch_backdrop blocks @@ -359,10 +356,7 @@ where T: Write + Seek let backdrop_name = value.to_string(); let stage = s.stage.unwrap_or(s.sprite); if !stage.costumes.iter().any(|c| c.name == backdrop_name) { - d.report( - DiagnosticKind::InvalidBackdropName(backdrop_name), - arg_span, - ); + d.report(DiagnosticKind::InvalidBackdropName(backdrop_name), arg_span); } } menu_value = Some(value.clone());