use crate::*; /// Compiles multiple source code files into one. pub struct Compiler { pub source_path: PathBuf, pub resolver: Resolver, } impl Compiler { pub fn from_string>(source_code: String, path: P) -> Self { let source_unit = SourceUnit::from_string(source_code, &path, parse_symbols); Self { source_path: path.as_ref().to_path_buf(), resolver: Resolver::new(source_unit) } } pub fn from_path>(path: P) -> Result { let source_unit = SourceUnit::from_path(&path, None, parse_symbols)?; Ok(Self { source_path: path.as_ref().to_path_buf(), resolver: Resolver::new(source_unit) }) } /// Find library files descending from the parent directory. pub fn include_libs_from_parent(&mut self, ext: &str) { if let Some(parent_path) = self.source_path.parent() { let parent_path = parent_path.to_owned(); self.include_libs_from_path(&parent_path, ext); } } /// Find library files at or descending from a path. pub fn include_libs_from_path(&mut self, path: &Path, ext: &str) { let libraries = gather_from_path(path, Some(ext), parse_symbols); self.resolver.add_library_source_units(libraries); self.resolver.resolve(); } /// Find library files from a PATH-style environment variable. pub fn include_libs_from_path_variable(&mut self, name: &str, ext: &str) { let libraries = gather_from_path_variable(name, Some(ext), parse_symbols); self.resolver.add_library_source_units(libraries); self.resolver.resolve(); } pub fn error(&self) -> Option { self.resolver.error() } pub fn get_compiled_source(&self) -> Result { self.resolver.get_merged_source_code(push_source_code) } } /// Parse all symbols from a source code string. fn parse_symbols(source_code: &str, path: Option<&Path>) -> Vec { use syntactic::*; use DefinitionType::*; use SymbolRole::*; let mut symbols = Vec::new(); let mut macro_name: Option = None; let mut parse_arg_list = false; // true if parsing macro argument list let mut after_separator = false; // true if prev token was separator macro_rules! push { ($name:expr, $source:expr, $role:expr) => { symbols.push(Symbol { name: $name, source: $source, role: $role, namespace: match ¯o_name { Some(name) => vec![name.to_owned()], None => vec![], } }) } } for token in SyntacticParser::from_source_code(&source_code, path) { match token.variant { TokenVariant::MacroDefinition(name) => { push!(name.clone(), token.source, Definition(MustPrecedeReference)); macro_name = Some(name); parse_arg_list = true; } TokenVariant::MacroDefinitionTerminator => { macro_name = None; } TokenVariant::LabelDefinition(name) => { push!(name.clone(), token.source, Definition(CanFollowReference)); } TokenVariant::Symbol(name) => if parse_arg_list && after_separator { push!(name, token.source, Definition(MustPrecedeReference)); } else { parse_arg_list = false; push!(name, token.source, Reference); } TokenVariant::Separator => { after_separator = true; continue; } TokenVariant::BlockOpen | TokenVariant::BlockClose => { continue; } TokenVariant::PackedBinaryLiteral(pbl) => { for field in pbl.fields { push!(field.name.to_string(), field.source, Reference) } } TokenVariant::ConstantExpression(expr) => { use ConstantExpressionTokenVariant as TokenVar; for token in expr.tokens { if let TokenVar::SymbolReference(name) = token.variant { push!(name, token.source, Reference); } } } _ => () }; after_separator = false; } return symbols; } /// Push source code to a source compilation string. fn push_source_code(compilation: &mut String, source_file: &SourceFile) { // Skip blank files. let source_code = &source_file.source_code; if source_code.chars().all(|c| c.is_whitespace()) { return; } // Ensure that the previous section is followed by two newline characters. if !compilation.is_empty() { if !compilation.ends_with('\n') { compilation.push('\n'); } if !compilation.ends_with("\n\n") { compilation.push('\n'); } } // Push a path comment and the source code. let path_str = source_file.path.as_os_str().to_string_lossy(); let path_comment = format!("(: {path_str} )\n"); compilation.push_str(&path_comment); compilation.push_str(&source_code); }