From 2c69eb3341822a3a0ed508fc60ffb4463e749fd7 Mon Sep 17 00:00:00 2001 From: felipegchi Date: Fri, 18 Nov 2022 19:56:04 -0300 Subject: [PATCH] feat: started to make kind-query --- src/kind-checker/src/compiler/mod.rs | 9 +- src/kind-checker/src/errors.rs | 13 + src/kind-checker/src/lib.rs | 12 +- src/kind-cli/Cargo.toml | 3 + src/kind-cli/src/lib.rs | 9 +- src/kind-cli/src/watch.rs | 86 ++++++ src/kind-derive/src/matching.rs | 10 +- src/kind-driver/src/errors.rs | 10 + src/kind-driver/src/lib.rs | 6 +- src/kind-driver/src/resolution.rs | 6 +- src/kind-parser/src/errors.rs | 23 +- src/kind-pass/src/errors.rs | 59 +++- src/kind-pass/src/unbound/mod.rs | 70 +++-- src/kind-query/Cargo.toml | 3 +- src/kind-query/src/errors.rs | 28 +- src/kind-query/src/graph.rs | 36 ++- src/kind-query/src/lib.rs | 410 ++++++++++++++++++++++++++- src/kind-query/src/names.rs | 22 +- src/kind-report/src/data.rs | 3 +- src/kind-report/src/report.rs | 4 +- src/kind-tree/src/symbol.rs | 14 + 21 files changed, 734 insertions(+), 102 deletions(-) create mode 100644 src/kind-cli/src/watch.rs diff --git a/src/kind-checker/src/compiler/mod.rs b/src/kind-checker/src/compiler/mod.rs index b4de6353..b3db7ea6 100644 --- a/src/kind-checker/src/compiler/mod.rs +++ b/src/kind-checker/src/compiler/mod.rs @@ -83,6 +83,11 @@ fn mk_ctr_name(ident: &QualifiedIdent) -> Box { mk_single_ctr(format!("{}.", ident)) } +fn mk_ctr_name_from_str(ident: &str) -> Box { + // Adds an empty segment (so it just appends a dot in the end) + mk_single_ctr(format!("{}.", ident)) +} + fn span_to_num(span: Span) -> Box { Box::new(Term::Num { numb: span.encode().0, @@ -506,12 +511,12 @@ fn codegen_entry(file: &mut lang::File, entry: &desugared::Entry) { /// Compiles a book into an format that is executed by the /// type checker in HVM. -pub fn codegen_book(book: &Book) -> lang::File { +pub fn codegen_book(book: &Book, functions_to_check: Vec) -> lang::File { let mut file = lang::File { rules: vec![] }; let functions_entry = lang::Rule { lhs: mk_ctr("Functions".to_owned(), vec![]), - rhs: codegen_vec(book.entrs.values().map(|x| mk_ctr_name(&x.name))), + rhs: codegen_vec(functions_to_check.iter().map(|x| mk_ctr_name_from_str(&x))), }; for entry in book.entrs.values() { diff --git a/src/kind-checker/src/errors.rs b/src/kind-checker/src/errors.rs index 9862349f..2084bcda 100644 --- a/src/kind-checker/src/errors.rs +++ b/src/kind-checker/src/errors.rs @@ -58,6 +58,19 @@ fn context_to_subtitles(ctx: &Context, subtitles: &mut Vec) { } impl Diagnostic for TypeError { + fn get_syntax_ctx(&self) -> Option { + match self { + TypeError::UnboundVariable(_, range) => Some(range.ctx), + TypeError::CantInferHole(_, range) => Some(range.ctx), + TypeError::CantInferLambda(_, range) => Some(range.ctx), + TypeError::InvalidCall(_, range) => Some(range.ctx), + TypeError::ImpossibleCase(_, range, _, _) => Some(range.ctx), + TypeError::Inspection(_, range, _) => Some(range.ctx), + TypeError::TooManyArguments(_, range) => Some(range.ctx), + TypeError::TypeMismatch(_, range, _, _) => Some(range.ctx), + } + } + fn to_diagnostic_frame(&self) -> DiagnosticFrame { match self { TypeError::TypeMismatch(ctx, range, detected, expected) => { diff --git a/src/kind-checker/src/lib.rs b/src/kind-checker/src/lib.rs index b3224523..0e960b6f 100644 --- a/src/kind-checker/src/lib.rs +++ b/src/kind-checker/src/lib.rs @@ -19,8 +19,8 @@ const CHECKER_HVM: &str = include_str!("checker.hvm"); /// Generates the checker in a string format that can be /// parsed by HVM. -pub fn gen_checker(book: &Book) -> String { - let base_check_code = compiler::codegen_book(book); +pub fn gen_checker(book: &Book, functions_to_check: Vec) -> String { + let base_check_code = compiler::codegen_book(book, functions_to_check); let mut check_code = CHECKER_HVM.to_string(); check_code.push_str(&base_check_code.to_string()); @@ -29,8 +29,8 @@ pub fn gen_checker(book: &Book) -> String { /// Type checks a dessugared book. It spawns an HVM instance in order /// to run a compiled version of the book -pub fn type_check(book: &Book, tx: Sender>) -> bool { - let check_code = gen_checker(book); +pub fn type_check(book: &Book, tx: Sender>, functions_to_check: Vec) -> bool { + let check_code = gen_checker(book, functions_to_check); let mut runtime = hvm::Runtime::from_code(&check_code).unwrap(); let main = runtime.alloc_code("Kind.API.check_all").unwrap(); @@ -39,7 +39,7 @@ pub fn type_check(book: &Book, tx: Sender>) -> bool { let term = runtime.readback(main); let errs = parse_report(&term) - .expect("Internal Error: Cannot parse the report message from the type checker"); + .expect(&format!("Internal Error: Cannot parse the report message from the type checker: {}", term)); let succeeded = errs.is_empty(); for err in errs { @@ -53,7 +53,7 @@ pub fn type_check(book: &Book, tx: Sender>) -> bool { /// we run the "eval_main" that runs the generated version that both HVM and /// and the checker can understand. pub fn eval_api(book: &Book) -> Box { - let check_code = gen_checker(book); + let check_code = gen_checker(book, book.names.keys().cloned().collect()); let mut runtime = hvm::Runtime::from_code(&check_code).unwrap(); let main = runtime.alloc_code("Kind.API.eval_main").unwrap(); diff --git a/src/kind-cli/Cargo.toml b/src/kind-cli/Cargo.toml index 78ebd748..a7b8b39c 100644 --- a/src/kind-cli/Cargo.toml +++ b/src/kind-cli/Cargo.toml @@ -17,8 +17,11 @@ path = "src/main.rs" kind-driver = { path = "../kind-driver" } kind-report = { path = "../kind-report" } kind-checker = { path = "../kind-checker" } +kind-query = { path = "../kind-query" } clap = { version = "4.0.10", features = ["derive"] } +notify-debouncer-mini = { version = "*", default-features = false } +notify = "5.0.0" [dev-dependencies] diff --git a/src/kind-cli/src/lib.rs b/src/kind-cli/src/lib.rs index eeae1121..35e2f55b 100644 --- a/src/kind-cli/src/lib.rs +++ b/src/kind-cli/src/lib.rs @@ -9,6 +9,9 @@ use kind_report::report::{FileCache, Report}; use kind_report::RenderConfig; use kind_driver as driver; +use watch::watch_session; + +mod watch; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -231,7 +234,7 @@ pub fn run_cli(config: Cli) { driver::desugar_book(session, &PathBuf::from(file.clone())) }) .map(|res| { - print!("Ok"); + print!("{}", res); res }); } @@ -253,8 +256,8 @@ pub fn run_cli(config: Cli) { res }); } - Command::Watch { file: _ } => { - todo!() + Command::Watch { file } => { + watch_session(root, PathBuf::from(file.clone()), render_config) } Command::Repl => { todo!() diff --git a/src/kind-cli/src/watch.rs b/src/kind-cli/src/watch.rs new file mode 100644 index 00000000..208962a2 --- /dev/null +++ b/src/kind-cli/src/watch.rs @@ -0,0 +1,86 @@ +use core::fmt; +use std::{io, path::PathBuf, collections::HashMap}; + +use kind_query::{SessionHandler, Storage, Session}; +use kind_report::{report::{FileCache, Report}, RenderConfig, data::Diagnostic}; + +struct ToWriteFmt(pub T); + +impl fmt::Write for ToWriteFmt +where + T: io::Write, +{ + fn write_str(&mut self, s: &str) -> fmt::Result { + self.0.write_all(s.as_bytes()).map_err(|_| fmt::Error) + } +} + +pub fn render_to_stderr(render_config: &RenderConfig, session: &T, err: &E) +where + T: FileCache, + E: Report, +{ + Report::render( + err, + session, + render_config, + &mut ToWriteFmt(std::io::stderr()), + ) + .unwrap(); +} + +pub struct WatchServer<'a> { + config: RenderConfig<'static>, + mapper: &'a mut HashMap, + inverse: &'a mut HashMap, +} + +impl<'a> SessionHandler for WatchServer<'a> { + fn on_errors(&mut self, storage: &Storage, _uri: usize, errs: Vec>) { + for err in errs { + render_to_stderr(&self.config, storage, &err) + } + } + + fn on_add_file(&mut self, file: PathBuf, id: usize) { + println!("File added {:?} ~ {:?}", file.clone(), id); + self.mapper.insert(file.clone(), id); + self.inverse.insert(id, file); + } + + fn on_rem_file(&mut self, id: usize) { + println!("File remove {:?}", id); + if let Some(res) = self.inverse.remove(&id) { + self.mapper.remove(&res); + } + } + + fn get_id_by_path(&mut self, path: &PathBuf) -> Option { + self.mapper.get(path).cloned() + } +} + +pub fn watch_session(root: PathBuf, path: PathBuf, config: RenderConfig<'static>) { + + let mut mapper = Default::default(); + let mut inverse = Default::default(); + + let mut handler = WatchServer { config, mapper: &mut mapper, inverse: &mut inverse }; + let mut storage = Storage::default(); + let mut session = Session::new(&mut handler, &mut storage, root); + + let main = session.init_project(path); + + session.check_module(main); + + let id = session.get_id_by_path(&PathBuf::from("./D.kind2")).unwrap(); + + session.remove_node(id); + + for (place, node) in &session.storage.graph.nodes { + println!("{} = {:?}", place, node) + } + + session.check_module(main); + +} \ No newline at end of file diff --git a/src/kind-derive/src/matching.rs b/src/kind-derive/src/matching.rs index 4eea57b5..1d03953e 100644 --- a/src/kind-derive/src/matching.rs +++ b/src/kind-derive/src/matching.rs @@ -205,11 +205,11 @@ pub fn derive_match(range: Range, sum: &SumTypeDecl) -> concrete::Entry { spine_params = sum .parameters .extend(&cons.args) - .map(|x| x.name.with_name(|f| format!("_{}_", f))) + .map(|x| x.name.with_name(|f| format!("{}_", f))) .to_vec(); spine = cons .args - .map(|x| x.name.with_name(|f| format!("_{}_", f))) + .map(|x| x.name.with_name(|f| format!("{}_", f))) .to_vec(); args_indices = sp .iter() @@ -227,7 +227,7 @@ pub fn derive_match(range: Range, sum: &SumTypeDecl) -> concrete::Entry { let renames = FxHashMap::from_iter( sum.parameters .extend(&cons.args) - .map(|x| (x.name.to_string(), format!("_{}_", x.name.to_string()))) + .map(|x| (x.name.to_string(), format!("{}_", x.name.to_string()))) .iter() .cloned(), ); @@ -246,12 +246,12 @@ pub fn derive_match(range: Range, sum: &SumTypeDecl) -> concrete::Entry { .parameters .extend(&sum.indices) .extend(&cons.args) - .map(|x| x.name.with_name(|f| format!("_{}_", f))) + .map(|x| x.name.with_name(|f| format!("{}_", f))) .to_vec(); spine = sum .indices .extend(&cons.args) - .map(|x| x.name.with_name(|f| format!("_{}_", f))) + .map(|x| x.name.with_name(|f| format!("{}_", f))) .to_vec(); args_indices = sum .indices diff --git a/src/kind-driver/src/errors.rs b/src/kind-driver/src/errors.rs index c8d4183a..10009d13 100644 --- a/src/kind-driver/src/errors.rs +++ b/src/kind-driver/src/errors.rs @@ -17,6 +17,16 @@ pub(crate) enum DriverError { } impl Diagnostic for DriverError { + fn get_syntax_ctx(&self) -> Option { + match self { + DriverError::CannotFindFile(_) => None, + DriverError::ThereIsntAMain => None, + DriverError::UnboundVariable(v, _) => Some(v[0].range.ctx), + DriverError::MultiplePaths(id, _) => Some(id.range.ctx), + DriverError::DefinedMultipleTimes(fst, _) => Some(fst.range.ctx), + } + } + fn to_diagnostic_frame(&self) -> DiagnosticFrame { match self { DriverError::UnboundVariable(idents, suggestions) => DiagnosticFrame { diff --git a/src/kind-driver/src/lib.rs b/src/kind-driver/src/lib.rs index 36a936b2..52d67d42 100644 --- a/src/kind-driver/src/lib.rs +++ b/src/kind-driver/src/lib.rs @@ -25,7 +25,9 @@ pub fn type_check_book(session: &mut Session, path: &PathBuf) -> Option Box { } pub fn generate_checker(book: &desugared::Book) -> String { - checker::gen_checker(book) + checker::gen_checker(book, book.names.keys().cloned().collect()) } diff --git a/src/kind-driver/src/resolution.rs b/src/kind-driver/src/resolution.rs index 400daa13..c6a95c60 100644 --- a/src/kind-driver/src/resolution.rs +++ b/src/kind-driver/src/resolution.rs @@ -80,15 +80,15 @@ fn try_to_insert_new_name<'a>( book: &'a mut Book, ) -> bool { if let Some(first_occorence) = book.names.get(ident.to_string().as_str()) { - /*session + session .diagnostic_sender .send(Box::new(DriverError::DefinedMultipleTimes( first_occorence.clone(), ident, ))) .unwrap(); - *failed = true;*/ - true + *failed = true; + false } else { book.names.insert(ident.to_string(), ident); true diff --git a/src/kind-parser/src/errors.rs b/src/kind-parser/src/errors.rs index 2b421acf..67ea4180 100644 --- a/src/kind-parser/src/errors.rs +++ b/src/kind-parser/src/errors.rs @@ -2,7 +2,7 @@ //! lexer and the parser. use kind_report::data::{Color, Diagnostic, DiagnosticFrame, Marker, Severity}; -use kind_span::Range; +use kind_span::{Range, SyntaxCtxIndex}; use crate::lexer::tokens::Token; @@ -45,6 +45,27 @@ fn encode_name(encode: EncodeSequence) -> &'static str { } impl Diagnostic for SyntaxError { + + fn get_syntax_ctx(&self) -> Option { + match self { + SyntaxError::UnfinishedString(range) => Some(range.ctx), + SyntaxError::UnfinishedChar(range) => Some(range.ctx), + SyntaxError::UnfinishedComment(range) => Some(range.ctx), + SyntaxError::InvalidEscapeSequence(_, range) => Some(range.ctx), + SyntaxError::InvalidNumberRepresentation(_, range) => Some(range.ctx), + SyntaxError::UnexpectedChar(_, range) => Some(range.ctx), + SyntaxError::UnexpectedToken(_,range, _) => Some(range.ctx), + SyntaxError::LowerCasedDefinition(_, range) => Some(range.ctx), + SyntaxError::NotAClauseOfDef(range, _) => Some(range.ctx), + SyntaxError::Unclosed(range) => Some(range.ctx), + SyntaxError::IgnoreRestShouldBeOnTheEnd(range) => Some(range.ctx), + SyntaxError::UnusedDocString(range) => Some(range.ctx), + SyntaxError::CannotUseUse(range) => Some(range.ctx), + SyntaxError::ImportsCannotHaveAlias(range) => Some(range.ctx), + SyntaxError::InvalidNumberType(_, range) => Some(range.ctx), + } + } + fn to_diagnostic_frame(&self) -> DiagnosticFrame { match self { SyntaxError::UnfinishedString(range) => DiagnosticFrame { diff --git a/src/kind-pass/src/errors.rs b/src/kind-pass/src/errors.rs index bc60ad21..e8facc15 100644 --- a/src/kind-pass/src/errors.rs +++ b/src/kind-pass/src/errors.rs @@ -1,5 +1,6 @@ use kind_report::data::{Color, Diagnostic, DiagnosticFrame, Marker, Severity}; -use kind_span::{Range, Span}; +use kind_span::{Range, Span, SyntaxCtxIndex}; +use kind_tree::symbol::Ident; pub enum Sugar { DoNotation, @@ -32,6 +33,7 @@ pub enum PassError { ShouldBeAParameter(Span, Range), NoFieldCoverage(Range, Vec), CannotPatternMatchOnErased(Range), + UnboundVariable(Vec, Vec), AttributeDoesNotExpectEqual(Range), AttributeDoesNotExpectArgs(Range), @@ -44,8 +46,63 @@ pub enum PassError { // TODO: A way to build an error message with methods impl Diagnostic for PassError { + fn get_syntax_ctx(&self) -> Option { + match self { + PassError::RepeatedVariable(range, _) => Some(range.ctx), + PassError::IncorrectArity(range, _, _, _) => Some(range.ctx), + PassError::DuplicatedNamed(range, _) => Some(range.ctx), + PassError::LetDestructOnlyForRecord(range) => Some(range.ctx), + PassError::LetDestructOnlyForSum(range) => Some(range.ctx), + PassError::NoCoverage(range, _) => Some(range.ctx), + PassError::CannotFindField(range, _, _) => Some(range.ctx), + PassError::CannotFindConstructor(range, _, _) => Some(range.ctx), + PassError::NeedToImplementMethods(range, _) => Some(range.ctx), + PassError::RuleWithIncorrectArity(range, _, _, _) => Some(range.ctx), + PassError::RulesWithInconsistentArity(range) => Some(range[0].0.ctx), + PassError::SugarIsBadlyImplemented(range, _, _) => Some(range.ctx), + PassError::CannotUseIrrelevant(_, range, _) => Some(range.ctx), + PassError::CannotFindAlias(_, range) => Some(range.ctx), + PassError::NotATypeConstructor(range, _) => Some(range.ctx), + PassError::ShouldBeAParameter(_, range) => Some(range.ctx), + PassError::NoFieldCoverage(range, _) => Some(range.ctx), + PassError::CannotPatternMatchOnErased(range) => Some(range.ctx), + PassError::UnboundVariable(ranges, _) => Some(ranges[0].range.ctx), + PassError::AttributeDoesNotExpectEqual(range) => Some(range.ctx), + PassError::AttributeDoesNotExpectArgs(range) => Some(range.ctx), + PassError::InvalidAttributeArgument(range) => Some(range.ctx), + PassError::AttributeExpectsAValue(range) => Some(range.ctx), + PassError::DuplicatedAttributeArgument(range, _) => Some(range.ctx), + PassError::CannotDerive(_, range) => Some(range.ctx), + PassError::AttributeDoesNotExists(range) => Some(range.ctx), + } + } + fn to_diagnostic_frame(&self) -> DiagnosticFrame { match self { + PassError::UnboundVariable(idents, suggestions) => DiagnosticFrame { + code: 100, + severity: Severity::Error, + title: format!("Cannot find the definition '{}'.", idents[0].to_str()), + subtitles: vec![], + hints: vec![if !suggestions.is_empty() { + format!( + "Maybe you're looking for {}", + suggestions.iter().map(|x| format!("'{}'", x)).collect::>().join(", ") + ) + } else { + "Take a look at the rules for name searching at https://kind.kindelia.org/hints/name-search".to_string() + }], + positions: idents + .iter() + .map(|ident| Marker { + position: ident.range, + color: Color::Fst, + text: "Here!".to_string(), + no_code: false, + main: true, + }) + .collect(), + }, PassError::CannotUseIrrelevant(var_decl, place, declarated_place) => { let mut positions = vec![Marker { position: *place, diff --git a/src/kind-pass/src/unbound/mod.rs b/src/kind-pass/src/unbound/mod.rs index 3ba17911..f1507ae9 100644 --- a/src/kind-pass/src/unbound/mod.rs +++ b/src/kind-pass/src/unbound/mod.rs @@ -1,5 +1,6 @@ -//! Collects all the unbound variables and -//! check if patterns are linear. +//! Collects all the unbound variables, +//! check if patterns are linear and check +//! if the name belongs to the current module. //! //! It also gets all of the identifiers used //! by sugars because it's useful to name resolution @@ -31,8 +32,10 @@ pub struct UnboundCollector { // Utils for keeping variables tracking and report duplicated ones. pub context_vars: Vec<(Range, String)>, + // Keep track of top level definitions. pub top_level_defs: FxHashMap, pub unbound_top_level: FxHashMap>, + pub unbound: FxHashMap>, pub emit_errs: bool, } @@ -53,19 +56,31 @@ impl UnboundCollector { } } -pub fn get_module_unbound( +/// Collects all of the unbound variables in a module. +/// +/// Invariant: All qualified ident should be expanded. +pub fn collect_module_info( diagnostic_sender: Sender>, module: &mut Module, emit_errs: bool, -) -> ( - FxHashMap>, - FxHashMap>, -) { - let mut state = UnboundCollector::new(diagnostic_sender, emit_errs); +) -> UnboundCollector { + let mut state = UnboundCollector::new(diagnostic_sender.clone(), emit_errs); state.visit_module(module); - (state.unbound, state.unbound_top_level) + + for idents in state.unbound.values() { + diagnostic_sender.send(Box::new(PassError::UnboundVariable( + idents.to_vec(), + vec![], + ))) + .unwrap(); + } + + state } +/// Collects all of the unbound variables in a book. +/// +/// Invariant: All qualified ident should be expanded. pub fn get_book_unbound( diagnostic_sender: Sender>, book: &mut Book, @@ -83,24 +98,32 @@ impl UnboundCollector { fn visit_top_level_names(&mut self, toplevel: &mut TopLevel) { match toplevel { TopLevel::SumType(sum) => { + debug_assert!(sum.name.aux.is_none()); self.top_level_defs - .insert(sum.name.to_string(), sum.name.range); + .insert(sum.name.get_root(), sum.name.range); for cons in &sum.constructors { let name_cons = sum.name.add_segment(cons.name.to_str()); + debug_assert!(name_cons.aux.is_none()); self.top_level_defs - .insert(name_cons.to_string(), name_cons.range); + .insert(name_cons.get_root(), name_cons.range); } } TopLevel::RecordType(rec) => { - self.top_level_defs - .insert(rec.name.to_string(), rec.name.range); let name_cons = rec.name.add_segment(rec.constructor.to_str()); + + debug_assert!(rec.name.aux.is_none()); + debug_assert!(name_cons.aux.is_none()); + self.top_level_defs - .insert(name_cons.to_string(), name_cons.range); + .insert(rec.name.get_root(), rec.name.range); + self.top_level_defs + .insert(name_cons.get_root(), name_cons.range); } + TopLevel::Entry(entry) => { + debug_assert!(entry.name.aux.is_none()); self.top_level_defs - .insert(entry.name.to_string(), entry.name.range); + .insert(entry.name.get_root(), entry.name.range); } } } @@ -111,11 +134,7 @@ impl Visitor for UnboundCollector { fn visit_ident(&mut self, ident: &mut Ident) { let name = ident.to_str(); - if self - .context_vars - .iter() - .all(|x| x.1 != name) - { + if self.context_vars.iter().all(|x| x.1 != name) { let entry = self .unbound .entry(name.to_string()) @@ -125,19 +144,16 @@ impl Visitor for UnboundCollector { } fn visit_qualified_ident(&mut self, ident: &mut QualifiedIdent) { - if !self.top_level_defs.contains_key(&ident.to_string()) { - let entry = self.unbound_top_level.entry(ident.to_string()).or_default(); + debug_assert!(ident.aux.is_none()); + if !self.top_level_defs.contains_key(&ident.get_root()) { + let entry = self.unbound_top_level.entry(ident.get_root()).or_default(); entry.insert(ident.clone()); } } fn visit_pat_ident(&mut self, ident: &mut PatIdent) { let name = ident.0.to_str(); - if let Some(fst) = self - .context_vars - .iter() - .find(|x| x.1 == name) - { + if let Some(fst) = self.context_vars.iter().find(|x| x.1 == name) { if self.emit_errs { self.errors .send(Box::new(PassError::RepeatedVariable(fst.0, ident.0.range))) diff --git a/src/kind-query/Cargo.toml b/src/kind-query/Cargo.toml index a44f73c2..a1e74564 100644 --- a/src/kind-query/Cargo.toml +++ b/src/kind-query/Cargo.toml @@ -14,4 +14,5 @@ kind-checker = { path = "../kind-checker" } kind-pass = { path = "../kind-pass" } kind-target-hvm = { path = "../kind-target-hvm" } -fxhash = "0.2.1" \ No newline at end of file +fxhash = "0.2.1" +pathdiff = "0.2.1" \ No newline at end of file diff --git a/src/kind-query/src/errors.rs b/src/kind-query/src/errors.rs index c8d4183a..ac1e9771 100644 --- a/src/kind-query/src/errors.rs +++ b/src/kind-query/src/errors.rs @@ -9,14 +9,20 @@ use kind_tree::symbol::{Ident, QualifiedIdent}; /// Describes all of the possible errors inside each /// of the passes inside this crate. pub(crate) enum DriverError { - CannotFindFile(String), UnboundVariable(Vec, Vec), MultiplePaths(QualifiedIdent, Vec), DefinedMultipleTimes(QualifiedIdent, QualifiedIdent), - ThereIsntAMain, } impl Diagnostic for DriverError { + fn get_syntax_ctx(&self) -> Option { + match self { + DriverError::UnboundVariable(v, _) => Some(v[0].range.ctx), + DriverError::MultiplePaths(id, _) => Some(id.range.ctx), + DriverError::DefinedMultipleTimes(fst, _) => Some(fst.range.ctx), + } + } + fn to_diagnostic_frame(&self) -> DiagnosticFrame { match self { DriverError::UnboundVariable(idents, suggestions) => DiagnosticFrame { @@ -83,23 +89,7 @@ impl Diagnostic for DriverError { }, ], }, - DriverError::CannotFindFile(file) => DiagnosticFrame { - code: 103, - severity: Severity::Error, - title: format!("Cannot find file '{}'", file), - subtitles: vec![], - hints: vec![], - positions: vec![], - }, - - DriverError::ThereIsntAMain => DiagnosticFrame { - code: 103, - severity: Severity::Error, - title: format!("Cannot find 'Main' function to run the file."), - subtitles: vec![], - hints: vec![], - positions: vec![], - }, } } + } diff --git a/src/kind-query/src/graph.rs b/src/kind-query/src/graph.rs index d4613229..0f7d87e0 100644 --- a/src/kind-query/src/graph.rs +++ b/src/kind-query/src/graph.rs @@ -1,18 +1,20 @@ -use std::collections::HashMap; - use fxhash::{FxHashSet, FxHashMap}; -struct Node { - data: T, - invalidated: bool, - children: FxHashSet, - parents: FxHashSet, - root: bool, +#[derive(Debug)] +pub struct Node { + pub data: T, + pub invalidated: bool, + pub hash: u64, + pub children: FxHashSet, + pub parents: FxHashSet, + pub root: bool, + pub failed: bool, } +#[derive(Debug)] pub struct Graph { // Using a hashmap to make it easier to add or remove node.s - nodes: FxHashMap>, + pub nodes: FxHashMap>, count: usize, } @@ -23,7 +25,15 @@ impl Default for Graph { } impl Graph { - pub fn add(&mut self, data: T, root: bool) { + pub fn get(&self, id: &usize) -> Option<&Node> { + self.nodes.get(id) + } + + pub fn get_mut(&mut self, id: &usize) -> Option<&mut Node> { + self.nodes.get_mut(id) + } + + pub fn add(&mut self, data: T, hash: u64, root: bool) { self.nodes.insert( self.count, Node { @@ -31,6 +41,8 @@ impl Graph { invalidated: false, children: FxHashSet::default(), parents: FxHashSet::default(), + hash, + failed: false, root }, ); @@ -76,9 +88,9 @@ impl Graph { fx } - fn flood_invalidation(&mut self, node: usize) { + pub fn flood_invalidation(&mut self, node: usize) { if let Some(node) = self.nodes.get_mut(&node) { - if node.invalidated { + if !node.invalidated { node.invalidated = true; for parent in node.parents.clone() { self.flood_invalidation(parent) diff --git a/src/kind-query/src/lib.rs b/src/kind-query/src/lib.rs index b1817e60..6ee48615 100644 --- a/src/kind-query/src/lib.rs +++ b/src/kind-query/src/lib.rs @@ -2,37 +2,419 @@ //! module. It is useful both for LSPs, Watch, Repl //! and many other things. +mod errors; mod graph; mod names; -mod errors; +use std::fs; use std::path::PathBuf; +use std::rc::Rc; +use std::sync::mpsc::Sender; +use errors::DriverError; use fxhash::FxHashMap; +use fxhash::FxHashSet; use graph::Graph; - +use kind_pass::desugar; +use kind_pass::erasure; +use kind_pass::expand; +use kind_pass::unbound; use kind_report::data::Diagnostic; +use kind_report::report::FileCache; +use kind_span::Range; use kind_tree::concrete; +use kind_tree::concrete::Book; +use kind_tree::concrete::Module; +use kind_tree::concrete::TopLevel; +use kind_tree::symbol::QualifiedIdent; -pub struct Resource { - path: PathBuf, - hash: usize, +pub enum Status { + Module, + Entry, +} +pub struct Resource { concrete_tree: concrete::Module, + exposed_entries: FxHashSet, +} - /// Useful for LSP URIs - ext_info: T, +pub struct File { + path: PathBuf, + input: String, + hash: u64, +} + +pub enum Resolution { + Added(T), + Reuse(T), + Fail, +} + +impl Resolution { + pub fn is_fail(&self) -> bool { + matches!(self, Resolution::Fail) + } } #[derive(Default)] -pub struct Session { - graph: Graph, - paths: FxHashMap, - resources: FxHashMap>, +pub struct Storage { + pub graph: Graph, + resources: FxHashMap>, + files: FxHashMap, + entries: FxHashMap, } -impl Session { - pub fn check(&mut self, path: PathBuf) { - +pub struct Session<'a> { + pub storage: &'a mut Storage, + handler: &'a mut dyn SessionHandler, + + root: PathBuf, + count: usize, +} + +impl FileCache for Storage { + fn fetch(&self, ctx: kind_span::SyntaxCtxIndex) -> Option<(PathBuf, &String)> { + let file = self.files.get(&ctx.0).unwrap(); + Some((file.path.clone(), &file.input)) + } +} + +pub trait SessionHandler { + fn on_add_file(&mut self, file: PathBuf, id: usize); + + fn on_rem_file(&mut self, id: usize); + + fn on_errors(&mut self, storage: &Storage, uri: usize, errs: Vec>); + + fn get_id_by_path(&mut self, path: &PathBuf) -> Option; +} + +pub fn add_module_to_book(book: &mut Book, module: &Module) { + for entry in &module.entries { + match entry { + TopLevel::SumType(sum) => { + for cons in &sum.constructors { + let name = sum.name.add_segment(cons.name.to_str()).to_string(); + book.count.insert(name, cons.extract_book_info(&sum)); + } + + let name = sum.name.to_string(); + book.count.insert(name.clone(), sum.extract_book_info()); + book.entries.insert(name, entry.clone()); + } + TopLevel::RecordType(rec) => { + let cons_ident = rec.name.add_segment(rec.constructor.to_str()).to_string(); + book.count + .insert(cons_ident, rec.extract_book_info_of_constructor()); + + let name = rec.name.to_string(); + book.count.insert(name.clone(), rec.extract_book_info()); + book.entries.insert(name, entry.clone()); + } + TopLevel::Entry(entr) => { + let name = entr.name.to_string(); + book.count.insert(name.clone(), entr.extract_book_info()); + book.entries.insert(name, entry.clone()); + } + } + } +} + +impl<'a> Session<'a> { + pub fn new( + handler: &'a mut dyn SessionHandler, + storage: &'a mut Storage, + root: PathBuf, + ) -> Session<'a> { + Session { + storage, + handler, + root, + count: 0, + } + } + + pub fn set_input(&mut self, module_id: usize, input: String) { + if let Some(file) = self.storage.files.get_mut(&module_id) { + self.storage.graph.flood_invalidation(module_id); + + file.hash = fxhash::hash64(&input); + file.input = input; + } else { + todo!() + } + } + + pub fn remove_node(&mut self, module_id: usize) { + self.storage.graph.remove(module_id); + self.storage.files.remove(&module_id); + if let Some(res) = self.storage.resources.remove(&module_id) { + for entry in &res.exposed_entries { + self.storage.entries.remove(entry); + } + }; + self.handler.on_rem_file(module_id) + } + + fn register_module(&mut self, path: PathBuf, _module_name: String, input: String) -> usize { + let module_id = self.count; + self.count += 1; + + let file = File { + path, + hash: fxhash::hash64(&input), + input, + }; + + self.storage.graph.add(module_id, 1, false); + self.storage.files.insert(module_id, file); + + module_id + } + + fn register_new_module( + &mut self, + errs: Sender>, + module_name: &QualifiedIdent, + ) -> Resolution { + let name = module_name.to_string(); + let node = self.storage.entries.get(&name); + if let Some((_, module_id)) = node { + Resolution::Reuse(*module_id) + } else { + let path = match names::ident_to_path(&self.root, module_name) { + Ok(Some(res)) => res, + Ok(None) => { + errs.send(Box::new(DriverError::UnboundVariable( + vec![module_name.to_ident()], + vec![], + ))) + .unwrap(); + return Resolution::Fail; + } + Err(err) => { + errs.send(err).unwrap(); + return Resolution::Fail; + } + }; + + let input = fs::read_to_string(&path).unwrap(); + + let id = self.register_module(path.clone(), name, input); + + self.handler.on_add_file(path, id); + + Resolution::Added(id) + } + } + + fn register_names( + &mut self, + errs: Sender>, + names: FxHashMap, + module_id: usize, + ) -> bool { + // Pre check to avoid the register of bad inputs. + let mut failed = false; + for (name, range) in &names { + if let Some((first, _)) = self.storage.entries.get(name) { + errs.send(Box::new(DriverError::DefinedMultipleTimes( + QualifiedIdent::new_static(name, None, first.clone()), + QualifiedIdent::new_static(name, None, range.clone()), + ))) + .unwrap(); + failed = true; + } + } + + if !failed { + for (name, range) in names { + self.storage.entries.insert(name, (range, module_id)); + } + } + + failed + } + + fn collect_resources(&mut self, root: usize, modules: &mut FxHashMap>) { + if !modules.contains_key(&root) { + let resource = self.storage.resources.get(&root).unwrap().clone(); + modules.insert(root, resource); + for child in &self.storage.graph.get(&root).unwrap().children.clone() { + self.collect_resources(*child, modules); + } + } + } + + pub fn get_id_by_path(&mut self, path: &PathBuf) -> Option { + self.handler.get_id_by_path(path) + } + + pub fn check_module(&mut self, module_id: usize) -> Option<()> { + let mut added = Vec::new(); + + let failed = self.compile_module(module_id, &mut added); + + if !failed { + let mut resources = Default::default(); + self.collect_resources(module_id, &mut resources); + + let mut concrete_book = Book::default(); + for module in resources.values() { + add_module_to_book(&mut concrete_book, &module.concrete_tree) + } + + let (rx, tx) = std::sync::mpsc::channel(); + let desugared_book = desugar::desugar_book(rx.clone(), &concrete_book)?; + + let changed_functions = added + .iter() + .map(|x| { + resources + .get(x) + .unwrap() + .exposed_entries + .iter() + .cloned() + .collect::>() + }) + .flatten() + .collect::>(); + + kind_checker::type_check(&desugared_book, rx.clone(), changed_functions); + + let entrypoints = FxHashSet::from_iter(["Main".to_string()]); + + erasure::erase_book(&desugared_book, rx.clone(), entrypoints)?; + + let errs = tx.try_iter().collect::>(); + + let mut groups = FxHashMap::default(); + + for err in errs { + if let Some(ctx) = err.get_syntax_ctx() { + let res: &mut Vec<_> = groups.entry(ctx).or_default(); + res.push(err); + } + } + + for (ctx, errs) in groups { + self.handler.on_errors(self.storage, ctx.0, errs) + } + } + + Some(()) + } + + pub fn init_project(&mut self, path: PathBuf) -> usize { + let input = fs::read_to_string(&path).unwrap(); + self.register_module(path, "Main".to_string(), input) + } + + fn compile_module(&mut self, module_id: usize, added: &mut Vec) -> bool { + let file = self.storage.files.get(&module_id).unwrap(); + let hash = file.hash; + + if let Some(node) = self.storage.graph.get(&module_id) { + if !node.invalidated && node.hash == file.hash { + return false; + } + } + + let (rx, tx) = std::sync::mpsc::channel(); + + // Parses the "module" + let (mut module, mut failed) = kind_parser::parse_book(rx.clone(), module_id, &file.input); + + // Expand aliases + failed |= expand::uses::expand_uses(&mut module, rx.clone()); + + // Collects all of the unbound variables and top level + // in order to recursively get all of the unbound files. + let state = unbound::collect_module_info(rx.clone(), &mut module, false); + + let module_definitions = state.top_level_defs.clone(); + + let last_names = if let Some(res) = self.storage.resources.get(&module_id) { + res.exposed_entries.clone() + } else { + FxHashSet::default() + }; + + let mut diff = module_definitions.clone(); + + for name in last_names { + diff.remove(&name); + } + + failed |= self.register_names(rx.clone(), diff, module_id); + + if !failed { + let mut nodes = FxHashSet::default(); + + for (_, idents) in state.unbound_top_level { + let first = idents.iter().last().unwrap(); + let result = self.register_new_module(rx.clone(), &first); + failed |= result.is_fail(); + failed |= match result { + Resolution::Reuse(id) => { + added.push(id); + nodes.insert(id); + self.compile_module(id, added) + } + Resolution::Added(id) => { + let file = self.storage.files.get(&id).unwrap(); + added.push(id); + nodes.insert(id); + self.compile_module(id, added) + } + Resolution::Fail => true, + }; + } + + let node = self.storage.graph.get_mut(&module_id).unwrap(); + node.hash = hash; + node.failed = false; + + let removed = node + .children + .difference(&nodes) + .cloned() + .collect::>(); + + let added = nodes + .difference(&node.children) + .cloned() + .collect::>(); + + node.children.extend(nodes); + + for id in added { + self.storage.graph.connect(module_id, id) + } + + for id in removed { + self.remove_node(id) + } + } + + let errs = tx.try_iter().collect::>(); + + let node = self.storage.graph.get_mut(&module_id).unwrap(); + node.failed = failed; + + if errs.is_empty() { + self.storage.resources.insert( + module_id, + Rc::new(Resource { + concrete_tree: module, + exposed_entries: FxHashSet::from_iter(module_definitions.keys().cloned()), + }), + ); + } + + self.handler.on_errors(self.storage, module_id, errs); + + failed } } diff --git a/src/kind-query/src/names.rs b/src/kind-query/src/names.rs index b04ac895..9341bd81 100644 --- a/src/kind-query/src/names.rs +++ b/src/kind-query/src/names.rs @@ -1,4 +1,4 @@ -use std::path::{PathBuf, Path}; +use std::path::{Path, PathBuf}; use kind_report::data::Diagnostic; use kind_tree::symbol::QualifiedIdent; @@ -10,7 +10,7 @@ const EXT: &str = "kind2"; /// Tries to accumulate on a buffer all of the /// paths that exists (so we can just throw an /// error about ambiguous resolution to the user) -pub(crate) fn accumulate_neighbour_paths( +pub(crate) fn get_module_path( ident: &QualifiedIdent, raw_path: &Path, ) -> Result, Box> { @@ -34,4 +34,22 @@ pub(crate) fn accumulate_neighbour_paths( } else { Ok(None) } +} + +/// Transforms an ident into a path +pub(crate) fn ident_to_path( + root: &Path, + ident: &QualifiedIdent, +) -> Result, Box> { + let name = ident.root.to_string(); + let segments = name.as_str().split('.').collect::>(); + let mut raw_path = root.to_path_buf(); + raw_path.push(PathBuf::from(segments.join("/"))); + match get_module_path(&ident, &raw_path) { + Ok(None) => { + raw_path.pop(); + get_module_path(&ident, &raw_path) + } + rest => rest, + } } \ No newline at end of file diff --git a/src/kind-report/src/data.rs b/src/kind-report/src/data.rs index 10339594..8979856e 100644 --- a/src/kind-report/src/data.rs +++ b/src/kind-report/src/data.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use kind_span::Range; +use kind_span::{Range, SyntaxCtxIndex}; #[derive(Debug, Clone)] pub enum Severity { @@ -60,5 +60,6 @@ pub enum Log { } pub trait Diagnostic { + fn get_syntax_ctx(&self) -> Option; fn to_diagnostic_frame(&self) -> DiagnosticFrame; } diff --git a/src/kind-report/src/report.rs b/src/kind-report/src/report.rs index f218b8c1..79fa7661 100644 --- a/src/kind-report/src/report.rs +++ b/src/kind-report/src/report.rs @@ -459,9 +459,7 @@ impl Report for Box { for (ctx, group) in groups { writeln!(fmt)?; let (file, code) = cache.fetch(ctx).unwrap(); - let diff = - pathdiff::diff_paths(&file.clone(), PathBuf::from(".").canonicalize().unwrap()) - .unwrap(); + let diff =file.clone(); write_code_block(&diff, config, &group, code, fmt)?; } diff --git a/src/kind-tree/src/symbol.rs b/src/kind-tree/src/symbol.rs index e0795f4b..8e511d83 100644 --- a/src/kind-tree/src/symbol.rs +++ b/src/kind-tree/src/symbol.rs @@ -4,6 +4,7 @@ use std::fmt::Display; use kind_span::{Range, SyntaxCtxIndex}; + /// Stores the name of a variable or constructor. /// It's simply a string because in the future i plan /// to store all the names and only reference them with @@ -51,6 +52,19 @@ impl QualifiedIdent { } } + /// Most of the times a qualified ident will not have the `aux` field + /// because it's removed at the `expand_uses` phase. It returns the root + /// and avoid a copy of the string. + #[inline] + pub fn to_str(&self) -> &str { + &self.root.0 + } + + #[inline] + pub fn get_root(&self) -> String { + self.root.0.clone() + } + pub fn change_root(&mut self, str: String) { self.root = Symbol(str); }