From 07bad43e572bc3e3e6fa4b275da1027165c5d764 Mon Sep 17 00:00:00 2001 From: sezna Date: Sat, 22 Jun 2024 15:20:48 -0700 Subject: [PATCH 1/4] add some docs --- petr-bind/src/binder.rs | 15 +++++++++++++-- petr-bind/src/lib.rs | 2 ++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/petr-bind/src/binder.rs b/petr-bind/src/binder.rs index 01ef274..f8c110a 100644 --- a/petr-bind/src/binder.rs +++ b/petr-bind/src/binder.rs @@ -69,21 +69,32 @@ pub struct Module { } pub struct Scope { + /// A `Scope` always has a parent, unless it is the root scope of the user code. + /// All scopes are descendents of one single root scope. parent: Option, + /// A mapping of the symbols that were declared in this scope. Note that any scopes that are + /// children of this scope inherit these symbols as well. items: BTreeMap, #[allow(dead_code)] // this will be read but is also very useful for debugging kind: ScopeKind, } -/// Mainly just used for debugging what generated this scope. +/// Not used in the compiler heavily yet, but extremely useful for understanding what kind of scope +/// you are in. #[derive(Clone, Copy, Debug)] pub enum ScopeKind { + /// A module scope. This is the top level scope for a module. Module(Identifier), + /// A function scope. This is the scope of a function body. Notably, function scopes are where + /// all the function parameters are declared. Function, + /// The root scope of the user code. There is only ever one ScopeKind::Root in a compilation. + /// All scopes are descendents of the root. Root, + /// This might not be needed -- the scope within a type constructor function. TypeConstructor, - // an expression with `let`s + /// For a let... expression, this is the scope of the expression and its bindings. ExpressionWithBindings, } diff --git a/petr-bind/src/lib.rs b/petr-bind/src/lib.rs index 09db5b9..fa59f75 100644 --- a/petr-bind/src/lib.rs +++ b/petr-bind/src/lib.rs @@ -1,4 +1,6 @@ //! Binds symbols from the AST into a symbol map from ID to a node representation. +//! The binder's only job is to create a data structure of all scopes and the symbols that those +//! scopes define. The resolver is then able to do scope-aware name resolution in the next step. pub use binder::{Bind, Binder, FunctionId, Item, Scope, ScopeId, TypeId}; mod binder; From 8a2b7607e6be651cd58d73c53d0bb480b6588d17 Mon Sep 17 00:00:00 2001 From: sezna Date: Sun, 23 Jun 2024 09:16:43 -0700 Subject: [PATCH 2/4] working dependencies from github --- Cargo.lock | 3 + pete/src/main.rs | 163 +++++++++++++++++++++------------ petr-ast/src/ast.rs | 29 +++++- petr-ast/src/pretty_print.rs | 2 +- petr-bind/src/binder.rs | 71 +++++++++++++- petr-bind/src/impls.rs | 4 +- petr-bind/src/lib.rs | 2 +- petr-ir/src/lib.rs | 2 +- petr-manifest/Cargo.toml | 4 - petr-manifest/src/lib.rs | 1 - petr-parse/src/parse_to_ast.rs | 6 +- petr-parse/src/parser.rs | 38 +++++--- petr-parse/src/parser/lexer.rs | 30 +++++- petr-pkg/Cargo.toml | 2 + petr-pkg/src/lib.rs | 63 +++++++++---- petr-pkg/src/manifest.rs | 2 + petr-profiling/src/lib.rs | 4 +- petr-resolve/Cargo.toml | 1 + petr-resolve/src/lib.rs | 12 ++- petr-resolve/src/resolver.rs | 87 ++++++++++++++++-- petr-typecheck/src/lib.rs | 2 +- petr-utils/src/common_types.rs | 22 +++++ 22 files changed, 430 insertions(+), 120 deletions(-) delete mode 100644 petr-manifest/Cargo.toml delete mode 100644 petr-manifest/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 5e15ecd..bda53cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1236,6 +1236,8 @@ dependencies = [ "git2", "petr-fmt", "serde", + "termcolor", + "thiserror", "toml", ] @@ -1251,6 +1253,7 @@ dependencies = [ name = "petr-resolve" version = "0.1.0" dependencies = [ + "either", "expect-test", "petr-ast", "petr-bind", diff --git a/pete/src/main.rs b/pete/src/main.rs index 362acf5..020fe4e 100644 --- a/pete/src/main.rs +++ b/pete/src/main.rs @@ -1,13 +1,14 @@ use std::{ fs, path::{Path, PathBuf}, + rc::Rc, }; use clap::Parser as ClapParser; use petr_ir::Lowerer; use petr_parse::Parser; use petr_pkg::BuildPlan; -use petr_utils::{IndexMap, SourceId, SpannedItem}; +use petr_utils::{Identifier, IndexMap, SourceId, SpannedItem}; use petr_vm::Vm; mod error { @@ -15,9 +16,11 @@ mod error { #[derive(Error, Debug)] pub enum PeteError { #[error(transparent)] - IoError(#[from] std::io::Error), + Io(#[from] std::io::Error), #[error(transparent)] - TomlSeriatlizeError(#[from] toml::ser::Error), + TomlSeriatlize(#[from] toml::ser::Error), + #[error(transparent)] + Pkg(#[from] petr_pkg::error::PkgError), } } @@ -80,58 +83,12 @@ fn main() -> Result<(), error::PeteError> { match cli.command { Commands::Run { target, path, time } => { let mut timings = petr_profiling::Timings::default(); - timings.start("full compile"); - timings.start("load project and dependencies"); - let (lockfile, buf, _build_plan) = load_project_and_dependencies(&path); - let lockfile_toml = toml::to_string(&lockfile).expect("Failed to serialize lockfile to TOML"); - let lockfile_path = path.join("petr.lock"); - fs::write(lockfile_path, lockfile_toml).expect("Failed to write lockfile"); - - timings.end("load project and dependencies"); - - // convert pathbufs into strings for the parser - let buf = buf - .into_iter() - .map(|(pathbuf, s)| (pathbuf.to_string_lossy().to_string(), s)) - .collect::>(); - - timings.start("parse"); - // parse - let parser = Parser::new(buf); - let (ast, parse_errs, interner, source_map) = parser.into_result(); - timings.end("parse"); - - render_errors(parse_errs, &source_map); - // errs.append(&mut parse_errs); - // resolve symbols - timings.start("symbol resolution"); - let (resolution_errs, resolved) = petr_resolve::resolve_symbols(ast, interner); - timings.end("symbol resolution"); - - // TODO impl diagnostic for resolution errors - if !resolution_errs.is_empty() { - dbg!(&resolution_errs); - } - // errs.append(&mut resolution_errs); - - timings.start("type check"); - // type check - let (type_errs, type_checker) = petr_typecheck::type_check(resolved); - - timings.end("type check"); - - // TODO impl diagnostic for type errors - if !type_errs.is_empty() { - dbg!(&type_errs); - } - // errs.append(&mut type_errs); - - timings.start("lowering"); - let lowerer: Lowerer = Lowerer::new(type_checker); - timings.end("lowering"); + let lowerer = compile(path, &mut timings)?; let (data, instructions) = lowerer.finalize(); + timings.end("full compile"); + timings.start("execution"); match target.to_lowercase().as_str() { "vm" => { @@ -144,7 +101,6 @@ fn main() -> Result<(), error::PeteError> { }, } timings.end("execution"); - timings.end("full compile"); if time { println!("{}", timings.render()); } @@ -173,7 +129,7 @@ fn main() -> Result<(), error::PeteError> { } }, Commands::Ir { path } => { - let (lockfile, buf, _build_plan) = load_project_and_dependencies(&path); + let (lockfile, buf, _build_plan) = load_project_and_dependencies(&path)?; let lockfile_toml = toml::to_string(&lockfile)?; let lockfile_path = path.join("petr.lock"); fs::write(lockfile_path, lockfile_toml)?; @@ -191,7 +147,7 @@ fn main() -> Result<(), error::PeteError> { render_errors(parse_errs, &source_map); // errs.append(&mut parse_errs); // resolve symbols - let (resolution_errs, resolved) = petr_resolve::resolve_symbols(ast, interner); + let (resolution_errs, resolved) = petr_resolve::resolve_symbols(ast, interner, todo!("copy dependencies behavior to IR")); // TODO impl diagnostic for resolution errors if !resolution_errs.is_empty() { @@ -215,13 +171,14 @@ fn main() -> Result<(), error::PeteError> { Ok(()) } -fn load_project_and_dependencies(path: &Path) -> (petr_pkg::Lockfile, Vec<(PathBuf, String)>, BuildPlan) { +#[allow(clippy::type_complexity)] +fn load_project_and_dependencies(path: &Path) -> Result<(petr_pkg::Lockfile, Vec<(PathBuf, String)>, BuildPlan), crate::error::PeteError> { let manifest = petr_pkg::manifest::find_manifest(Some(path.to_path_buf())).expect("Failed to find manifest"); let dependencies = manifest.dependencies; - let (lockfile, build_plan) = petr_pkg::load_dependencies(dependencies); + let (lockfile, build_plan) = petr_pkg::load_dependencies(dependencies)?; let files = load_files(path); - (lockfile, files, build_plan) + Ok((lockfile, files, build_plan)) } fn load_files(path: &Path) -> Vec<(PathBuf, String)> { @@ -259,3 +216,93 @@ fn render_errors( eprintln!("{:?}", rendered); } } + +fn compile( + path: PathBuf, + timings: &mut petr_profiling::Timings, +) -> Result { + timings.start("full compile"); + timings.start("load project and dependencies"); + let (lockfile, buf, build_plan) = load_project_and_dependencies(&path)?; + let lockfile_toml = toml::to_string(&lockfile).expect("Failed to serialize lockfile to TOML"); + let lockfile_path = path.join("petr.lock"); + fs::write(lockfile_path, lockfile_toml).expect("Failed to write lockfile"); + timings.end("load project and dependencies"); + + // convert pathbufs into strings for the parser + let buf = buf + .into_iter() + .map(|(pathbuf, s)| (pathbuf.to_string_lossy().to_string(), s)) + .collect::>(); + + timings.start("parsing stage"); + timings.start("parse user code"); + // parse + // construct an interner for symbols, which will be used throughout the whole compilation. + let parser = Parser::new(buf); + let (ast, mut parse_errs, mut interner, mut source_map) = parser.into_result(); + + timings.end("parse user code"); + timings.start("parse dependencies"); + + let mut dependencies = Vec::with_capacity(build_plan.items.len()); + + for item in build_plan.items { + let (lockfile, buf, _build_plan) = load_project_and_dependencies(&item.path_to_source)?; + // TODO(alex) -- transitive dependencies, get these build plans too + let lockfile_toml = toml::to_string(&lockfile)?; + let lockfile_path = path.join("petr.lock"); + fs::write(lockfile_path, lockfile_toml)?; + // the idea here is that we re-use the interner and source map, + // so we don't have to worry about scoping symbol IDs and source IDs to packages + let parser = Parser::new_with_existing_interner_and_source_map( + buf.into_iter() + .map(|(pathbuf, s)| (pathbuf.to_string_lossy().to_string(), s)) + .collect::>(), + interner, + source_map, + ); + let (ast, mut new_parse_errs, new_interner, new_source_map) = parser.into_result(); + interner = new_interner; + parse_errs.append(&mut new_parse_errs); + source_map = new_source_map; + let name = Identifier { + id: interner.insert(Rc::from(item.manifest.name)), + }; + dependencies.push((item.key, name, item.depends_on, ast)); + } + + timings.end("parse dependencies"); + timings.end("parsing stage"); + + render_errors(parse_errs, &source_map); + // errs.append(&mut parse_errs); + // resolve symbols + timings.start("symbol resolution"); + let (resolution_errs, resolved) = petr_resolve::resolve_symbols(ast, interner, dependencies); + timings.end("symbol resolution"); + + // TODO impl diagnostic for resolution errors + if !resolution_errs.is_empty() { + dbg!(&resolution_errs); + } + // errs.append(&mut resolution_errs); + + timings.start("type check"); + // type check + let (type_errs, type_checker) = petr_typecheck::type_check(resolved); + + timings.end("type check"); + + // TODO impl diagnostic for type errors + if !type_errs.is_empty() { + dbg!(&type_errs); + } + // errs.append(&mut type_errs); + + timings.start("lowering"); + let lowerer: Lowerer = Lowerer::new(type_checker); + timings.end("lowering"); + + Ok(lowerer) +} diff --git a/petr-ast/src/ast.rs b/petr-ast/src/ast.rs index 37f4ab1..35e03f1 100644 --- a/petr-ast/src/ast.rs +++ b/petr-ast/src/ast.rs @@ -9,6 +9,31 @@ pub struct Ast { pub modules: Vec, } +impl std::fmt::Debug for Ast { + fn fmt( + &self, + f: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + writeln!(f, "AST")?; + for module in self.modules.iter() { + let path = module.name.iter().map(|x| format!("{}", x.id)).collect::>().join("."); + writeln!(f, "Module: {path}")?; + for node in module.nodes.iter() { + match node.item() { + AstNode::FunctionDeclaration(fun) => writeln!(f, " Function: {}", fun.item().name.id)?, + AstNode::TypeDeclaration(ty) => writeln!(f, " Type: {}", ty.item().name.id)?, + AstNode::ImportStatement(i) => writeln!( + f, + " Import: {}", + i.item().path.iter().map(|x| format!("{}", x.id)).collect::>().join(".") + )?, + } + } + } + Ok(()) + } +} + pub struct Module { pub name: Path, pub nodes: Vec>, @@ -27,7 +52,7 @@ pub enum AstNode { } pub struct ImportStatement { - pub path: Box<[Identifier]>, + pub path: Path, pub alias: Option, pub visibility: Visibility, } @@ -49,7 +74,7 @@ impl TypeDeclaration { } } -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Visibility { Local, Exported, diff --git a/petr-ast/src/pretty_print.rs b/petr-ast/src/pretty_print.rs index c72bd08..e29d914 100644 --- a/petr-ast/src/pretty_print.rs +++ b/petr-ast/src/pretty_print.rs @@ -42,7 +42,7 @@ impl PrettyPrint for ImportStatement { "{}{} {}", " ".repeat(indentation), if self.is_exported() { "export" } else { "import" }, - self.path.iter().map(|id| interner.get(id.id)).collect::>().join("."), + self.path.identifiers.iter().map(|id| interner.get(id.id)).collect::>().join("."), ); if let Some(alias) = self.alias { buf.push_str(&format!(" as {}", interner.get(alias.id))); diff --git a/petr-bind/src/binder.rs b/petr-bind/src/binder.rs index f8c110a..a34ffe9 100644 --- a/petr-bind/src/binder.rs +++ b/petr-bind/src/binder.rs @@ -49,7 +49,7 @@ pub enum Item { Type(TypeId), FunctionParameter(Ty), Module(ModuleId), - Import { path: Box<[Identifier]>, alias: Option }, + Import { path: Path, alias: Option }, } pub struct Binder { @@ -193,6 +193,20 @@ impl Binder { id } + pub fn get_scope( + &self, + scope: ScopeId, + ) -> &Scope { + self.scopes.get(scope) + } + + pub fn get_scope_kind( + &self, + scope: ScopeId, + ) -> ScopeKind { + self.scopes.get(scope).kind + } + fn pop_scope(&mut self) { let _ = self.scope_chain.pop(); } @@ -313,6 +327,61 @@ impl Binder { binder } + pub fn from_ast_and_deps( + ast: &Ast, + // TODO better type here + dependencies: Vec<( + /* Key */ String, + /*Name from manifest*/ Identifier, + /*Things this depends on*/ Vec, + Ast, + )>, + ) -> Self { + let mut binder = Self::new(); + + for dependency in dependencies { + let (key, name, _depends_on, dep_ast) = dependency; + let dep_scope = binder.create_scope_from_path(&Path::new(vec![name])); + binder.with_specified_scope(dep_scope, |binder, scope_id| { + for module in dep_ast.modules { + let module_scope = binder.create_scope_from_path(&module.name); + binder.with_specified_scope(module_scope, |binder, scope_id| { + let exports = module.nodes.iter().filter_map(|node| match node.item() { + petr_ast::AstNode::FunctionDeclaration(decl) => decl.bind(binder), + petr_ast::AstNode::TypeDeclaration(decl) => decl.bind(binder), + petr_ast::AstNode::ImportStatement(stmt) => stmt.bind(binder), + }); + let exports = BTreeMap::from_iter(exports); + // TODO do I need to track this module id? + let _module_id = binder.modules.insert(Module { + root_scope: scope_id, + exports, + }); + }); + } + }) + } + + for module in &ast.modules { + let module_scope = binder.create_scope_from_path(&module.name); + binder.with_specified_scope(module_scope, |binder, scope_id| { + let exports = module.nodes.iter().filter_map(|node| match node.item() { + petr_ast::AstNode::FunctionDeclaration(decl) => decl.bind(binder), + petr_ast::AstNode::TypeDeclaration(decl) => decl.bind(binder), + petr_ast::AstNode::ImportStatement(stmt) => stmt.bind(binder), + }); + let exports = BTreeMap::from_iter(exports); + // TODO do I need to track this module id? + let _module_id = binder.modules.insert(Module { + root_scope: scope_id, + exports, + }); + }); + } + + binder + } + /// given a path, create a scope for each segment. The last scope is returned. /// e.g. for the path "a.b.c", create scopes for "a", "b", and "c", and return the scope for "c" fn create_scope_from_path( diff --git a/petr-bind/src/impls.rs b/petr-bind/src/impls.rs index b17c3ce..d9826cd 100644 --- a/petr-bind/src/impls.rs +++ b/petr-bind/src/impls.rs @@ -87,7 +87,9 @@ impl Bind for ImportStatement { }; // the alias, if any, or the last path element if there is no alias - let name = self.alias.unwrap_or_else(|| *self.path.last().expect("should never be empty")); + let name = self + .alias + .unwrap_or_else(|| *self.path.identifiers.last().expect("should never be empty")); binder.insert_into_current_scope(name.id, item.clone()); diff --git a/petr-bind/src/lib.rs b/petr-bind/src/lib.rs index fa59f75..4ea4fe7 100644 --- a/petr-bind/src/lib.rs +++ b/petr-bind/src/lib.rs @@ -2,6 +2,6 @@ //! The binder's only job is to create a data structure of all scopes and the symbols that those //! scopes define. The resolver is then able to do scope-aware name resolution in the next step. -pub use binder::{Bind, Binder, FunctionId, Item, Scope, ScopeId, TypeId}; +pub use binder::{Bind, Binder, FunctionId, Item, ModuleId, Scope, ScopeId, ScopeKind, TypeId}; mod binder; mod impls; diff --git a/petr-ir/src/lib.rs b/petr-ir/src/lib.rs index 0a87589..81aba62 100644 --- a/petr-ir/src/lib.rs +++ b/petr-ir/src/lib.rs @@ -355,7 +355,7 @@ mod tests { errs.into_iter().for_each(|err| eprintln!("{:?}", render_error(&source_map, err))); panic!("fmt failed: code didn't parse"); } - let (errs, resolved) = resolve_symbols(ast, interner); + let (errs, resolved) = resolve_symbols(ast, interner, Default::default()); if !errs.is_empty() { dbg!(&errs); } diff --git a/petr-manifest/Cargo.toml b/petr-manifest/Cargo.toml deleted file mode 100644 index fec2c97..0000000 --- a/petr-manifest/Cargo.toml +++ /dev/null @@ -1,4 +0,0 @@ -[package] -name = "petr-manifest" -version = "0.1.0" -edition = "2021" diff --git a/petr-manifest/src/lib.rs b/petr-manifest/src/lib.rs deleted file mode 100644 index 8b13789..0000000 --- a/petr-manifest/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/petr-parse/src/parse_to_ast.rs b/petr-parse/src/parse_to_ast.rs index c4b8e64..786ed44 100644 --- a/petr-parse/src/parse_to_ast.rs +++ b/petr-parse/src/parse_to_ast.rs @@ -112,7 +112,7 @@ impl Parse for ImportStatement { let path: Vec = p.sequence_one_or_more(Token::Dot)?; let alias = if p.try_token(Token::As).is_some() { Some(p.parse()?) } else { None }; Some(Self { - path: path.into_boxed_slice(), + path: Path::new(path), visibility, alias, }) @@ -129,7 +129,7 @@ impl Parse for FunctionDeclaration { Token::ExportFunctionKeyword => Visibility::Exported, _ => unreachable!(), }; - let name = p.parse()?; + let name: Identifier = p.parse()?; p.token(Token::OpenParen)?; let parameters = if p.try_token(Token::CloseParen).is_some() { vec![].into_boxed_slice() @@ -366,7 +366,7 @@ fn file_name_to_module_name(name: &str) -> Result>, ParseErrorKind> .collect::>() .into_iter() .rev() - .map(|comp| comp.as_os_str().to_string_lossy().replace("-", "_").replace(".pt", "")) + .map(|comp| comp.as_os_str().to_string_lossy().replace('-', "_").replace(".pt", "")) .map(Rc::from) .collect::>(); if name.iter().any(|part| !is_valid_identifier(part)) { diff --git a/petr-parse/src/parser.rs b/petr-parse/src/parser.rs index 33c2c62..b2c0d30 100644 --- a/petr-parse/src/parser.rs +++ b/petr-parse/src/parser.rs @@ -173,7 +173,11 @@ impl Parser { } } - pub fn new(sources: impl IntoIterator, impl Into)>) -> Self { + pub fn new_with_existing_interner_and_source_map( + sources: impl IntoIterator, impl Into)>, + interner: SymbolInterner, + mut source_map: IndexMap, + ) -> Self { // TODO we hold two copies of the source for now: one in source_maps, and one outside the parser // for the lexer to hold on to and not have to do self-referential pointers. let sources = sources @@ -185,24 +189,30 @@ impl Parser { }) .collect::>(); let sources_for_lexer = sources.iter().map(|(_, source)| *source); + + let lexer = Lexer::new_with_offset_into_sources(sources_for_lexer, source_map.len()); + + for (name, source) in sources.into_iter() { + source_map.insert((name, source)); + } + Self { - interner: SymbolInterner::default(), - lexer: Lexer::new(sources_for_lexer), - errors: Default::default(), - comments: Default::default(), - peek: None, - source_map: { - let mut source_map = IndexMap::default(); - for (name, source) in sources.into_iter() { - source_map.insert((name, source)); - } - source_map - }, - help: Default::default(), + // reuse the interner if provided, otherwise create a new one + interner, + lexer, + errors: Default::default(), + comments: Default::default(), + peek: None, + source_map, + help: Default::default(), file_barrier: false, } } + pub fn new(sources: impl IntoIterator, impl Into)>) -> Self { + Self::new_with_existing_interner_and_source_map(sources, Default::default(), Default::default()) + } + pub fn drain_comments(&mut self) -> Vec { self.comments.drain(..).map(|spanned_item| spanned_item.into_item()).collect() } diff --git a/petr-parse/src/parser/lexer.rs b/petr-parse/src/parser/lexer.rs index 4cfdc54..dc0fc74 100644 --- a/petr-parse/src/parser/lexer.rs +++ b/petr-parse/src/parser/lexer.rs @@ -142,6 +142,7 @@ pub struct Lexer { sources: LexedSources, source: SourceId, has_started_lexing: bool, + offset: usize, } impl Lexer { @@ -156,11 +157,34 @@ impl Lexer { sources: map, source: 0.into(), has_started_lexing: false, + offset: 0, } } + pub fn new_with_offset_into_sources( + sources: impl IntoIterator, + offset: usize, + ) -> Self { + let mut map: IndexMap<_, _> = Default::default(); + let sources = sources.into_iter(); + for source in sources { + let lexer = Token::lexer(source); + map.insert(lexer); + } + Self { + sources: map, + source: 0.into(), + offset, + has_started_lexing: false, + } + } + + pub fn current_source(&self) -> SourceId { + (Into::::into(self.source) + self.offset).into() + } + pub fn span(&self) -> Span { - Span::new(self.source, self.current_lexer().span().into()) + Span::new(self.current_source(), self.current_lexer().span().into()) } pub fn slice(&self) -> &str { @@ -171,13 +195,13 @@ impl Lexer { let pre_advance_span = self.span(); if !self.has_started_lexing { self.has_started_lexing = true; - return self.span().with_item(Token::NewFile(self.source)); + return self.span().with_item(Token::NewFile(self.current_source())); } let current_lexer = self.current_lexer_mut(); match current_lexer.next() { None => match self.advance_lexer() { - Some(_) => self.span().with_item(Token::NewFile(self.source)), + Some(_) => self.span().with_item(Token::NewFile(self.current_source())), None => pre_advance_span.with_item(Token::Eof), }, diff --git a/petr-pkg/Cargo.toml b/petr-pkg/Cargo.toml index 83566c7..375b16d 100644 --- a/petr-pkg/Cargo.toml +++ b/petr-pkg/Cargo.toml @@ -11,3 +11,5 @@ git2 = "0.19.0" serde = { version = "1.0.203", features = ["derive"] } toml = "0.8.14" petr-fmt = { path = "../petr-fmt" } +thiserror = "1.0.61" +termcolor = "1.4.1" diff --git a/petr-pkg/src/lib.rs b/petr-pkg/src/lib.rs index c2d15e8..a9dd277 100644 --- a/petr-pkg/src/lib.rs +++ b/petr-pkg/src/lib.rs @@ -8,6 +8,19 @@ pub mod manifest; use manifest::Manifest; use serde::{Deserialize, Serialize}; +use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; + +pub mod error { + use thiserror::Error; + #[derive(Error, Debug)] + pub enum PkgError { + // TODO stop using generic errors and use codes instead + #[error("Pkg Error: {0}")] + Generic(String), + #[error(transparent)] + Io(#[from] std::io::Error), + } +} #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(untagged)] @@ -49,37 +62,49 @@ use std::{ /// An ordered list of dependencies to build in order. #[derive(Debug)] pub struct BuildPlan { - items: Vec, + pub items: Vec, } #[derive(Debug)] pub struct BuildableItem { - path_to_source: PathBuf, - depends_on: Vec, - key: DependencyKey, + pub path_to_source: PathBuf, + pub depends_on: Vec, + pub key: DependencyKey, + pub manifest: Manifest, } pub type DependencyKey = String; -pub fn load_dependencies(deps: BTreeMap) -> (Lockfile, BuildPlan) { +pub fn load_dependencies(deps: BTreeMap) -> Result<(Lockfile, BuildPlan), crate::error::PkgError> { let mut entries = Vec::new(); let mut files = Vec::new(); - for (_dep_name, dep_source) in deps { + // TODO should probably use dep_name instead of manifest.name so user + // can control how a library shows up in their code + let mut stdout = StandardStream::stdout(ColorChoice::Always); + stdout.set_color(ColorSpec::new().set_bold(true))?; + println!("Fetching {} {}", deps.len(), if deps.len() == 1 { "dependency" } else { "dependencies" }); + + for (dep_name, dep_source) in deps { + // TODO better styling for compilation prints + stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))?; + print!("Fetching "); + stdout.set_color(ColorSpec::new().set_fg(None).set_bold(false))?; + println!("{dep_name}"); let dep = match dep_source { Dependency::Git(ref git_dep) => load_git_dependency(git_dep), Dependency::Path(ref path_dep) => load_path_dependency(path_dep, path_dep.path.clone()), - }; + }?; entries.push(dep.lock.clone()); files.push(dep); } let lockfile = Lockfile { entries }; - (lockfile, order_dependencies(files)) + Ok((lockfile, order_dependencies(files)?)) } -fn order_dependencies(deps: Vec) -> BuildPlan { +fn order_dependencies(deps: Vec) -> Result { let mut graph: BTreeMap> = BTreeMap::new(); for dep in &deps { @@ -168,7 +193,7 @@ fn order_dependencies(deps: Vec) -> BuildPlan { let transient_dep = match dependency { Dependency::Git(git_dep) => load_git_dependency(git_dep), Dependency::Path(path_dep) => load_path_dependency(path_dep, key.clone()), - }; + }?; all_deps.push(transient_dep); } } @@ -191,11 +216,12 @@ fn order_dependencies(deps: Vec) -> BuildPlan { }) .collect(), key, + manifest: dep.manifest.clone(), } }) .collect(); - BuildPlan { items } + Ok(BuildPlan { items }) } #[derive(Clone)] @@ -207,7 +233,7 @@ struct LoadDependencyResult { key: String, } -fn load_git_dependency(dep: &GitDependency) -> LoadDependencyResult { +fn load_git_dependency(dep: &GitDependency) -> Result { // determine an os-independent directory in `~/.petr` to store clones in. On windows this is `C:\Users\\.petr`, on UNIX systems this is `~/.petr` // etc. // clone the git repository into the directory, then read all files in the directory (potentially using load_path_dependency) @@ -218,7 +244,7 @@ fn load_git_dependency(dep: &GitDependency) -> LoadDependencyResult { fs::create_dir_all(&petr_dir).expect("Failed to create .petr directory"); } - let repo_dir = petr_dir.join(dep.git.replace("/", "_")); + let repo_dir = petr_dir.join(dep.git.replace('/', "_")); if !repo_dir.exists() { let _ = git2::Repository::clone(&dep.git, &repo_dir).expect("Failed to clone Git repository"); @@ -234,9 +260,9 @@ fn load_git_dependency(dep: &GitDependency) -> LoadDependencyResult { fn load_path_dependency( dep: &PathDependency, key: DependencyKey, -) -> LoadDependencyResult { +) -> Result { let path = Path::new(&dep.path).join("src"); - let entries = fs::read_dir(&path).unwrap_or_else(|_| panic!("Failed to read directory: {}", dep.path)); + let entries = fs::read_dir(path).unwrap_or_else(|_| panic!("Failed to read directory: {}", dep.path)); let files: Vec<_> = entries .filter_map(|entry| { @@ -254,7 +280,8 @@ fn load_path_dependency( // TODO(alex) canonicalize path dependencies so they are relative to the pete.toml file let petr_toml_path = Path::new(&dep.path).join("pete.toml"); - let manifest_content = fs::read_to_string(&petr_toml_path).expect("Failed to read pete.toml"); + let manifest_content = fs::read_to_string(&petr_toml_path) + .map_err(|e| error::PkgError::Generic(format!("Could not read petr.toml file at {petr_toml_path:?}: {e:?}")))?; let manifest: Manifest = toml::from_str(&manifest_content).expect("Failed to parse pete.toml"); let lockfile_entry = LockfileEntry { @@ -263,12 +290,12 @@ fn load_path_dependency( depends_on: manifest.dependencies.clone(), }; - LoadDependencyResult { + Ok(LoadDependencyResult { lock: lockfile_entry, files, manifest, key, - } + }) } fn calculate_lockfile_hash(sources: Vec<(String, String)>) -> String { diff --git a/petr-pkg/src/manifest.rs b/petr-pkg/src/manifest.rs index 4df6bd5..ec823dd 100644 --- a/petr-pkg/src/manifest.rs +++ b/petr-pkg/src/manifest.rs @@ -6,6 +6,7 @@ use crate::Dependency; pub struct Manifest { pub author: Option, pub license: Option, + pub name: String, #[serde(default)] pub formatter: FormatterConfigManifestFormat, #[serde(default)] @@ -104,6 +105,7 @@ pub fn find_manifest(path: Option) -> Result".into()), license: Some("MIT".into()), formatter: Default::default(), diff --git a/petr-profiling/src/lib.rs b/petr-profiling/src/lib.rs index 75a5bc3..568e5d1 100644 --- a/petr-profiling/src/lib.rs +++ b/petr-profiling/src/lib.rs @@ -32,11 +32,11 @@ impl Timings { key: &'static str, ) { let Some(entry) = self.pending.get_mut(key) else { - eprintln!("Profiling error: tried to end event that didn't exist"); + eprintln!("Profiling error: tried to end event {key} which didn't exist"); return; }; let Some(start) = entry.pop() else { - eprintln!("Profiling error: tried to end event that didn't exist"); + eprintln!("Profiling error: tried to end event {key} which didn't exist"); return; }; self.entries.entry(key).or_default().push(ProfileEntry { time: start.elapsed() }); diff --git a/petr-resolve/Cargo.toml b/petr-resolve/Cargo.toml index 67c9a76..c53e042 100644 --- a/petr-resolve/Cargo.toml +++ b/petr-resolve/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +either = "1.12.0" petr-ast = { path = "../petr-ast" } petr-bind = { path = "../petr-bind" } petr-utils = { path = "../petr-utils", optional = true } diff --git a/petr-resolve/src/lib.rs b/petr-resolve/src/lib.rs index 0501eeb..d9d9da2 100644 --- a/petr-resolve/src/lib.rs +++ b/petr-resolve/src/lib.rs @@ -1,8 +1,9 @@ //! given bindings, fully resolve an AST //! This crate's job is to tee up the type checker for the next stage of compilation. +use petr_ast::Ast; pub use petr_ast::{Intrinsic as IntrinsicName, Literal, Ty}; -use petr_utils::SymbolInterner; +use petr_utils::{Identifier, SymbolInterner}; pub use resolved::QueryableResolvedItems; use resolver::Resolver; pub use resolver::{Expr, ExprKind, Function, FunctionCall, Intrinsic, ResolutionError, Type}; @@ -13,7 +14,14 @@ mod resolver; pub fn resolve_symbols( ast: petr_ast::Ast, interner: SymbolInterner, + // TODO better type here + dependencies: Vec<( + /* Key */ String, + /*Name from manifest*/ Identifier, + /*Things this depends on*/ Vec, + Ast, + )>, ) -> (Vec, QueryableResolvedItems) { - let resolver = Resolver::new_from_single_ast(ast, interner); + let resolver = Resolver::new(ast, interner, dependencies); resolver.into_queryable() } diff --git a/petr-resolve/src/resolver.rs b/petr-resolve/src/resolver.rs index 0e63447..20bf627 100644 --- a/petr-resolve/src/resolver.rs +++ b/petr-resolve/src/resolver.rs @@ -1,5 +1,5 @@ use petr_ast::{Ast, Commented, Expression, FunctionDeclaration, FunctionParameter}; -use petr_bind::{Binder, FunctionId, Item, ScopeId, TypeId}; +use petr_bind::{Binder, FunctionId, Item, ScopeId, ScopeKind, TypeId}; use petr_utils::{Identifier, SpannedItem, SymbolInterner}; use thiserror::Error; @@ -134,6 +134,27 @@ impl Resolver { resolver } + pub fn new( + ast: Ast, + interner: SymbolInterner, + // TODO better type here + dependencies: Vec<( + /* Key */ String, + /*Name from manifest*/ Identifier, + /*Things this depends on*/ Vec, + Ast, + )>, + ) -> Self { + let binder = Binder::from_ast_and_deps(&ast, dependencies); + let mut resolver = Self { + errs: Vec::new(), + resolved: ResolvedItems::new(), + interner, + }; + resolver.add_package(&binder); + resolver + } + pub fn add_package( &mut self, binder: &Binder, @@ -450,7 +471,7 @@ impl Resolve for petr_ast::FunctionCall { ) -> Option { let func_name = self.func_name; let resolved_id = match binder.find_symbol_in_scope(func_name.id, scope_id) { - Some(Item::Function(resolved_id, _func_scope)) => resolved_id, + Some(Item::Function(resolved_id, _func_scope)) => *resolved_id, Some(Item::Import { path, .. }) => { let mut path_iter = path.iter(); let Some(first_item) = ({ @@ -479,7 +500,12 @@ impl Resolve for petr_ast::FunctionCall { match next_symbol { Item::Module(id) => rover = binder.get_module(*id), - Item::Function(func, _scope) if is_last => func_id = Some(func), + Item::Function(func, _scope) if is_last => func_id = Some(*func), + Item::Import { path, alias } => match path.resolve(resolver, binder, scope_id) { + Some(either::Left(func)) => func_id = Some(func), + Some(either::Right(_ty)) => todo!("push error -- tried to call ty as func"), + None => todo!("push error -- import not found"), + }, a => todo!("push error -- import path item is not a module, it is a {a:?}"), } } @@ -502,10 +528,57 @@ impl Resolve for petr_ast::FunctionCall { }) .collect(); - Some(FunctionCall { - function: *resolved_id, - args, - }) + Some(FunctionCall { function: resolved_id, args }) + } +} + +impl Resolve for petr_utils::Path { + // TODO globs + type Resolved = either::Either; + + fn resolve( + &self, + resolver: &mut Resolver, + binder: &Binder, + scope_id: ScopeId, + ) -> Option { + let mut path_iter = self.identifiers.iter(); + let Some(first_item) = ({ + let item = path_iter.next().expect("import with no items was parsed -- should be an invariant"); + binder.find_symbol_in_scope(item.id, scope_id) + }) else { + let name = self.identifiers.iter().map(|x| resolver.interner.get(x.id)).collect::>().join("."); + resolver.errs.push(ResolutionError::NotFound(name)); + return None; + }; + + let first_item = match first_item { + Item::Module(id) => id, + _ => todo!("push error -- import path is not a module"), + }; + + let mut rover = binder.get_module(*first_item); + // iterate over the rest of the path to find the path item + for (ix, item) in path_iter.enumerate() { + let is_last = ix == self.identifiers.len() - 2; // -2 because we advanced the iter by one already + let Some(next_symbol) = binder.find_symbol_in_scope(item.id, rover.root_scope) else { + todo!("push item not found err") + }; + + match next_symbol { + Item::Module(id) => rover = binder.get_module(*id), + Item::Function(func, _scope) if is_last => return Some(either::Either::Left(*func)), + Item::Type(ty) if is_last => return Some(either::Either::Right(*ty)), + Item::Import { path, alias } => match path.resolve(resolver, binder, scope_id) { + Some(either::Left(func)) => return Some(either::Left(func)), + Some(either::Right(ty)) => return Some(either::Right(ty)), + None => todo!("push error -- import not found"), + }, + a => todo!("push error -- import path item is not a module, it is a {a:?}"), + } + } + + todo!("import of module not supported, must be type or function") } } diff --git a/petr-typecheck/src/lib.rs b/petr-typecheck/src/lib.rs index de66646..01f6516 100644 --- a/petr-typecheck/src/lib.rs +++ b/petr-typecheck/src/lib.rs @@ -501,7 +501,7 @@ mod tests { errs.into_iter().for_each(|err| eprintln!("{:?}", render_error(&source_map, err))); panic!("fmt failed: code didn't parse"); } - let (errs, resolved) = resolve_symbols(ast, interner); + let (errs, resolved) = resolve_symbols(ast, interner, Default::default()); assert!(errs.is_empty(), "can't typecheck: unresolved symbols"); let type_checker = TypeChecker::new(resolved); let res = pretty_print_type_checker(type_checker); diff --git a/petr-utils/src/common_types.rs b/petr-utils/src/common_types.rs index 59d7a83..e8b24c2 100644 --- a/petr-utils/src/common_types.rs +++ b/petr-utils/src/common_types.rs @@ -23,6 +23,28 @@ pub struct Path { pub identifiers: Box<[Identifier]>, } +impl Path { + pub fn new(identifiers: Vec) -> Self { + Self { + identifiers: identifiers.into_boxed_slice(), + } + } +} + +impl Path { + pub fn len(&self) -> usize { + self.identifiers.len() + } + + pub fn is_empty(&self) -> bool { + self.identifiers.is_empty() + } + + pub fn iter(&self) -> std::slice::Iter<'_, Identifier> { + self.identifiers.iter() + } +} + #[cfg(not(feature = "debug"))] idx_map_key!( /// The ID of an ident in the symbol interner From 1df4b49c814f1ec7d6afcb1bf63a796301b96dd0 Mon Sep 17 00:00:00 2001 From: sezna Date: Mon, 24 Jun 2024 06:54:25 -0700 Subject: [PATCH 3/4] wip: codegen and type checking for let bindings --- Cargo.lock | 1 + pete/Cargo.toml | 1 + pete/src/main.rs | 57 +++++-------- petr-ast/src/ast.rs | 4 + petr-bind/src/binder.rs | 62 +++++++++----- petr-bind/src/impls.rs | 42 +++++++--- petr-bind/src/lib.rs | 2 +- petr-ir/src/lib.rs | 93 ++++++++++++++------- petr-parse/src/parse_to_ast.rs | 1 + petr-parse/src/parser.rs | 26 ++++-- petr-pkg/src/lib.rs | 5 +- petr-resolve/src/resolved.rs | 6 +- petr-resolve/src/resolver.rs | 39 ++++++--- petr-typecheck/src/lib.rs | 148 ++++++++++++++++++++++++++++++++- 14 files changed, 363 insertions(+), 124 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bda53cd..bf6cb93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1158,6 +1158,7 @@ dependencies = [ "petr-typecheck", "petr-utils", "petr-vm", + "termcolor", "thiserror", "toml", ] diff --git a/pete/Cargo.toml b/pete/Cargo.toml index 1ba8304..da8fac9 100644 --- a/pete/Cargo.toml +++ b/pete/Cargo.toml @@ -19,6 +19,7 @@ petr-profiling = { path = "../petr-profiling" } miette = { version = "7.2.0", features = ["fancy"] } thiserror = "1.0" toml = "0.8" +termcolor = "1.4.1" [features] debug = ["petr-utils/debug", "petr-resolve/debug"] diff --git a/pete/src/main.rs b/pete/src/main.rs index 020fe4e..5f4f0d8 100644 --- a/pete/src/main.rs +++ b/pete/src/main.rs @@ -10,6 +10,7 @@ use petr_parse::Parser; use petr_pkg::BuildPlan; use petr_utils::{Identifier, IndexMap, SourceId, SpannedItem}; use petr_vm::Vm; +use termcolor::{ColorChoice, ColorSpec, StandardStream, WriteColor}; mod error { use thiserror::Error; @@ -129,41 +130,7 @@ fn main() -> Result<(), error::PeteError> { } }, Commands::Ir { path } => { - let (lockfile, buf, _build_plan) = load_project_and_dependencies(&path)?; - let lockfile_toml = toml::to_string(&lockfile)?; - let lockfile_path = path.join("petr.lock"); - fs::write(lockfile_path, lockfile_toml)?; - - // convert pathbufs into strings for the parser - let buf = buf - .into_iter() - .map(|(pathbuf, s)| (pathbuf.to_string_lossy().to_string(), s)) - .collect::>(); - - // parse - let parser = Parser::new(buf); - let (ast, parse_errs, interner, source_map) = parser.into_result(); - - render_errors(parse_errs, &source_map); - // errs.append(&mut parse_errs); - // resolve symbols - let (resolution_errs, resolved) = petr_resolve::resolve_symbols(ast, interner, todo!("copy dependencies behavior to IR")); - - // TODO impl diagnostic for resolution errors - if !resolution_errs.is_empty() { - dbg!(&resolution_errs); - } - // errs.append(&mut resolution_errs); - - // type check - let (type_errs, type_checker) = petr_typecheck::type_check(resolved); - - // TODO impl diagnostic for type errors - if !type_errs.is_empty() { - dbg!(&type_errs); - } - // errs.append(&mut type_errs); - let lowerer: Lowerer = Lowerer::new(type_checker); + let lowerer = compile(path, &mut petr_profiling::Timings::default())?; println!("{}", lowerer.pretty_print()); }, @@ -175,6 +142,26 @@ fn main() -> Result<(), error::PeteError> { fn load_project_and_dependencies(path: &Path) -> Result<(petr_pkg::Lockfile, Vec<(PathBuf, String)>, BuildPlan), crate::error::PeteError> { let manifest = petr_pkg::manifest::find_manifest(Some(path.to_path_buf())).expect("Failed to find manifest"); let dependencies = manifest.dependencies; + let mut stdout = StandardStream::stdout(ColorChoice::Always); + + if !dependencies.is_empty() { + stdout.set_color(ColorSpec::new().set_bold(true))?; + /* + todo!( + "instead of saying fetching, pay attention to if it already exists + and print if it does or doesn't. also, check if checksum agrees with lockfile + and use rev etc on github dep to determine thet key" + ); + */ + println!( + "Fetching {} {} for package {}", + dependencies.len(), + if dependencies.len() == 1 { "dependency" } else { "dependencies" }, + manifest.name + ); + + stdout.set_color(ColorSpec::new().set_bold(false))?; + } let (lockfile, build_plan) = petr_pkg::load_dependencies(dependencies)?; let files = load_files(path); diff --git a/petr-ast/src/ast.rs b/petr-ast/src/ast.rs index 35e03f1..1852558 100644 --- a/petr-ast/src/ast.rs +++ b/petr-ast/src/ast.rs @@ -116,8 +116,12 @@ pub enum Expression { pub struct ExpressionWithBindings { pub bindings: Vec, pub expression: Box, + pub expr_id: ExprId, } +#[derive(Clone, Debug, PartialOrd, Ord, Eq, PartialEq, Copy)] +pub struct ExprId(pub usize); + #[derive(Clone)] pub struct Binding { pub name: Identifier, diff --git a/petr-bind/src/binder.rs b/petr-bind/src/binder.rs index a34ffe9..24ab784 100644 --- a/petr-bind/src/binder.rs +++ b/petr-bind/src/binder.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use petr_ast::{Ast, Expression, FunctionDeclaration, Ty, TypeDeclaration}; +use petr_ast::{Ast, Binding, ExprId, Expression, FunctionDeclaration, Ty, TypeDeclaration}; use petr_utils::{idx_map_key, Identifier, IndexMap, Path, SymbolId}; // TODO: // - i don't know if type cons needs a scope. Might be good to remove that. @@ -16,11 +16,6 @@ idx_map_key!( FunctionParameterId ); -idx_map_key!( - /// The ID type of an Expr. - ExprId -); - idx_map_key!( /// The ID type of a function. FunctionId @@ -55,7 +50,10 @@ pub enum Item { pub struct Binder { scopes: IndexMap>, scope_chain: Vec, - bindings: IndexMap, + /// Some expressions define their own scopes, like expressions with bindings + // TODO rename to expr_scopes + exprs: BTreeMap, + bindings: IndexMap, functions: IndexMap, types: IndexMap, modules: IndexMap, @@ -133,9 +131,14 @@ impl Binder { types: IndexMap::default(), bindings: IndexMap::default(), modules: IndexMap::default(), + exprs: BTreeMap::new(), } } + pub fn current_scope_id(&self) -> ScopeId { + *self.scope_chain.last().expect("there's always at least one scope") + } + pub fn get_function( &self, function_id: FunctionId, @@ -178,8 +181,8 @@ impl Binder { name: SymbolId, item: Item, ) { - let scope_id = self.scope_chain.last().expect("there's always at least one scope"); - self.scopes.get_mut(*scope_id).insert(name, item); + let scope_id = self.current_scope_id(); + self.scopes.get_mut(scope_id).insert(name, item); } fn push_scope( @@ -276,19 +279,21 @@ impl Binder { pub(crate) fn insert_function( &mut self, - arg: &FunctionDeclaration, + func: &FunctionDeclaration, ) -> Option<(Identifier, Item)> { - let function_id = self.functions.insert(arg.clone()); + let function_id = self.functions.insert(func.clone()); let func_body_scope = self.with_scope(ScopeKind::Function, |binder, function_body_scope| { - for param in arg.parameters.iter() { + for param in func.parameters.iter() { binder.insert_into_current_scope(param.name.id, Item::FunctionParameter(param.ty)); } + + func.body.bind(binder); function_body_scope }); let item = Item::Function(function_id, func_body_scope); - self.insert_into_current_scope(arg.name.id, item.clone()); - if arg.is_exported() { - Some((arg.name, item)) + self.insert_into_current_scope(func.name.id, item.clone()); + if func.is_exported() { + Some((func.name, item)) } else { None } @@ -296,7 +301,7 @@ impl Binder { pub(crate) fn insert_binding( &mut self, - binding: Expression, + binding: Binding, ) -> BindingId { self.bindings.insert(binding) } @@ -340,9 +345,9 @@ impl Binder { let mut binder = Self::new(); for dependency in dependencies { - let (key, name, _depends_on, dep_ast) = dependency; + let (_key, name, _depends_on, dep_ast) = dependency; let dep_scope = binder.create_scope_from_path(&Path::new(vec![name])); - binder.with_specified_scope(dep_scope, |binder, scope_id| { + binder.with_specified_scope(dep_scope, |binder, _scope_id| { for module in dep_ast.modules { let module_scope = binder.create_scope_from_path(&module.name); binder.with_specified_scope(module_scope, |binder, scope_id| { @@ -388,7 +393,7 @@ impl Binder { &mut self, path: &Path, ) -> ScopeId { - let mut current_scope_id = *self.scope_chain.last().expect("there's always one scope: invariant"); + let mut current_scope_id = self.current_scope_id(); for segment in path.identifiers.iter() { let next_scope = self.create_scope(ScopeKind::Module(*segment)); let module = Module { @@ -422,7 +427,7 @@ impl Binder { pub fn get_binding( &self, binding_id: BindingId, - ) -> &Expression { + ) -> &Binding { self.bindings.get(binding_id) } @@ -431,7 +436,7 @@ impl Binder { kind: ScopeKind, ) -> ScopeId { let scope = Scope { - parent: Some(*self.scope_chain.last().expect("always at least one scope")), + parent: Some(self.current_scope_id()), items: BTreeMap::new(), kind, }; @@ -459,6 +464,21 @@ impl Binder { ) -> impl Iterator { self.scopes.get(scope).items.iter() } + + pub fn insert_expression( + &mut self, + id: ExprId, + scope: ScopeId, + ) { + self.exprs.insert(id, scope); + } + + pub fn get_expr_scope( + &self, + id: ExprId, + ) -> Option { + self.exprs.get(&id).copied() + } } pub trait Bind { diff --git a/petr-bind/src/impls.rs b/petr-bind/src/impls.rs index d9826cd..d1fc3f9 100644 --- a/petr-bind/src/impls.rs +++ b/petr-bind/src/impls.rs @@ -1,7 +1,7 @@ use petr_ast::{Commented, Expression, ExpressionWithBindings, FunctionDeclaration, ImportStatement, TypeDeclaration}; use petr_utils::{Identifier, SpannedItem}; -use crate::{binder::ScopeKind, Bind, Binder, Item}; +use crate::{binder::ScopeKind, Bind, Binder, Item, ScopeId}; impl Bind for TypeDeclaration { type Output = Option<(Identifier, Item)>; @@ -15,29 +15,51 @@ impl Bind for TypeDeclaration { } impl Bind for Expression { + // the scope that the expression lives in type Output = (); fn bind( &self, binder: &mut Binder, - ) { + ) -> Self::Output { + println!("binding expr"); // only lists get their own scope for now match self { Expression::List(list) => { - for item in list.elements.iter() { - item.bind(binder); - } + list.bind(binder); }, - Expression::Binding(ExpressionWithBindings { bindings, expression }) => { - binder.with_scope(ScopeKind::ExpressionWithBindings, |binder, _scope_id| { + Expression::Binding(ExpressionWithBindings { + bindings, + expression, + expr_id, + }) => { + binder.with_scope(ScopeKind::ExpressionWithBindings, |binder, scope_id| { for binding in bindings.iter() { - let binding_id = binder.insert_binding(*expression.clone()); + let binding_id = binder.insert_binding(binding.clone()); binder.insert_into_current_scope(binding.name.id, Item::Binding(binding_id)); } + // TODO: functions get inserted as Items with scopes, so we should probably + // insert bound expressions as an Item with their own scope, not sure how yet. expression.bind(binder); - }); + println!("inserting binder into expression"); + binder.insert_expression(*expr_id, scope_id); + // + }) }, - _ => self.bind(binder), + _ => (), + } + } +} + +impl Bind for petr_ast::List { + type Output = (); + + fn bind( + &self, + binder: &mut Binder, + ) -> Self::Output { + for item in self.elements.iter() { + item.bind(binder); } } } diff --git a/petr-bind/src/lib.rs b/petr-bind/src/lib.rs index 4ea4fe7..46557b2 100644 --- a/petr-bind/src/lib.rs +++ b/petr-bind/src/lib.rs @@ -2,6 +2,6 @@ //! The binder's only job is to create a data structure of all scopes and the symbols that those //! scopes define. The resolver is then able to do scope-aware name resolution in the next step. -pub use binder::{Bind, Binder, FunctionId, Item, ModuleId, Scope, ScopeId, ScopeKind, TypeId}; +pub use binder::{Bind, Binder, BindingId, FunctionId, Item, ModuleId, Scope, ScopeId, ScopeKind, TypeId}; mod binder; mod impls; diff --git a/petr-ir/src/lib.rs b/petr-ir/src/lib.rs index 81aba62..b416479 100644 --- a/petr-ir/src/lib.rs +++ b/petr-ir/src/lib.rs @@ -64,6 +64,7 @@ impl Lowerer { type_checker, variables_in_scope: Default::default(), }; + println!("about to lower all fns"); lowerer.lower_all_functions().expect("errors should get caught before lowering"); lowerer } @@ -102,6 +103,7 @@ impl Lowerer { let func_label = self.new_function_label(); let mut buf = vec![]; self.with_variable_context(|ctx| -> Result<_, _> { + println!("working on fn"); // TODO: func should have type checked types...not just the AST type for (param_name, param_ty) in &func.params { // in order, assign parameters to registers @@ -114,11 +116,13 @@ impl Lowerer { }; buf.push(IrOpcode::StackPop(ty_reg)); // insert param into mapping + ctx.insert_var(param_name, param_reg); } else { todo!("make reg a ptr to the value") } } + println!("func"); // TODO we could support other return dests let return_dest = ReturnDestination::Stack; @@ -150,6 +154,7 @@ impl Lowerer { body: &TypedExpr, return_destination: ReturnDestination, ) -> Result, LoweringError> { + println!("lowering expr"); use TypedExpr::*; match body { @@ -181,22 +186,39 @@ impl Lowerer { List { .. } => todo!(), Unit => todo!(), Variable { name, ty } => { + for (ix, map) in self.variables_in_scope.iter().enumerate() { + println!("scope {}", ix); + for (var, item) in map { + println!("var {}", var); + } + } + println!("looking for var {}", name.id); let var_reg = self - .variables_in_scope - .last() - .expect("should be at least one scope") - .get(&name.id) - .expect("var did not exist TODO err"); + .get_variable(name.id) + .unwrap_or_else(|| panic!("var {} did not exist TODO err", name.id)); Ok(match return_destination { - ReturnDestination::Reg(reg) => vec![IrOpcode::Copy(reg, *var_reg)], + ReturnDestination::Reg(reg) => vec![IrOpcode::Copy(reg, var_reg)], ReturnDestination::Stack => vec![IrOpcode::StackPush(TypedReg { ty: self.to_ir_type(ty), - reg: *var_reg, + reg: var_reg, })], }) }, Intrinsic { ty: _ty, intrinsic } => self.lower_intrinsic(intrinsic, return_destination), ErrorRecovery => Err(LoweringError), + ExprWithBindings { bindings, expression } => self.with_variable_context(|ctx| -> Result<_, _> { + let mut buf = vec![]; + for (name, expr) in bindings { + println!("doing expr with bindings"); + let reg = ctx.fresh_reg(); + let mut expr = ctx.lower_expr(expr, ReturnDestination::Reg(reg))?; + buf.append(&mut expr); + ctx.insert_var(name, reg); + } + let mut expr = ctx.lower_expr(expression, return_destination)?; + buf.append(&mut expr); + Ok(buf) + }), } } @@ -225,6 +247,7 @@ impl Lowerer { Integer => IrTy::Int64, Boolean => IrTy::Boolean, String => IrTy::String, + Variable(_) => todo!("untyped variable"), } } @@ -241,6 +264,7 @@ impl Lowerer { return_destination: ReturnDestination, ) -> Result, LoweringError> { let mut buf = vec![]; + println!("lower intrinsic"); match intrinsic { petr_typecheck::Intrinsic::Puts(arg) => { // puts takes one arg and it is a string @@ -264,6 +288,18 @@ impl Lowerer { Ok(buf) } + fn get_variable( + &self, + id: SymbolId, + ) -> Option { + for scope in self.variables_in_scope.iter().rev() { + if let Some(reg) = scope.get(&id) { + return Some(*reg); + } + } + None + } + /// Produces a new context for variables in a scope to be allocated fn with_variable_context( &mut self, @@ -449,8 +485,8 @@ mod tests { fn func_args() { check( r#" - function add(x in 'int, y in 'int) returns 'int x - function main() returns 'int ~add(1, 2) + function test(x in 'int, y in 'int) returns 'int x + function main() returns 'int ~test(1, 2) "#, expect![[r#" ; DATA_SECTION @@ -477,7 +513,7 @@ mod tests { } #[test] - fn let_bindings() { + fn let_bindings_with_ops() { check( r#" function add(x in 'int, y in 'int) returns 'int @@ -486,26 +522,23 @@ mod tests { + a + b + x y function main() returns 'int ~add(1, 2) "#, - expect![[r#" - ; DATA_SECTION - 0: Int64(1) - 1: Int64(2) - - ; PROGRAM_SECTION - function 0: - 0 pop v0 - 1 pop v1 - 2 push v0 - 3 ret - ENTRY: function 1: - 4 ld v2 datalabel0 - 5 push v2 - 6 ld v3 datalabel1 - 7 push v3 - 8 ppc - 9 jumpi functionid0 - 10 ret - "#]], + expect![[r#""#]], + ); + } + #[test] + fn let_bindings() { + check( + r#" + function hi(x in 'int, y in 'int) returns 'int + let a = x, + b = y, + c = 20, + d = 30, + e = 42, + a + function main() returns 'int ~hi(1, 2) + "#, + expect![[r#""#]], ); } } diff --git a/petr-parse/src/parse_to_ast.rs b/petr-parse/src/parse_to_ast.rs index 786ed44..d7fdf92 100644 --- a/petr-parse/src/parse_to_ast.rs +++ b/petr-parse/src/parse_to_ast.rs @@ -282,6 +282,7 @@ impl Parse for ExpressionWithBindings { Some(ExpressionWithBindings { bindings, expression: Box::new(expression), + expr_id: p.new_expr_id(), }) } } diff --git a/petr-parse/src/parser.rs b/petr-parse/src/parser.rs index b2c0d30..e048461 100644 --- a/petr-parse/src/parser.rs +++ b/petr-parse/src/parser.rs @@ -7,7 +7,7 @@ use std::rc::Rc; use lexer::Lexer; pub use lexer::Token; use miette::Diagnostic; -use petr_ast::{Ast, Comment, List, Module}; +use petr_ast::{Ast, Comment, ExprId, List, Module}; use petr_utils::{IndexMap, SourceId, Span, SpannedItem, SymbolId, SymbolInterner}; use thiserror::Error; @@ -118,14 +118,17 @@ fn format_toks(toks: &[Token]) -> String { type Result = std::result::Result; pub struct Parser { - interner: SymbolInterner, - lexer: Lexer, - errors: Vec>, - comments: Vec>, - peek: Option>, + interner: SymbolInterner, + /// some exprs need to be assigned an ID, because they generate a scope + /// which is stored in the binder and needs to be retrieved later + expr_id_assigner: usize, + lexer: Lexer, + errors: Vec>, + comments: Vec>, + peek: Option>, // the tuple is the file name and content - source_map: IndexMap, - help: Vec, + source_map: IndexMap, + help: Vec, /// whether or not to continue advancing if one source file ends /// TODO can maybe remove this now that modules aren't spanned items file_barrier: bool, @@ -206,9 +209,16 @@ impl Parser { source_map, help: Default::default(), file_barrier: false, + expr_id_assigner: 0, } } + pub fn new_expr_id(&mut self) -> ExprId { + let id = self.expr_id_assigner; + self.expr_id_assigner += 1; + ExprId(id) + } + pub fn new(sources: impl IntoIterator, impl Into)>) -> Self { Self::new_with_existing_interner_and_source_map(sources, Default::default(), Default::default()) } diff --git a/petr-pkg/src/lib.rs b/petr-pkg/src/lib.rs index a9dd277..9f33054 100644 --- a/petr-pkg/src/lib.rs +++ b/petr-pkg/src/lib.rs @@ -79,12 +79,9 @@ pub fn load_dependencies(deps: BTreeMap) -> Result<(Lockfile let mut entries = Vec::new(); let mut files = Vec::new(); + let mut stdout = StandardStream::stdout(ColorChoice::Always); // TODO should probably use dep_name instead of manifest.name so user // can control how a library shows up in their code - let mut stdout = StandardStream::stdout(ColorChoice::Always); - stdout.set_color(ColorSpec::new().set_bold(true))?; - println!("Fetching {} {}", deps.len(), if deps.len() == 1 { "dependency" } else { "dependencies" }); - for (dep_name, dep_source) in deps { // TODO better styling for compilation prints stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))?; diff --git a/petr-resolve/src/resolved.rs b/petr-resolve/src/resolved.rs index d46ee0d..951e47a 100644 --- a/petr-resolve/src/resolved.rs +++ b/petr-resolve/src/resolved.rs @@ -1,9 +1,9 @@ use std::collections::BTreeMap; -use petr_bind::{FunctionId, TypeId}; +use petr_bind::{BindingId, FunctionId, TypeId}; use petr_utils::SymbolInterner; -use crate::resolver::{Function, TypeDeclaration}; +use crate::resolver::{Binding, Function, TypeDeclaration}; /// Contains things that have already been resolved. /// Resolved items cannot be queried during resolution. This is because the resolution /// stage should only query the binder, then the type checking stage can query @@ -12,6 +12,7 @@ use crate::resolver::{Function, TypeDeclaration}; pub(crate) struct ResolvedItems { pub resolved_functions: BTreeMap, pub resolved_types: BTreeMap, + pub bindings: BTreeMap, } impl ResolvedItems { @@ -35,6 +36,7 @@ impl ResolvedItems { Self { resolved_functions: Default::default(), resolved_types: Default::default(), + bindings: Default::default(), } } } diff --git a/petr-resolve/src/resolver.rs b/petr-resolve/src/resolver.rs index 20bf627..bb1f627 100644 --- a/petr-resolve/src/resolver.rs +++ b/petr-resolve/src/resolver.rs @@ -1,5 +1,5 @@ use petr_ast::{Ast, Commented, Expression, FunctionDeclaration, FunctionParameter}; -use petr_bind::{Binder, FunctionId, Item, ScopeId, ScopeKind, TypeId}; +use petr_bind::{Binder, FunctionId, Item, ScopeId, TypeId}; use petr_utils::{Identifier, SpannedItem, SymbolInterner}; use thiserror::Error; @@ -119,7 +119,7 @@ pub struct Intrinsic { } impl Resolver { - // TODO: one for dependencies/packages which creates more scopes in the same binder + #[cfg(test)] pub fn new_from_single_ast( ast: Ast, interner: SymbolInterner, @@ -181,7 +181,17 @@ impl Resolver { FunctionParameter(_ty) => { // I don't think we have to do anything here but not sure }, - Binding(_) => todo!(), + Binding(a) => { + let binding = binder.get_binding(*a); + let resolved_expr = binding.val.resolve(self, binder, scope_id).expect("TODO err"); + self.resolved.bindings.insert( + *a, + crate::resolver::Binding { + name: binding.name, + expression: resolved_expr, + }, + ); + }, // TODO not sure if we can skip this, or if we should resolve it during imports Module(id) => { let module = binder.get_module(*id); @@ -349,7 +359,7 @@ impl Resolve for Expression { Expression::Variable(var) => { let item = match binder.find_symbol_in_scope(var.id, scope_id) { Some(item @ Item::FunctionParameter(_) | item @ Item::Binding(_)) => item, - _ => todo!("variable references non-variable item"), + a => todo!("variable references non-variable item: {a:?}"), /* None => { let var_name = resolver.interner.get(var.id); @@ -361,10 +371,16 @@ impl Resolve for Expression { }; match item { Item::Binding(binding_id) => { - // TODO not sure what to do here - let _binding = binder.get_binding(*binding_id); - todo!() - //Expr::new(ExprKind::Variable { name: *var, ty: todo!() }) + let binding = binder.get_binding(*binding_id); + // let expr = binding.resolve(resolver, binder, scope_id).expect("TODO errs"); + // resolver.resolved.bindings.insert(*binding_id, expr.clone()); + + Expr::new(ExprKind::Variable { + name: *var, + // I Think this works for inference -- instantiating a new generic + // type. Should revisit for soundness. + ty: Type::Generic(binding.name), + }) }, Item::FunctionParameter(ty) => { let ty = match ty.resolve(resolver, binder, scope_id) { @@ -392,6 +408,7 @@ impl Resolve for Expression { Expr::new(ExprKind::Intrinsic(resolved)) }, Expression::Binding(bound_expression) => { + let scope_id = binder.get_expr_scope(bound_expression.expr_id).expect("invariant: scope should exist"); let mut bindings: Vec = Vec::with_capacity(bound_expression.bindings.len()); for binding in &bound_expression.bindings { let rhs = binding.val.resolve(resolver, binder, scope_id)?; @@ -501,7 +518,7 @@ impl Resolve for petr_ast::FunctionCall { match next_symbol { Item::Module(id) => rover = binder.get_module(*id), Item::Function(func, _scope) if is_last => func_id = Some(*func), - Item::Import { path, alias } => match path.resolve(resolver, binder, scope_id) { + Item::Import { path, alias: _ } => match path.resolve(resolver, binder, scope_id) { Some(either::Left(func)) => func_id = Some(func), Some(either::Right(_ty)) => todo!("push error -- tried to call ty as func"), None => todo!("push error -- import not found"), @@ -569,7 +586,7 @@ impl Resolve for petr_utils::Path { Item::Module(id) => rover = binder.get_module(*id), Item::Function(func, _scope) if is_last => return Some(either::Either::Left(*func)), Item::Type(ty) if is_last => return Some(either::Either::Right(*ty)), - Item::Import { path, alias } => match path.resolve(resolver, binder, scope_id) { + Item::Import { path, alias: _ } => match path.resolve(resolver, binder, scope_id) { Some(either::Left(func)) => return Some(either::Left(func)), Some(either::Right(ty)) => return Some(either::Right(ty)), None => todo!("push error -- import not found"), @@ -578,7 +595,7 @@ impl Resolve for petr_utils::Path { } } - todo!("import of module not supported, must be type or function") + todo!("import of module not supported yet, must be type or function") } } diff --git a/petr-typecheck/src/lib.rs b/petr-typecheck/src/lib.rs index 01f6516..c8ed38e 100644 --- a/petr-typecheck/src/lib.rs +++ b/petr-typecheck/src/lib.rs @@ -59,11 +59,13 @@ pub struct TypeChecker { pub type TypeVariable = Type<&'static str>; +/// TODO get rid of this type and use polytype directly pub enum PetrType { Unit, Integer, Boolean, String, + Variable(usize), } impl TypeChecker { @@ -84,6 +86,7 @@ impl TypeChecker { ty if ty == bool_ty => PetrType::Boolean, ty if ty == unit_ty => PetrType::Unit, ty if ty == string_ty => PetrType::String, + Type::Variable(otherwise) => PetrType::Variable(otherwise), other => todo!("{other:?}"), } } @@ -277,6 +280,17 @@ pub enum Intrinsic { Puts(Box), } +impl std::fmt::Debug for Intrinsic { + fn fmt( + &self, + f: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + match self { + Intrinsic::Puts(expr) => write!(f, "puts({:?})", expr), + } + } +} + #[derive(Clone)] pub enum TypedExpr { FunctionCall { @@ -303,6 +317,47 @@ pub enum TypedExpr { }, // TODO put a span here? ErrorRecovery, + ExprWithBindings { + bindings: Vec<(Identifier, TypedExpr)>, + expression: Box, + }, +} + +impl std::fmt::Debug for TypedExpr { + fn fmt( + &self, + f: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + use TypedExpr::*; + match self { + FunctionCall { func, args, .. } => { + write!(f, "function call to {} with args: ", func)?; + for (name, arg) in args { + write!(f, "{}: {:?}, ", name.id, arg)?; + } + Ok(()) + }, + Literal { value, .. } => write!(f, "literal: {}", value), + List { elements, .. } => { + write!(f, "list: [")?; + for elem in elements { + write!(f, "{:?}, ", elem)?; + } + write!(f, "]") + }, + Unit => write!(f, "unit"), + Variable { name, .. } => write!(f, "variable: {}", name.id), + Intrinsic { intrinsic, .. } => write!(f, "intrinsic: {:?}", intrinsic), + ErrorRecovery => write!(f, "error recovery"), + ExprWithBindings { bindings, expression } => { + write!(f, "bindings: ")?; + for (name, expr) in bindings { + write!(f, "{}: {:?}, ", name.id, expr)?; + } + write!(f, "expression: {:?}", expression) + }, + } + } } impl TypedExpr { @@ -316,6 +371,7 @@ impl TypedExpr { Variable { ty, .. } => ty.clone(), Intrinsic { ty, .. } => ty.clone(), ErrorRecovery => tp!(error), + ExprWithBindings { expression, .. } => expression.ty(), } } } @@ -388,7 +444,21 @@ impl TypeCheck for Expr { // type constructor expressions take inputs that should line up with a type decl and return a type todo!() }, - ExprKind::ExpressionWithBindings { .. } => todo!(), + ExprKind::ExpressionWithBindings { bindings, expression } => { + // for each binding, type check the rhs + ctx.with_type_scope(|ctx| { + let mut type_checked_bindings = Vec::with_capacity(bindings.len()); + for binding in bindings { + let binding_ty = binding.expression.type_check(ctx); + type_checked_bindings.push((binding.name, binding_ty)); + } + + TypedExpr::ExprWithBindings { + bindings: type_checked_bindings, + expression: Box::new(expression.type_check(ctx)), + } + }) + }, } } } @@ -487,7 +557,7 @@ mod tests { use expect_test::{expect, Expect}; use petr_resolve::resolve_symbols; - use petr_utils::render_error; + use petr_utils::{render_error, SymbolInterner}; use super::*; fn check( @@ -523,12 +593,25 @@ mod tests { let func = type_checker.resolved.get_function(*id); let name = type_checker.resolved.interner.get(func.name.id); + format!("function {}", name) }, }; s.push_str(&text); s.push_str(" → "); s.push_str(&ty.to_string()); + + s.push('\n'); + match id { + TypeOrFunctionId::TypeId(_) => (), + TypeOrFunctionId::FunctionId(func) => { + let func = type_checker.typed_functions.get(func).unwrap(); + let body = &func.body; + s.push_str(&pretty_print_typed_expr(&type_checker.resolved.interner, body)); + s.push('\n'); + }, + } + s.push('\n'); } @@ -541,6 +624,39 @@ mod tests { s } + fn pretty_print_typed_expr( + interner: &SymbolInterner, + typed_expr: &TypedExpr, + ) -> String { + match typed_expr { + TypedExpr::ExprWithBindings { bindings, expression } => { + let mut s = String::new(); + for (name, expr) in bindings { + let ident = interner.get(name.id); + s.push_str(&format!("{ident}: {:?} ({}),\n", expr, expr.ty())); + } + s.push_str(&format!("{:?} ({})", pretty_print_typed_expr(interner, expression), expression.ty())); + s + }, + TypedExpr::Variable { name, ty } => { + let name = interner.get(name.id); + format!("variable: {name} ({ty})") + }, + + TypedExpr::FunctionCall { func, args, ty } => { + let mut s = String::new(); + s.push_str(&format!("function call to {} with args: ", func)); + for (name, arg) in args { + let name = interner.get(name.id); + s.push_str(&format!("{name}: {:?}, ", arg.ty())); + } + s.push_str(&format!("returns {ty}")); + s + }, + otherwise => format!("{:?}", otherwise), + } + } + #[test] fn identity_resolution_concrete_type() { check( @@ -744,4 +860,32 @@ mod tests { "#]], ); } + + #[test] + fn infer_let_bindings() { + check( + r#" + function hi(x in 'int, y in 'int) returns 'int + let a = x, + b = y, + c = 20, + d = 30, + e = 42, + a +function main() returns 'int ~hi(1, 2)"#, + expect![[r#" + function hi → (int → int) → int + a: variable: symbolid2 (int), + b: variable: symbolid4 (int), + c: literal: 20 (int), + d: literal: 30 (int), + e: literal: 42 (int), + "variable: a (int)" (int) + + function main → int + function call to functionid0 with args: x: Constructed("int", []), y: Constructed("int", []), + + "#]], + ) + } } From 4f1d40e8859d6543690d478de3869be716027226 Mon Sep 17 00:00:00 2001 From: sezna Date: Mon, 24 Jun 2024 07:51:10 -0700 Subject: [PATCH 4/4] progress on bindings --- petr-bind/src/impls.rs | 4 +--- petr-ir/src/lib.rs | 13 ------------- petr-typecheck/src/lib.rs | 11 ++++++++--- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/petr-bind/src/impls.rs b/petr-bind/src/impls.rs index d1fc3f9..a687a47 100644 --- a/petr-bind/src/impls.rs +++ b/petr-bind/src/impls.rs @@ -1,7 +1,7 @@ use petr_ast::{Commented, Expression, ExpressionWithBindings, FunctionDeclaration, ImportStatement, TypeDeclaration}; use petr_utils::{Identifier, SpannedItem}; -use crate::{binder::ScopeKind, Bind, Binder, Item, ScopeId}; +use crate::{binder::ScopeKind, Bind, Binder, Item}; impl Bind for TypeDeclaration { type Output = Option<(Identifier, Item)>; @@ -22,7 +22,6 @@ impl Bind for Expression { &self, binder: &mut Binder, ) -> Self::Output { - println!("binding expr"); // only lists get their own scope for now match self { Expression::List(list) => { @@ -41,7 +40,6 @@ impl Bind for Expression { // TODO: functions get inserted as Items with scopes, so we should probably // insert bound expressions as an Item with their own scope, not sure how yet. expression.bind(binder); - println!("inserting binder into expression"); binder.insert_expression(*expr_id, scope_id); // }) diff --git a/petr-ir/src/lib.rs b/petr-ir/src/lib.rs index b416479..49c17f3 100644 --- a/petr-ir/src/lib.rs +++ b/petr-ir/src/lib.rs @@ -64,7 +64,6 @@ impl Lowerer { type_checker, variables_in_scope: Default::default(), }; - println!("about to lower all fns"); lowerer.lower_all_functions().expect("errors should get caught before lowering"); lowerer } @@ -103,7 +102,6 @@ impl Lowerer { let func_label = self.new_function_label(); let mut buf = vec![]; self.with_variable_context(|ctx| -> Result<_, _> { - println!("working on fn"); // TODO: func should have type checked types...not just the AST type for (param_name, param_ty) in &func.params { // in order, assign parameters to registers @@ -122,7 +120,6 @@ impl Lowerer { todo!("make reg a ptr to the value") } } - println!("func"); // TODO we could support other return dests let return_dest = ReturnDestination::Stack; @@ -154,7 +151,6 @@ impl Lowerer { body: &TypedExpr, return_destination: ReturnDestination, ) -> Result, LoweringError> { - println!("lowering expr"); use TypedExpr::*; match body { @@ -186,13 +182,6 @@ impl Lowerer { List { .. } => todo!(), Unit => todo!(), Variable { name, ty } => { - for (ix, map) in self.variables_in_scope.iter().enumerate() { - println!("scope {}", ix); - for (var, item) in map { - println!("var {}", var); - } - } - println!("looking for var {}", name.id); let var_reg = self .get_variable(name.id) .unwrap_or_else(|| panic!("var {} did not exist TODO err", name.id)); @@ -209,7 +198,6 @@ impl Lowerer { ExprWithBindings { bindings, expression } => self.with_variable_context(|ctx| -> Result<_, _> { let mut buf = vec![]; for (name, expr) in bindings { - println!("doing expr with bindings"); let reg = ctx.fresh_reg(); let mut expr = ctx.lower_expr(expr, ReturnDestination::Reg(reg))?; buf.append(&mut expr); @@ -264,7 +252,6 @@ impl Lowerer { return_destination: ReturnDestination, ) -> Result, LoweringError> { let mut buf = vec![]; - println!("lower intrinsic"); match intrinsic { petr_typecheck::Intrinsic::Puts(arg) => { // puts takes one arg and it is a string diff --git a/petr-typecheck/src/lib.rs b/petr-typecheck/src/lib.rs index c8ed38e..e78d890 100644 --- a/petr-typecheck/src/lib.rs +++ b/petr-typecheck/src/lib.rs @@ -435,9 +435,14 @@ impl TypeCheck for Expr { }, ExprKind::Unit => TypedExpr::Unit, ExprKind::ErrorRecovery => TypedExpr::ErrorRecovery, - ExprKind::Variable { name, ty } => TypedExpr::Variable { - ty: ctx.to_type_var(ty), - name: *name, + ExprKind::Variable { name, ty } => { + // look up variable in scope + // find its expr return type + + TypedExpr::Variable { + ty: ctx.to_type_var(ty), + name: *name, + } }, ExprKind::Intrinsic(intrinsic) => intrinsic.type_check(ctx), ExprKind::TypeConstructor => {