diff --git a/src/eval/capture.rs b/src/eval/capture.rs index 786da36e7..4e24bc908 100644 --- a/src/eval/capture.rs +++ b/src/eval/capture.rs @@ -1,32 +1,23 @@ use std::rc::Rc; -use super::{Scope, Scopes}; -use crate::syntax::{NodeKind, RedRef}; +use super::{Scope, Scopes, Value}; +use crate::syntax::ast::{ClosureParam, Expr, Ident, Imports, TypedNode}; +use crate::syntax::RedRef; /// A visitor that captures variable slots. pub struct CapturesVisitor<'a> { external: &'a Scopes<'a>, + internal: Scopes<'a>, captures: Scope, } impl<'a> CapturesVisitor<'a> { /// Create a new visitor for the given external scopes. pub fn new(external: &'a Scopes) -> Self { - Self { external, captures: Scope::new() } - } - - pub fn visit(&mut self, node: RedRef) { - match node.kind() { - NodeKind::Ident(ident) => { - if let Some(slot) = self.external.get(ident.as_str()) { - self.captures.def_slot(ident.as_str(), Rc::clone(slot)); - } - } - _ => { - for child in node.children() { - self.visit(child); - } - } + Self { + external, + internal: Scopes::new(None), + captures: Scope::new(), } } @@ -34,4 +25,152 @@ impl<'a> CapturesVisitor<'a> { pub fn finish(self) -> Scope { self.captures } + + /// Bind a new internal variable. + pub fn bind(&mut self, ident: Ident) { + self.internal.def_mut(ident.take(), Value::None); + } + + /// Capture a variable if it isn't internal. + pub fn capture(&mut self, ident: Ident) { + if self.internal.get(&ident).is_none() { + if let Some(slot) = self.external.get(&ident) { + self.captures.def_slot(ident.take(), Rc::clone(slot)); + } + } + } + + /// Visit any node and collect all captured variables. + pub fn visit(&mut self, node: RedRef) { + match node.cast() { + // Every identifier is a potential variable that we need to capture. + // Identifiers that shouldn't count as captures because they + // actually bind a new name are handled further below (individually + // through the expressions that contain them). + Some(Expr::Ident(ident)) => self.capture(ident), + + // A closure contains parameter bindings, which are bound before the + // body is evaluated. Take must be taken so that the default values + // of named parameters cannot access previous parameter bindings. + Some(Expr::Closure(expr)) => { + for param in expr.params() { + if let ClosureParam::Named(named) = param { + self.visit(named.expr().as_red()); + } + } + + for param in expr.params() { + match param { + ClosureParam::Pos(ident) => self.bind(ident), + ClosureParam::Named(named) => self.bind(named.name()), + ClosureParam::Sink(ident) => self.bind(ident), + } + } + + self.visit(expr.body().as_red()); + } + + // A let expression contains a binding, but that binding is only + // active after the body is evaluated. + Some(Expr::Let(expr)) => { + if let Some(init) = expr.init() { + self.visit(init.as_red()); + } + self.bind(expr.binding()); + } + + // A for loop contains one or two bindings in its pattern. These are + // active after the iterable is evaluated but before the body is + // evaluated. + Some(Expr::For(expr)) => { + self.visit(expr.iter().as_red()); + let pattern = expr.pattern(); + if let Some(key) = pattern.key() { + self.bind(key); + } + self.bind(pattern.value()); + self.visit(expr.body().as_red()); + } + + // An import contains items, but these are active only after the + // path is evaluated. + Some(Expr::Import(expr)) => { + self.visit(expr.path().as_red()); + if let Imports::Items(items) = expr.imports() { + for item in items { + self.bind(item); + } + } + } + + // Blocks and templates create a scope. + Some(Expr::Block(_) | Expr::Template(_)) => { + self.internal.enter(); + for child in node.children() { + self.visit(child); + } + self.internal.exit(); + } + + // Everything else is traversed from left to right. + _ => { + for child in node.children() { + self.visit(child); + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parse::parse; + use crate::source::SourceId; + use crate::syntax::RedNode; + + #[track_caller] + fn test(src: &str, result: &[&str]) { + let green = parse(src); + let red = RedNode::from_root(green, SourceId::from_raw(0)); + + let mut scopes = Scopes::new(None); + scopes.def_const("x", 0); + scopes.def_const("y", 0); + scopes.def_const("z", 0); + + let mut visitor = CapturesVisitor::new(&scopes); + visitor.visit(red.as_ref()); + + let captures = visitor.finish(); + let mut names: Vec<_> = captures.iter().map(|(k, _)| k).collect(); + names.sort(); + + assert_eq!(names, result); + } + + #[test] + fn test_captures() { + // Let binding and function definition. + test("#let x = x", &["x"]); + test("#let x; {x + y}", &["y"]); + test("#let f(x, y) = x + y", &[]); + + // Closure with different kinds of params. + test("{(x, y) => x + z}", &["z"]); + test("{(x: y, z) => x + z}", &["y"]); + test("{(..x) => x + y}", &["y"]); + test("{(x, y: x + z) => x + y}", &["x", "z"]); + + // For loop. + test("#for x in y { x + z }", &["y", "z"]); + test("#for x, y in y { x + y }", &["y"]); + + // Import. + test("#import x, y from z", &["z"]); + test("#import x, y, z from x + y", &["x", "y"]); + + // Scoping. + test("{ let x = 1; { let y = 2; y }; x + y }", &["y"]); + } } diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 7c984691a..540b58b98 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -174,8 +174,8 @@ impl Eval for Expr { fn eval(&self, ctx: &mut EvalContext) -> TypResult { match self { - Self::Ident(v) => v.eval(ctx), Self::Lit(v) => v.eval(ctx), + Self::Ident(v) => v.eval(ctx), Self::Array(v) => v.eval(ctx).map(Value::Array), Self::Dict(v) => v.eval(ctx).map(Value::Dict), Self::Template(v) => v.eval(ctx).map(Value::Template), @@ -200,17 +200,17 @@ impl Eval for Lit { type Output = Value; fn eval(&self, _: &mut EvalContext) -> TypResult { - Ok(match *self { - Self::None(_) => Value::None, - Self::Auto(_) => Value::Auto, - Self::Bool(_, v) => Value::Bool(v), - Self::Int(_, v) => Value::Int(v), - Self::Float(_, v) => Value::Float(v), - Self::Length(_, v, unit) => Value::Length(Length::with_unit(v, unit)), - Self::Angle(_, v, unit) => Value::Angle(Angle::with_unit(v, unit)), - Self::Percent(_, v) => Value::Relative(Relative::new(v / 100.0)), - Self::Fractional(_, v) => Value::Fractional(Fractional::new(v)), - Self::Str(_, ref v) => Value::Str(v.into()), + Ok(match self.kind() { + LitKind::None => Value::None, + LitKind::Auto => Value::Auto, + LitKind::Bool(v) => Value::Bool(v), + LitKind::Int(v) => Value::Int(v), + LitKind::Float(v) => Value::Float(v), + LitKind::Length(v, unit) => Value::Length(Length::with_unit(v, unit)), + LitKind::Angle(v, unit) => Value::Angle(Angle::with_unit(v, unit)), + LitKind::Percent(v) => Value::Relative(Relative::new(v / 100.0)), + LitKind::Fractional(v) => Value::Fractional(Fractional::new(v)), + LitKind::Str(ref v) => Value::Str(v.into()), }) } } @@ -219,9 +219,9 @@ impl Eval for Ident { type Output = Value; fn eval(&self, ctx: &mut EvalContext) -> TypResult { - match ctx.scopes.get(&self.string) { + match ctx.scopes.get(self) { Some(slot) => Ok(slot.borrow().clone()), - None => bail!(self.span, "unknown variable"), + None => bail!(self.span(), "unknown variable"), } } } @@ -239,7 +239,7 @@ impl Eval for DictExpr { fn eval(&self, ctx: &mut EvalContext) -> TypResult { self.items() - .map(|x| Ok((x.name().string.into(), x.expr().eval(ctx)?))) + .map(|x| Ok((x.name().take().into(), x.expr().eval(ctx)?))) .collect() } } @@ -401,7 +401,7 @@ impl Eval for CallArgs { CallArg::Named(x) => { items.push(Arg { span, - name: Some(x.name().string.into()), + name: Some(x.name().take().into()), value: Spanned::new(x.expr().eval(ctx)?, x.expr().span()), }); } @@ -457,23 +457,23 @@ impl Eval for ClosureExpr { for param in self.params() { match param { ClosureParam::Pos(name) => { - params.push((name.string, None)); + params.push((name.take(), None)); } ClosureParam::Named(named) => { - params.push((named.name().string, Some(named.expr().eval(ctx)?))); + params.push((named.name().take(), Some(named.expr().eval(ctx)?))); } ClosureParam::Sink(name) => { if sink.is_some() { - bail!(name.span, "only one argument sink is allowed"); + bail!(name.span(), "only one argument sink is allowed"); } - sink = Some(name.string); + sink = Some(name.take()); } } } // Clone the body expression so that we don't have a lifetime // dependence on the AST. - let name = self.name().map(|name| name.string); + let name = self.name().map(Ident::take); let body = self.body(); // Define the actual function. @@ -533,7 +533,7 @@ impl Eval for LetExpr { Some(expr) => expr.eval(ctx)?, None => Value::None, }; - ctx.scopes.def_mut(self.binding().string, value); + ctx.scopes.def_mut(self.binding().take(), value); Ok(Value::None) } } @@ -589,7 +589,7 @@ impl Eval for ForExpr { #[allow(unused_parens)] for ($($value),*) in $iter { - $(ctx.scopes.def_mut(&$binding.string, $value);)* + $(ctx.scopes.def_mut(&$binding, $value);)* let value = self.body().eval(ctx)?; output = ops::join(output, value) @@ -603,7 +603,10 @@ impl Eval for ForExpr { let iter = self.iter().eval(ctx)?; let pattern = self.pattern(); - match (pattern.key(), pattern.value(), iter) { + let key = pattern.key().map(Ident::take); + let value = pattern.value().take(); + + match (key, value, iter) { (None, v, Value::Str(string)) => iter!(for (v => value) in string.iter()), (None, v, Value::Array(array)) => { iter!(for (v => value) in array.into_iter()) @@ -644,10 +647,10 @@ impl Eval for ImportExpr { } Imports::Items(idents) => { for ident in idents { - if let Some(slot) = module.scope.get(&ident.string) { - ctx.scopes.def_mut(ident.string, slot.borrow().clone()); + if let Some(slot) = module.scope.get(&ident) { + ctx.scopes.def_mut(ident.take(), slot.borrow().clone()); } else { - bail!(ident.span, "unresolved import"); + bail!(ident.span(), "unresolved import"); } } } @@ -691,12 +694,12 @@ impl Access for Expr { impl Access for Ident { fn access<'a>(&self, ctx: &'a mut EvalContext) -> TypResult> { - match ctx.scopes.get(&self.string) { + match ctx.scopes.get(self) { Some(slot) => match slot.try_borrow_mut() { Ok(guard) => Ok(guard), - Err(_) => bail!(self.span, "cannot mutate a constant"), + Err(_) => bail!(self.span(), "cannot mutate a constant"), }, - None => bail!(self.span, "unknown variable"), + None => bail!(self.span(), "unknown variable"), } } } diff --git a/src/eval/scope.rs b/src/eval/scope.rs index eb057ae3b..2290affdb 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -120,6 +120,8 @@ impl Scope { impl Debug for Scope { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.values.fmt(f) + f.debug_map() + .entries(self.values.iter().map(|(k, v)| (k, v.borrow()))) + .finish() } } diff --git a/src/geom/relative.rs b/src/geom/relative.rs index 754aa6c85..e46c51de1 100644 --- a/src/geom/relative.rs +++ b/src/geom/relative.rs @@ -3,7 +3,7 @@ use super::*; /// A relative length. /// /// _Note_: `50%` is represented as `0.5` here, but stored as `50.0` in the -/// corresponding [literal](crate::syntax::ast::Lit::Percent). +/// corresponding [literal](crate::syntax::ast::LitKind::Percent). #[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct Relative(N64); diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 505482cad..78e4f896d 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -418,7 +418,7 @@ fn item(p: &mut Parser) -> ParseResult { if p.at(&NodeKind::Colon) { marker.perform(p, NodeKind::Named, |p| { - if matches!(marker.peek(p).unwrap().kind(), &NodeKind::Ident(_)) { + if let Some(NodeKind::Ident(_)) = marker.peek(p).map(|c| c.kind()) { p.eat(); expr(p) } else { diff --git a/src/source.rs b/src/source.rs index 46d6b84bb..713380c58 100644 --- a/src/source.rs +++ b/src/source.rs @@ -145,7 +145,7 @@ impl SourceFile { } pub fn ast(&self) -> TypResult { - let red = RedNode::new_root(self.root.clone(), self.id); + let red = RedNode::from_root(self.root.clone(), self.id); let errors = red.errors(); if errors.is_empty() { Ok(red.cast().unwrap()) diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index dc71e2295..067bd6daf 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -1,6 +1,8 @@ //! A typed layer over the red-green tree. -use super::{NodeKind, RedNode, RedRef, Span}; +use std::ops::Deref; + +use super::{Green, GreenData, NodeKind, RedNode, RedRef, Span}; use crate::geom::{AngularUnit, LengthUnit}; use crate::util::EcoString; @@ -8,13 +10,24 @@ use crate::util::EcoString; pub trait TypedNode: Sized { /// Convert from a red node to a typed node. fn from_red(value: RedRef) -> Option; + + /// A reference to the underlying red node. + fn as_red(&self) -> RedRef<'_>; + + /// The source code location. + fn span(&self) -> Span { + self.as_red().span() + } } macro_rules! node { ($(#[$attr:meta])* $name:ident) => { - node!{$(#[$attr])* $name => $name} + node!{$(#[$attr])* $name: $name} }; - ($(#[$attr:meta])* $variant:ident => $name:ident) => { + ($(#[$attr:meta])* $name:ident: $variant:ident) => { + node!{$(#[$attr])* $name: NodeKind::$variant} + }; + ($(#[$attr:meta])* $name:ident: $($variant:pat)|*) => { #[derive(Debug, Clone, PartialEq)] #[repr(transparent)] $(#[$attr])* @@ -22,22 +35,14 @@ macro_rules! node { impl TypedNode for $name { fn from_red(node: RedRef) -> Option { - if node.kind() != &NodeKind::$variant { - return None; + if matches!(node.kind(), $($variant)|*) { + Some(Self(node.own())) + } else { + None } - - Some(Self(node.own())) - } - } - - impl $name { - /// The source code location. - pub fn span(&self) -> Span { - self.0.span() } - /// The underlying red node. - pub fn as_red(&self) -> RedRef { + fn as_red(&self) -> RedRef<'_> { self.0.as_ref() } } @@ -52,7 +57,27 @@ node! { impl Markup { /// The markup nodes. pub fn nodes(&self) -> impl Iterator + '_ { - self.0.children().filter_map(RedRef::cast) + self.0.children().filter_map(|node| match node.kind() { + NodeKind::Space(_) => Some(MarkupNode::Space), + NodeKind::Linebreak => Some(MarkupNode::Linebreak), + NodeKind::Parbreak => Some(MarkupNode::Parbreak), + NodeKind::Strong => Some(MarkupNode::Strong), + NodeKind::Emph => Some(MarkupNode::Emph), + NodeKind::Text(s) => Some(MarkupNode::Text(s.clone())), + NodeKind::UnicodeEscape(c) => Some(MarkupNode::Text((*c).into())), + NodeKind::EnDash => Some(MarkupNode::Text("\u{2013}".into())), + NodeKind::EmDash => Some(MarkupNode::Text("\u{2014}".into())), + NodeKind::NonBreakingSpace => Some(MarkupNode::Text("\u{00A0}".into())), + NodeKind::Raw(raw) => Some(MarkupNode::Raw(RawNode { + block: raw.block, + lang: raw.lang.clone(), + text: raw.text.clone(), + })), + NodeKind::Heading => node.cast().map(MarkupNode::Heading), + NodeKind::List => node.cast().map(MarkupNode::List), + NodeKind::Enum => node.cast().map(MarkupNode::Enum), + _ => node.cast().map(MarkupNode::Expr), + }) } } @@ -83,28 +108,6 @@ pub enum MarkupNode { Expr(Expr), } -impl TypedNode for MarkupNode { - fn from_red(node: RedRef) -> Option { - match node.kind() { - NodeKind::Space(_) => Some(MarkupNode::Space), - NodeKind::Linebreak => Some(MarkupNode::Linebreak), - NodeKind::Parbreak => Some(MarkupNode::Parbreak), - NodeKind::Strong => Some(MarkupNode::Strong), - NodeKind::Emph => Some(MarkupNode::Emph), - NodeKind::Text(s) => Some(MarkupNode::Text(s.clone())), - NodeKind::UnicodeEscape(c) => Some(MarkupNode::Text((*c).into())), - NodeKind::EnDash => Some(MarkupNode::Text("\u{2013}".into())), - NodeKind::EmDash => Some(MarkupNode::Text("\u{2014}".into())), - NodeKind::NonBreakingSpace => Some(MarkupNode::Text("\u{00A0}".into())), - NodeKind::Raw(_) => node.cast().map(MarkupNode::Raw), - NodeKind::Heading => node.cast().map(MarkupNode::Heading), - NodeKind::List => node.cast().map(MarkupNode::List), - NodeKind::Enum => node.cast().map(MarkupNode::Enum), - _ => node.cast().map(MarkupNode::Expr), - } - } -} - /// A raw block with optional syntax highlighting: `` `...` ``. #[derive(Debug, Clone, PartialEq)] pub struct RawNode { @@ -118,22 +121,9 @@ pub struct RawNode { pub block: bool, } -impl TypedNode for RawNode { - fn from_red(node: RedRef) -> Option { - match node.kind() { - NodeKind::Raw(raw) => Some(Self { - block: raw.block, - lang: raw.lang.clone(), - text: raw.text.clone(), - }), - _ => None, - } - } -} - node! { /// A section heading: `= Introduction`. - Heading => HeadingNode + HeadingNode: Heading } impl HeadingNode { @@ -154,7 +144,7 @@ impl HeadingNode { node! { /// An item in an unordered list: `- ...`. - List => ListNode + ListNode: List } impl ListNode { @@ -166,7 +156,7 @@ impl ListNode { node! { /// An item in an enumeration (ordered list): `1. ...`. - Enum => EnumNode + EnumNode: Enum } impl EnumNode { @@ -190,10 +180,10 @@ impl EnumNode { /// An expression. #[derive(Debug, Clone, PartialEq)] pub enum Expr { - /// An identifier: `left`. - Ident(Ident), /// A literal: `1`, `true`, ... Lit(Lit), + /// An identifier: `left`. + Ident(Ident), /// An array expression: `(1, "hi", 12cm)`. Array(ArrayExpr), /// A dictionary expression: `(thickness: 3pt, pattern: dashed)`. @@ -251,6 +241,29 @@ impl TypedNode for Expr { _ => node.cast().map(Self::Lit), } } + + fn as_red(&self) -> RedRef<'_> { + match self { + Self::Lit(v) => v.as_red(), + Self::Ident(v) => v.as_red(), + Self::Array(v) => v.as_red(), + Self::Dict(v) => v.as_red(), + Self::Template(v) => v.as_red(), + Self::Group(v) => v.as_red(), + Self::Block(v) => v.as_red(), + Self::Unary(v) => v.as_red(), + Self::Binary(v) => v.as_red(), + Self::Call(v) => v.as_red(), + Self::Closure(v) => v.as_red(), + Self::With(v) => v.as_red(), + Self::Let(v) => v.as_red(), + Self::If(v) => v.as_red(), + Self::While(v) => v.as_red(), + Self::For(v) => v.as_red(), + Self::Import(v) => v.as_red(), + Self::Include(v) => v.as_red(), + } + } } impl Expr { @@ -267,99 +280,72 @@ impl Expr { | Self::Include(_) ) } +} - /// Return the expression's span. - pub fn span(&self) -> Span { - match self { - Self::Ident(ident) => ident.span, - Self::Lit(lit) => lit.span(), - Self::Array(array) => array.span(), - Self::Dict(dict) => dict.span(), - Self::Template(template) => template.span(), - Self::Group(group) => group.span(), - Self::Block(block) => block.span(), - Self::Unary(unary) => unary.span(), - Self::Binary(binary) => binary.span(), - Self::Call(call) => call.span(), - Self::Closure(closure) => closure.span(), - Self::With(with) => with.span(), - Self::Let(let_) => let_.span(), - Self::If(if_) => if_.span(), - Self::While(while_) => while_.span(), - Self::For(for_) => for_.span(), - Self::Import(import) => import.span(), - Self::Include(include) => include.span(), +node! { + /// A literal: `1`, `true`, ... + Lit: NodeKind::None + | NodeKind::Auto + | NodeKind::Bool(_) + | NodeKind::Int(_) + | NodeKind::Float(_) + | NodeKind::Length(_, _) + | NodeKind::Angle(_, _) + | NodeKind::Percentage(_) + | NodeKind::Fraction(_) + | NodeKind::Str(_) +} + +impl Lit { + /// The kind of literal. + pub fn kind(&self) -> LitKind { + match *self.0.kind() { + NodeKind::None => LitKind::None, + NodeKind::Auto => LitKind::Auto, + NodeKind::Bool(v) => LitKind::Bool(v), + NodeKind::Int(v) => LitKind::Int(v), + NodeKind::Float(v) => LitKind::Float(v), + NodeKind::Length(v, unit) => LitKind::Length(v, unit), + NodeKind::Angle(v, unit) => LitKind::Angle(v, unit), + NodeKind::Percentage(v) => LitKind::Percent(v), + NodeKind::Fraction(v) => LitKind::Fractional(v), + NodeKind::Str(ref v) => LitKind::Str(v.clone()), + _ => panic!("literal is of wrong kind"), } } } -/// A literal: `1`, `true`, ... +/// The kind of a literal. #[derive(Debug, Clone, PartialEq)] -pub enum Lit { +pub enum LitKind { /// The none literal: `none`. - None(Span), + None, /// The auto literal: `auto`. - Auto(Span), + Auto, /// A boolean literal: `true`, `false`. - Bool(Span, bool), + Bool(bool), /// An integer literal: `120`. - Int(Span, i64), + Int(i64), /// A floating-point literal: `1.2`, `10e-4`. - Float(Span, f64), + Float(f64), /// A length literal: `12pt`, `3cm`. - Length(Span, f64, LengthUnit), + Length(f64, LengthUnit), /// An angle literal: `1.5rad`, `90deg`. - Angle(Span, f64, AngularUnit), + Angle(f64, AngularUnit), /// A percent literal: `50%`. /// /// _Note_: `50%` is stored as `50.0` here, but as `0.5` in the /// corresponding [value](crate::geom::Relative). - Percent(Span, f64), + Percent(f64), /// A fraction unit literal: `1fr`. - Fractional(Span, f64), + Fractional(f64), /// A string literal: `"hello!"`. - Str(Span, EcoString), -} - -impl TypedNode for Lit { - fn from_red(node: RedRef) -> Option { - match *node.kind() { - NodeKind::None => Some(Self::None(node.span())), - NodeKind::Auto => Some(Self::Auto(node.span())), - NodeKind::Bool(v) => Some(Self::Bool(node.span(), v)), - NodeKind::Int(v) => Some(Self::Int(node.span(), v)), - NodeKind::Float(v) => Some(Self::Float(node.span(), v)), - NodeKind::Length(v, unit) => Some(Self::Length(node.span(), v, unit)), - NodeKind::Angle(v, unit) => Some(Self::Angle(node.span(), v, unit)), - NodeKind::Percentage(v) => Some(Self::Percent(node.span(), v)), - NodeKind::Fraction(v) => Some(Self::Fractional(node.span(), v)), - NodeKind::Str(ref v) => Some(Self::Str(node.span(), v.clone())), - _ => None, - } - } -} - -impl Lit { - /// The source code location. - pub fn span(&self) -> Span { - match *self { - Self::None(span) => span, - Self::Auto(span) => span, - Self::Bool(span, _) => span, - Self::Int(span, _) => span, - Self::Float(span, _) => span, - Self::Length(span, _, _) => span, - Self::Angle(span, _, _) => span, - Self::Percent(span, _) => span, - Self::Fractional(span, _) => span, - Self::Str(span, _) => span, - } - } + Str(EcoString), } node! { /// An array expression: `(1, "hi", 12cm)`. - Array => ArrayExpr + ArrayExpr: Array } impl ArrayExpr { @@ -371,7 +357,7 @@ impl ArrayExpr { node! { /// A dictionary expression: `(thickness: 3pt, pattern: dashed)`. - Dict => DictExpr + DictExpr: Dict } impl DictExpr { @@ -400,7 +386,7 @@ impl Named { node! { /// A template expression: `[*Hi* there!]`. - Template => TemplateExpr + TemplateExpr: Template } impl TemplateExpr { @@ -412,7 +398,7 @@ impl TemplateExpr { node! { /// A grouped expression: `(1 + 2)`. - Group => GroupExpr + GroupExpr: Group } impl GroupExpr { @@ -424,7 +410,7 @@ impl GroupExpr { node! { /// A block expression: `{ let x = 1; x + 2 }`. - Block => BlockExpr + BlockExpr: Block } impl BlockExpr { @@ -436,14 +422,15 @@ impl BlockExpr { node! { /// A unary operation: `-x`. - Unary => UnaryExpr + UnaryExpr: Unary } impl UnaryExpr { /// The operator: `-`. pub fn op(&self) -> UnOp { self.0 - .cast_first_child() + .children() + .find_map(|node| UnOp::from_token(node.kind())) .expect("unary expression is missing operator") } @@ -464,12 +451,6 @@ pub enum UnOp { Not, } -impl TypedNode for UnOp { - fn from_red(node: RedRef) -> Option { - Self::from_token(node.kind()) - } -} - impl UnOp { /// Try to convert the token into a unary operation. pub fn from_token(token: &NodeKind) -> Option { @@ -501,14 +482,15 @@ impl UnOp { node! { /// A binary operation: `a + b`. - Binary => BinaryExpr + BinaryExpr: Binary } impl BinaryExpr { /// The binary operator: `+`. pub fn op(&self) -> BinOp { self.0 - .cast_first_child() + .children() + .find_map(|node| BinOp::from_token(node.kind())) .expect("binary expression is missing operator") } @@ -566,12 +548,6 @@ pub enum BinOp { DivAssign, } -impl TypedNode for BinOp { - fn from_red(node: RedRef) -> Option { - Self::from_token(node.kind()) - } -} - impl BinOp { /// Try to convert the token into a binary operation. pub fn from_token(token: &NodeKind) -> Option { @@ -671,7 +647,7 @@ pub enum Associativity { node! { /// An invocation of a function: `foo(...)`. - Call => CallExpr + CallExpr: Call } impl CallExpr { @@ -717,6 +693,14 @@ impl TypedNode for CallArg { _ => node.cast().map(CallArg::Pos), } } + + fn as_red(&self) -> RedRef<'_> { + match self { + Self::Pos(v) => v.as_red(), + Self::Named(v) => v.as_red(), + Self::Spread(v) => v.as_red(), + } + } } impl CallArg { @@ -732,7 +716,7 @@ impl CallArg { node! { /// A closure expression: `(x, y) => z`. - Closure => ClosureExpr + ClosureExpr: Closure } impl ClosureExpr { @@ -779,6 +763,14 @@ impl TypedNode for ClosureParam { _ => None, } } + + fn as_red(&self) -> RedRef<'_> { + match self { + Self::Pos(v) => v.as_red(), + Self::Named(v) => v.as_red(), + Self::Sink(v) => v.as_red(), + } + } } node! { @@ -840,7 +832,17 @@ node! { impl ImportExpr { /// The items to be imported. pub fn imports(&self) -> Imports { - self.0.cast_first_child().expect("import is missing items") + self.0 + .children() + .find_map(|node| match node.kind() { + NodeKind::Star => Some(Imports::Wildcard), + NodeKind::ImportItems => { + let items = node.children().filter_map(RedRef::cast).collect(); + Some(Imports::Items(items)) + } + _ => None, + }) + .expect("import is missing items") } /// The location of the importable file. @@ -858,19 +860,6 @@ pub enum Imports { Items(Vec), } -impl TypedNode for Imports { - fn from_red(node: RedRef) -> Option { - match node.kind() { - NodeKind::Star => Some(Imports::Wildcard), - NodeKind::ImportItems => { - let items = node.children().filter_map(RedRef::cast).collect(); - Some(Imports::Items(items)) - } - _ => None, - } - } -} - node! { /// An include expression: `include "chapter1.typ"`. IncludeExpr @@ -967,23 +956,28 @@ impl ForPattern { } } -/// An identifier. -#[derive(Debug, Clone, PartialEq)] -pub struct Ident { - /// The source code location. - pub span: Span, - /// The identifier string. - pub string: EcoString, +node! { + /// An identifier. + Ident: NodeKind::Ident(_) } -impl TypedNode for Ident { - fn from_red(node: RedRef) -> Option { - match node.kind() { - NodeKind::Ident(string) => Some(Ident { - span: node.span(), - string: string.clone(), - }), - _ => None, +impl Ident { + /// Take out the contained [`EcoString`]. + pub fn take(self) -> EcoString { + match self.0.green { + Green::Token(GreenData { kind: NodeKind::Ident(id), .. }) => id, + _ => panic!("identifier is of wrong kind"), + } + } +} + +impl Deref for Ident { + type Target = str; + + fn deref(&self) -> &Self::Target { + match &self.0.green { + Green::Token(GreenData { kind: NodeKind::Ident(id), .. }) => id, + _ => panic!("identifier is of wrong kind"), } } } diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index fc05ad50c..0660d57b9 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -187,11 +187,20 @@ pub struct RedNode { } impl RedNode { - /// Create a new root node from a [`GreenNode`]. - pub fn new_root(root: Rc, id: SourceId) -> Self { + /// Create a new red node from a root [`GreenNode`]. + pub fn from_root(root: Rc, id: SourceId) -> Self { Self { id, offset: 0, green: root.into() } } + /// Create a new red node from a node kind and a span. + pub fn from_data(kind: NodeKind, span: Span) -> Self { + Self { + id: span.source, + offset: span.start, + green: Green::Token(GreenData { kind, len: span.len() }), + } + } + /// Convert to a borrowed representation. pub fn as_ref(&self) -> RedRef<'_> { RedRef { @@ -540,7 +549,7 @@ pub enum NodeKind { /// A percentage: `50%`. /// /// _Note_: `50%` is stored as `50.0` here, as in the corresponding - /// [literal](ast::Lit::Percent). + /// [literal](ast::LitKind::Percent). Percentage(f64), /// A fraction unit: `3fr`. Fraction(f64), diff --git a/src/syntax/pretty.rs b/src/syntax/pretty.rs index fa423e94b..9e4510b62 100644 --- a/src/syntax/pretty.rs +++ b/src/syntax/pretty.rs @@ -198,8 +198,8 @@ impl Pretty for EnumNode { impl Pretty for Expr { fn pretty(&self, p: &mut Printer) { match self { - Self::Ident(v) => v.pretty(p), Self::Lit(v) => v.pretty(p), + Self::Ident(v) => v.pretty(p), Self::Array(v) => v.pretty(p), Self::Dict(v) => v.pretty(p), Self::Template(v) => v.pretty(p), @@ -222,17 +222,17 @@ impl Pretty for Expr { impl Pretty for Lit { fn pretty(&self, p: &mut Printer) { - match self { - Self::None(_) => p.push_str("none"), - Self::Auto(_) => p.push_str("auto"), - Self::Bool(_, v) => write!(p, "{}", v).unwrap(), - Self::Int(_, v) => write!(p, "{}", v).unwrap(), - Self::Float(_, v) => write!(p, "{}", v).unwrap(), - Self::Length(_, v, u) => write!(p, "{}{:?}", v, u).unwrap(), - Self::Angle(_, v, u) => write!(p, "{}{:?}", v, u).unwrap(), - Self::Percent(_, v) => write!(p, "{}%", v).unwrap(), - Self::Fractional(_, v) => write!(p, "{}fr", v).unwrap(), - Self::Str(_, v) => write!(p, "{:?}", v).unwrap(), + match self.kind() { + LitKind::None => p.push_str("none"), + LitKind::Auto => p.push_str("auto"), + LitKind::Bool(v) => write!(p, "{}", v).unwrap(), + LitKind::Int(v) => write!(p, "{}", v).unwrap(), + LitKind::Float(v) => write!(p, "{}", v).unwrap(), + LitKind::Length(v, u) => write!(p, "{}{:?}", v, u).unwrap(), + LitKind::Angle(v, u) => write!(p, "{}{:?}", v, u).unwrap(), + LitKind::Percent(v) => write!(p, "{}%", v).unwrap(), + LitKind::Fractional(v) => write!(p, "{}fr", v).unwrap(), + LitKind::Str(v) => write!(p, "{:?}", v).unwrap(), } } } @@ -508,7 +508,7 @@ impl Pretty for IncludeExpr { impl Pretty for Ident { fn pretty(&self, p: &mut Printer) { - p.push_str(&self.string); + p.push_str(self); } } diff --git a/src/syntax/span.rs b/src/syntax/span.rs index c26011bdb..47d965898 100644 --- a/src/syntax/span.rs +++ b/src/syntax/span.rs @@ -88,6 +88,11 @@ impl Span { Self { end, ..self } } + /// The byte length of the spanned region. + pub fn len(self) -> usize { + self.end - self.start + } + /// A new span at the position of this span's start. pub fn at_start(&self) -> Span { Self::at(self.source, self.start) diff --git a/tests/typ/code/closure.typ b/tests/typ/code/closure.typ index 3b8b42619..14e74e7ef 100644 --- a/tests/typ/code/closure.typ +++ b/tests/typ/code/closure.typ @@ -56,6 +56,52 @@ test(f(), 3) } +--- +// Import bindings. +{ + let b = "target.typ" + let f() = { + import b from b + b + } + test(f(), 1) +} + +--- +// For loop bindings. +{ + let v = (1, 2, 3) + let s = 0 + let f() = { + for v in v { s += v } + } + f() + test(s, 6) +} + +--- +// Let + closure bindings. +{ + let g = "hi" + let f() = { + let g() = "bye" + g() + } + test(f(), "bye") +} + +--- +// Parameter bindings. +{ + let x = 5 + let g() = { + let f(x, y: x) = x + y + f + } + + test(g()(8), 13) +} + --- // Don't leak environment. {