diff --git a/rox_compiler/src/compile.rs b/rox_compiler/src/compile.rs index 80edfc0..0f7b1fa 100644 --- a/rox_compiler/src/compile.rs +++ b/rox_compiler/src/compile.rs @@ -186,150 +186,178 @@ struct ScriptWithOverrides{ source:String, } -fn extract_script_overrides(mut source:String)->AResult{ - let mut overrides=PropertiesOverride::default(); - let mut count=0; - for line in source.lines(){ - //only string type properties are supported atm - if let Some(captures)=lazy_regex::regex!(r#"^\-\-\!\s*Properties\.([A-z]\w*)\s*\=\s*"(\w+)"$"#) - .captures(line){ - count+=line.len(); - match &captures[1]{ - "Name"=>overrides.name=Some(captures[2].to_owned()), - "ClassName"=>overrides.class=Some(captures[2].to_owned()), - other=>Err(anyhow::Error::msg(format!("Unimplemented property {other}")))?, +pub enum ScriptWithOverridesError{ + UnimplementedProperty(String), +} + +impl ScriptWithOverrides{ + fn from_source(mut source:String)->Result{ + let mut overrides=PropertiesOverride::default(); + let mut count=0; + for line in source.lines(){ + //only string type properties are supported atm + if let Some(captures)=lazy_regex::regex!(r#"^\-\-\!\s*Properties\.([A-z]\w*)\s*\=\s*"(\w+)"$"#) + .captures(line){ + count+=line.len(); + match &captures[1]{ + "Name"=>overrides.name=Some(captures[2].to_owned()), + "ClassName"=>overrides.class=Some(captures[2].to_owned()), + other=>Err(ScriptWithOverridesError::UnimplementedProperty(other.to_owned()))?, + } + }else{ + break; } - }else{ - break; } + Ok(ScriptWithOverrides{overrides,source:source.split_off(count)}) } - Ok(ScriptWithOverrides{overrides,source:source.split_off(count)}) } -async fn script_node(search_name:&str,mut file:FileWithName,hint:ScriptHint)->AResult{ - //read entire file - let mut buf=String::new(); - file.file.read_to_string(&mut buf).await?; - //regex script according to Properties lines at the top - let script_with_overrides=extract_script_overrides(buf)?; - //script - Ok(CompileNode{ - blacklist:Some(file.name), - name:script_with_overrides.overrides.name.unwrap_or_else(||search_name.to_owned()), - class:match (script_with_overrides.overrides.class.as_deref(),hint){ - (Some("ModuleScript"),_) - |(None,ScriptHint::ModuleScript)=>CompileClass::ModuleScript(script_with_overrides.source), - (Some("LocalScript"),_) - |(None,ScriptHint::LocalScript)=>CompileClass::LocalScript(script_with_overrides.source), - (Some("Script"),_) - |(None,ScriptHint::Script)=>CompileClass::Script(script_with_overrides.source), - other=>Err(anyhow::Error::msg(format!("Invalid hint or class {other:?}")))?, - }, - }) +enum CompileClass{ + Folder, + Script(String), + LocalScript(String), + ModuleScript(String), + Model(Vec), } -async fn model_node(search_name:&str,mut file:FileWithName)->AResult{ - //read entire file - let mut buf=Vec::new(); - file.file.read_to_end(&mut buf).await?; - //model - Ok(CompileNode{ - blacklist:Some(file.name), - name:search_name.to_owned(),//wrong but gets overwritten by internal model name - class:CompileClass::Model(buf), - }) +struct CompileNode{ + name:String, + blacklist:Option, + class:CompileClass, } -async fn locate_override_file(entry:&tokio::fs::DirEntry,style:Option)->AResult{ - let contents_folder=entry.path(); - let file_name=entry.file_name(); - let search_name=file_name.to_str().unwrap(); - //scan inside the folder for an object to define the class of the folder - let script_query=async {match style{ - Some(DecompileStyle::Rox)=>QuerySingle::rox(&contents_folder,search_name).resolve().await, - Some(DecompileStyle::RoxRojo)=>QueryQuad::rox_rojo(&contents_folder,search_name).resolve().await, - Some(DecompileStyle::Rojo)=>QueryTriple::rojo(&contents_folder).resolve().await, - //try all three and complain if there is ambiguity - None=>mega_triple_join(tokio::join!( - QuerySingle::rox(&contents_folder,search_name).resolve(), - //true=search for module here to avoid ambiguity with QuerySingle::rox results - QueryTriple::rox_rojo(&contents_folder,search_name,true).resolve(), - QueryTriple::rojo(&contents_folder).resolve(), - )) - }}; - //model files are rox & rox-rojo only, so it's a lot less work... - let model_query=get_file_async(contents_folder.clone(),format!("{}.rbxmx",search_name)); - //model? script? both? - Ok(match tokio::join!(script_query,model_query){ - (Ok(FileHint{file,hint}),Err(QueryResolveError::NotFound))=>script_node(search_name,file,hint).await?, - (Err(QueryResolveError::NotFound),Ok(file))=>model_node(search_name,file).await?, - (Ok(_),Ok(_))=>Err(QueryResolveError::Ambiguous)?, - //neither - (Err(QueryResolveError::NotFound),Err(QueryResolveError::NotFound))=>CompileNode{ - name:search_name.to_owned(), - blacklist:None, - class:CompileClass::Folder, - }, - //other error - (Err(e),_) - |(_,Err(e))=>Err(e)? - }) +enum CompileNodeError{ + IO(std::io::Error), + ScriptWithOverrides(ScriptWithOverridesError), + InvalidHintOrClass(Option,ScriptHint), + QueryResolveError(QueryResolveError), + /// Conversion from OsString to String failed + FileName(std::ffi::OsString), + ExtensionNotSupportedInStyle{ + extension:String, + style:Option, + }, + NoExtension, } - enum FileDiscernment{ Model, Script(ScriptHint), } -async fn discern_file(entry:&tokio::fs::DirEntry,style:Option)->AResult{ - let mut file_name=entry - .file_name() - .into_string() - .map_err(|e|anyhow::Error::msg(format!("insane file name {e:?}")))?; - //reject goobers - let is_goober=match style{ - Some(DecompileStyle::Rojo)=>true, - _=>false, - }; - let (ext_len,file_discernment)={ - if let Some(captures)=lazy_regex::regex!(r"^.*(.module.lua|.client.lua|.server.lua)$") - .captures(file_name.as_str()){ - let ext=&captures[1]; - (ext.len(),match ext{ - ".module.lua"=>{ - if is_goober{ - Err(anyhow::Error::msg(format!("File extension {ext} not supported in style {style:?}")))?; - } - FileDiscernment::Script(ScriptHint::ModuleScript) - }, - ".client.lua"=>FileDiscernment::Script(ScriptHint::LocalScript), - ".server.lua"=>FileDiscernment::Script(ScriptHint::Script), - _=>panic!("Regex failed"), - }) - }else if let Some(captures)=lazy_regex::regex!(r"^.*(.rbxmx|.lua)$") - .captures(file_name.as_str()){ - let ext=&captures[1]; - (ext.len(),match ext{ - ".rbxmx"=>{ - if is_goober{ - Err(anyhow::Error::msg(format!("File extension {ext} not supported in style {style:?}")))?; - } - FileDiscernment::Model - }, - ".lua"=>FileDiscernment::Script(ScriptHint::ModuleScript), - _=>panic!("Regex failed"), - }) - }else{ - return Err(anyhow::Error::msg("No file extension")); - } - }; - file_name.truncate(file_name.len()-ext_len); - let file=tokio::fs::File::open(entry.path()).await?; - Ok(match file_discernment{ - FileDiscernment::Model=>model_node(file_name.as_str(),FileWithName{file,name:file_name.clone()}).await?, - FileDiscernment::Script(hint)=>script_node(file_name.as_str(),FileWithName{file,name:file_name.clone()},hint).await?, - }) +impl CompileNode{ + async fn script(search_name:&str,mut file:FileWithName,hint:ScriptHint)->Result{ + //read entire file + let mut buf=String::new(); + file.file.read_to_string(&mut buf).await.map_err(CompileNodeError::IO)?; + //regex script according to Properties lines at the top + let script_with_overrides=ScriptWithOverrides::from_source(buf).map_err(CompileNodeError::ScriptWithOverrides)?; + //script + Ok(Self{ + blacklist:Some(file.name), + name:script_with_overrides.overrides.name.unwrap_or_else(||search_name.to_owned()), + class:match (script_with_overrides.overrides.class.as_deref(),hint){ + (Some("ModuleScript"),_) + |(None,ScriptHint::ModuleScript)=>CompileClass::ModuleScript(script_with_overrides.source), + (Some("LocalScript"),_) + |(None,ScriptHint::LocalScript)=>CompileClass::LocalScript(script_with_overrides.source), + (Some("Script"),_) + |(None,ScriptHint::Script)=>CompileClass::Script(script_with_overrides.source), + other=>Err(CompileNodeError::InvalidHintOrClass(other.0.map(|s|s.to_owned()),other.1))?, + }, + }) + } + async fn model(search_name:&str,mut file:FileWithName)->Result{ + //read entire file + let mut buf=Vec::new(); + file.file.read_to_end(&mut buf).await.map_err(CompileNodeError::IO)?; + //model + Ok(Self{ + blacklist:Some(file.name), + name:search_name.to_owned(),//wrong but gets overwritten by internal model name + class:CompileClass::Model(buf), + }) + } + + async fn from_folder(entry:&tokio::fs::DirEntry,style:Option)->Result{ + let contents_folder=entry.path(); + let file_name=entry.file_name(); + let search_name=file_name.to_str().unwrap(); + //scan inside the folder for an object to define the class of the folder + let script_query=async {match style{ + Some(DecompileStyle::Rox)=>QuerySingle::rox(&contents_folder,search_name).resolve().await, + Some(DecompileStyle::RoxRojo)=>QueryQuad::rox_rojo(&contents_folder,search_name).resolve().await, + Some(DecompileStyle::Rojo)=>QueryTriple::rojo(&contents_folder).resolve().await, + //try all three and complain if there is ambiguity + None=>mega_triple_join(tokio::join!( + QuerySingle::rox(&contents_folder,search_name).resolve(), + //true=search for module here to avoid ambiguity with QuerySingle::rox results + QueryTriple::rox_rojo(&contents_folder,search_name,true).resolve(), + QueryTriple::rojo(&contents_folder).resolve(), + )) + }}; + //model files are rox & rox-rojo only, so it's a lot less work... + let model_query=get_file_async(contents_folder.clone(),format!("{}.rbxmx",search_name)); + //model? script? both? + Ok(match tokio::join!(script_query,model_query){ + (Ok(FileHint{file,hint}),Err(QueryResolveError::NotFound))=>Self::script(search_name,file,hint).await?, + (Err(QueryResolveError::NotFound),Ok(file))=>Self::model(search_name,file).await?, + (Ok(_),Ok(_))=>Err(CompileNodeError::QueryResolveError(QueryResolveError::Ambiguous))?, + //neither + (Err(QueryResolveError::NotFound),Err(QueryResolveError::NotFound))=>Self{ + name:search_name.to_owned(), + blacklist:None, + class:CompileClass::Folder, + }, + //other error + (Err(e),_) + |(_,Err(e))=>Err(CompileNodeError::QueryResolveError(e))? + }) + } + + async fn from_file(entry:&tokio::fs::DirEntry,style:Option)->Result{ + let mut file_name=entry + .file_name() + .into_string() + .map_err(CompileNodeError::FileName)?; + //reject goobers + let is_goober=match style{ + Some(DecompileStyle::Rojo)=>true, + _=>false, + }; + let (ext_len,file_discernment)={ + if let Some(captures)=lazy_regex::regex!(r"^.*(.module.lua|.client.lua|.server.lua|.rbxmx|.lua)$") + .captures(file_name.as_str()){ + let ext=&captures[1]; + (ext.len(),match ext{ + ".module.lua"=>{ + if is_goober{ + Err(CompileNodeError::ExtensionNotSupportedInStyle{extension:ext.to_owned(),style})?; + } + FileDiscernment::Script(ScriptHint::ModuleScript) + }, + ".client.lua"=>FileDiscernment::Script(ScriptHint::LocalScript), + ".server.lua"=>FileDiscernment::Script(ScriptHint::Script), + ".rbxmx"=>{ + if is_goober{ + Err(CompileNodeError::ExtensionNotSupportedInStyle{extension:ext.to_owned(),style})?; + } + FileDiscernment::Model + }, + ".lua"=>FileDiscernment::Script(ScriptHint::ModuleScript), + _=>panic!("Regex failed"), + }) + }else{ + return Err(CompileNodeError::NoExtension); + } + }; + file_name.truncate(file_name.len()-ext_len); + let file=tokio::fs::File::open(entry.path()).await.map_err(CompileNodeError::IO)?; + Ok(match file_discernment{ + FileDiscernment::Model=>Self::model(file_name.as_str(),FileWithName{file,name:file_name.clone()}).await?, + FileDiscernment::Script(hint)=>Self::script(file_name.as_str(),FileWithName{file,name:file_name.clone()},hint).await?, + }) + } } #[derive(Debug)] @@ -348,20 +376,6 @@ enum PreparedData{ Builder(rbx_dom_weak::InstanceBuilder), } -enum CompileClass{ - Folder, - Script(String), - LocalScript(String), - ModuleScript(String), - Model(Vec), -} - -struct CompileNode{ - name:String, - blacklist:Option, - class:CompileClass, -} - enum CompileStackInstruction{ TraverseReferent(rbx_dom_weak::types::Ref,Option), PopFolder, @@ -446,8 +460,8 @@ async fn compile(config:CompileConfig,&mut dom:rbx_dom_weak::WeakDom)->Result<() let met=entry.metadata().await?; //discern that bad boy let compile_class=match met.is_dir(){ - true=>locate_override_file(&entry,style).await?, - false=>discern_file(&entry,style).await?, + true=>CompileNode::from_folder(&entry,style).await?, + false=>CompileNode::from_file(&entry,style).await?, }; //prepare data structure Ok(Some((compile_class.blacklist,match compile_class.class{