From 6d0911d7a858ff6a770ca1556e91791fb1838ec1 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 29 Jun 2021 10:54:34 +0200 Subject: [PATCH 1/4] Mutable visitor --- src/eval/capture.rs | 2 +- src/syntax/visit.rs | 225 ++++++++++++++++++++++++++------------------ 2 files changed, 132 insertions(+), 95 deletions(-) diff --git a/src/eval/capture.rs b/src/eval/capture.rs index 64275e938..10f7ec83b 100644 --- a/src/eval/capture.rs +++ b/src/eval/capture.rs @@ -1,7 +1,7 @@ use std::rc::Rc; use super::{Scope, Scopes, Value}; -use crate::syntax::visit::{visit_expr, Visit}; +use crate::syntax::visit::{immutable::visit_expr, Visit}; use crate::syntax::{Expr, Ident}; /// A visitor that captures variable slots. diff --git a/src/syntax/visit.rs b/src/syntax/visit.rs index 524183613..657b379ae 100644 --- a/src/syntax/visit.rs +++ b/src/syntax/visit.rs @@ -1,54 +1,91 @@ -//! Syntax tree traversal. +//! Mutable and immutable syntax tree traversal. -use super::*; +use crate::syntax::*; -macro_rules! visit { - ($(fn $name:ident($v:ident $(, $node:ident: &$ty:ty)?) $body:block)*) => { - /// Traverses the syntax tree. - pub trait Visit<'ast> { - $(fn $name(&mut self $(, $node: &'ast $ty)?) { - $name(self, $($node)?); - })* +/// Implement the immutable and the mutable visitor version. +macro_rules! impl_visitors { + ($($name:ident($($tts:tt)*) $body:block)*) => { + macro_rules! r { + (rc: $x:expr) => { $x.as_ref() }; + ($x:expr) => { &$x }; + } + impl_visitor! { + /// Walk syntax trees immutably. + Visit, + /// Immutable visitor functions. + immutable, + [$(($name($($tts)*) $body))*] + } + + macro_rules! r { + (rc: $x:expr) => { std::rc::Rc::make_mut(&mut $x) }; + ($x:expr) => { &mut $x }; + } + + impl_visitor! { + /// Walk syntax trees mutably. + VisitMut, + /// Mutable visitor functions. + mutable, + [$(($name($($tts)*) $body mut))*] mut + } + }; +} + +/// Implement an immutable or mutable visitor. +macro_rules! impl_visitor { + ( + #[doc = $visit_doc:expr] $visit:ident, + #[doc = $module_doc:expr] $module:ident, + [$(( + $name:ident($v:ident, $node:ident: $ty:ty) + $body:block + $($fmut:tt)? + ))*] + $($mut:tt)? + ) => { + #[doc = $visit_doc] + pub trait $visit<'ast> { /// Visit a definition of a binding. /// /// Bindings are, for example, left-hand side of let expressions, /// and key/value patterns in for loops. - fn visit_binding(&mut self, _: &'ast Ident) {} + fn visit_binding(&mut self, _: &'ast $($mut)? Ident) {} /// Visit the entry into a scope. fn visit_enter(&mut self) {} /// Visit the exit from a scope. fn visit_exit(&mut self) {} + + $(fn $name(&mut self, $node: &'ast $($fmut)? $ty) { + $module::$name(self, $node); + })* } - $(visit! { - @$(concat!("Walk a node of type [`", stringify!($ty), "`]."), )? - pub fn $name<'ast, V>( - #[allow(unused)] $v: &mut V - $(, #[allow(unused)] $node: &'ast $ty)? - ) - where - V: Visit<'ast> + ?Sized - $body - })* - }; - - (@$doc:expr, $($tts:tt)*) => { - #[doc = $doc] - $($tts)* + #[doc = $module_doc] + pub mod $module { + use super::*; + $( + #[allow(unused_variables)] + pub fn $name<'ast, V>($v: &mut V, $node: &'ast $($fmut)? $ty) + where + V: $visit<'ast> + ?Sized + $body + )* + } }; } -visit! { - fn visit_tree(v, node: &Tree) { - for node in node { - v.visit_node(&node); +impl_visitors! { + visit_tree(v, tree: Tree) { + for node in tree { + v.visit_node(node); } } - fn visit_node(v, node: &Node) { + visit_node(v, node: Node) { match node { Node::Text(_) => {} Node::Space => {} @@ -64,20 +101,20 @@ visit! { } } - fn visit_heading(v, node: &HeadingNode) { - v.visit_tree(&node.body); + visit_heading(v, heading: HeadingNode) { + v.visit_tree(r!(rc: heading.body)); } - fn visit_list(v, node: &ListItem) { - v.visit_tree(&node.body); + visit_list(v, item: ListItem) { + v.visit_tree(r!(item.body)); } - fn visit_enum(v, node: &EnumItem) { - v.visit_tree(&node.body); + visit_enum(v, item: EnumItem) { + v.visit_tree(r!(item.body)); } - fn visit_expr(v, node: &Expr) { - match node { + visit_expr(v, expr: Expr) { + match expr { Expr::None(_) => {} Expr::Auto(_) => {} Expr::Bool(_, _) => {} @@ -109,121 +146,121 @@ visit! { } } - fn visit_array(v, node: &ArrayExpr) { - for expr in &node.items { - v.visit_expr(&expr); + visit_array(v, array: ArrayExpr) { + for expr in r!(array.items) { + v.visit_expr(expr); } } - fn visit_dict(v, node: &DictExpr) { - for named in &node.items { - v.visit_expr(&named.expr); + visit_dict(v, dict: DictExpr) { + for named in r!(dict.items) { + v.visit_expr(r!(named.expr)); } } - fn visit_template(v, node: &TemplateExpr) { + visit_template(v, template: TemplateExpr) { v.visit_enter(); - v.visit_tree(&node.tree); + v.visit_tree(r!(rc: template.tree)); v.visit_exit(); } - fn visit_group(v, node: &GroupExpr) { - v.visit_expr(&node.expr); + visit_group(v, group: GroupExpr) { + v.visit_expr(r!(group.expr)); } - fn visit_block(v, node: &BlockExpr) { - if node.scoping { + visit_block(v, block: BlockExpr) { + if block.scoping { v.visit_enter(); } - for expr in &node.exprs { - v.visit_expr(&expr); + for expr in r!(block.exprs) { + v.visit_expr(expr); } - if node.scoping { + if block.scoping { v.visit_exit(); } } - fn visit_binary(v, node: &BinaryExpr) { - v.visit_expr(&node.lhs); - v.visit_expr(&node.rhs); + visit_binary(v, binary: BinaryExpr) { + v.visit_expr(r!(binary.lhs)); + v.visit_expr(r!(binary.rhs)); } - fn visit_unary(v, node: &UnaryExpr) { - v.visit_expr(&node.expr); + visit_unary(v, unary: UnaryExpr) { + v.visit_expr(r!(unary.expr)); } - fn visit_call(v, node: &CallExpr) { - v.visit_expr(&node.callee); - v.visit_args(&node.args); + visit_call(v, call: CallExpr) { + v.visit_expr(r!(call.callee)); + v.visit_args(r!(call.args)); } - fn visit_closure(v, node: &ClosureExpr) { - for param in node.params.iter() { + visit_closure(v, closure: ClosureExpr) { + for param in r!(rc: closure.params) { v.visit_binding(param); } - v.visit_expr(&node.body); + v.visit_expr(r!(rc: closure.body)); } - fn visit_args(v, node: &CallArgs) { - for arg in &node.items { + visit_args(v, args: CallArgs) { + for arg in r!(args.items) { v.visit_arg(arg); } } - fn visit_arg(v, node: &CallArg) { - match node { - CallArg::Pos(expr) => v.visit_expr(&expr), - CallArg::Named(named) => v.visit_expr(&named.expr), + visit_arg(v, arg: CallArg) { + match arg { + CallArg::Pos(expr) => v.visit_expr(expr), + CallArg::Named(named) => v.visit_expr(r!(named.expr)), } } - fn visit_with(v, node: &WithExpr) { - v.visit_expr(&node.callee); - v.visit_args(&node.args); + visit_with(v, with_expr: WithExpr) { + v.visit_expr(r!(with_expr.callee)); + v.visit_args(r!(with_expr.args)); } - fn visit_let(v, node: &LetExpr) { - if let Some(init) = &node.init { - v.visit_expr(&init); + visit_let(v, let_expr: LetExpr) { + if let Some(init) = r!(let_expr.init) { + v.visit_expr(init); } - v.visit_binding(&node.binding); + v.visit_binding(r!(let_expr.binding)); } - fn visit_if(v, node: &IfExpr) { - v.visit_expr(&node.condition); - v.visit_expr(&node.if_body); - if let Some(body) = &node.else_body { - v.visit_expr(&body); + visit_if(v, if_expr: IfExpr) { + v.visit_expr(r!(if_expr.condition)); + v.visit_expr(r!(if_expr.if_body)); + if let Some(body) = r!(if_expr.else_body) { + v.visit_expr(body); } } - fn visit_while(v, node: &WhileExpr) { - v.visit_expr(&node.condition); - v.visit_expr(&node.body); + visit_while(v, while_expr: WhileExpr) { + v.visit_expr(r!(while_expr.condition)); + v.visit_expr(r!(while_expr.body)); } - fn visit_for(v, node: &ForExpr) { - v.visit_expr(&node.iter); - match &node.pattern { + visit_for(v, for_expr: ForExpr) { + v.visit_expr(r!(for_expr.iter)); + match r!(for_expr.pattern) { ForPattern::Value(value) => v.visit_binding(value), ForPattern::KeyValue(key, value) => { v.visit_binding(key); v.visit_binding(value); } } - v.visit_expr(&node.body); + v.visit_expr(r!(for_expr.body)); } - fn visit_import(v, node: &ImportExpr) { - v.visit_expr(&node.path); - if let Imports::Idents(idents) = &node.imports { + visit_import(v, import_expr: ImportExpr) { + v.visit_expr(r!(import_expr.path)); + if let Imports::Idents(idents) = r!(import_expr.imports) { for ident in idents { v.visit_binding(ident); } } } - fn visit_include(v, node: &IncludeExpr) { - v.visit_expr(&node.path); + visit_include(v, include_expr: IncludeExpr) { + v.visit_expr(r!(include_expr.path)); } } From b89cd128ae118571efe8a5a5b40b9365e3007746 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 29 Jun 2021 13:08:16 +0200 Subject: [PATCH 2/4] Wide calls --- src/parse/mod.rs | 86 ++++++++++++++++++++++------ src/parse/tokens.rs | 5 +- src/syntax/expr.rs | 4 +- src/syntax/token.rs | 7 ++- tests/ref/code/call-wide.png | Bin 0 -> 1864 bytes tests/typ/code/call-wide.typ | 47 +++++++++++++++ tools/support/typst.tmLanguage.json | 8 +-- 7 files changed, 129 insertions(+), 28 deletions(-) create mode 100644 tests/ref/code/call-wide.png create mode 100644 tests/typ/code/call-wide.typ diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 381d44e2f..e8e168033 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -15,6 +15,7 @@ pub use tokens::*; use std::rc::Rc; use crate::diag::Pass; +use crate::syntax::visit::{mutable::visit_expr, VisitMut}; use crate::syntax::*; /// Parse a string of source code. @@ -25,7 +26,7 @@ pub fn parse(src: &str) -> Pass { /// Parse a syntax tree. fn tree(p: &mut Parser) -> Tree { - tree_while(p, true, |_| true) + tree_while(p, true, &mut |_| true) } /// Parse a syntax tree that stays right of the column at the start of the next @@ -38,31 +39,70 @@ fn tree_indented(p: &mut Parser) -> Tree { }); let column = p.column(p.next_start()); - tree_while(p, false, |p| match p.peek() { + tree_while(p, false, &mut |p| match p.peek() { Some(Token::Space(n)) if n >= 1 => p.column(p.next_end()) >= column, _ => true, }) } /// Parse a syntax tree. -fn tree_while( - p: &mut Parser, - mut at_start: bool, - mut f: impl FnMut(&mut Parser) -> bool, -) -> Tree { +fn tree_while(p: &mut Parser, mut at_start: bool, f: &mut F) -> Tree +where + F: FnMut(&mut Parser) -> bool, +{ + /// Visitor that adds a recursively parsed rest template to the first wide + /// call's argument list and diagnoses all following wide calls. + struct WideVisitor<'a, 's, F> { + p: &'a mut Parser<'s>, + f: &'a mut F, + found: bool, + } + + impl<'ast, 'a, 's, F> VisitMut<'ast> for WideVisitor<'a, 's, F> + where + F: FnMut(&mut Parser) -> bool, + { + fn visit_expr(&mut self, node: &'ast mut Expr) { + visit_expr(self, node); + + if let Expr::Call(call) = node { + if call.wide { + let start = self.p.next_start(); + let tree = if !self.found { + tree_while(self.p, true, self.f) + } else { + self.p.diag(error!(call.callee.span(), "duplicate wide call")); + Tree::default() + }; + + call.args.items.push(CallArg::Pos(Expr::Template(TemplateExpr { + span: self.p.span(start), + tree: Rc::new(tree), + }))); + + self.found = true; + } + } + } + + // Don't recurse into templates. + fn visit_template(&mut self, _: &'ast mut TemplateExpr) {} + } + // We use `at_start` to keep track of whether we are at the start of a line // or template to know whether things like headings are allowed. let mut tree = vec![]; while !p.eof() && f(p) { - if let Some(node) = node(p, &mut at_start) { - match node { - Node::Space => {} - Node::Parbreak(_) => {} - _ => at_start = false, + if let Some(mut node) = node(p, &mut at_start) { + at_start &= matches!(node, Node::Space | Node::Parbreak(_)); + if let Node::Expr(expr) = &mut node { + let mut visitor = WideVisitor { p, f, found: false }; + visitor.visit_expr(expr); } tree.push(node); } } + tree } @@ -236,12 +276,13 @@ fn expr_with(p: &mut Parser, atomic: bool, min_prec: usize) -> Option { }; loop { - // Parenthesis or bracket means this is a function call. + // Exclamation mark, parenthesis or bracket means this is a function + // call. if matches!( p.peek_direct(), - Some(Token::LeftParen) | Some(Token::LeftBracket), + Some(Token::Excl) | Some(Token::LeftParen) | Some(Token::LeftBracket), ) { - lhs = call(p, lhs); + lhs = call(p, lhs)?; continue; } @@ -516,7 +557,9 @@ fn block(p: &mut Parser, scoping: bool) -> Expr { } /// Parse a function call. -fn call(p: &mut Parser, callee: Expr) -> Expr { +fn call(p: &mut Parser, callee: Expr) -> Option { + let wide = p.eat_if(Token::Excl); + let mut args = match p.peek_direct() { Some(Token::LeftParen) => { p.start_group(Group::Paren, TokenMode::Code); @@ -524,10 +567,14 @@ fn call(p: &mut Parser, callee: Expr) -> Expr { p.end_group(); args } - _ => CallArgs { + Some(Token::LeftBracket) => CallArgs { span: Span::at(callee.span().end), items: vec![], }, + _ => { + p.expected_at("argument list", p.prev_end()); + return None; + } }; if p.peek_direct() == Some(Token::LeftBracket) { @@ -535,11 +582,12 @@ fn call(p: &mut Parser, callee: Expr) -> Expr { args.items.push(CallArg::Pos(body)); } - Expr::Call(CallExpr { + Some(Expr::Call(CallExpr { span: p.span(callee.span().start), callee: Box::new(callee), + wide, args, - }) + })) } /// Parse the arguments to a function call. diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index abc3d6a6c..4d90dded6 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -117,7 +117,7 @@ impl<'s> Tokens<'s> { // Length two. '=' if self.s.eat_if('=') => Token::EqEq, - '!' if self.s.eat_if('=') => Token::BangEq, + '!' if self.s.eat_if('=') => Token::ExclEq, '<' if self.s.eat_if('=') => Token::LtEq, '>' if self.s.eat_if('=') => Token::GtEq, '+' if self.s.eat_if('=') => Token::PlusEq, @@ -135,6 +135,7 @@ impl<'s> Tokens<'s> { '-' => Token::Hyph, '*' => Token::Star, '/' => Token::Slash, + '!' => Token::Excl, '=' => Token::Eq, '<' => Token::Lt, '>' => Token::Gt, @@ -750,7 +751,7 @@ mod tests { t!(Code[" a1"]: "/" => Slash); t!(Code: "=" => Eq); t!(Code: "==" => EqEq); - t!(Code: "!=" => BangEq); + t!(Code: "!=" => ExclEq); t!(Code: "<" => Lt); t!(Code: "<=" => LtEq); t!(Code: ">" => Gt); diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index 62f023990..aabff1eaf 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -295,7 +295,7 @@ impl BinOp { Token::And => Self::And, Token::Or => Self::Or, Token::EqEq => Self::Eq, - Token::BangEq => Self::Neq, + Token::ExclEq => Self::Neq, Token::Lt => Self::Lt, Token::LtEq => Self::Leq, Token::Gt => Self::Gt, @@ -388,6 +388,8 @@ pub struct CallExpr { pub span: Span, /// The function to call. pub callee: Box, + /// Whether the call is wide, that is, capturing the template behind it. + pub wide: bool, /// The arguments to the function. pub args: CallArgs, } diff --git a/src/syntax/token.rs b/src/syntax/token.rs index 3f07bb338..250622643 100644 --- a/src/syntax/token.rs +++ b/src/syntax/token.rs @@ -42,12 +42,14 @@ pub enum Token<'s> { Hyph, /// A slash: `/`. Slash, + /// An exlamation mark. + Excl, /// A single equals sign: `=`. Eq, /// Two equals signs: `==`. EqEq, /// An exclamation mark followed by an equals sign: `!=`. - BangEq, + ExclEq, /// A less-than sign: `<`. Lt, /// A less-than sign followed by an equals sign: `<=`. @@ -227,9 +229,10 @@ impl<'s> Token<'s> { Self::Plus => "plus", Self::Hyph => "minus", Self::Slash => "slash", + Self::Excl => "exclamation mark", Self::Eq => "assignment operator", Self::EqEq => "equality operator", - Self::BangEq => "inequality operator", + Self::ExclEq => "inequality operator", Self::Lt => "less-than operator", Self::LtEq => "less-than or equal operator", Self::Gt => "greater-than operator", diff --git a/tests/ref/code/call-wide.png b/tests/ref/code/call-wide.png new file mode 100644 index 0000000000000000000000000000000000000000..028d7aa4e33423b2b48276d486e8b0b6b49750fc GIT binary patch literal 1864 zcmZ`)c{JOJ7XFE}e(@~vES8tD(0z9 zl~F2MrlUkKwN*-pl2A*V;z_KJ(ja1~7VSLGpEGlgbG~!$_s9L`-tW8j-f?p|r>cBN z82|uP$Mb{>0H6ThnstbLqcpB-9~OJu{7 zlaqqEg~54Y%hdZ9Q|~*cxt-H30|NtW@l=&~s(GxsNm5<%PV(rTggsnZHN@iacx`QM zx&6bXgFiC_Z&JGl^LolIznGbUNMTSIp63 zwnM2gl}e>hC_zC%fq{W;Zf>rwu2?MA-rnBA!UBy(>+9>IP$(o4sjjZR+pAZ~stf@3 z>pBu}o^fKCB=Vxz2gzHd$7PW!!hM;Jv5D6_*pAwb?EOu&idRaVTmLHw3&i$U=t^_7 zY0d>-lo5yI^4G764G&xy%E)AaS$~2nKIm0CWRkK6I(E{4xNn?aNtdG$TH>s7&CY!6IdE`4?sYd85AkYZX1*A@N;GH4ptY5L<+%KVK8A1%1P6krG+ z8}}W@$9z(K2%KE3OaX>G$h6R7!Q=Fpw_FH&nO)CTq7WM%5*0R9#tkn_FesJ45N%o_ zETx%K>PEN-zD+z4AhYY=@Rjp@Zu;z_s!29tNA@*}qCU$`J^%N@M%g0%kIG0?nn=4{ z_I$C$ngg!%iAQ(z@TU>!eCxZq(>E?r@fqH*L8&)sq#=3mg_D}7|2tUK59GGK2Dqmt zdC^mL^`7B)=D74<(>M4+IbpCah849~BN4_svko<&$V})aq1T0eve<=UwJO|pA3#oF zdQNvN@uC4;GU7O+>VV2qqZ|9%^|V!!sc%iiY8w%wALxD8!yc2KD1pVI#|DGj3s;<} zZHurNs73K)wMc#{i9D_ZMoLWmBuip-wSH1}@&nm20~H&+ZH?f&?g91nKEqI-QKovwlm?a{5fE}J)H9_>9A3Y`w@`F4=?uCIvYvtDATl^>_~8j zN=+Ozv~YXk!A~{M{#x)Tixv6%9*dF!agq3D?gBWKWB#LMGsamYlVP0cWmjLkbYiSc z9?gw5M<&ojh)c}O?6&;;G+t|FCT3mZgM&uy7c1RKrocJg7tDtsD}ho+Yo_1?)|D_D z=L^nAPHP-|uh>3-4A08+H%CnS^zV-sW|U|M0?ZUiCLw`@vix%lBVtFP%5(rB>Uw$& z8mep1WgNGCG~88x%fP>{lF5XCqu=stGX=f@$Sd5&moy`*qKjQR#FY^6z7cwBD$L`b z2g`f$P4^R%7P+&0IEiX4J;Hf#L9-{=QG{{ktld-hbuJjLjoxYootQZ5wY%yEVfW?p z>|7=^Cg6QeoSbYMa6EGb^fk#YVFIlJ%%gN$VTOu9OWfUgh*z#c?`JN05lq5l`>XDp z*U_=XeUp_rcIH5GfOX_l)I^??ObqYgsswkmFYg|H&}5~b?p zLO`k1Mxm!wzGD@=G0oglxZUIT^(ojVlU~aCjaV1Y!b$XV9~~3TJnG5)y~d*uZGUDxFvJ_@6JCMwOW4Ose| vK9ZVpqA$UNxTh_(NTXooslWJkI)4Tw4{)2W;;7o}z6-#S=t8K*2c`TUVi8@x literal 0 HcmV?d00001 diff --git a/tests/typ/code/call-wide.typ b/tests/typ/code/call-wide.typ new file mode 100644 index 000000000..2bb6d82e4 --- /dev/null +++ b/tests/typ/code/call-wide.typ @@ -0,0 +1,47 @@ +// Test wide calls. + +--- +// Test multiple wide calls in separate expressions. +#font!(color: eastern) - First +#font!(color: forest) - Second + +--- +// Test in heading. +# A #align!(right) B +C + +--- +// Test evaluation semantics. +// Ref: false + +#let r +#let x = 1 +#let f(x, body) = (x, body) + +[ + { r = f!(x) } + { x = 2 } +] + +#test(repr(r), "(1,