diff --git a/crates/steel-core/src/compiler/modules.rs b/crates/steel-core/src/compiler/modules.rs index 4a9228ccb..d94ad88c9 100644 --- a/crates/steel-core/src/compiler/modules.rs +++ b/crates/steel-core/src/compiler/modules.rs @@ -2376,7 +2376,7 @@ impl<'a> ModuleBuilder<'a> { match *for_syntax { x if x == *FOR_SYNTAX => { if l.args.len() != 2 { - stop!(ArityMismatch => "provide expects a single identifier in the (for-syntax )") + stop!(ArityMismatch => "provide expects a single identifier in the (for-syntax )"; opt l.location) } // Collect the for syntax expressions @@ -2395,11 +2395,11 @@ impl<'a> ModuleBuilder<'a> { } } } else { - stop!(TypeMismatch => "provide expects either an identifier or a (for-syntax )") + stop!(TypeMismatch => "provide expects either an identifier or a (for-syntax )"; opt l.location) } } _ => { - stop!(TypeMismatch => "provide expects either a (for-syntax ) or an ident") + stop!(TypeMismatch => "provide expects either a (for-syntax ) or an ident"; opt expr.span()) } } } @@ -2427,7 +2427,7 @@ impl<'a> ModuleBuilder<'a> { if let Some(provide) = l.first_ident() { if *provide == *PROVIDE { if l.len() == 1 { - stop!(Generic => "provide expects at least one identifier to provide"); + stop!(Generic => "provide expects at least one identifier to provide"; opt l.location); } // Swap out the value inside the list @@ -2583,7 +2583,7 @@ impl<'a> ModuleBuilder<'a> { match l.first_ident() { Some(x) if *x == *ONLY_IN => { if l.args.len() < 2 { - stop!(BadSyntax => "only-in expects a require-spec and optionally a list of ids to bind (maybe renamed)"); + stop!(BadSyntax => "only-in expects a require-spec and optionally a list of ids to bind (maybe renamed)"; opt l.location); } self.parse_require_object_inner(home, r, &l.args[1], require_object)?; @@ -2623,10 +2623,12 @@ impl<'a> ModuleBuilder<'a> { Some(x) if *x == *PREFIX_IN => { if l.args.len() != 3 { - stop!(BadSyntax => "prefix-in expects a prefix to prefix a given file or module"; r.location.span; r.location.source.clone()); + stop!(BadSyntax => "prefix-in expects a prefix to prefix a given file or module"; opt l.location); } - if let Some(prefix) = l.args[1].atom_identifier() { + let prefix = &l.args[1]; + + if let Some(prefix) = prefix.atom_identifier() { match &mut require_object.prefix { Some(existing_prefix) => { // Append the new symbol to the existing prefix @@ -2639,17 +2641,18 @@ impl<'a> ModuleBuilder<'a> { self.parse_require_object_inner(home, r, &l.args[2], require_object)?; } else { - stop!(TypeMismatch => "prefix-in expects an identifier to use for the prefix"); + stop!(TypeMismatch => "prefix-in expects an identifier to use for the prefix"; opt prefix.span()); } } Some(x) if *x == *FOR_SYNTAX => { // We're expecting something like (for-syntax "foo") if l.args.len() != 2 { - stop!(BadSyntax => "for-syntax expects one string literal referring to a file or module"; r.location.span; r.location.source.clone()); + stop!(BadSyntax => "for-syntax expects one string literal referring to a file or module"; opt l.location); } - if let Some(path) = l.args[1].string_literal() { + let mod_name = &l.args[1]; + if let Some(path) = mod_name.string_literal() { if let Some(lib) = BUILT_INS.iter().find(|x| x.0 == path) { // self.built_ins.push(PathBuf::from(lib.0)); @@ -2700,7 +2703,7 @@ impl<'a> ModuleBuilder<'a> { } } - stop!(Generic => format!("Module not found: {:?}", current); r.location.span) + stop!(Generic => format!("Module not found: {:?}", current); mod_name.span().unwrap()) } } @@ -2708,17 +2711,17 @@ impl<'a> ModuleBuilder<'a> { require_object.path = Some(PathOrBuiltIn::Path(current)); } } else { - stop!(BadSyntax => "for-syntax expects a string literal referring to a file or module"; r.location.span; r.location.source.clone()); + stop!(BadSyntax => "for-syntax expects a string literal referring to a file or module"; opt mod_name.span()); } } _ => { - stop!(BadSyntax => "require accepts either a string literal or a for-syntax expression"; r.location.span; r.location.source.clone()) + stop!(BadSyntax => "require accepts either a string literal, a for-syntax expression or an only-in expression"; opt l.location) } } } _ => { - stop!(Generic => "require expected a string literal referring to a file/module"; r.location.span; r.location.source.clone()) + stop!(Generic => "require object expected a string literal referring to a file/module"; opt atom.span()) } } diff --git a/crates/steel-core/src/rerrs.rs b/crates/steel-core/src/rerrs.rs index 06aa43ffa..6df0f03fd 100644 --- a/crates/steel-core/src/rerrs.rs +++ b/crates/steel-core/src/rerrs.rs @@ -396,6 +396,12 @@ macro_rules! stop { ($type:ident => $thing:expr; $span:expr) => { return Err($crate::rerrs::SteelErr::new($crate::rerrs::ErrorKind::$type, ($thing).to_string()).with_span($span)) }; + ($type:ident => $thing:expr; opt $span:expr) => { + match $span { + Some(span) => stop!($type => $thing; span), + None => stop!($type => $thing) + } + }; ($type:ident => $thing:expr; $span:expr; $source:expr) => { return Err($crate::rerrs::SteelErr::new($crate::rerrs::ErrorKind::$type, ($thing).to_string()).with_span($span)) diff --git a/crates/steel-core/src/steel_vm/const_evaluation.rs b/crates/steel-core/src/steel_vm/const_evaluation.rs index c2b306c0e..fa7b75645 100644 --- a/crates/steel-core/src/steel_vm/const_evaluation.rs +++ b/crates/steel-core/src/steel_vm/const_evaluation.rs @@ -621,7 +621,7 @@ impl<'a> ConsumingVisitor for ConstantEvaluator<'a> { if let ExprKind::LambdaFunction(f) = &func { if !f.rest { if !f.args.is_empty() { - stop!(ArityMismatch => format!("function expected {} arguments, found 0", f.args.len())) + stop!(ArityMismatch => format!("function expected {} arguments, found 0", f.args.len()); f.location.span) } // If the body is constant we can safely remove the application @@ -894,8 +894,8 @@ impl<'a> ConsumingVisitor for ConstantEvaluator<'a> { Ok(ExprKind::Set(s)) } - fn visit_require(&mut self, _s: crate::parser::ast::Require) -> Self::Output { - stop!(Generic => "unexpected require - require is only allowed at the top level"); + fn visit_require(&mut self, s: crate::parser::ast::Require) -> Self::Output { + stop!(Generic => "unexpected require - require is only allowed at the top level"; s.location.span); } // TODO come back to this diff --git a/crates/steel-core/src/steel_vm/engine.rs b/crates/steel-core/src/steel_vm/engine.rs index a0748cb59..3ef6dfd99 100644 --- a/crates/steel-core/src/steel_vm/engine.rs +++ b/crates/steel-core/src/steel_vm/engine.rs @@ -1606,10 +1606,14 @@ impl Engine { /// Emit the unexpanded AST pub fn emit_ast_to_string(expr: &str) -> Result { + let parsed = Self::emit_ast(expr)?; + Ok(parsed.into_iter().map(|x| x.to_pretty(60)).join("\n\n")) + } + + pub fn emit_ast(expr: &str) -> Result> { let parsed: std::result::Result, ParseError> = Parser::new(expr, None).collect(); - let parsed = parsed?; - Ok(parsed.into_iter().map(|x| x.to_pretty(60)).join("\n\n")) + Ok(parsed?) } /// Emit the fully expanded AST as a pretty printed string diff --git a/crates/steel-parser/src/ast.rs b/crates/steel-parser/src/ast.rs index 7fcd0adc4..f8ad063b9 100644 --- a/crates/steel-parser/src/ast.rs +++ b/crates/steel-parser/src/ast.rs @@ -155,6 +155,24 @@ macro_rules! expr_list { } impl ExprKind { + pub fn span(&self) -> Option { + match self { + ExprKind::Atom(expr) => Some(expr.syn.span), + ExprKind::If(expr) => Some(expr.location.span), + ExprKind::Let(expr) => Some(expr.location.span), + ExprKind::Define(expr) => Some(expr.location.span), + ExprKind::LambdaFunction(expr) => Some(expr.location.span), + ExprKind::Begin(expr) => Some(expr.location.span), + ExprKind::Return(expr) => Some(expr.location.span), + ExprKind::Quote(expr) => Some(expr.location.span), + ExprKind::Macro(expr) => Some(expr.location.span), + ExprKind::SyntaxRules(expr) => Some(expr.location.span), + ExprKind::List(expr) => expr.location, + ExprKind::Set(expr) => Some(expr.location.span), + ExprKind::Require(expr) => Some(expr.location.span), + } + } + pub fn to_string_literal(&self) -> Option<&String> { if let ExprKind::Atom(a) = self { if let TokenType::StringLiteral(s) = &a.syn.ty { @@ -972,9 +990,13 @@ impl List { } } - pub fn with_span(mut self, location: Span) -> Self { - self.location = Some(location); - self + pub fn with_spans(args: Vec, open: Span, close: Span) -> Self { + List { + args, + improper: false, + location: Some(Span::merge(open, close)), + syntax_object_id: SyntaxObjectId::fresh().0, + } } pub fn make_improper(mut self) -> Self { diff --git a/crates/steel-parser/src/parser.rs b/crates/steel-parser/src/parser.rs index 9376ed32a..72b3b5931 100644 --- a/crates/steel-parser/src/parser.rs +++ b/crates/steel-parser/src/parser.rs @@ -478,9 +478,26 @@ impl<'a> Parser<'a> { } } - fn read_from_tokens(&mut self) -> Result { - let mut stack: Vec> = Vec::new(); - let mut current_frame: Vec = Vec::new(); + fn maybe_lower_frame(&self, frame: Frame, close: Span) -> Result { + let result = self.maybe_lower(frame.exprs); + + match result { + Ok(ExprKind::List(mut list)) => { + list.location = Some(Span::merge(frame.open, close)); + + Ok(ExprKind::List(list)) + } + _ => result, + } + } + + fn read_from_tokens(&mut self, open: Span) -> Result { + let mut stack: Vec = Vec::new(); + + let mut current_frame = Frame { + open, + exprs: vec![], + }; self.quote_stack = Vec::new(); @@ -499,7 +516,7 @@ impl<'a> Parser<'a> { TokenType::Error => return Err(tokentype_error_to_parse_error(&token)), // TODO TokenType::QuoteTick => { // quote_count += 1; - // self.quote_stack.push(current_frame.len()); + // self.quote_stack.push(current_frame.exprs.len()); self.shorthand_quote_stack.push(stack.len()); let last_context = self.quote_context; @@ -541,7 +558,7 @@ impl<'a> Parser<'a> { debug_assert!(matches!(popped, ParsingContext::QuoteTick(_))) } - current_frame.push(quote_inner?); + current_frame.exprs.push(quote_inner?); } TokenType::Unquote => { // println!("Entering context: Unquote"); @@ -572,7 +589,7 @@ impl<'a> Parser<'a> { debug_assert!(matches!(popped, ParsingContext::UnquoteTick(_))) } // println!("Exiting Context: {:?}", self.context.pop()); - current_frame.push(quote_inner?); + current_frame.exprs.push(quote_inner?); } TokenType::QuasiQuote => { // println!("Entering context: Quasiquote"); @@ -601,7 +618,7 @@ impl<'a> Parser<'a> { debug_assert!(matches!(popped, ParsingContext::QuasiquoteTick(_))) } - current_frame.push(quote_inner?); + current_frame.exprs.push(quote_inner?); } TokenType::UnquoteSplice => { // println!("Entering context: UnquoteSplicing"); @@ -636,18 +653,27 @@ impl<'a> Parser<'a> { } // println!("Exiting Context: {:?}", self.context.pop()); - current_frame.push(quote_inner?); + current_frame.exprs.push(quote_inner?); } TokenType::OpenParen => { stack.push(current_frame); - current_frame = Vec::new(); + + current_frame = Frame { + open: token.span, + exprs: vec![], + }; } TokenType::CloseParen => { + let close = token.span; // This is the match that we'll want to move inside the below stack.pop() match statement // As we close the current context, we check what our current state is - if let Some(mut prev_frame) = stack.pop() { - match prev_frame.first_mut().and_then(|x| x.atom_identifier_mut()) { + match prev_frame + .exprs + .first_mut() + .and_then(|x| x.atom_identifier_mut()) + { Some(ident) if *ident == *UNQUOTE => { // self.increment_quasiquote_context_if_not_in_quote_context(); if self.quasiquote_depth == 0 && !self.quote_context { @@ -685,7 +711,7 @@ impl<'a> Parser<'a> { self.context.pop(); } - match current_frame.first() { + match current_frame.exprs.first() { Some(ExprKind::Atom(Atom { syn: SyntaxObject { @@ -698,32 +724,34 @@ impl<'a> Parser<'a> { | ParsingContext::QuasiquoteTick(_) | ParsingContext::Quote(_) | ParsingContext::QuoteTick(_), - ) => prev_frame - .push(ExprKind::List(List::new(current_frame))), + ) => prev_frame.exprs.push(ExprKind::List( + current_frame.to_list(close), + )), _ => { - prev_frame.push( - self.maybe_lower(current_frame).map_err( - |x| { - x.set_source( - self.source_name.clone(), - ) - }, - )?, + prev_frame.exprs.push( + self.maybe_lower_frame( + current_frame, + close, + ) + .map_err(|x| { + x.set_source(self.source_name.clone()) + })?, ); } }, _ => { // println!("Converting to list"); // println!("Context here: {:?}", self.context); - prev_frame - .push(ExprKind::List(List::new(current_frame))) + prev_frame.exprs.push(ExprKind::List( + current_frame.to_list(close), + )) } } } Some(ParsingContext::QuoteTick(_)) | Some(ParsingContext::QuasiquoteTick(_)) => { - match current_frame.first() { + match current_frame.exprs.first() { Some(ExprKind::Atom(Atom { syn: SyntaxObject { @@ -732,10 +760,11 @@ impl<'a> Parser<'a> { }, })) => { // println!("Converting to quote inside quote tick"); - prev_frame.push( - self.maybe_lower(current_frame).map_err( - |x| x.set_source(self.source_name.clone()), - )?, + prev_frame.exprs.push( + self.maybe_lower_frame(current_frame, close) + .map_err(|x| { + x.set_source(self.source_name.clone()) + })?, ); } _ => { @@ -746,8 +775,9 @@ impl<'a> Parser<'a> { // } // println!("Converting to list inside quote tick"); - prev_frame - .push(ExprKind::List(List::new(current_frame))) + prev_frame.exprs.push(ExprKind::List( + current_frame.to_list(close), + )) } } } @@ -771,10 +801,10 @@ impl<'a> Parser<'a> { // self.context.pop(); // } - prev_frame.push( - self.maybe_lower(current_frame).map_err(|x| { - x.set_source(self.source_name.clone()) - })?, + prev_frame.exprs.push( + self.maybe_lower_frame(current_frame, close).map_err( + |x| x.set_source(self.source_name.clone()), + )?, ); } @@ -796,16 +826,16 @@ impl<'a> Parser<'a> { self.context.pop(); } - prev_frame.push( - self.maybe_lower(current_frame).map_err(|x| { - x.set_source(self.source_name.clone()) - })?, + prev_frame.exprs.push( + self.maybe_lower_frame(current_frame, close).map_err( + |x| x.set_source(self.source_name.clone()), + )?, ); } // Else case, just go ahead and assume it is a normal frame - _ => prev_frame.push( - self.maybe_lower(current_frame) + _ => prev_frame.exprs.push( + self.maybe_lower_frame(current_frame, close) .map_err(|x| x.set_source(self.source_name.clone()))?, ), } @@ -813,7 +843,7 @@ impl<'a> Parser<'a> { // Reinitialize current frame here current_frame = prev_frame; } else { - // println!("Else case: {:?}", current_frame); + // println!("Else case: {:?}", current_frame.exprs); // println!("Context: {:?}", self.context); // dbg!(&self.quote_stack); @@ -824,45 +854,50 @@ impl<'a> Parser<'a> { | Some(ParsingContext::QuasiquoteTick(_)) => { // | Some(ParsingContext::Quote(d)) && d > 0 => { - return Ok(ExprKind::List(List::new(current_frame))); + return Ok(ExprKind::List(current_frame.to_list(close))); } Some(ParsingContext::Quote(x)) if *x > 0 => { self.context.pop(); - return Ok(ExprKind::List(List::new(current_frame))); + return Ok(ExprKind::List(current_frame.to_list(close))); } Some(ParsingContext::Quote(0)) => { self.context.pop(); return self - .maybe_lower(current_frame) + .maybe_lower_frame(current_frame, close) .map_err(|x| x.set_source(self.source_name.clone())); } _ => { // dbg!(self.quasiquote_depth); - // println!("=> {}", List::new(current_frame.clone())); + // println!("=> {}", List::new(current_frame.exprs.clone())); // println!("----------------------------------------"); if self.quasiquote_depth > 0 { // TODO/HACK - @Matt // If we're in a define syntax situation, go ahead and just return a normal one if current_frame + .exprs .first() .map(|x| x.define_syntax_ident()) .unwrap_or_default() { - return self.maybe_lower(current_frame).map_err( - |x| x.set_source(self.source_name.clone()), - ); + return self + .maybe_lower_frame(current_frame, close) + .map_err(|x| { + x.set_source(self.source_name.clone()) + }); } // println!("Should still be quoted here"); - return Ok(ExprKind::List(List::new(current_frame))); + return Ok(ExprKind::List( + current_frame.to_list(close), + )); } return self - .maybe_lower(current_frame) + .maybe_lower_frame(current_frame, close) .map_err(|x| x.set_source(self.source_name.clone())); } } @@ -871,7 +906,7 @@ impl<'a> Parser<'a> { _ => { if let TokenType::Quote = &token.ty { - // self.quote_stack.push(current_frame.len()); + // self.quote_stack.push(current_frame.exprs.len()); self.quote_stack.push(stack.len()); } @@ -879,7 +914,7 @@ impl<'a> Parser<'a> { // Mark what context we're inside with the context stack: // This only works when its the first argument - check the function call in open paren? - if current_frame.is_empty() { + if current_frame.exprs.is_empty() { match &token.ty { TokenType::Quote => { if self.context == [ParsingContext::QuoteTick(0)] { @@ -915,7 +950,7 @@ impl<'a> Parser<'a> { // println!("{}", token); - current_frame.push(ExprKind::Atom(Atom::new( + current_frame.exprs.push(ExprKind::Atom(Atom::new( SyntaxObject::from_token_with_source( &token, &self.source_name.clone(), @@ -1107,7 +1142,7 @@ impl<'a> Parser<'a> { } TokenType::OpenParen => { - let value = self.read_from_tokens(); + let value = self.read_from_tokens(res.span); // self.quote_stack.clear(); // self.context.clear(); @@ -1312,39 +1347,39 @@ impl ASTLowerPass { } } ExprKind::Atom(a) if self.quote_depth == 0 => { - let value = std::mem::take(&mut value.args); + let value = std::mem::replace(value, List::new(vec![])); *expr = match &a.syn.ty { - TokenType::If => parse_if(value.into_iter(), a.syn), + TokenType::If => parse_if(value.args.into_iter(), a.syn), TokenType::Identifier(expr) if *expr == *IF => { - parse_if(value.into_iter(), a.syn) + parse_if(value.args.into_iter(), a.syn) } - TokenType::Define => parse_define(value.into_iter(), a.syn), + TokenType::Define => parse_define(value.args.into_iter(), a.syn), TokenType::Identifier(expr) if *expr == *DEFINE => { - parse_define(value.into_iter(), a.syn) + parse_define(value.args.into_iter(), a.syn) } - TokenType::Let => parse_let(value.into_iter(), a.syn.clone()), + TokenType::Let => parse_let(value.args.into_iter(), a.syn.clone()), TokenType::Identifier(expr) if *expr == *LET => { - parse_let(value.into_iter(), a.syn) + parse_let(value.args.into_iter(), a.syn) } // TODO: Deprecate - TokenType::TestLet => parse_new_let(value.into_iter(), a.syn), + TokenType::TestLet => parse_new_let(value.args.into_iter(), a.syn), TokenType::Identifier(expr) if *expr == *PLAIN_LET => { - parse_new_let(value.into_iter(), a.syn) + parse_new_let(value.args.into_iter(), a.syn) } TokenType::Quote => parse_single_argument( - value.into_iter(), + value.args.into_iter(), a.syn, "quote", |expr, syn| ast::Quote::new(expr, syn).into(), ), TokenType::Identifier(expr) if *expr == *QUOTE => { parse_single_argument( - value.into_iter(), + value.args.into_iter(), a.syn, "quote", |expr, syn| ast::Quote::new(expr, syn).into(), @@ -1352,45 +1387,45 @@ impl ASTLowerPass { } TokenType::Return => parse_single_argument( - value.into_iter(), + value.args.into_iter(), a.syn, "return!", |expr, syn| ast::Return::new(expr, syn).into(), ), TokenType::Identifier(expr) if *expr == *RETURN => { parse_single_argument( - value.into_iter(), + value.args.into_iter(), a.syn, "return!", |expr, syn| ast::Return::new(expr, syn).into(), ) } - TokenType::Require => parse_require(&a, value), + TokenType::Require => parse_require(&a, value.args), TokenType::Identifier(expr) if *expr == *REQUIRE => { - parse_require(&a, value) + parse_require(&a, value.args) } - TokenType::Set => parse_set(&a, value), + TokenType::Set => parse_set(&a, value.args), TokenType::Identifier(expr) if *expr == *SET => { - parse_set(&a, value) + parse_set(&a, value.args) } - TokenType::Begin => parse_begin(a, value), + TokenType::Begin => parse_begin(a, value.args), TokenType::Identifier(expr) if *expr == *BEGIN => { - parse_begin(a, value) + parse_begin(a, value.args) } - TokenType::Lambda => parse_lambda(a, value), + TokenType::Lambda => parse_lambda(a, value.args), TokenType::Identifier(expr) if *expr == *LAMBDA || *expr == *LAMBDA_FN || *expr == *LAMBDA_SYMBOL => { - parse_lambda(a, value) + parse_lambda(a, value.args) } - _ => Ok(ExprKind::List(List::new(value))), + _ => Ok(ExprKind::List(value)), }?; Ok(()) @@ -1480,6 +1515,17 @@ pub fn lower_entire_ast(expr: &mut ExprKind) -> Result<()> { ASTLowerPass { quote_depth: 0 }.lower(expr) } +struct Frame { + open: Span, + exprs: Vec, +} + +impl Frame { + fn to_list(self, close: Span) -> List { + List::with_spans(self.exprs, self.open, close) + } +} + #[cfg(test)] mod parser_tests { // use super::TokenType::*; diff --git a/src/lib.rs b/src/lib.rs index 2f31f4154..5f306e484 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,7 +33,13 @@ enum EmitAction { /// Output a debug display of the fully transformed bytecode Bytecode { default_file: Option }, /// Print a debug display of the fully expanded AST - Ast { default_file: Option }, + Ast { + default_file: Option, + #[arg(long)] + expanded: Option, + #[arg(long)] + pretty: Option, + }, /// Enter the repl with the given file loaded Interactive { default_file: Option, @@ -148,14 +154,27 @@ pub fn run(clap_args: Args) -> Result<(), Box> { Args { default_file: None, - action: Some(EmitAction::Ast { - default_file: Some(path), - }), + action: + Some(EmitAction::Ast { + default_file: Some(path), + expanded, + pretty, + }), .. } => { let contents = fs::read_to_string(path.clone())?; - let res = vm.emit_fully_expanded_ast_to_string(&contents, Some(path.clone())); + let expanded = expanded.unwrap_or(true); + let pretty = pretty.unwrap_or(true); + + let res = match (expanded, pretty) { + (true, true) => vm.emit_fully_expanded_ast_to_string(&contents, Some(path.clone())), + (true, false) => vm + .emit_fully_expanded_ast(&contents, Some(path.clone())) + .map(|ast| format!("{:#?}", ast)), + (false, true) => Engine::emit_ast_to_string(&contents), + (false, false) => Engine::emit_ast(&contents).map(|ast| format!("{:#?}", ast)), + }; match res { Ok(ast) => println!("{ast}"),