[fine] Many more import-related shuffles
This commit is contained in:
parent
994268abb6
commit
a3ae4339cf
8 changed files with 159 additions and 67 deletions
|
|
@ -139,7 +139,7 @@ fn generate_test_for_file(path: PathBuf) -> String {
|
|||
#disabled
|
||||
fn #name() {
|
||||
let source : std::rc::Rc<str> = #contents.into();
|
||||
let mut runtime = crate::test_runtime(source.clone());
|
||||
let mut runtime = crate::test_runtime(#display_path, source.clone());
|
||||
let (_errors, _module) = runtime.load_module("__test__").unwrap();
|
||||
|
||||
#(#assertions)*
|
||||
|
|
|
|||
|
|
@ -661,7 +661,8 @@ fn compile_load_declaration(c: &mut Compiler, t: TreeRef, declaration: &Declarat
|
|||
// Must be a static don't worry about it.
|
||||
Declaration::Class { .. } => return OK,
|
||||
|
||||
Declaration::Import { .. } => todo!(),
|
||||
// fix later
|
||||
Declaration::Import { .. } => ice!(c, t, "import compile not supported"),
|
||||
};
|
||||
|
||||
c.push(instruction);
|
||||
|
|
@ -1000,6 +1001,7 @@ fn compile_statement(c: &mut Compiler, t: TreeRef, gen_value: bool) {
|
|||
let cr = match tree.kind {
|
||||
TreeKind::Error => None,
|
||||
|
||||
TreeKind::Import => compile_import_statement(c, gen_value),
|
||||
TreeKind::Block => compile_block_statement(c, t, gen_value),
|
||||
TreeKind::ClassDecl => compile_class_declaration(c, t, tree, gen_value),
|
||||
TreeKind::ExpressionStatement => compile_expression_statement(c, tree, gen_value),
|
||||
|
|
@ -1288,3 +1290,10 @@ fn compile_for_statement(c: &mut Compiler, tree: &Tree, gen_value: bool) -> CR {
|
|||
|
||||
OK
|
||||
}
|
||||
|
||||
fn compile_import_statement(c: &mut Compiler, gen_value: bool) -> CR {
|
||||
if gen_value {
|
||||
c.push(Instruction::PushNothing);
|
||||
}
|
||||
OK
|
||||
}
|
||||
|
|
|
|||
123
fine/src/lib.rs
123
fine/src/lib.rs
|
|
@ -1,8 +1,8 @@
|
|||
use std::{collections::HashMap, fs, rc::Rc};
|
||||
use std::{collections::HashMap, fs, path::PathBuf, rc::Rc};
|
||||
|
||||
use compiler::compile;
|
||||
use parser::parse;
|
||||
use semantics::{check, Error, Semantics};
|
||||
use semantics::{check, Error, ImportRecord, Semantics};
|
||||
use vm::{eval, Context};
|
||||
|
||||
pub mod compiler;
|
||||
|
|
@ -17,51 +17,69 @@ pub enum ModuleSource {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub enum ModuleLoadError {
|
||||
IO(std::io::Error),
|
||||
IO(String, std::io::Error),
|
||||
}
|
||||
|
||||
pub trait ModuleLoader {
|
||||
fn normalize_module_name(&self, name: String) -> String;
|
||||
fn normalize_module_name(&self, source: &str, name: String) -> String;
|
||||
fn load_module(&self, name: &String) -> Result<ModuleSource, ModuleLoadError>;
|
||||
}
|
||||
|
||||
pub struct StandardModuleLoader {}
|
||||
pub struct StandardModuleLoader {
|
||||
base_path: PathBuf,
|
||||
}
|
||||
|
||||
impl StandardModuleLoader {
|
||||
pub fn new(base_path: PathBuf) -> Self {
|
||||
StandardModuleLoader { base_path }
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleLoader for StandardModuleLoader {
|
||||
fn normalize_module_name(&self, name: String) -> String {
|
||||
match std::fs::canonicalize(&name) {
|
||||
fn normalize_module_name(&self, source: &str, name: String) -> String {
|
||||
let p = self.base_path.join(source).join(name.clone());
|
||||
let result = match std::fs::canonicalize(&p) {
|
||||
Ok(p) => match p.into_os_string().into_string() {
|
||||
Ok(s) => s,
|
||||
Err(_) => name,
|
||||
Err(_e) => {
|
||||
eprintln!("ERROR INTO OS STRING: {}", _e.to_string_lossy());
|
||||
name.clone()
|
||||
}
|
||||
},
|
||||
Err(_) => name,
|
||||
}
|
||||
Err(_e) => {
|
||||
eprintln!("ERROR CANONICAL {}: {_e}", p.display());
|
||||
name.clone()
|
||||
}
|
||||
};
|
||||
eprintln!("**** {source} {name} => {result}");
|
||||
result
|
||||
}
|
||||
|
||||
fn load_module(&self, name: &String) -> Result<ModuleSource, ModuleLoadError> {
|
||||
match fs::read_to_string(name) {
|
||||
Ok(c) => Ok(ModuleSource::SourceText(c)),
|
||||
Err(e) => Err(ModuleLoadError::IO(e)),
|
||||
Err(e) => Err(ModuleLoadError::IO(name.clone(), e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Module {
|
||||
module: Rc<compiler::Module>,
|
||||
id: u64,
|
||||
semantics: Rc<Semantics>,
|
||||
}
|
||||
|
||||
impl Module {
|
||||
pub fn semantics(&self) -> Rc<Semantics> {
|
||||
self.semantics.clone()
|
||||
pub fn id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn compiled(&self) -> Rc<compiler::Module> {
|
||||
self.module.clone()
|
||||
pub fn semantics(&self) -> Rc<Semantics> {
|
||||
self.semantics.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Runtime {
|
||||
next_module_id: u64,
|
||||
modules: HashMap<String, Rc<Module>>,
|
||||
loader: Box<dyn ModuleLoader>,
|
||||
}
|
||||
|
|
@ -69,6 +87,7 @@ pub struct Runtime {
|
|||
impl Runtime {
|
||||
pub fn new(loader: Box<dyn ModuleLoader>) -> Self {
|
||||
Runtime {
|
||||
next_module_id: 0,
|
||||
modules: HashMap::new(),
|
||||
loader,
|
||||
}
|
||||
|
|
@ -77,55 +96,72 @@ impl Runtime {
|
|||
pub fn load_module(&mut self, name: &str) -> Result<(Vec<Error>, Rc<Module>), ModuleLoadError> {
|
||||
let mut init_pending = HashMap::new();
|
||||
let mut names = Vec::new();
|
||||
let name = self.loader.normalize_module_name(name.to_string());
|
||||
let name = self.loader.normalize_module_name("", name.to_string());
|
||||
names.push(name.clone());
|
||||
|
||||
let mut id_assign = self.next_module_id;
|
||||
|
||||
while let Some(name) = names.pop() {
|
||||
if self.modules.contains_key(&name) {
|
||||
if self.modules.contains_key(&name) || init_pending.contains_key(&name) {
|
||||
// Either already loaded or pending load.
|
||||
continue;
|
||||
}
|
||||
if !init_pending.contains_key(&name) {
|
||||
let loaded = self.loader.load_module(&name)?;
|
||||
match loaded {
|
||||
ModuleSource::SourceText(source) => {
|
||||
let source: Rc<str> = source.into();
|
||||
let (tree, lines) = parse(&source);
|
||||
let semantics = Rc::new(Semantics::new(source, tree, lines));
|
||||
|
||||
let mut normalized = Vec::new();
|
||||
for import in semantics.imports() {
|
||||
let import = self.loader.normalize_module_name(import);
|
||||
names.push(import.clone());
|
||||
normalized.push(import);
|
||||
}
|
||||
// TODO: Errors here are bad! Remember, run everything!
|
||||
match self.loader.load_module(&name)? {
|
||||
ModuleSource::SourceText(source) => {
|
||||
let source: Rc<str> = source.into();
|
||||
let (tree, lines) = parse(&source);
|
||||
let semantics = Rc::new(Semantics::new(source, tree, lines));
|
||||
|
||||
init_pending.insert(name, (normalized, semantics));
|
||||
let mut normalized_imports = Vec::new();
|
||||
for import in semantics.imports() {
|
||||
let normalized = self.loader.normalize_module_name(&name, import.clone());
|
||||
|
||||
names.push(normalized.clone());
|
||||
normalized_imports.push((import, normalized));
|
||||
}
|
||||
|
||||
init_pending.insert(name, (id_assign, normalized_imports, semantics));
|
||||
id_assign += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (_, (imports, semantics)) in init_pending.iter() {
|
||||
for (_, (_, imports, semantics)) in init_pending.iter() {
|
||||
let mut import_table = HashMap::new();
|
||||
for import in imports.iter() {
|
||||
let target = if let Some(module) = self.modules.get(&*import) {
|
||||
Rc::downgrade(&module.semantics)
|
||||
for (import, normalized) in imports.iter() {
|
||||
// NOTE: We look up the load(ed|ing) module here by normalized name, because that's how
|
||||
// we track it...
|
||||
let target = if let Some(module) = self.modules.get(&*normalized) {
|
||||
ImportRecord {
|
||||
name: normalized.clone(),
|
||||
module_id: module.id(),
|
||||
semantics: Rc::downgrade(&module.semantics),
|
||||
}
|
||||
} else {
|
||||
Rc::downgrade(&init_pending.get(&*import).unwrap().1)
|
||||
let (module_id, _, semantics) = init_pending.get(&*normalized).unwrap();
|
||||
ImportRecord {
|
||||
name: normalized.clone(),
|
||||
module_id: *module_id,
|
||||
semantics: Rc::downgrade(semantics),
|
||||
}
|
||||
};
|
||||
|
||||
// ...but we set it into the import table here with the name
|
||||
// that the source code used, for more better binding.
|
||||
import_table.insert(import.clone(), target);
|
||||
}
|
||||
semantics.set_imports(import_table);
|
||||
}
|
||||
|
||||
let mut errors = Vec::new();
|
||||
for (name, (_, semantics)) in init_pending.into_iter() {
|
||||
for (name, (id, _, semantics)) in init_pending.into_iter() {
|
||||
check(&semantics);
|
||||
errors.append(&mut semantics.snapshot_errors());
|
||||
let module = compile(&semantics);
|
||||
self.modules
|
||||
.insert(name, Rc::new(Module { semantics, module }));
|
||||
self.modules.insert(name, Rc::new(Module { id, semantics }));
|
||||
}
|
||||
self.next_module_id = id_assign;
|
||||
|
||||
let result = self.modules.get(&name).unwrap().clone();
|
||||
Ok((errors, result))
|
||||
|
|
@ -133,7 +169,7 @@ impl Runtime {
|
|||
}
|
||||
|
||||
pub fn process_file(file: &str) {
|
||||
let mut runtime = Runtime::new(Box::new(StandardModuleLoader {}));
|
||||
let mut runtime = Runtime::new(Box::new(StandardModuleLoader::new(PathBuf::from("."))));
|
||||
|
||||
let (errors, module) = match runtime.load_module(file) {
|
||||
Ok(r) => r,
|
||||
|
|
@ -152,7 +188,8 @@ pub fn process_file(file: &str) {
|
|||
}
|
||||
|
||||
// shrug
|
||||
let module = module.module.clone();
|
||||
let semantics = module.semantics();
|
||||
let module = compile(&semantics);
|
||||
let main_function = module.functions[module.init].clone();
|
||||
|
||||
let mut context = Context::new(module.clone());
|
||||
|
|
|
|||
|
|
@ -104,6 +104,13 @@ impl std::ops::Deref for ClassRef {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ImportRecord {
|
||||
pub name: String,
|
||||
pub module_id: u64,
|
||||
pub semantics: Weak<Semantics>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Type {
|
||||
// Signals a type error. If you receive this then you know that an error
|
||||
|
|
@ -156,7 +163,7 @@ pub enum Type {
|
|||
Alternate(Box<[Type]>),
|
||||
|
||||
// A module of some kind. What module?
|
||||
Module(Rc<str>),
|
||||
Module(ImportRecord),
|
||||
}
|
||||
|
||||
impl Type {
|
||||
|
|
@ -251,7 +258,7 @@ impl fmt::Display for Type {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
Module(name) => write!(f, "module {}", name),
|
||||
Module(name) => write!(f, "module {}", name.name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -583,7 +590,11 @@ fn set_logical_parents(
|
|||
// Process escapes and convert a string constant in source to a runtime String value.
|
||||
pub fn string_constant_to_string(s: &str) -> String {
|
||||
let mut result = String::new();
|
||||
let mut input = s.chars();
|
||||
if s.len() <= 2 {
|
||||
return result;
|
||||
}
|
||||
|
||||
let mut input = s[1..s.len() - 1].chars();
|
||||
while let Some(ch) = input.next() {
|
||||
if ch == '\\' {
|
||||
if let Some(ch) = input.next() {
|
||||
|
|
@ -615,7 +626,7 @@ pub struct Semantics {
|
|||
syntax_tree: Rc<SyntaxTree>,
|
||||
lines: Rc<Lines>,
|
||||
|
||||
import_map: OnceCell<HashMap<String, Weak<Semantics>>>,
|
||||
import_map: OnceCell<HashMap<String, ImportRecord>>,
|
||||
|
||||
// Instead of physical parents, this is the set of *logical* parents.
|
||||
// This is what is used for binding.
|
||||
|
|
@ -661,7 +672,7 @@ impl Semantics {
|
|||
semantics
|
||||
}
|
||||
|
||||
pub fn set_imports(&self, imports: HashMap<String, Weak<Semantics>>) {
|
||||
pub fn set_imports(&self, imports: HashMap<String, ImportRecord>) {
|
||||
self.import_map.set(imports).expect("imports already set");
|
||||
}
|
||||
|
||||
|
|
@ -678,7 +689,22 @@ impl Semantics {
|
|||
}
|
||||
|
||||
pub fn imports(&self) -> Vec<String> {
|
||||
vec![]
|
||||
self.syntax_tree
|
||||
.root()
|
||||
.map(|file| {
|
||||
self.syntax_tree[file]
|
||||
.children_of_kind(&self.syntax_tree, TreeKind::Import)
|
||||
.filter_map(|import| {
|
||||
let tok = self.syntax_tree[import].nth_token(1)?;
|
||||
if tok.kind != TokenKind::String {
|
||||
None
|
||||
} else {
|
||||
Some(string_constant_to_string(tok.as_str(&self.source)))
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or(Vec::new())
|
||||
}
|
||||
|
||||
pub fn snapshot_errors(&self) -> Vec<Error> {
|
||||
|
|
@ -2213,7 +2239,20 @@ impl Semantics {
|
|||
if tok.kind != TokenKind::String {
|
||||
return Some(Type::Error); // Already reported as syntax error
|
||||
}
|
||||
Some(Type::Module(tok.as_str(&self.source).into()))
|
||||
|
||||
// do we bind it here? it's not normalized....
|
||||
let Some(import_map) = self.import_map.get() else {
|
||||
self.internal_compiler_error(None, "import map not initialized");
|
||||
};
|
||||
|
||||
let name = tok.as_str(&self.source);
|
||||
match import_map.get(name) {
|
||||
Some(import) => Some(Type::Module(import.clone())),
|
||||
None => {
|
||||
self.report_error_tree(tree, format!("unable to resolve module import {name}"));
|
||||
Some(Type::Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Really want to TEST THIS also uh can we generate bytecode for functions and call it??
|
||||
|
|
@ -2488,7 +2527,7 @@ pub fn check(s: &Semantics) {
|
|||
TreeKind::WhileStatement => check_while_statement(s, tree),
|
||||
|
||||
TreeKind::Import => {
|
||||
// TODO: Check Import Statement
|
||||
let _ = s.type_of(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
use fine::compiler::{Function, Module};
|
||||
use fine::compiler::{compile, Function, Module};
|
||||
use fine::semantics::{Error, Type};
|
||||
use fine::vm::{eval_export_fn, Context};
|
||||
use fine::{ModuleLoadError, ModuleLoader, ModuleSource, Runtime, StandardModuleLoader};
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::fmt::Write as _;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
||||
fn rebase_section(source_path: &str, section: &str, value: &str) {
|
||||
|
|
@ -135,20 +136,26 @@ struct TestLoader {
|
|||
}
|
||||
|
||||
impl TestLoader {
|
||||
fn new(source: Rc<str>) -> Box<Self> {
|
||||
fn new(base_path: PathBuf, source: Rc<str>) -> Box<Self> {
|
||||
let base_path = base_path
|
||||
.parent()
|
||||
.map(|p| p.to_owned())
|
||||
.unwrap_or(base_path);
|
||||
|
||||
Box::new(TestLoader {
|
||||
source,
|
||||
base: StandardModuleLoader {},
|
||||
base: StandardModuleLoader::new(base_path),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleLoader for TestLoader {
|
||||
fn normalize_module_name(&self, name: String) -> String {
|
||||
fn normalize_module_name(&self, base: &str, name: String) -> String {
|
||||
if name == "__test__" {
|
||||
name
|
||||
} else {
|
||||
self.base.normalize_module_name(name)
|
||||
let base = if base == "__test__" { "" } else { base };
|
||||
self.base.normalize_module_name(base, name)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -161,8 +168,8 @@ impl ModuleLoader for TestLoader {
|
|||
}
|
||||
}
|
||||
|
||||
fn test_runtime(source: Rc<str>) -> Runtime {
|
||||
Runtime::new(TestLoader::new(source))
|
||||
fn test_runtime(_source_path: &str, source: Rc<str>) -> Runtime {
|
||||
Runtime::new(TestLoader::new(_source_path.into(), source))
|
||||
}
|
||||
|
||||
fn assert_type_at(module: Rc<fine::Module>, pos: usize, expected: &str, _source_path: &str) {
|
||||
|
|
@ -237,7 +244,7 @@ fn dump_function(out: &mut String, function: &Function) -> std::fmt::Result {
|
|||
let strings = function.strings();
|
||||
writeln!(out, " strings ({}):", strings.len())?;
|
||||
for (i, s) in strings.iter().enumerate() {
|
||||
writeln!(out, " {}: {}", i, s)?; // TODO: ESCAPE
|
||||
writeln!(out, " {}: \"{}\"", i, s)?; // TODO: ESCAPE
|
||||
}
|
||||
|
||||
let code = function.instructions();
|
||||
|
|
@ -259,7 +266,7 @@ fn dump_module(out: &mut String, module: &Module) -> std::fmt::Result {
|
|||
|
||||
fn assert_compiles_to(module: Rc<fine::Module>, expected: &str, source_path: &str) {
|
||||
let semantics = module.semantics();
|
||||
let module = module.compiled();
|
||||
let module = compile(&semantics);
|
||||
|
||||
let mut actual = String::new();
|
||||
dump_module(&mut actual, &module).expect("no dumping?");
|
||||
|
|
@ -294,7 +301,7 @@ fn assert_no_errors(module: Rc<fine::Module>, errors: &[Error]) {
|
|||
|
||||
fn assert_eval_ok(module: Rc<fine::Module>, expected: &str) {
|
||||
let semantics = module.semantics();
|
||||
let module = module.compiled();
|
||||
let module = compile(&semantics);
|
||||
|
||||
let mut context = Context::new(module.clone());
|
||||
context.init().expect("Unable to initialize module");
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ fun test() -> f64 {
|
|||
// | 1: Return
|
||||
// | function Point (6 args, 0 locals):
|
||||
// | strings (1):
|
||||
// | 0: Point
|
||||
// | 0: "Point"
|
||||
// | code (6):
|
||||
// | 0: LoadArgument(1)
|
||||
// | 1: LoadArgument(0)
|
||||
|
|
@ -47,7 +47,7 @@ fun test() -> f64 {
|
|||
// | 5: Return
|
||||
// | function Line (4 args, 0 locals):
|
||||
// | strings (1):
|
||||
// | 0: Line
|
||||
// | 0: "Line"
|
||||
// | code (6):
|
||||
// | 0: LoadArgument(1)
|
||||
// | 1: LoadArgument(0)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import "./foo" as foo;
|
||||
import "./foo.fine" as foo;
|
||||
|
||||
fun test() -> string {
|
||||
foo.hello() + " world"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue