feat: started to make kind-query

This commit is contained in:
felipegchi 2022-11-18 19:56:04 -03:00
parent 58684b874b
commit 2c69eb3341
21 changed files with 734 additions and 102 deletions

View File

@ -83,6 +83,11 @@ fn mk_ctr_name(ident: &QualifiedIdent) -> Box<Term> {
mk_single_ctr(format!("{}.", ident))
}
fn mk_ctr_name_from_str(ident: &str) -> Box<Term> {
// 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<Term> {
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<String>) -> 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() {

View File

@ -58,6 +58,19 @@ fn context_to_subtitles(ctx: &Context, subtitles: &mut Vec<Subtitle>) {
}
impl Diagnostic for TypeError {
fn get_syntax_ctx(&self) -> Option<kind_span::SyntaxCtxIndex> {
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) => {

View File

@ -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>) -> 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<Box<dyn Diagnostic>>) -> bool {
let check_code = gen_checker(book);
pub fn type_check(book: &Book, tx: Sender<Box<dyn Diagnostic>>, functions_to_check: Vec<String>) -> 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<Box<dyn Diagnostic>>) -> 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<Box<dyn Diagnostic>>) -> 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<Term> {
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();

View File

@ -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]

View File

@ -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!()

86
src/kind-cli/src/watch.rs Normal file
View File

@ -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<T>(pub T);
impl<T> fmt::Write for ToWriteFmt<T>
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<T, E>(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<PathBuf, usize>,
inverse: &'a mut HashMap<usize, PathBuf>,
}
impl<'a> SessionHandler for WatchServer<'a> {
fn on_errors(&mut self, storage: &Storage, _uri: usize, errs: Vec<Box<dyn Diagnostic>>) {
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<usize> {
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);
}

View File

@ -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

View File

@ -17,6 +17,16 @@ pub(crate) enum DriverError {
}
impl Diagnostic for DriverError {
fn get_syntax_ctx(&self) -> Option<kind_span::SyntaxCtxIndex> {
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 {

View File

@ -25,7 +25,9 @@ pub fn type_check_book(session: &mut Session, path: &PathBuf) -> Option<desugare
let concrete_book = to_book(session, path)?;
let desugared_book = desugar::desugar_book(session.diagnostic_sender.clone(), &concrete_book)?;
let succeeded = checker::type_check(&desugared_book, session.diagnostic_sender.clone());
let all = desugared_book.names.keys().cloned().collect();
let succeeded = checker::type_check(&desugared_book, session.diagnostic_sender.clone(), all);
if !succeeded {
return None;
@ -114,5 +116,5 @@ pub fn eval_in_checker(book: &desugared::Book) -> Box<backend::Term> {
}
pub fn generate_checker(book: &desugared::Book) -> String {
checker::gen_checker(book)
checker::gen_checker(book, book.names.keys().cloned().collect())
}

View File

@ -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

View File

@ -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<SyntaxCtxIndex> {
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 {

View File

@ -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<String>),
CannotPatternMatchOnErased(Range),
UnboundVariable(Vec<Ident>, Vec<String>),
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<SyntaxCtxIndex> {
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::<Vec<String>>().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,

View File

@ -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<String, Range>,
pub unbound_top_level: FxHashMap<String, FxHashSet<QualifiedIdent>>,
pub unbound: FxHashMap<String, Vec<Ident>>,
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<Box<dyn Diagnostic>>,
module: &mut Module,
emit_errs: bool,
) -> (
FxHashMap<String, Vec<Ident>>,
FxHashMap<String, FxHashSet<QualifiedIdent>>,
) {
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<Box<dyn Diagnostic>>,
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)))

View File

@ -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"
fxhash = "0.2.1"
pathdiff = "0.2.1"

View File

@ -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<Ident>, Vec<String>),
MultiplePaths(QualifiedIdent, Vec<PathBuf>),
DefinedMultipleTimes(QualifiedIdent, QualifiedIdent),
ThereIsntAMain,
}
impl Diagnostic for DriverError {
fn get_syntax_ctx(&self) -> Option<kind_span::SyntaxCtxIndex> {
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![],
},
}
}
}

