From 02614bac1714a97a87a21bffddd0617c9f015939 Mon Sep 17 00:00:00 2001 From: Matthew Paras <34500476+mattwparas@users.noreply.github.com> Date: Sat, 30 Dec 2023 17:35:41 -0800 Subject: [PATCH] Some bug fixes with requiring modules, syntax objects (#128) * optimistic cdr is null check * add r5rs test suite to test runner * add string replace function --- Cargo.lock | 4 +- cogs/r5rs.scm | 13 +- crates/steel-core/Cargo.toml | 2 +- crates/steel-core/src/compiler/compiler.rs | 8 + crates/steel-core/src/compiler/modules.rs | 58 ++++- crates/steel-core/src/compiler/program.rs | 12 +- .../steel-core/src/parser/expand_visitor.rs | 32 ++- crates/steel-core/src/parser/expander.rs | 26 +- crates/steel-core/src/parser/kernel.rs | 13 +- .../steel-core/src/parser/tryfrom_visitor.rs | 196 +++++++++++++++ crates/steel-core/src/primitives/lists.rs | 61 ++++- crates/steel-core/src/primitives/strings.rs | 8 + crates/steel-core/src/rvals.rs | 3 +- .../steel-core/src/scheme/modules/match.scm | 228 +++++++++++++++++- .../src/scheme/modules/parameters.scm | 13 +- .../src/steel_vm/const_evaluation.rs | 10 +- crates/steel-core/src/steel_vm/primitives.rs | 19 +- crates/steel-core/src/steel_vm/vm.rs | 6 + crates/steel-language-server/src/backend.rs | 168 ++++++++----- .../steel-language-server/src/diagnostics.rs | 6 +- crates/steel-parser/src/parser.rs | 10 +- 21 files changed, 772 insertions(+), 124 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f8034c2ce..c709666e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1483,9 +1483,9 @@ dependencies = [ [[package]] name = "im-lists" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280cfba4e434565a1275d357b3561ca98265e3715de9406eabd6198f7137fd21" +checksum = "a8ce6c776654abe411e4ccd52524f87e8ca16e2c487dbe127521eb048a46f7e6" [[package]] name = "im-rc" diff --git a/cogs/r5rs.scm b/cogs/r5rs.scm index 2134641ce..3d19fefd7 100644 --- a/cogs/r5rs.scm +++ b/cogs/r5rs.scm @@ -37,6 +37,10 @@ (set-test-mode!) +(provide __module__) + +(define __module__ 'r5rs-test-suite) + (check-equal? "Parsing hex" #x0f 15) (check-equal? "Parsing octal" #o0777 511) (check-equal? "Parsing binary" #b0110 6) @@ -390,7 +394,6 @@ (check-equal? "Basic functionality of make-string" "aaa" (make-string 3 #\a)) (check-equal? "make-string with no character" "\0\0\0" (make-string 3)) (check-equal? "make-string with zero length" "" (make-string 0)) -(check-equal? "make-string with multiple additional characters" "error" (make-string 3 #\a #\b)) (check-equal? "string-equality with constructor, equal" #t (string=? "a" (string #\a))) (check-equal? "string-equality with constructor, not equal" #f (string=? "a" (string #\b))) @@ -422,8 +425,12 @@ (check-equal? "case-insensitive string >=, true" #t (string-ci>=? "aa" "A")) (check-equal? "case-insensitive string >=, same string" #t (string-ci>=? "a" "A")) -(check-equal? "make-string creates single character string 'a' correctly" #t (string=? "a" (make-string 1 #\a))) -(check-equal? "make-string with character 'b' does not create string 'a'" #f (string=? "a" (make-string 1 #\b))) +(check-equal? "make-string creates single character string 'a' correctly" + #t + (string=? "a" (make-string 1 #\a))) +(check-equal? "make-string with character 'b' does not create string 'a'" + #f + (string=? "a" (make-string 1 #\b))) (check-equal? "string-append with empty string" "abc" (string-append "abc" "")) diff --git a/crates/steel-core/Cargo.toml b/crates/steel-core/Cargo.toml index cfb07bde5..a72523e5c 100644 --- a/crates/steel-core/Cargo.toml +++ b/crates/steel-core/Cargo.toml @@ -26,7 +26,7 @@ serde = { version = "1.0.193", features = ["derive", "rc"] } serde_derive = "1.0.193" bincode = "1.3.3" pretty = "0.12.1" -im-lists = "0.6.0" +im-lists = "0.7.0" quickscope = "0.2.0" lasso = { version = "0.7.2", features = ["multi-threaded", "serialize"] } once_cell = "1.18.0" diff --git a/crates/steel-core/src/compiler/compiler.rs b/crates/steel-core/src/compiler/compiler.rs index 912bac7b8..a760f500e 100644 --- a/crates/steel-core/src/compiler/compiler.rs +++ b/crates/steel-core/src/compiler/compiler.rs @@ -649,6 +649,8 @@ impl Compiler { for module in &self.lifted_macro_environments { if let Some(macro_env) = self.modules().get(module).map(|x| &x.macro_map) { + let source_id = sources.get_source_id(module).unwrap(); + x = crate::parser::expand_visitor::expand(x, macro_env)? } } @@ -837,6 +839,12 @@ impl Compiler { for module in &self.lifted_macro_environments { if let Some(macro_env) = self.modules().get(module).map(|x| &x.macro_map) { + let source_id = sources.get_source_id(module).unwrap(); + + // x = crate::parser::expand_visitor::expand_with_source_id( + // x, macro_env, source_id, + // )? + x = crate::parser::expand_visitor::expand(x, macro_env)? } } diff --git a/crates/steel-core/src/compiler/modules.rs b/crates/steel-core/src/compiler/modules.rs index 2bea79f52..095d6dbf5 100644 --- a/crates/steel-core/src/compiler/modules.rs +++ b/crates/steel-core/src/compiler/modules.rs @@ -11,7 +11,7 @@ use crate::{ ast::{AstTools, Atom, Begin, Define, ExprKind, List, Quote}, expand_visitor::{ expand_kernel, expand_kernel_in_env, expand_kernel_in_env_with_allowed, - expand_kernel_in_env_with_change, + expand_kernel_in_env_with_change, expand_with_source_id, }, interner::InternedString, kernel::Kernel, @@ -53,6 +53,7 @@ use crate::parser::ast::IteratorExtensions; use super::{ compiler::KernelDefMacroSpec, passes::{ + analysis::is_a_builtin_definition, begin::FlattenBegin, mangle::{collect_globals, NameMangler, NameUnMangler}, }, @@ -630,7 +631,13 @@ impl ModuleManager { // } if expander.changed || changed { - let fully_expanded = expand(first_round_expanded, &module.macro_map)?; + let source_id = sources.get_source_id(&module.name).unwrap(); + + let fully_expanded = expand( + first_round_expanded, + &module.macro_map, + // source_id, + )?; let module_name = module.name.to_str().unwrap().to_string(); @@ -1323,13 +1330,42 @@ impl CompiledModule { SyntaxObject::default(TokenType::Quote), ))); - // println!("------ {}", module_define.to_pretty(60)); + let mut offset = None; + + // Find offset of first non builtin require definition: + for (idx, expr) in exprs.iter().enumerate() { + if let ExprKind::Define(d) = expr { + if !is_a_builtin_definition(d) { + // println!("Found offset at: {:?}", offset); + + offset = Some(idx); + break; + } + } + } exprs.push(module_define); + if let Some(offset) = offset { + for (idx, expr) in provide_definitions.into_iter().enumerate() { + exprs.insert(offset + idx, expr); + } + } else { + provide_definitions.append(&mut exprs); + } + + // println!("------ {}", module_define.to_pretty(60)); + + // exprs.pretty_print(); + + // exprs.push(module_define); + // Construct the overall definition // TODO: Perhaps mangle these as well, especially if they have contracts associated with them - provide_definitions.append(&mut exprs); + + // if offset.is_none() { + // provide_definitions.append(&mut exprs); + // } // Try this out? // let mut analysis = Analysis::from_exprs(&provide_definitions); @@ -1340,10 +1376,8 @@ impl CompiledModule { // .replace_non_shadowed_globals_with_builtins() // .remove_unused_globals_with_prefix("mangler"); - // println!("------ {}", provide_definitions.to_pretty(60)); - Ok(ExprKind::Begin(Begin::new( - provide_definitions, + exprs, SyntaxObject::default(TokenType::Begin), ))) } @@ -1810,6 +1844,8 @@ impl<'a> ModuleBuilder<'a> { let mut first_round_expanded = expander.expand(x)?; let mut changed = false; + // dbg!(expander.changed); + // (first_round_expanded, changed) = expand_kernel_in_env_with_allowed( // first_round_expanded, // self.kernel.as_mut(), @@ -1837,7 +1873,7 @@ impl<'a> ModuleBuilder<'a> { // } if expander.changed || changed { - // expand(first_round_expanded, &module.macro_map) + // let source_id = self.sources.get_source_id(&module.name).unwrap(); let fully_expanded = expand(first_round_expanded, &module.macro_map)?; @@ -2421,7 +2457,11 @@ impl<'a> ModuleBuilder<'a> { } fn parse_builtin(mut self, input: &str) -> Result { - let parsed = Parser::new_from_source(input, self.name.clone(), None) + let id = self + .sources + .add_source(input.to_string(), Some(self.name.clone())); + + let parsed = Parser::new_from_source(input, self.name.clone(), Some(id)) .without_lowering() .map(|x| x.and_then(lower_macro_and_require_definitions)) .collect::, ParseError>>()?; diff --git a/crates/steel-core/src/compiler/program.rs b/crates/steel-core/src/compiler/program.rs index 6cce79221..451d5b3db 100644 --- a/crates/steel-core/src/compiler/program.rs +++ b/crates/steel-core/src/compiler/program.rs @@ -424,12 +424,12 @@ pub fn inline_num_operations(instructions: &mut [Instruction]) { { let replaced = match *ident { x if x == *PRIM_PLUS && *payload_size == 2 => Some(OpCode::BINOPADD), - x if x == *PRIM_PLUS => Some(OpCode::ADD), - x if x == *PRIM_MINUS => Some(OpCode::SUB), - x if x == *PRIM_DIV => Some(OpCode::DIV), - x if x == *PRIM_STAR => Some(OpCode::MUL), - x if x == *PRIM_EQUAL => Some(OpCode::EQUAL), - x if x == *PRIM_LTE => Some(OpCode::LTE), + x if x == *PRIM_PLUS && *payload_size > 0 => Some(OpCode::ADD), + x if x == *PRIM_MINUS && *payload_size > 0 => Some(OpCode::SUB), + x if x == *PRIM_DIV && *payload_size > 0 => Some(OpCode::DIV), + x if x == *PRIM_STAR && *payload_size > 0 => Some(OpCode::MUL), + x if x == *PRIM_EQUAL && *payload_size > 0 => Some(OpCode::EQUAL), + x if x == *PRIM_LTE && *payload_size > 0 => Some(OpCode::LTE), _ => None, }; diff --git a/crates/steel-core/src/parser/expand_visitor.rs b/crates/steel-core/src/parser/expand_visitor.rs index aef986ea1..daed6f105 100644 --- a/crates/steel-core/src/parser/expand_visitor.rs +++ b/crates/steel-core/src/parser/expand_visitor.rs @@ -2,6 +2,7 @@ use quickscope::ScopeSet; use steel_parser::ast::{parse_lambda, LAMBDA, LAMBDA_SYMBOL}; +use steel_parser::parser::SourceId; use crate::compiler::passes::reader::MultipleArityFunctions; use crate::compiler::passes::Folder; @@ -53,6 +54,21 @@ pub fn expand(expr: ExprKind, map: &HashMap) -> Resu map, changed: false, in_scope_values: ScopeSet::new(), + source_id: None, + } + .visit(expr) +} + +pub fn expand_with_source_id( + expr: ExprKind, + map: &HashMap, + source_id: SourceId, +) -> Result { + Expander { + map, + changed: false, + in_scope_values: ScopeSet::new(), + source_id: Some(source_id), } .visit(expr) } @@ -62,6 +78,7 @@ pub struct Expander<'a> { pub(crate) changed: bool, // We're going to actually check if the macro is in scope in_scope_values: ScopeSet, + source_id: Option, } impl<'a> Expander<'a> { @@ -70,6 +87,7 @@ impl<'a> Expander<'a> { map, changed: false, in_scope_values: ScopeSet::new(), + source_id: None, } } @@ -187,9 +205,17 @@ impl<'a> ConsumingVisitor for Expander<'a> { // If this macro has been overwritten by any local value, respect // the local binding and do not expand the macro if !self.in_scope_values.contains(s) { - let expanded = m.expand(l.clone(), *sp)?; - self.changed = true; - return self.visit(expanded); + if self.source_id.is_none() + || self.source_id.is_some() && self.source_id == sp.source_id() + { + let expanded = m.expand(l.clone(), *sp)?; + self.changed = true; + return self.visit(expanded); + } + + // let expanded = m.expand(l.clone(), *sp)?; + // self.changed = true; + // return self.visit(expanded); } } } diff --git a/crates/steel-core/src/parser/expander.rs b/crates/steel-core/src/parser/expander.rs index c049073cb..992bbd7e3 100644 --- a/crates/steel-core/src/parser/expander.rs +++ b/crates/steel-core/src/parser/expander.rs @@ -1,4 +1,4 @@ -use crate::compiler::program::{BEGIN, DEFINE}; +use crate::compiler::program::{BEGIN, DEFINE, IF}; use crate::parser::ast::{Atom, ExprKind, List, Macro, PatternPair}; use crate::parser::parser::SyntaxObject; use crate::parser::rename_idents::RenameIdentifiersVisitor; @@ -359,6 +359,7 @@ pub enum MacroPattern { BooleanLiteral(bool), QuotedExpr(Box), Quote(InternedString), + Keyword(InternedString), } // pub enum QuotedLiteral { @@ -382,6 +383,7 @@ impl std::fmt::Debug for MacroPattern { MacroPattern::QuotedExpr(s) => f.debug_tuple("QuotedExpr").field(s).finish(), MacroPattern::Quote(i) => f.debug_tuple("Quote").field(&i.resolve()).finish(), MacroPattern::ManyNested(n) => f.debug_tuple("ManyNested").field(n).finish(), + MacroPattern::Keyword(k) => f.debug_tuple("Keyword").field(k).finish(), } } } @@ -459,6 +461,9 @@ impl MacroPattern { TokenType::Define => { pattern_vec.push(MacroPattern::Syntax(*DEFINE)); } + TokenType::If => { + pattern_vec.push(MacroPattern::Syntax(*IF)); + } TokenType::BooleanLiteral(b) => { pattern_vec.push(MacroPattern::BooleanLiteral(b)); } @@ -474,6 +479,9 @@ impl MacroPattern { TokenType::StringLiteral(s) => { pattern_vec.push(MacroPattern::StringLiteral(s)); } + TokenType::Keyword(k) => { + pattern_vec.push(MacroPattern::Keyword(k)); + } // TODO: Crunch the quoted values here, and pull them in so that this // holds a body of possible quoted values TokenType::Quote => { @@ -602,6 +610,12 @@ pub fn match_vec_pattern(args: &[MacroPattern], list: &[ExprKind]) -> bool { .. }, }) if *v == *BEGIN => continue, + ExprKind::Atom(Atom { + syn: + SyntaxObject { + ty: TokenType::If, .. + }, + }) if *v == *IF => continue, ExprKind::Atom(Atom { syn: SyntaxObject { @@ -611,6 +625,16 @@ pub fn match_vec_pattern(args: &[MacroPattern], list: &[ExprKind]) -> bool { }) => continue, _ => return false, }, + MacroPattern::Keyword(k) => match val { + ExprKind::Atom(Atom { + syn: + SyntaxObject { + ty: TokenType::Keyword(s), + .. + }, + }) if s == k => continue, + _ => return false, + }, MacroPattern::BooleanLiteral(b) => match val { ExprKind::Atom(Atom { syn: diff --git a/crates/steel-core/src/parser/kernel.rs b/crates/steel-core/src/parser/kernel.rs index d159bcde3..ee011997e 100644 --- a/crates/steel-core/src/parser/kernel.rs +++ b/crates/steel-core/src/parser/kernel.rs @@ -521,7 +521,7 @@ impl Kernel { let span = get_span(&expr); let syntax_objects = - super::tryfrom_visitor::SyntaxObjectFromExprKind::try_from_expr_kind(expr.clone())?; + super::tryfrom_visitor::SyntaxObjectFromExprKind::try_from_expr_kind(expr)?; let function = if environment == "default" { // TODO: This actually needs to go through the proper resolution process, @@ -546,18 +546,7 @@ impl Kernel { .call_function_with_args(function, vec![syntax_objects]) .map_err(|x| x.set_span(span))?; - // dbg!(&result); - // This shouldn't be lowering all the way. It should just be back to list right? TryFromSteelValVisitorForExprKind::root(&result) - - // let span = result.as_ref().map(get_span); - - // dbg!(&span); - - // result - - // TODO: We don't want this forever, but for now its okay - // .and_then(|x| RewriteSpan::new(span).visit(x)) } } diff --git a/crates/steel-core/src/parser/tryfrom_visitor.rs b/crates/steel-core/src/parser/tryfrom_visitor.rs index 4232e4e14..89abf07e0 100644 --- a/crates/steel-core/src/parser/tryfrom_visitor.rs +++ b/crates/steel-core/src/parser/tryfrom_visitor.rs @@ -5,6 +5,7 @@ use crate::{parser::ast::ExprKind, rvals::Syntax}; use crate::rerrs::SteelErr; use crate::rvals::{Result, SteelVal}; +use super::visitors::VisitorMut; use super::{ast::Atom, span::Span, visitors::ConsumingVisitor}; use std::convert::TryFrom; @@ -202,6 +203,8 @@ impl ConsumingVisitor for TryFromExprKindForSteelVal { } } +// TODO: Have this take a reference, so we don't have to +// clone the whole thing pub struct SyntaxObjectFromExprKind { _inside_quote: bool, } @@ -215,6 +218,199 @@ impl SyntaxObjectFromExprKind { } } +pub struct SyntaxObjectFromExprKindRef { + _inside_quote: bool, +} + +impl SyntaxObjectFromExprKindRef { + pub fn try_from_expr_kind_ref(e: &ExprKind) -> Result { + SyntaxObjectFromExprKindRef { + _inside_quote: false, + } + .visit(e) + } +} + +impl VisitorMut for SyntaxObjectFromExprKindRef { + type Output = Result; + + fn visit_if(&mut self, f: &steel_parser::ast::If) -> Self::Output { + let raw = TryFromExprKindForSteelVal::try_from_expr_kind_quoted(ExprKind::If(Box::new( + f.clone(), + )))?; + + let span = f.location.span; + + let expr = [ + SteelVal::try_from(f.location.clone())?, + self.visit(&f.test_expr)?, + self.visit(&f.then_expr)?, + self.visit(&f.else_expr)?, + ]; + // Ok(Syntax::new_with_source(SteelVal::ListV(expr.into_iter().collect()), span).into()) + + Ok(Syntax::proto(raw, SteelVal::ListV(expr.into_iter().collect()), span).into()) + } + + fn visit_define(&mut self, define: &steel_parser::ast::Define) -> Self::Output { + let raw: SteelVal = TryFromExprKindForSteelVal::try_from_expr_kind_quoted( + ExprKind::Define(Box::new(define.clone())), + )?; + + let span = define.location.span; + + let expr = [ + SteelVal::try_from(define.location.clone())?, + self.visit(&define.name)?, + self.visit(&define.body)?, + ]; + Ok(Syntax::proto(raw, SteelVal::ListV(expr.into_iter().collect()), span).into()) + } + + fn visit_lambda_function( + &mut self, + lambda_function: &steel_parser::ast::LambdaFunction, + ) -> Self::Output { + let raw = TryFromExprKindForSteelVal::try_from_expr_kind_quoted(ExprKind::LambdaFunction( + Box::new(lambda_function.clone()), + ))?; + + let span = lambda_function.location.span; + + let args = lambda_function + .args + .iter() + .map(|x| self.visit(&x)) + .collect::>>()?; + + let expr = [ + SteelVal::try_from(lambda_function.location.clone())?, + SteelVal::ListV(args), + self.visit(&lambda_function.body)?, + ]; + + Ok(Syntax::proto(raw, SteelVal::ListV(expr.into_iter().collect()), span).into()) + } + + fn visit_begin(&mut self, begin: &steel_parser::ast::Begin) -> Self::Output { + let raw: SteelVal = + TryFromExprKindForSteelVal::try_from_expr_kind_quoted(ExprKind::Begin(begin.clone()))?; + + let span = begin.location.span; + let mut exprs = vec![SteelVal::try_from(begin.location.clone())?]; + for expr in &begin.exprs { + exprs.push(self.visit(&expr)?); + } + Ok(Syntax::proto(raw, SteelVal::ListV(exprs.into()), span).into()) + } + + fn visit_return(&mut self, r: &steel_parser::ast::Return) -> Self::Output { + let span = r.location.span; + let expr = [ + SteelVal::try_from(r.location.clone())?, + self.visit(&r.expr)?, + ]; + Ok(Syntax::new_with_source(SteelVal::ListV(expr.into_iter().collect()), span).into()) + } + + // TODO: quotes are handled incorrectly here + // Interior values should be handled on their own separately - they should evaluate + // like this: '(a b c) => '(a b c) + // '(a b 'c) => '(a b 'c) --- currently this ends up as '(a b c) + fn visit_quote(&mut self, quote: &steel_parser::ast::Quote) -> Self::Output { + let span = quote.location.span; + + // if self.inside_quote { + let raw = TryFromExprKindForSteelVal::try_from_expr_kind_quoted(ExprKind::Quote( + Box::new(quote.clone()), + ))?; + Ok(Syntax::proto( + raw, + SteelVal::ListV( + vec![SteelVal::SymbolV("quote".into()), self.visit("e.expr)?].into(), + ), + span, + ) + .into()) + // } else { + // self.inside_quote = true; + // let res = self.visit(quote.expr); + // self.inside_quote = false; + // res + // } + } + + fn visit_macro(&mut self, _m: &steel_parser::ast::Macro) -> Self::Output { + // TODO + stop!(Generic => "internal compiler error - could not translate macro to steel value") + } + + fn visit_atom(&mut self, a: &steel_parser::ast::Atom) -> Self::Output { + let span = a.syn.span; + + let atom = SteelVal::try_from(a.syn.clone())?; + + Ok(Syntax::proto(atom.clone(), atom, span).into()) + } + + fn visit_list(&mut self, l: &steel_parser::ast::List) -> Self::Output { + let raw = TryFromExprKindForSteelVal::try_from_expr_kind_quoted(ExprKind::List(l.clone()))?; + + let items: std::result::Result, SteelErr> = + l.args.iter().map(|x| self.visit(&x)).collect(); + + let items = items?; + + let span_vec = items + .iter() + .map(|x| { + if let SteelVal::SyntaxObject(s) = x { + s.syntax_loc() + } else { + unreachable!() + } + }) + .collect::>(); + + let span = Span::coalesce_span(&span_vec); + + // TODO: we're currently erasing the source here... This isn't what we want to do but we don't have + // a great model to access the source otherwise + // log::debug!( + // "Erasing the source information during kernel level expansion for: {:?}", + // raw + // ); + Ok(Syntax::proto(raw, items.into(), span).into()) + } + + fn visit_syntax_rules(&mut self, _s: &steel_parser::ast::SyntaxRules) -> Self::Output { + // TODO + stop!(Generic => "internal compiler error - could not translate syntax-rules to steel value") + } + + fn visit_set(&mut self, s: &steel_parser::ast::Set) -> Self::Output { + let raw: SteelVal = TryFromExprKindForSteelVal::try_from_expr_kind_quoted(ExprKind::Set( + Box::new(s.clone()), + ))?; + + let span = s.location.span; + let expr = [ + SteelVal::try_from(s.location.clone())?, + self.visit(&s.variable)?, + self.visit(&s.expr)?, + ]; + Ok(Syntax::proto(raw, SteelVal::ListV(expr.into_iter().collect()), span).into()) + } + + fn visit_require(&mut self, _s: &steel_parser::ast::Require) -> Self::Output { + stop!(Generic => "internal compiler error - could not translate require to steel value") + } + + fn visit_let(&mut self, _l: &steel_parser::ast::Let) -> Self::Output { + todo!() + } +} + impl ConsumingVisitor for SyntaxObjectFromExprKind { type Output = Result; diff --git a/crates/steel-core/src/primitives/lists.rs b/crates/steel-core/src/primitives/lists.rs index d6d2e6561..dc2372f6b 100644 --- a/crates/steel-core/src/primitives/lists.rs +++ b/crates/steel-core/src/primitives/lists.rs @@ -104,7 +104,8 @@ pub fn list_module() -> BuiltInModule { .register_native_fn_definition(SECOND_DEFINITION) .register_native_fn_definition(THIRD_DEFINITION) .register_native_fn_definition(TAKE_DEFINITION) - .register_native_fn_definition(LIST_TAIL_DEFINITION); + .register_native_fn_definition(LIST_TAIL_DEFINITION) + .register_native_fn_definition(CDR_IS_NULL_DEFINITION); module } @@ -293,6 +294,46 @@ pub fn cons(args: &mut [SteelVal]) -> Result { } } +#[macro_export] +/// `panic!()` in debug builds, optimization hint in release. +macro_rules! debug_unreachable { + () => { + if cfg!(debug_assertions) { + std::hint::unreachable_unchecked() + } else { + unreachable!(); + } + }; + ($e:expr) => { + if cfg!(debug_assertions) { + std::hint::unreachable_unchecked() + } else { + unreachable!($e); + } + }; +} + +// Warning: here be dragons +// This is really just for use cases where we absolutely, 100% know, that within the body of a function, +// it is _not possible_ for the value to be anything but a list. Eliding these checks in hot loops +// can prove to be beneficial. +// unsafe fn unsafe_cons(args: &mut [SteelVal]) -> SteelVal { +// match (args[0].clone(), &mut args[1]) { +// (left, SteelVal::ListV(right)) => { +// right.cons_mut(left); + +// // Consider moving in a default value instead of cloning? +// SteelVal::ListV(right.clone()) +// } +// _ => debug_unreachable!(), +// // Silly, but this then gives us a special "pair" that is different +// // from a real bonafide list +// // (left, right) => Ok(SteelVal::Pair(Gc::new(Pair::cons(left, right.clone())))), +// // TODO: Replace with an immutable pair here! +// // (left, right) => Ok(SteelVal::ListV(vec![left, right.clone()].into())), +// } +// } + /// Returns a newly allocated list of the elements in the range (n, m] /// /// (range n m) -> (listof int?) @@ -437,6 +478,24 @@ pub(crate) const CDR_DOC: DocTemplate<'static> = DocTemplate { ], }; +// Optimistic check to see if the rest is null before making an allocation +#[steel_derive::native(name = "cdr-null?", constant = true, arity = "Exact(1)")] +fn cdr_is_null(args: &[SteelVal]) -> Result { + arity_check!(cdr_is_null, args, 1); + + match &args[0] { + SteelVal::ListV(l) => { + if l.is_empty() { + stop!(Generic => "cdr-null? expects a non empty list"); + } + Ok(SteelVal::BoolV(!l.cdr_exists())) + } + other => { + stop!(TypeMismatch => "cdr-null? expects a list, found: {}", other); + } + } +} + fn cdr(args: &mut [SteelVal]) -> Result { arity_check!(rest, args, 1); diff --git a/crates/steel-core/src/primitives/strings.rs b/crates/steel-core/src/primitives/strings.rs index 1bdb4c0f5..69583cd11 100644 --- a/crates/steel-core/src/primitives/strings.rs +++ b/crates/steel-core/src/primitives/strings.rs @@ -54,6 +54,7 @@ pub fn string_module() -> BuiltInModule { .register_native_fn_definition(STRING_CONSTRUCTOR_DEFINITION) .register_native_fn_definition(STRING_TO_NUMBER_DEFINITION) .register_native_fn_definition(NUMBER_TO_STRING_DEFINITION) + .register_native_fn_definition(REPLACE_DEFINITION) .register_fn("char-upcase", char_upcase) .register_fn("char-whitespace?", char::is_whitespace) .register_fn("char-digit?", |c: char| char::is_digit(c, 10)) @@ -253,6 +254,13 @@ pub fn make_string(k: usize, mut c: RestArgsIter<'_, char>) -> Result Ok((0..k).into_iter().map(|_| c).collect::().into()) } +#[function(name = "string-replace")] +pub fn replace(value: &SteelString, from: &SteelString, to: &SteelString) -> Result { + Ok(SteelVal::StringV( + value.replace(from.as_str(), to.as_str()).into(), + )) +} + /// Concatenatives all of the inputs to their string representation, separated by spaces. /// /// (to-string xs ...) diff --git a/crates/steel-core/src/rvals.rs b/crates/steel-core/src/rvals.rs index f1089282e..4b2476a3c 100644 --- a/crates/steel-core/src/rvals.rs +++ b/crates/steel-core/src/rvals.rs @@ -782,7 +782,7 @@ impl AsRefSteelVal for Syntax { if let SteelVal::SyntaxObject(s) = val { Ok(SRef::Temporary(s)) } else { - stop!(TypeMismatch => "Value cannot be referenced as a syntax object") + stop!(TypeMismatch => "Value cannot be referenced as a syntax object: {}", val) } } } @@ -1626,6 +1626,7 @@ impl Hash for SteelVal { HashMapV(hm) => hm.hash(state), IterV(s) => s.hash(state), HashSetV(hs) => hs.hash(state), + SyntaxObject(s) => s.raw.hash(state), _ => { println!("Trying to hash: {self:?}"); unimplemented!() diff --git a/crates/steel-core/src/scheme/modules/match.scm b/crates/steel-core/src/scheme/modules/match.scm index 73d2584d5..e661eab28 100644 --- a/crates/steel-core/src/scheme/modules/match.scm +++ b/crates/steel-core/src/scheme/modules/match.scm @@ -1,7 +1,8 @@ ;; These _don't_ need to be provided for syntax. ;; However in the mean time, this will work. (provide match - match-define) + match-define + match-syntax) ;; ------------------- match functions ---------------------- @@ -116,6 +117,123 @@ (define (number->symbol n) (~> n number->string string->symbol)) + (define (match-p-syntax-object pattern + input + final-body-expr + depth + bound-vars + check-var? + cdr-depth + introduced-identifiers) + + (cond + [(quoted? pattern) `(and (equal? ,pattern (syntax-e ,input)) ,final-body-expr)] + [(and (list? pattern) (not (null? pattern)) (= cdr-depth 0)) + (cond + [(equal? (car pattern) 'list) + `(if (list? (syntax-e ,input)) + ;; Recur after dropping the list + ,(match-p-syntax-object (cdr pattern) + `(syntax-e ,input) + final-body-expr + depth + bound-vars + check-var? + (+ 1 cdr-depth) + introduced-identifiers) + + #f)] + + [else (error "list pattern must start with `list - found " (car pattern))])] + + [(and (list? pattern) (not (null? pattern)) (starts-with-many? pattern)) + (if (null? (cddr pattern)) + (begin + (vector-push! introduced-identifiers (car pattern)) + + `(let ([,(car pattern) ,input]) ,final-body-expr)) + (begin + + (vector-push! introduced-identifiers (car (cdr pattern))) + (vector-push! introduced-identifiers (car pattern)) + + `(let ([collected (collect-until-last ,input)]) + ,(if (null? (cdddr pattern)) + `(let ([,(car (cdr pattern)) (car collected)] + [,(car pattern) (reverse (car (cdr collected)))]) + + ,final-body-expr) + + #f))))] + + ;; If the pattern is to be ignored, just return the body - the automatically match + [(ignore? pattern) final-body-expr] + + ;; If this is a free variable, bind against it. + ;; Note: We currently don't have the ability to check if this is a free variable + ;; within the context of the macro expansion + [(var? pattern) + + (if check-var? + `(if (equal? ,pattern ,(syntax-e input)) ,final-body-expr #f) + (begin + ;; Keep track of the introduced identifiers + (vector-push! introduced-identifiers pattern) + + `(let ([,pattern ,input]) ,final-body-expr)))] + + ;; If the pattern is the same, just return whether they match + [(atom? pattern) `(and (equal? ,pattern (syntax-e ,input)) ,final-body-expr)] + + ;; If there is no pattern, just return whether the pattern and input match + [(null? pattern) `(and (null? ,input) ,final-body-expr)] + + ;; TODO: Not sure how we can even get here? + ; (displayln "getting here!") + [(null? input) #f] + + [else + + (define cdr-input-depth (gensym-ident (concat-symbols 'cdr-input (number->symbol depth)))) + (define car-input-depth + (gensym-ident (concat-symbols 'car-input (number->symbol (+ 1 depth))))) + + ;; If the pattern is an atom, then we're going to bind the pattern here! + (define car-pattern-is-atom? (atom? (car pattern))) + (define should-check-var? + (and car-pattern-is-atom? (hashset-contains? bound-vars (car pattern)))) + + (define remaining + (match-p-syntax-object + (cdr pattern) + cdr-input-depth + final-body-expr + depth + (if car-pattern-is-atom? (hashset-insert bound-vars (car pattern)) bound-vars) + should-check-var? + ;; Increment the cdr depth since we're traversing across the list + (+ 1 cdr-depth) + introduced-identifiers)) + + (if remaining + + `(if (not (null? ,input)) + ;; Save our spot in the recursion so we don't have to recompute a bunch + ;; of stuff + (let ([,cdr-input-depth (cdr ,input)] [,car-input-depth (car ,input)]) + ,(match-p-syntax-object + (car pattern) + car-input-depth + remaining + (+ 1 depth) + (if car-pattern-is-atom? (hashset-insert bound-vars (car pattern)) bound-vars) + should-check-var? + 0 + introduced-identifiers)) + #f) + + #f)])) + ;; TODO: Insert a check to remove the `list` part from the pattern if the cdr-depth is 0? (define (match-p-syntax pattern input @@ -233,6 +351,18 @@ #f)])) + ;; Find a way to merge these? + (define (go-match-syntax pattern input final-body-expr introduced-identifiers) + (define compile-pattern (compile-cons-to-list pattern 0)) + (match-p-syntax-object compile-pattern + input + final-body-expr + 0 + (hashset) + #f + 0 + introduced-identifiers)) + (define (go-match pattern input final-body-expr introduced-identifiers) (define compile-pattern (compile-cons-to-list pattern 0)) (match-p-syntax compile-pattern input final-body-expr 0 (hashset) #f 0 introduced-identifiers))) @@ -276,6 +406,22 @@ (syntax/loc res (syntax-span expression))) +(defmacro (single-match-syntax expression) + (define unwrapped (syntax-e expression)) + ;; Unwrapping entirely, not what we want! We want to + ;; wrap it back up with the span of the original definition! + (define variable (syntax->datum (second unwrapped))) + (define pattern (syntax->datum (third unwrapped))) + (define body (list-ref unwrapped 3)) + ;; Keep track of all of the identifiers that this + ;; expression introduces + ;; TODO: Keep one top level around and clear each time. Then + ;; we won't keep around any garbage + (define introduced-identifiers (mutable-vector)) + (define res (go-match-syntax pattern variable body introduced-identifiers)) + (syntax/loc res + (syntax-span expression))) + ;; ----------------- match! syntax -------------------- ;; TODO add case for the remaining - when there is no else case given @@ -287,6 +433,17 @@ (begin e0 e1 ...)] + + ;; Adding guard on one of the expressions + [(match-dispatch expr [p1 #:when when-expr e2 ...] c0 c1 ...) + (let ([match? (single-match expr + p1 + (if when-expr + (begin + e2 ...) + #f))]) + (if (not (equal? #f match?)) match? (match-dispatch expr c0 c1 ...)))] + ;; Generic recursive case [(match-dispatch expr [p1 e2 ...] c0 c1 ...) (let ([match? (single-match expr @@ -297,6 +454,18 @@ match? (match-dispatch expr c0 c1 ...)))] + + [(match-dispatch expr (p1 #:when when-expr e2 ...)) + (let ([match? (single-match expr + p1 + (if when-expr + (begin + e2 ...) + #f))]) + (if (not (equal? #f match?)) + match? + (error! "Unable to match expression: " expr " to any of the given patterns")))] + ;; When there isn't an else case given, the last case ;; Should include a failure mode [(match-dispatch expr (p1 e2 ...)) @@ -308,6 +477,57 @@ match? (error! "Unable to match expression: " expr " to any of the given patterns")))])) +(define-syntax match-syntax-dispatch + (syntax-rules (else) + ;; Explicitly giving an else case + [(match-syntax-dispatch expr [else e0 e1 ...]) + (begin + e0 + e1 ...)] + + ;; Adding guard on one of the expressions + [(match-syntax-dispatch expr [p1 #:when when-expr e2 ...] c0 c1 ...) + (let ([match? (single-match-syntax expr + p1 + (if when-expr + (begin + e2 ...) + #f))]) + (if (not (equal? #f match?)) match? (match-syntax-dispatch expr c0 c1 ...)))] + + ;; Generic recursive case + [(match-syntax-dispatch expr [p1 e2 ...] c0 c1 ...) + (let ([match? (single-match-syntax expr + p1 + (begin + e2 ...))]) + (if (not (equal? #f match?)) + match? + + (match-syntax-dispatch expr c0 c1 ...)))] + + [(match-syntax-dispatch expr (p1 #:when when-expr e2 ...)) + (let ([match? (single-match-syntax expr + p1 + (if when-expr + (begin + e2 ...) + #f))]) + (if (not (equal? #f match?)) + match? + (error! "Unable to match expression: " expr " to any of the given patterns")))] + + ;; When there isn't an else case given, the last case + ;; Should include a failure mode + [(match-syntax-dispatch expr (p1 e2 ...)) + (let ([match? (single-match-syntax expr + p1 + (begin + e2 ...))]) + (if (not (equal? #f match?)) + match? + (error! "Unable to match expression: " expr " to any of the given patterns")))])) + (define-syntax match-define (syntax-rules () [(match-define pattern expr) @@ -323,6 +543,12 @@ pats ...) (let ([evald-expr expr]) (match-dispatch evald-expr pat pats ...))])) +(define-syntax match-syntax + (syntax-rules () + [(match-syntax expr pat) (let ([evald-expr expr]) (match-syntax-dispatch evald-expr pat))] + [(match-syntax expr pat pats ...) + (let ([evald-expr expr]) (match-syntax-dispatch evald-expr pat pats ...))])) + ; (match (list 10 20 30 40 50) ; [(?x 20 ?y 40 ?z) (+ ?x ?y ?z)]) diff --git a/crates/steel-core/src/scheme/modules/parameters.scm b/crates/steel-core/src/scheme/modules/parameters.scm index 5e62371a5..fb82cb2c5 100644 --- a/crates/steel-core/src/scheme/modules/parameters.scm +++ b/crates/steel-core/src/scheme/modules/parameters.scm @@ -110,14 +110,14 @@ (when (not (equal? ls tail)) (begin ;; TODO: This is probably wrong! - ; (displayln "FIRST" ls) + ; (displayln "setting winders first" ls) (set! winders (cdr ls)) ((cdr (car ls))) (f (cdr ls))))) (let f ([ls new]) (when (not (equal? ls tail)) (begin - ; (displayln "SECOND" ls) + ; (displayln "setting winders second" ls) (f (cdr ls)) ((car (car ls))) (set! winders ls))))))) @@ -146,15 +146,22 @@ (lambda (in body out) (in) (set! winders (cons (cons in out) winders)) - (let ([ans* (call-with-exception-handler (lambda (err) ;; Catch the exception on the way out + + ; (displayln winders) + + (displayln "catching exception here") + (set! winders (cdr winders)) (out) (raise-error err) void) (lambda () (body)))]) + + ; (displayln winders) + (set! winders (cdr winders)) (out) ans*))) diff --git a/crates/steel-core/src/steel_vm/const_evaluation.rs b/crates/steel-core/src/steel_vm/const_evaluation.rs index cea145578..ef8a738bf 100644 --- a/crates/steel-core/src/steel_vm/const_evaluation.rs +++ b/crates/steel-core/src/steel_vm/const_evaluation.rs @@ -238,6 +238,8 @@ impl<'a> ConstantEvaluator<'a> { TokenType::Identifier(s) => { // If we found a set identifier, skip it if self.set_idents.get(s).is_some() || self.expr_level_set_idents.contains(s) { + self.bindings.borrow_mut().unbind(s); + return None; }; self.bindings.borrow_mut().get(s) @@ -697,6 +699,8 @@ impl<'a> ConsumingVisitor for ConstantEvaluator<'a> { let parent = Rc::clone(&self.bindings); self.bindings = Rc::new(RefCell::new(new_env)); + // println!("Visiting body: {}", l.body); + let output = self.visit(l.body)?; // Unwind the 'recursion' @@ -731,8 +735,12 @@ impl<'a> ConsumingVisitor for ConstantEvaluator<'a> { } // Found no arguments are there are no non constant arguments + // TODO: @Matt 12/30/23 - this is causing a miscompilation - actually used + // arguments is found to be empty. if actually_used_arguments.is_empty() && non_constant_arguments.is_empty() { - // debug!("Found no used arguments or non constant arguments, returning the body"); + // println!("Found no used arguments or non constant arguments, returning the body"); + + // println!("Output: {}", output); // Unwind the recursion before we bail out self.bindings = parent; diff --git a/crates/steel-core/src/steel_vm/primitives.rs b/crates/steel-core/src/steel_vm/primitives.rs index 0f21325b5..d9074f832 100644 --- a/crates/steel-core/src/steel_vm/primitives.rs +++ b/crates/steel-core/src/steel_vm/primitives.rs @@ -9,7 +9,10 @@ use super::{ }; use crate::{ gc::Gc, - parser::{interner::InternedString, span::Span, tryfrom_visitor::TryFromExprKindForSteelVal}, + parser::{ + ast::TryFromSteelValVisitorForExprKind, interner::InternedString, span::Span, + tryfrom_visitor::TryFromExprKindForSteelVal, + }, primitives::{ fs_module, hashmaps::hashmap_module, @@ -1586,7 +1589,19 @@ fn syntax_module() -> BuiltInModule { .register_fn("syntax-span", crate::rvals::Syntax::syntax_loc) .register_fn("#%syntax/raw", crate::rvals::Syntax::proto) .register_fn("syntax-e", crate::rvals::Syntax::syntax_e) - .register_value("syntax?", gen_pred!(SyntaxObject)); + .register_value("syntax?", gen_pred!(SyntaxObject)) + .register_fn("#%debug-syntax->exprkind", |value| { + let expr = TryFromSteelValVisitorForExprKind::root(&value); + + match expr { + Ok(v) => { + println!("{}", v.to_pretty(60)); + } + Err(e) => { + println!("{}", e); + } + } + }); module } diff --git a/crates/steel-core/src/steel_vm/vm.rs b/crates/steel-core/src/steel_vm/vm.rs index 1d0f395ca..28692bb06 100644 --- a/crates/steel-core/src/steel_vm/vm.rs +++ b/crates/steel-core/src/steel_vm/vm.rs @@ -3128,6 +3128,12 @@ impl<'a> VmCore<'a> { // We should have arity at this point, drop the stack up to this point // take the last arity off the stack, go back and replace those in order let back = self.thread.stack.len() - new_arity; + + // println!("{}..{}", offset, back); + // println!("{:?}", self.thread.stack); + // println!("{}..{}", offset, back); + // println!("{:?}", self.thread.stack); + let _ = self.thread.stack.drain(offset..back); // TODO: Perhaps add call to minor collection here? diff --git a/crates/steel-language-server/src/backend.rs b/crates/steel-language-server/src/backend.rs index 74952a26d..aefb4a866 100644 --- a/crates/steel-language-server/src/backend.rs +++ b/crates/steel-language-server/src/backend.rs @@ -1,9 +1,11 @@ +#![allow(unused)] + use std::{ cell::RefCell, collections::{HashMap, HashSet}, error::Error, path::PathBuf, - sync::Arc, + sync::{Arc, RwLock}, }; use dashmap::{DashMap, DashSet}; @@ -16,9 +18,12 @@ use steel::{ query_top_level_define, query_top_level_define_on_condition, RequiredIdentifierInformation, SemanticAnalysis, }, - parser::{ast::ExprKind, expander::SteelMacro, interner::InternedString, parser::SourceId}, - rvals::FromSteelVal, - steel_vm::{builtin::BuiltInModule, engine::Engine}, + parser::{ + ast::ExprKind, expander::SteelMacro, interner::InternedString, parser::SourceId, + span::Span, tryfrom_visitor::SyntaxObjectFromExprKindRef, + }, + rvals::{FromSteelVal, SteelString}, + steel_vm::{builtin::BuiltInModule, engine::Engine, register_fn::RegisterFn}, }; use tower_lsp::jsonrpc::Result; use tower_lsp::lsp_types::notification::Notification; @@ -767,6 +772,11 @@ impl Backend { free_identifiers_and_unused.append(&mut static_arity_checking); + // TODO: Enable this once the syntax object let conversion is implemented + // let mut user_defined_lints = + // LINT_ENGINE.with_borrow_mut(|x| x.diagnostics(&rope, &analysis.exprs)); + // free_identifiers_and_unused.append(&mut user_defined_lints); + // All the diagnostics total free_identifiers_and_unused }); @@ -848,71 +858,95 @@ impl steel::steel_vm::engine::ModuleResolver for ExternalModuleResolver { thread_local! { pub static ENGINE: RefCell = RefCell::new(Engine::new()); + // pub static LINT_ENGINE: RefCell = RefCell::new(configure_lints().unwrap()); + pub static DIAGNOSTICS: RefCell> = RefCell::new(Vec::new()); +} + +// At one time, call the lints, collecting the diagnostics each time. +struct UserDefinedLintEngine { + engine: Engine, + lints: Arc>>, +} + +impl UserDefinedLintEngine { + pub fn diagnostics(&mut self, rope: &Rope, ast: &[ExprKind]) -> Vec { + let lints = self.lints.read().unwrap(); + + let syntax_objects: Vec<_> = ast + .iter() + .map(SyntaxObjectFromExprKindRef::try_from_expr_kind_ref) + .collect(); + + for lint in lints.iter() { + for object in &syntax_objects { + if let Ok(o) = object.clone() { + self.engine + .call_function_by_name_with_args(lint, vec![o]) + .ok(); + } + } + } + + DIAGNOSTICS.with_borrow_mut(|x| { + x.drain(..) + .filter_map(|d| { + let start_position = offset_to_position(d.span.start, &rope)?; + let end_position = offset_to_position(d.span.end, &rope)?; + + Some(Diagnostic::new_simple( + Range::new(start_position, end_position), + d.message.to_string(), + )) + }) + .collect() + }) + } +} + +#[derive(Clone)] +struct SteelDiagnostic { + span: Span, + message: SteelString, } -// #[tokio::main] -// async fn main() { -// env_logger::init(); - -// let stdin = tokio::io::stdin(); -// let stdout = tokio::io::stdout(); - -// // Use this to resolve the module configuration files -// let mut resolver_engine = Engine::new(); - -// let globals_set = Arc::new(DashSet::new()); - -// globals_set.insert("#%ignore-unused-identifier".into()); -// globals_set.insert("#%register-global".into()); - -// let cloned_set = globals_set.clone(); -// resolver_engine.register_fn("#%register-global", move |global: String| { -// cloned_set.insert(InternedString::from(global)) -// }); - -// let ignore_unused_set = Arc::new(DashSet::new()); -// let cloned_ignore_set = ignore_unused_set.clone(); -// resolver_engine.register_fn("#%ignore-unused-identifier", move |global: String| { -// cloned_ignore_set.insert(InternedString::from(global)); -// }); - -// ENGINE.with_borrow_mut(|x| { -// x.register_module_resolver( -// ExternalModuleResolver::new( -// &mut resolver_engine, -// PathBuf::from("/home/matt/.config/steel-lsp/"), -// ) -// .unwrap(), -// ) -// }); - -// let defined_globals = DashSet::new(); - -// ENGINE.with_borrow(|engine| { -// for global in engine.globals() { -// let resolved = global.resolve(); -// if !resolved.starts_with("#") -// && !resolved.starts_with("%") -// && !resolved.starts_with("mangler#%") -// { -// defined_globals.insert(resolved.to_string()); -// } -// } -// }); - -// let (service, socket) = LspService::build(|client| Backend { -// client, -// ast_map: DashMap::new(), -// document_map: DashMap::new(), -// _macro_map: DashMap::new(), -// globals_set, -// ignore_set: ignore_unused_set, -// defined_globals, -// }) -// .finish(); - -// Server::new(stdin, stdout, socket).serve(service).await; -// } +fn configure_lints() -> std::result::Result> { + let mut engine = Engine::new(); + + let mut diagnostics = BuiltInModule::new("lsp/diagnostics"); + let lints = Arc::new(RwLock::new(HashSet::new())); + + diagnostics.register_fn("suggest", move |span: Span, message: SteelString| { + DIAGNOSTICS.with_borrow_mut(|x| x.push(SteelDiagnostic { span, message })); + }); + + let engine_lints = lints.clone(); + diagnostics.register_fn("#%register-lint", move |name: String| { + engine_lints.write().unwrap().insert(name); + }); + + let mut directory = PathBuf::from( + std::env::var("STEEL_LSP_HOME").expect("Have you set your STEEL_LSP_HOME path?"), + ); + + directory.push("lints"); + + engine.register_module(diagnostics); + + // Load all of the lints - we'll want to grab + // all functions that get registered via define-lint - which means + // just give me a name + for file in std::fs::read_dir(&directory)? { + let file = file?; + + if file.path().is_file() { + let contents = std::fs::read_to_string(file.path())?; + + engine.compile_and_run_raw_program(&contents)?; + } + } + + Ok(UserDefinedLintEngine { engine, lints }) +} pub fn offset_to_position(offset: usize, rope: &Rope) -> Option { let line = rope.try_char_to_line(offset).ok()?; diff --git a/crates/steel-language-server/src/diagnostics.rs b/crates/steel-language-server/src/diagnostics.rs index 8475669e4..bc6f8154e 100644 --- a/crates/steel-language-server/src/diagnostics.rs +++ b/crates/steel-language-server/src/diagnostics.rs @@ -169,11 +169,13 @@ impl DiagnosticGenerator for StaticArityChecker { .trim_end_matches(interned.resolve()) .trim_end_matches("__%#__"); - let module = context + let Some(module) = context .engine .modules() .get(&PathBuf::from(module_path_to_check)) - .unwrap(); + else { + continue; + }; let module_ast = module.get_ast(); diff --git a/crates/steel-parser/src/parser.rs b/crates/steel-parser/src/parser.rs index c5cfec724..8624d7378 100644 --- a/crates/steel-parser/src/parser.rs +++ b/crates/steel-parser/src/parser.rs @@ -573,7 +573,6 @@ impl<'a> Parser<'a> { self.decrement_quasiquote_context_if_not_in_quote_context(); if let Some(popped) = popped_value { - // println!("Popped: {:?}", popped); debug_assert!(matches!(popped, ParsingContext::QuasiquoteTick(_))) } @@ -1059,9 +1058,8 @@ impl<'a> Parser<'a> { return Some(value); } - + // Make this also handle quasisyntax TokenType::QuasiQuote => { - // println!("Entering Context: Quasiquote - top level"); self.context.push(ParsingContext::QuasiquoteTick(0)); self.increment_quasiquote_context_if_not_in_quote_context(); @@ -1071,10 +1069,6 @@ impl<'a> Parser<'a> { .unwrap_or(Err(ParseError::UnexpectedEOF(self.source_name.clone()))) .map(|x| self.construct_quasiquote(x, res.span)); - // println!("{:?}", self.context.pop()); - - // println!("Top level Context: {:?}", self.context); - let popped_value = self.context.pop(); if let Some(popped) = popped_value { @@ -1083,8 +1077,6 @@ impl<'a> Parser<'a> { self.decrement_quasiquote_context_if_not_in_quote_context(); - // println!("Exiting context: {:?}", self.context.pop()); - return Some(value); }