diff options
Diffstat (limited to 'src/compiler.rs')
-rw-r--r-- | src/compiler.rs | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/src/compiler.rs b/src/compiler.rs new file mode 100644 index 0000000..068c6d5 --- /dev/null +++ b/src/compiler.rs @@ -0,0 +1,144 @@ +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<P: AsRef<Path>>(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<P: AsRef<Path>>(path: P) -> Result<Self, FileError> { + 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<ResolverError> { + self.resolver.error() + } + + pub fn get_compiled_source(&self) -> Result<String, MergeError> { + 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<Symbol> { + use SyntacticTokenVariant as SynVar; + use DefinitionType::*; + use SymbolRole::*; + let mut symbols = Vec::new(); + let mut macro_name: Option<String> = 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 { + SynVar::MacroDefinition(name) => { + push!(name.clone(), token.source, Definition(MustPrecedeReference)); + macro_name = Some(name); + parse_arg_list = true; + } + SynVar::MacroDefinitionTerminator => { + macro_name = None; + } + SynVar::LabelDefinition(name) => { + push!(name.clone(), token.source, Definition(CanFollowReference)); + } + SynVar::Symbol(name) => if parse_arg_list && after_separator { + push!(name, token.source, Definition(MustPrecedeReference)); + } else { + parse_arg_list = false; + push!(name, token.source, Reference); + } + SynVar::Separator => { + after_separator = true; + continue; + } + SynVar::BlockOpen | SynVar::BlockClose => { + continue; + } + SynVar::PackedBinaryLiteral(pbl) => { + for field in pbl.fields { + push!(field.name.to_string(), field.source, Reference) + } + } + SynVar::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); +} |