use crate::parser::Module;
use crate::tokenizer::{Token, tokenize};
+
use sorel_ir::*;
use std::collections::{HashSet, HashMap};
use std::path::PathBuf;
use std::rc::Rc;
+use std::cell::RefCell;
use std::include_str;
-use anyhow::{Result, bail};
+use anyhow::{Result, bail, anyhow};
macro_rules! push_num {
($num:ident) => { IR::StackPush(*$num as u64) };
($num:ident, $num_typ:ty) => { IR::StackPush(*$num as $num_typ as u64) };
}
+type WrappedIRModule = Rc<RefCell<IRModule>>;
+
+#[derive(Debug, PartialEq, Clone)]
+enum ModuleID {
+ SourceFile(PathBuf),
+ StdSpecifier(String)
+}
+
+impl Default for ModuleID {
+ fn default() -> Self {
+ ModuleID::StdSpecifier(String::from(""))
+ }
+}
+
+impl ToString for ModuleID {
+ fn to_string(&self) -> String {
+ match self {
+ ModuleID::SourceFile(f) => f.to_string_lossy().to_string(),
+ ModuleID::StdSpecifier(s) => s.clone()
+ }
+ }
+}
+
#[derive(Debug, Default)]
struct IRModule {
data: Vec<IR>,
text: Vec<IR>,
- imports: Vec<Rc<IRModule>>,
+ imports: Vec<WrappedIRModule>,
exports: Vec<String>,
externs: Vec<String>,
// TODO these next two should form an enum, not two options
- source_file: Option<PathBuf>,
- std_specifier: Option<String>,
+ module_id: ModuleID,
number: usize,
}
}
let mut found: Option<usize> = None;
for imported in &self.imports {
+ let imported = imported.borrow();
if imported.exports.contains(name) {
found = Some(imported.number);
// Don't break here, since the last one should win.
}
#[derive(Default)]
-struct ImportTree {
+pub(crate) struct ImportTree {
data: Vec<IR>,
text: Vec<IR>,
- all_modules: HashMap<String, Rc<IRModule>>,
+ all_modules: HashMap<String, WrappedIRModule>,
all_exports: HashSet<String>,
- entrypoint: Rc<IRModule>,
+ entrypoint: WrappedIRModule,
module_count: usize,
collapse_seen: HashSet<String>,
}
}
impl ImportTree {
- fn import(&mut self, importer_dir: &PathBuf, specifier: &str, is_entrypoint: bool) -> Result<Rc<IRModule>> {
- Ok(if specifier.starts_with("std:") {
+ fn import(&mut self, importer_dir: &PathBuf, specifier: &str, is_entrypoint: bool) -> Result<WrappedIRModule> {
+ let (contents, module_id) = if specifier.starts_with("std:") {
if self.all_modules.contains_key(specifier) {
let module = self.all_modules.get(specifier).unwrap().clone();
return Ok(module);
}
let contents = std_import(specifier)?;
- let parsed = &Module::parse(tokenize(&contents)?, is_entrypoint)?;
- let module = self.generate_internal(None, Some(specifier.to_string()), parsed);
- let module = Rc::new(module);
- self.all_modules.insert(specifier.to_string(), module.clone());
- module
+ (contents.to_string(), ModuleID::StdSpecifier(specifier.to_string()))
} else {
let mut path = PathBuf::from(specifier);
if path.is_relative() {
return Ok(module);
}
- let contents = &std::fs::read_to_string(&path)?;
- let tokens = tokenize(&contents)?;
- let parsed = &Module::parse(tokens, is_entrypoint)?;
-
- let module = self.generate_internal(Some(path), None, parsed);
- let module = Rc::new(module);
- self.all_modules.insert(path_key, module.clone());
- if is_entrypoint {
- self.entrypoint = module.clone();
- }
- module
- })
-
+ let contents = std::fs::read_to_string(&path)?;
+ (contents, ModuleID::SourceFile(path))
+ };
+ let tokens = tokenize(&contents)?;
+ let parsed = &Module::parse(tokens, is_entrypoint)?;
+ let module = self.generate_internal(module_id.clone(), parsed)?;
+ let module = Rc::new(RefCell::new(module));
+ self.all_modules.insert(module_id.to_string(), module.clone());
+ if is_entrypoint {
+ self.entrypoint = module.clone();
+ }
+ Ok(module)
}
- fn generate_internal(&mut self, path: Option<PathBuf>, std_specifier: Option<String>, module: &Module) -> IRModule {
+ fn generate_internal(&mut self, module_id: ModuleID, module: &Module) -> Result<IRModule> {
// Eventually these will end up being sections in assembly
let mut text = vec![];
let mut data = vec![];
let mut imports = vec![];
- if let Some(ref path) = path {
- module.imports.iter().for_each(|imported| {
- if let Some(parent_path) = path.parent() {
- match self.import(&parent_path.to_path_buf(), imported, false) {
- Ok(module) => {
- imports.push(module);
- },
- Err(msg) => {
- eprintln!("{}", msg);
- }
- }
- } else {
- }
- });
- } else {
- // We're in a stdlib module, which can only import other stdlib
- // modules.
- module.imports.iter().for_each(|imported| {
- match self.import(&PathBuf::new(), imported, false) {
- Ok(module) => {
- imports.push(module);
- },
- Err(msg) => {
- eprintln!("{}", msg);
- }
- }
- });
- }
-
+
+ let parent_path = match module_id {
+ ModuleID::SourceFile(ref path) => {
+ path.parent().ok_or(anyhow!("no parent for path: {:?}", path))?.to_path_buf()
+ },
+ // A stdlib module can only import other stdlib
+ // modules, so no need for parent path.
+ ModuleID::StdSpecifier(_) => PathBuf::new(),
+ };
+ module.imports.iter().try_for_each(|imported| -> Result<()> {
+ let new_module = self.import(&parent_path, imported, false)?;
+ imports.push(new_module);
+ Ok(())
+ })?;
+
let exports: Vec<_> = module.exports.iter().map(|s| {
self.all_exports.insert(s.to_string());
s.to_string()
let number = self.module_count;
self.module_count += 1;
- IRModule {
+ Ok(IRModule {
text: text.into_iter().flatten().collect::<Vec<_>>(),
data,
imports,
exports,
externs,
- source_file: path,
- std_specifier,
+ module_id,
number,
- }
+ })
}
- fn collapse(&mut self, module: Rc<IRModule>) -> Result<()> {
- let seen_key = if let Some(source_file) = &module.source_file {
- source_file.to_string_lossy().to_string()
- } else {
- module.std_specifier.clone().unwrap()
- };
+ fn collapse(&mut self, module: WrappedIRModule, is_entrypoint: bool) -> Result<()> {
+ let module = module.borrow_mut();
+ let seen_key = module.module_id.to_string();
if self.collapse_seen.contains(&seen_key) {
return Ok(())
}
for imported in module.imports.clone() {
- self.collapse(imported)?;
+ self.collapse(imported, false)?;
}
- let is_entrypoint = module.source_file == self.entrypoint.source_file;
-
let module_number = module.number;
for string in &module.data {
let dir = std::env::current_dir()?;
let mut tree: ImportTree = Default::default();
let module = tree.import(&dir, path, true)?;
- tree.collapse(module)?;
+ tree.collapse(module, true)?;
// TODO remove unused words
Ok(IRObject {
data: tree.data,