View File

@ -1,18 +1,20 @@
use std::collections::HashMap;
use fxhash::{FxHashSet, FxHashMap};
struct Node<T> {
data: T,
invalidated: bool,
children: FxHashSet<usize>,
parents: FxHashSet<usize>,
root: bool,
#[derive(Debug)]
pub struct Node<T> {
pub data: T,
pub invalidated: bool,
pub hash: u64,
pub children: FxHashSet<usize>,
pub parents: FxHashSet<usize>,
pub root: bool,
pub failed: bool,
}
#[derive(Debug)]
pub struct Graph<T> {
// Using a hashmap to make it easier to add or remove node.s
nodes: FxHashMap<usize, Node<T>>,
pub nodes: FxHashMap<usize, Node<T>>,
count: usize,
}
@ -23,7 +25,15 @@ impl<T> Default for Graph<T> {
}
impl<T> Graph<T> {
pub fn add(&mut self, data: T, root: bool) {
pub fn get(&self, id: &usize) -> Option<&Node<T>> {
self.nodes.get(id)
}
pub fn get_mut(&mut self, id: &usize) -> Option<&mut Node<T>> {
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<T> Graph<T> {
invalidated: false,
children: FxHashSet::default(),
parents: FxHashSet::default(),
hash,
failed: false,
root
},
);
@ -76,9 +88,9 @@ impl<T> Graph<T> {
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)

View File

@ -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<T> {
path: PathBuf,
hash: usize,
pub enum Status {
Module,
Entry,
}
pub struct Resource {
concrete_tree: concrete::Module,
exposed_entries: FxHashSet<String>,
}
/// Useful for LSP URIs
ext_info: T,
pub struct File {
path: PathBuf,
input: String,
hash: u64,
}
pub enum Resolution<T> {
Added(T),
Reuse(T),
Fail,
}
impl<T> Resolution<T> {
pub fn is_fail(&self) -> bool {
matches!(self, Resolution::Fail)
}
}
#[derive(Default)]
pub struct Session<T> {
graph: Graph<usize>,
paths: FxHashMap<String, usize>,
resources: FxHashMap<usize, Resource<T>>,
pub struct Storage {
pub graph: Graph<usize>,
resources: FxHashMap<usize, Rc<Resource>>,
files: FxHashMap<usize, File>,
entries: FxHashMap<String, (Range, usize)>,
}
impl<T> Session<T> {
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<Box<dyn Diagnostic>>);
fn get_id_by_path(&mut self, path: &PathBuf) -> Option<usize>;
}
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<Box<dyn Diagnostic>>,
module_name: &QualifiedIdent,
) -> Resolution<usize> {
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<Box<dyn Diagnostic>>,
names: FxHashMap<String, Range>,
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<usize, Rc<Resource>>) {
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<usize> {
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::<Vec<_>>()
})
.flatten()
.collect::<Vec<_>>();
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::<Vec<_>>();
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<usize>) -> 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::<Vec<_>>();
let added = nodes
.difference(&node.children)
.cloned()
.collect::<Vec<_>>();
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::<Vec<_>>();
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
}
}

View File

@ -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<Option<PathBuf>, Box<dyn Diagnostic>> {
@ -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<Option<PathBuf>, Box<dyn Diagnostic>> {
let name = ident.root.to_string();
let segments = name.as_str().split('.').collect::<Vec<&str>>();
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,
}
}

View File

@ -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<SyntaxCtxIndex>;
fn to_diagnostic_frame(&self) -> DiagnosticFrame;
}

View File

@ -459,9 +459,7 @@ impl Report for Box<dyn Diagnostic> {
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)?;
}

View File

@ -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);
}