mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Fine-grained capturing
This commit is contained in:
parent
95866d5fc9
commit
75fffc1f9b
@ -1,32 +1,23 @@
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use super::{Scope, Scopes};
|
use super::{Scope, Scopes, Value};
|
||||||
use crate::syntax::{NodeKind, RedRef};
|
use crate::syntax::ast::{ClosureParam, Expr, Ident, Imports, TypedNode};
|
||||||
|
use crate::syntax::RedRef;
|
||||||
|
|
||||||
/// A visitor that captures variable slots.
|
/// A visitor that captures variable slots.
|
||||||
pub struct CapturesVisitor<'a> {
|
pub struct CapturesVisitor<'a> {
|
||||||
external: &'a Scopes<'a>,
|
external: &'a Scopes<'a>,
|
||||||
|
internal: Scopes<'a>,
|
||||||
captures: Scope,
|
captures: Scope,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CapturesVisitor<'a> {
|
impl<'a> CapturesVisitor<'a> {
|
||||||
/// Create a new visitor for the given external scopes.
|
/// Create a new visitor for the given external scopes.
|
||||||
pub fn new(external: &'a Scopes) -> Self {
|
pub fn new(external: &'a Scopes) -> Self {
|
||||||
Self { external, captures: Scope::new() }
|
Self {
|
||||||
}
|
external,
|
||||||
|
internal: Scopes::new(None),
|
||||||
pub fn visit(&mut self, node: RedRef) {
|
captures: Scope::new(),
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,4 +25,152 @@ impl<'a> CapturesVisitor<'a> {
|
|||||||
pub fn finish(self) -> Scope {
|
pub fn finish(self) -> Scope {
|
||||||
self.captures
|
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"]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,8 +174,8 @@ impl Eval for Expr {
|
|||||||
|
|
||||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||||
match self {
|
match self {
|
||||||
Self::Ident(v) => v.eval(ctx),
|
|
||||||
Self::Lit(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::Array(v) => v.eval(ctx).map(Value::Array),
|
||||||
Self::Dict(v) => v.eval(ctx).map(Value::Dict),
|
Self::Dict(v) => v.eval(ctx).map(Value::Dict),
|
||||||
Self::Template(v) => v.eval(ctx).map(Value::Template),
|
Self::Template(v) => v.eval(ctx).map(Value::Template),
|
||||||
@ -200,17 +200,17 @@ impl Eval for Lit {
|
|||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, _: &mut EvalContext) -> TypResult<Self::Output> {
|
fn eval(&self, _: &mut EvalContext) -> TypResult<Self::Output> {
|
||||||
Ok(match *self {
|
Ok(match self.kind() {
|
||||||
Self::None(_) => Value::None,
|
LitKind::None => Value::None,
|
||||||
Self::Auto(_) => Value::Auto,
|
LitKind::Auto => Value::Auto,
|
||||||
Self::Bool(_, v) => Value::Bool(v),
|
LitKind::Bool(v) => Value::Bool(v),
|
||||||
Self::Int(_, v) => Value::Int(v),
|
LitKind::Int(v) => Value::Int(v),
|
||||||
Self::Float(_, v) => Value::Float(v),
|
LitKind::Float(v) => Value::Float(v),
|
||||||
Self::Length(_, v, unit) => Value::Length(Length::with_unit(v, unit)),
|
LitKind::Length(v, unit) => Value::Length(Length::with_unit(v, unit)),
|
||||||
Self::Angle(_, v, unit) => Value::Angle(Angle::with_unit(v, unit)),
|
LitKind::Angle(v, unit) => Value::Angle(Angle::with_unit(v, unit)),
|
||||||
Self::Percent(_, v) => Value::Relative(Relative::new(v / 100.0)),
|
LitKind::Percent(v) => Value::Relative(Relative::new(v / 100.0)),
|
||||||
Self::Fractional(_, v) => Value::Fractional(Fractional::new(v)),
|
LitKind::Fractional(v) => Value::Fractional(Fractional::new(v)),
|
||||||
Self::Str(_, ref v) => Value::Str(v.into()),
|
LitKind::Str(ref v) => Value::Str(v.into()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -219,9 +219,9 @@ impl Eval for Ident {
|
|||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||||
match ctx.scopes.get(&self.string) {
|
match ctx.scopes.get(self) {
|
||||||
Some(slot) => Ok(slot.borrow().clone()),
|
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::Output> {
|
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
|
||||||
self.items()
|
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()
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -401,7 +401,7 @@ impl Eval for CallArgs {
|
|||||||
CallArg::Named(x) => {
|
CallArg::Named(x) => {
|
||||||
items.push(Arg {
|
items.push(Arg {
|
||||||
span,
|
span,
|
||||||
name: Some(x.name().string.into()),
|
name: Some(x.name().take().into()),
|
||||||
value: Spanned::new(x.expr().eval(ctx)?, x.expr().span()),
|
value: Spanned::new(x.expr().eval(ctx)?, x.expr().span()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -457,23 +457,23 @@ impl Eval for ClosureExpr {
|
|||||||
for param in self.params() {
|
for param in self.params() {
|
||||||
match param {
|
match param {
|
||||||
ClosureParam::Pos(name) => {
|
ClosureParam::Pos(name) => {
|
||||||
params.push((name.string, None));
|
params.push((name.take(), None));
|
||||||
}
|
}
|
||||||
ClosureParam::Named(named) => {
|
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) => {
|
ClosureParam::Sink(name) => {
|
||||||
if sink.is_some() {
|
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
|
// Clone the body expression so that we don't have a lifetime
|
||||||
// dependence on the AST.
|
// dependence on the AST.
|
||||||
let name = self.name().map(|name| name.string);
|
let name = self.name().map(Ident::take);
|
||||||
let body = self.body();
|
let body = self.body();
|
||||||
|
|
||||||
// Define the actual function.
|
// Define the actual function.
|
||||||
@ -533,7 +533,7 @@ impl Eval for LetExpr {
|
|||||||
Some(expr) => expr.eval(ctx)?,
|
Some(expr) => expr.eval(ctx)?,
|
||||||
None => Value::None,
|
None => Value::None,
|
||||||
};
|
};
|
||||||
ctx.scopes.def_mut(self.binding().string, value);
|
ctx.scopes.def_mut(self.binding().take(), value);
|
||||||
Ok(Value::None)
|
Ok(Value::None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -589,7 +589,7 @@ impl Eval for ForExpr {
|
|||||||
|
|
||||||
#[allow(unused_parens)]
|
#[allow(unused_parens)]
|
||||||
for ($($value),*) in $iter {
|
for ($($value),*) in $iter {
|
||||||
$(ctx.scopes.def_mut(&$binding.string, $value);)*
|
$(ctx.scopes.def_mut(&$binding, $value);)*
|
||||||
|
|
||||||
let value = self.body().eval(ctx)?;
|
let value = self.body().eval(ctx)?;
|
||||||
output = ops::join(output, value)
|
output = ops::join(output, value)
|
||||||
@ -603,7 +603,10 @@ impl Eval for ForExpr {
|
|||||||
|
|
||||||
let iter = self.iter().eval(ctx)?;
|
let iter = self.iter().eval(ctx)?;
|
||||||
let pattern = self.pattern();
|
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::Str(string)) => iter!(for (v => value) in string.iter()),
|
||||||
(None, v, Value::Array(array)) => {
|
(None, v, Value::Array(array)) => {
|
||||||
iter!(for (v => value) in array.into_iter())
|
iter!(for (v => value) in array.into_iter())
|
||||||
@ -644,10 +647,10 @@ impl Eval for ImportExpr {
|
|||||||
}
|
}
|
||||||
Imports::Items(idents) => {
|
Imports::Items(idents) => {
|
||||||
for ident in idents {
|
for ident in idents {
|
||||||
if let Some(slot) = module.scope.get(&ident.string) {
|
if let Some(slot) = module.scope.get(&ident) {
|
||||||
ctx.scopes.def_mut(ident.string, slot.borrow().clone());
|
ctx.scopes.def_mut(ident.take(), slot.borrow().clone());
|
||||||
} else {
|
} else {
|
||||||
bail!(ident.span, "unresolved import");
|
bail!(ident.span(), "unresolved import");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -691,12 +694,12 @@ impl Access for Expr {
|
|||||||
|
|
||||||
impl Access for Ident {
|
impl Access for Ident {
|
||||||
fn access<'a>(&self, ctx: &'a mut EvalContext) -> TypResult<RefMut<'a, Value>> {
|
fn access<'a>(&self, ctx: &'a mut EvalContext) -> TypResult<RefMut<'a, Value>> {
|
||||||
match ctx.scopes.get(&self.string) {
|
match ctx.scopes.get(self) {
|
||||||
Some(slot) => match slot.try_borrow_mut() {
|
Some(slot) => match slot.try_borrow_mut() {
|
||||||
Ok(guard) => Ok(guard),
|
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"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,6 +120,8 @@ impl Scope {
|
|||||||
|
|
||||||
impl Debug for Scope {
|
impl Debug for Scope {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use super::*;
|
|||||||
/// A relative length.
|
/// A relative length.
|
||||||
///
|
///
|
||||||
/// _Note_: `50%` is represented as `0.5` here, but stored as `50.0` in the
|
/// _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)]
|
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
pub struct Relative(N64);
|
pub struct Relative(N64);
|
||||||
|
|
||||||
|
@ -418,7 +418,7 @@ fn item(p: &mut Parser) -> ParseResult<NodeKind> {
|
|||||||
|
|
||||||
if p.at(&NodeKind::Colon) {
|
if p.at(&NodeKind::Colon) {
|
||||||
marker.perform(p, NodeKind::Named, |p| {
|
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();
|
p.eat();
|
||||||
expr(p)
|
expr(p)
|
||||||
} else {
|
} else {
|
||||||
|
@ -145,7 +145,7 @@ impl SourceFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn ast(&self) -> TypResult<Markup> {
|
pub fn ast(&self) -> TypResult<Markup> {
|
||||||
let red = RedNode::new_root(self.root.clone(), self.id);
|
let red = RedNode::from_root(self.root.clone(), self.id);
|
||||||
let errors = red.errors();
|
let errors = red.errors();
|
||||||
if errors.is_empty() {
|
if errors.is_empty() {
|
||||||
Ok(red.cast().unwrap())
|
Ok(red.cast().unwrap())
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
//! A typed layer over the red-green tree.
|
//! 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::geom::{AngularUnit, LengthUnit};
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
@ -8,13 +10,24 @@ use crate::util::EcoString;
|
|||||||
pub trait TypedNode: Sized {
|
pub trait TypedNode: Sized {
|
||||||
/// Convert from a red node to a typed node.
|
/// Convert from a red node to a typed node.
|
||||||
fn from_red(value: RedRef) -> Option<Self>;
|
fn from_red(value: RedRef) -> Option<Self>;
|
||||||
|
|
||||||
|
/// 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 {
|
macro_rules! node {
|
||||||
($(#[$attr:meta])* $name:ident) => {
|
($(#[$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)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
$(#[$attr])*
|
$(#[$attr])*
|
||||||
@ -22,22 +35,14 @@ macro_rules! node {
|
|||||||
|
|
||||||
impl TypedNode for $name {
|
impl TypedNode for $name {
|
||||||
fn from_red(node: RedRef) -> Option<Self> {
|
fn from_red(node: RedRef) -> Option<Self> {
|
||||||
if node.kind() != &NodeKind::$variant {
|
if matches!(node.kind(), $($variant)|*) {
|
||||||
return None;
|
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.
|
fn as_red(&self) -> RedRef<'_> {
|
||||||
pub fn as_red(&self) -> RedRef {
|
|
||||||
self.0.as_ref()
|
self.0.as_ref()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,7 +57,27 @@ node! {
|
|||||||
impl Markup {
|
impl Markup {
|
||||||
/// The markup nodes.
|
/// The markup nodes.
|
||||||
pub fn nodes(&self) -> impl Iterator<Item = MarkupNode> + '_ {
|
pub fn nodes(&self) -> impl Iterator<Item = MarkupNode> + '_ {
|
||||||
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),
|
Expr(Expr),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypedNode for MarkupNode {
|
|
||||||
fn from_red(node: RedRef) -> Option<Self> {
|
|
||||||
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: `` `...` ``.
|
/// A raw block with optional syntax highlighting: `` `...` ``.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct RawNode {
|
pub struct RawNode {
|
||||||
@ -118,22 +121,9 @@ pub struct RawNode {
|
|||||||
pub block: bool,
|
pub block: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypedNode for RawNode {
|
|
||||||
fn from_red(node: RedRef) -> Option<Self> {
|
|
||||||
match node.kind() {
|
|
||||||
NodeKind::Raw(raw) => Some(Self {
|
|
||||||
block: raw.block,
|
|
||||||
lang: raw.lang.clone(),
|
|
||||||
text: raw.text.clone(),
|
|
||||||
}),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
node! {
|
node! {
|
||||||
/// A section heading: `= Introduction`.
|
/// A section heading: `= Introduction`.
|
||||||
Heading => HeadingNode
|
HeadingNode: Heading
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HeadingNode {
|
impl HeadingNode {
|
||||||
@ -154,7 +144,7 @@ impl HeadingNode {
|
|||||||
|
|
||||||
node! {
|
node! {
|
||||||
/// An item in an unordered list: `- ...`.
|
/// An item in an unordered list: `- ...`.
|
||||||
List => ListNode
|
ListNode: List
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ListNode {
|
impl ListNode {
|
||||||
@ -166,7 +156,7 @@ impl ListNode {
|
|||||||
|
|
||||||
node! {
|
node! {
|
||||||
/// An item in an enumeration (ordered list): `1. ...`.
|
/// An item in an enumeration (ordered list): `1. ...`.
|
||||||
Enum => EnumNode
|
EnumNode: Enum
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EnumNode {
|
impl EnumNode {
|
||||||
@ -190,10 +180,10 @@ impl EnumNode {
|
|||||||
/// An expression.
|
/// An expression.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Expr {
|
pub enum Expr {
|
||||||
/// An identifier: `left`.
|
|
||||||
Ident(Ident),
|
|
||||||
/// A literal: `1`, `true`, ...
|
/// A literal: `1`, `true`, ...
|
||||||
Lit(Lit),
|
Lit(Lit),
|
||||||
|
/// An identifier: `left`.
|
||||||
|
Ident(Ident),
|
||||||
/// An array expression: `(1, "hi", 12cm)`.
|
/// An array expression: `(1, "hi", 12cm)`.
|
||||||
Array(ArrayExpr),
|
Array(ArrayExpr),
|
||||||
/// A dictionary expression: `(thickness: 3pt, pattern: dashed)`.
|
/// A dictionary expression: `(thickness: 3pt, pattern: dashed)`.
|
||||||
@ -251,6 +241,29 @@ impl TypedNode for Expr {
|
|||||||
_ => node.cast().map(Self::Lit),
|
_ => 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 {
|
impl Expr {
|
||||||
@ -267,99 +280,72 @@ impl Expr {
|
|||||||
| Self::Include(_)
|
| Self::Include(_)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the expression's span.
|
node! {
|
||||||
pub fn span(&self) -> Span {
|
/// A literal: `1`, `true`, ...
|
||||||
match self {
|
Lit: NodeKind::None
|
||||||
Self::Ident(ident) => ident.span,
|
| NodeKind::Auto
|
||||||
Self::Lit(lit) => lit.span(),
|
| NodeKind::Bool(_)
|
||||||
Self::Array(array) => array.span(),
|
| NodeKind::Int(_)
|
||||||
Self::Dict(dict) => dict.span(),
|
| NodeKind::Float(_)
|
||||||
Self::Template(template) => template.span(),
|
| NodeKind::Length(_, _)
|
||||||
Self::Group(group) => group.span(),
|
| NodeKind::Angle(_, _)
|
||||||
Self::Block(block) => block.span(),
|
| NodeKind::Percentage(_)
|
||||||
Self::Unary(unary) => unary.span(),
|
| NodeKind::Fraction(_)
|
||||||
Self::Binary(binary) => binary.span(),
|
| NodeKind::Str(_)
|
||||||
Self::Call(call) => call.span(),
|
}
|
||||||
Self::Closure(closure) => closure.span(),
|
|
||||||
Self::With(with) => with.span(),
|
impl Lit {
|
||||||
Self::Let(let_) => let_.span(),
|
/// The kind of literal.
|
||||||
Self::If(if_) => if_.span(),
|
pub fn kind(&self) -> LitKind {
|
||||||
Self::While(while_) => while_.span(),
|
match *self.0.kind() {
|
||||||
Self::For(for_) => for_.span(),
|
NodeKind::None => LitKind::None,
|
||||||
Self::Import(import) => import.span(),
|
NodeKind::Auto => LitKind::Auto,
|
||||||
Self::Include(include) => include.span(),
|
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)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Lit {
|
pub enum LitKind {
|
||||||
/// The none literal: `none`.
|
/// The none literal: `none`.
|
||||||
None(Span),
|
None,
|
||||||
/// The auto literal: `auto`.
|
/// The auto literal: `auto`.
|
||||||
Auto(Span),
|
Auto,
|
||||||
/// A boolean literal: `true`, `false`.
|
/// A boolean literal: `true`, `false`.
|
||||||
Bool(Span, bool),
|
Bool(bool),
|
||||||
/// An integer literal: `120`.
|
/// An integer literal: `120`.
|
||||||
Int(Span, i64),
|
Int(i64),
|
||||||
/// A floating-point literal: `1.2`, `10e-4`.
|
/// A floating-point literal: `1.2`, `10e-4`.
|
||||||
Float(Span, f64),
|
Float(f64),
|
||||||
/// A length literal: `12pt`, `3cm`.
|
/// A length literal: `12pt`, `3cm`.
|
||||||
Length(Span, f64, LengthUnit),
|
Length(f64, LengthUnit),
|
||||||
/// An angle literal: `1.5rad`, `90deg`.
|
/// An angle literal: `1.5rad`, `90deg`.
|
||||||
Angle(Span, f64, AngularUnit),
|
Angle(f64, AngularUnit),
|
||||||
/// A percent literal: `50%`.
|
/// A percent literal: `50%`.
|
||||||
///
|
///
|
||||||
/// _Note_: `50%` is stored as `50.0` here, but as `0.5` in the
|
/// _Note_: `50%` is stored as `50.0` here, but as `0.5` in the
|
||||||
/// corresponding [value](crate::geom::Relative).
|
/// corresponding [value](crate::geom::Relative).
|
||||||
Percent(Span, f64),
|
Percent(f64),
|
||||||
/// A fraction unit literal: `1fr`.
|
/// A fraction unit literal: `1fr`.
|
||||||
Fractional(Span, f64),
|
Fractional(f64),
|
||||||
/// A string literal: `"hello!"`.
|
/// A string literal: `"hello!"`.
|
||||||
Str(Span, EcoString),
|
Str(EcoString),
|
||||||
}
|
|
||||||
|
|
||||||
impl TypedNode for Lit {
|
|
||||||
fn from_red(node: RedRef) -> Option<Self> {
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
node! {
|
node! {
|
||||||
/// An array expression: `(1, "hi", 12cm)`.
|
/// An array expression: `(1, "hi", 12cm)`.
|
||||||
Array => ArrayExpr
|
ArrayExpr: Array
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArrayExpr {
|
impl ArrayExpr {
|
||||||
@ -371,7 +357,7 @@ impl ArrayExpr {
|
|||||||
|
|
||||||
node! {
|
node! {
|
||||||
/// A dictionary expression: `(thickness: 3pt, pattern: dashed)`.
|
/// A dictionary expression: `(thickness: 3pt, pattern: dashed)`.
|
||||||
Dict => DictExpr
|
DictExpr: Dict
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DictExpr {
|
impl DictExpr {
|
||||||
@ -400,7 +386,7 @@ impl Named {
|
|||||||
|
|
||||||
node! {
|
node! {
|
||||||
/// A template expression: `[*Hi* there!]`.
|
/// A template expression: `[*Hi* there!]`.
|
||||||
Template => TemplateExpr
|
TemplateExpr: Template
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TemplateExpr {
|
impl TemplateExpr {
|
||||||
@ -412,7 +398,7 @@ impl TemplateExpr {
|
|||||||
|
|
||||||
node! {
|
node! {
|
||||||
/// A grouped expression: `(1 + 2)`.
|
/// A grouped expression: `(1 + 2)`.
|
||||||
Group => GroupExpr
|
GroupExpr: Group
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GroupExpr {
|
impl GroupExpr {
|
||||||
@ -424,7 +410,7 @@ impl GroupExpr {
|
|||||||
|
|
||||||
node! {
|
node! {
|
||||||
/// A block expression: `{ let x = 1; x + 2 }`.
|
/// A block expression: `{ let x = 1; x + 2 }`.
|
||||||
Block => BlockExpr
|
BlockExpr: Block
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockExpr {
|
impl BlockExpr {
|
||||||
@ -436,14 +422,15 @@ impl BlockExpr {
|
|||||||
|
|
||||||
node! {
|
node! {
|
||||||
/// A unary operation: `-x`.
|
/// A unary operation: `-x`.
|
||||||
Unary => UnaryExpr
|
UnaryExpr: Unary
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnaryExpr {
|
impl UnaryExpr {
|
||||||
/// The operator: `-`.
|
/// The operator: `-`.
|
||||||
pub fn op(&self) -> UnOp {
|
pub fn op(&self) -> UnOp {
|
||||||
self.0
|
self.0
|
||||||
.cast_first_child()
|
.children()
|
||||||
|
.find_map(|node| UnOp::from_token(node.kind()))
|
||||||
.expect("unary expression is missing operator")
|
.expect("unary expression is missing operator")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -464,12 +451,6 @@ pub enum UnOp {
|
|||||||
Not,
|
Not,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypedNode for UnOp {
|
|
||||||
fn from_red(node: RedRef) -> Option<Self> {
|
|
||||||
Self::from_token(node.kind())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UnOp {
|
impl UnOp {
|
||||||
/// Try to convert the token into a unary operation.
|
/// Try to convert the token into a unary operation.
|
||||||
pub fn from_token(token: &NodeKind) -> Option<Self> {
|
pub fn from_token(token: &NodeKind) -> Option<Self> {
|
||||||
@ -501,14 +482,15 @@ impl UnOp {
|
|||||||
|
|
||||||
node! {
|
node! {
|
||||||
/// A binary operation: `a + b`.
|
/// A binary operation: `a + b`.
|
||||||
Binary => BinaryExpr
|
BinaryExpr: Binary
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BinaryExpr {
|
impl BinaryExpr {
|
||||||
/// The binary operator: `+`.
|
/// The binary operator: `+`.
|
||||||
pub fn op(&self) -> BinOp {
|
pub fn op(&self) -> BinOp {
|
||||||
self.0
|
self.0
|
||||||
.cast_first_child()
|
.children()
|
||||||
|
.find_map(|node| BinOp::from_token(node.kind()))
|
||||||
.expect("binary expression is missing operator")
|
.expect("binary expression is missing operator")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -566,12 +548,6 @@ pub enum BinOp {
|
|||||||
DivAssign,
|
DivAssign,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypedNode for BinOp {
|
|
||||||
fn from_red(node: RedRef) -> Option<Self> {
|
|
||||||
Self::from_token(node.kind())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BinOp {
|
impl BinOp {
|
||||||
/// Try to convert the token into a binary operation.
|
/// Try to convert the token into a binary operation.
|
||||||
pub fn from_token(token: &NodeKind) -> Option<Self> {
|
pub fn from_token(token: &NodeKind) -> Option<Self> {
|
||||||
@ -671,7 +647,7 @@ pub enum Associativity {
|
|||||||
|
|
||||||
node! {
|
node! {
|
||||||
/// An invocation of a function: `foo(...)`.
|
/// An invocation of a function: `foo(...)`.
|
||||||
Call => CallExpr
|
CallExpr: Call
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CallExpr {
|
impl CallExpr {
|
||||||
@ -717,6 +693,14 @@ impl TypedNode for CallArg {
|
|||||||
_ => node.cast().map(CallArg::Pos),
|
_ => 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 {
|
impl CallArg {
|
||||||
@ -732,7 +716,7 @@ impl CallArg {
|
|||||||
|
|
||||||
node! {
|
node! {
|
||||||
/// A closure expression: `(x, y) => z`.
|
/// A closure expression: `(x, y) => z`.
|
||||||
Closure => ClosureExpr
|
ClosureExpr: Closure
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClosureExpr {
|
impl ClosureExpr {
|
||||||
@ -779,6 +763,14 @@ impl TypedNode for ClosureParam {
|
|||||||
_ => None,
|
_ => 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! {
|
node! {
|
||||||
@ -840,7 +832,17 @@ node! {
|
|||||||
impl ImportExpr {
|
impl ImportExpr {
|
||||||
/// The items to be imported.
|
/// The items to be imported.
|
||||||
pub fn imports(&self) -> Imports {
|
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.
|
/// The location of the importable file.
|
||||||
@ -858,19 +860,6 @@ pub enum Imports {
|
|||||||
Items(Vec<Ident>),
|
Items(Vec<Ident>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypedNode for Imports {
|
|
||||||
fn from_red(node: RedRef) -> Option<Self> {
|
|
||||||
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! {
|
node! {
|
||||||
/// An include expression: `include "chapter1.typ"`.
|
/// An include expression: `include "chapter1.typ"`.
|
||||||
IncludeExpr
|
IncludeExpr
|
||||||
@ -967,23 +956,28 @@ impl ForPattern {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An identifier.
|
node! {
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
/// An identifier.
|
||||||
pub struct Ident {
|
Ident: NodeKind::Ident(_)
|
||||||
/// The source code location.
|
|
||||||
pub span: Span,
|
|
||||||
/// The identifier string.
|
|
||||||
pub string: EcoString,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypedNode for Ident {
|
impl Ident {
|
||||||
fn from_red(node: RedRef) -> Option<Self> {
|
/// Take out the contained [`EcoString`].
|
||||||
match node.kind() {
|
pub fn take(self) -> EcoString {
|
||||||
NodeKind::Ident(string) => Some(Ident {
|
match self.0.green {
|
||||||
span: node.span(),
|
Green::Token(GreenData { kind: NodeKind::Ident(id), .. }) => id,
|
||||||
string: string.clone(),
|
_ => panic!("identifier is of wrong kind"),
|
||||||
}),
|
}
|
||||||
_ => None,
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -187,11 +187,20 @@ pub struct RedNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RedNode {
|
impl RedNode {
|
||||||
/// Create a new root node from a [`GreenNode`].
|
/// Create a new red node from a root [`GreenNode`].
|
||||||
pub fn new_root(root: Rc<GreenNode>, id: SourceId) -> Self {
|
pub fn from_root(root: Rc<GreenNode>, id: SourceId) -> Self {
|
||||||
Self { id, offset: 0, green: root.into() }
|
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.
|
/// Convert to a borrowed representation.
|
||||||
pub fn as_ref(&self) -> RedRef<'_> {
|
pub fn as_ref(&self) -> RedRef<'_> {
|
||||||
RedRef {
|
RedRef {
|
||||||
@ -540,7 +549,7 @@ pub enum NodeKind {
|
|||||||
/// A percentage: `50%`.
|
/// A percentage: `50%`.
|
||||||
///
|
///
|
||||||
/// _Note_: `50%` is stored as `50.0` here, as in the corresponding
|
/// _Note_: `50%` is stored as `50.0` here, as in the corresponding
|
||||||
/// [literal](ast::Lit::Percent).
|
/// [literal](ast::LitKind::Percent).
|
||||||
Percentage(f64),
|
Percentage(f64),
|
||||||
/// A fraction unit: `3fr`.
|
/// A fraction unit: `3fr`.
|
||||||
Fraction(f64),
|
Fraction(f64),
|
||||||
|
@ -198,8 +198,8 @@ impl Pretty for EnumNode {
|
|||||||
impl Pretty for Expr {
|
impl Pretty for Expr {
|
||||||
fn pretty(&self, p: &mut Printer) {
|
fn pretty(&self, p: &mut Printer) {
|
||||||
match self {
|
match self {
|
||||||
Self::Ident(v) => v.pretty(p),
|
|
||||||
Self::Lit(v) => v.pretty(p),
|
Self::Lit(v) => v.pretty(p),
|
||||||
|
Self::Ident(v) => v.pretty(p),
|
||||||
Self::Array(v) => v.pretty(p),
|
Self::Array(v) => v.pretty(p),
|
||||||
Self::Dict(v) => v.pretty(p),
|
Self::Dict(v) => v.pretty(p),
|
||||||
Self::Template(v) => v.pretty(p),
|
Self::Template(v) => v.pretty(p),
|
||||||
@ -222,17 +222,17 @@ impl Pretty for Expr {
|
|||||||
|
|
||||||
impl Pretty for Lit {
|
impl Pretty for Lit {
|
||||||
fn pretty(&self, p: &mut Printer) {
|
fn pretty(&self, p: &mut Printer) {
|
||||||
match self {
|
match self.kind() {
|
||||||
Self::None(_) => p.push_str("none"),
|
LitKind::None => p.push_str("none"),
|
||||||
Self::Auto(_) => p.push_str("auto"),
|
LitKind::Auto => p.push_str("auto"),
|
||||||
Self::Bool(_, v) => write!(p, "{}", v).unwrap(),
|
LitKind::Bool(v) => write!(p, "{}", v).unwrap(),
|
||||||
Self::Int(_, v) => write!(p, "{}", v).unwrap(),
|
LitKind::Int(v) => write!(p, "{}", v).unwrap(),
|
||||||
Self::Float(_, v) => write!(p, "{}", v).unwrap(),
|
LitKind::Float(v) => write!(p, "{}", v).unwrap(),
|
||||||
Self::Length(_, v, u) => write!(p, "{}{:?}", v, u).unwrap(),
|
LitKind::Length(v, u) => write!(p, "{}{:?}", v, u).unwrap(),
|
||||||
Self::Angle(_, v, u) => write!(p, "{}{:?}", v, u).unwrap(),
|
LitKind::Angle(v, u) => write!(p, "{}{:?}", v, u).unwrap(),
|
||||||
Self::Percent(_, v) => write!(p, "{}%", v).unwrap(),
|
LitKind::Percent(v) => write!(p, "{}%", v).unwrap(),
|
||||||
Self::Fractional(_, v) => write!(p, "{}fr", v).unwrap(),
|
LitKind::Fractional(v) => write!(p, "{}fr", v).unwrap(),
|
||||||
Self::Str(_, v) => write!(p, "{:?}", v).unwrap(),
|
LitKind::Str(v) => write!(p, "{:?}", v).unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -508,7 +508,7 @@ impl Pretty for IncludeExpr {
|
|||||||
|
|
||||||
impl Pretty for Ident {
|
impl Pretty for Ident {
|
||||||
fn pretty(&self, p: &mut Printer) {
|
fn pretty(&self, p: &mut Printer) {
|
||||||
p.push_str(&self.string);
|
p.push_str(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,6 +88,11 @@ impl Span {
|
|||||||
Self { end, ..self }
|
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.
|
/// A new span at the position of this span's start.
|
||||||
pub fn at_start(&self) -> Span {
|
pub fn at_start(&self) -> Span {
|
||||||
Self::at(self.source, self.start)
|
Self::at(self.source, self.start)
|
||||||
|
@ -56,6 +56,52 @@
|
|||||||
test(f(), 3)
|
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.
|
// Don't leak environment.
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user