summaryrefslogtreecommitdiff
path: root/src/compiler.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/compiler.rs')
-rw-r--r--src/compiler.rs144
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 &macro_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);
+}