mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Refactor function parsing ♻
This commit is contained in:
parent
ed4fdcb0ad
commit
2467cd6272
138
src/func.rs
138
src/func.rs
@ -2,25 +2,31 @@
|
|||||||
|
|
||||||
/// Useful things for creating functions.
|
/// Useful things for creating functions.
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
|
pub use async_trait::async_trait;
|
||||||
pub use crate::layout::prelude::*;
|
pub use crate::layout::prelude::*;
|
||||||
|
pub use crate::layout::Commands;
|
||||||
pub use crate::layout::Command::{self, *};
|
pub use crate::layout::Command::{self, *};
|
||||||
pub use crate::style::*;
|
pub use crate::style::*;
|
||||||
pub use crate::syntax::prelude::*;
|
pub use crate::syntax::expr::*;
|
||||||
pub use super::{expect_no_body, parse_maybe_body, OptionExt};
|
pub use crate::syntax::parsing::{
|
||||||
|
parse, FuncArgs, FuncBody, FuncCall, FuncHeader, ParseState,
|
||||||
|
};
|
||||||
|
pub use crate::syntax::span::{Span, SpanVec, Spanned};
|
||||||
|
pub use crate::syntax::tree::{DynamicNode, SyntaxNode, SyntaxTree};
|
||||||
|
pub use crate::syntax::value::*;
|
||||||
|
pub use crate::{Pass, Feedback};
|
||||||
|
pub use super::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::syntax::parsing::{parse, ParseState};
|
use prelude::*;
|
||||||
use crate::syntax::span::{Span, Spanned};
|
|
||||||
use crate::syntax::tree::SyntaxTree;
|
|
||||||
use crate::Feedback;
|
|
||||||
|
|
||||||
/// Extra methods on `Option`s used for function argument parsing.
|
/// Extra methods on `Option`s used for function argument parsing.
|
||||||
pub trait OptionExt<T>: Sized {
|
pub trait OptionExt<T>: Sized {
|
||||||
/// Calls `f` with `val` if this is `Some(val)`.
|
/// Call `f` with `val` if this is `Some(val)`.
|
||||||
fn with(self, f: impl FnOnce(T));
|
fn with(self, f: impl FnOnce(T));
|
||||||
|
|
||||||
/// Reports an error about a missing argument with the given name and span
|
/// Report an error about a missing argument with the given name and span if
|
||||||
/// if the option is `None`.
|
/// the option is `None`.
|
||||||
fn or_missing(self, span: Span, arg: &str, f: &mut Feedback) -> Self;
|
fn or_missing(self, span: Span, arg: &str, f: &mut Feedback) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,8 +45,19 @@ impl<T> OptionExt<T> for Option<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses a function's body if there is one or returns `None` otherwise.
|
/// Generate `unexpected argument` errors for all remaining arguments.
|
||||||
pub fn parse_maybe_body(
|
pub fn drain_args(args: FuncArgs, f: &mut Feedback) {
|
||||||
|
for arg in args.pos.0 {
|
||||||
|
error!(@f, arg.span, "unexpected argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
for arg in args.key.0 {
|
||||||
|
error!(@f, arg.span, "unexpected argument");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a function's body if there is one or return `None` otherwise.
|
||||||
|
pub fn parse_body_maybe(
|
||||||
body: Option<Spanned<&str>>,
|
body: Option<Spanned<&str>>,
|
||||||
state: &ParseState,
|
state: &ParseState,
|
||||||
f: &mut Feedback,
|
f: &mut Feedback,
|
||||||
@ -52,106 +69,9 @@ pub fn parse_maybe_body(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates an error if there is function body even though none was expected.
|
/// Generate an error if there is function body even though none was expected.
|
||||||
pub fn expect_no_body(body: Option<Spanned<&str>>, f: &mut Feedback) {
|
pub fn expect_no_body(body: Option<Spanned<&str>>, f: &mut Feedback) {
|
||||||
if let Some(body) = body {
|
if let Some(body) = body {
|
||||||
error!(@f, body.span, "unexpected body");
|
error!(@f, body.span, "unexpected body");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implement a custom function concisely.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// Look at the source code of the `library` module for examples on how the
|
|
||||||
/// macro works.
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! function {
|
|
||||||
// Entry point.
|
|
||||||
($(#[$outer:meta])* $v:vis $storage:ident $name:ident $($r:tt)*) => {
|
|
||||||
function!(@def($name) $(#[$outer])* $v $storage $name $($r)*);
|
|
||||||
};
|
|
||||||
(@def($name:ident) $definition:item $($r:tt)*) => {
|
|
||||||
$definition
|
|
||||||
function!(@meta($name) $($r)*);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Metadata.
|
|
||||||
(@meta($name:ident) type Meta = $meta:ty; $($r:tt)*) => {
|
|
||||||
function!(@parse($name, $meta) $($r)*);
|
|
||||||
};
|
|
||||||
(@meta($name:ident) $($r:tt)*) => {
|
|
||||||
function!(@parse($name, ()) $($r)*);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Parse trait.
|
|
||||||
(@parse($($a:tt)*) parse(default) $($r:tt)*) => {
|
|
||||||
function!(@parse($($a)*) parse(_h, _b, _c, _f, _m) { Default::default() } $($r)*);
|
|
||||||
};
|
|
||||||
(@parse($($a:tt)*) parse($h:ident, $b:ident, $c:ident, $f:ident) $($r:tt)* ) => {
|
|
||||||
function!(@parse($($a)*) parse($h, $b, $c, $f, _metadata) $($r)*);
|
|
||||||
};
|
|
||||||
(@parse($name:ident, $meta:ty) parse(
|
|
||||||
$header:ident,
|
|
||||||
$body:ident,
|
|
||||||
$state:ident,
|
|
||||||
$feedback:ident,
|
|
||||||
$metadata:ident
|
|
||||||
) $code:block $($r:tt)*) => {
|
|
||||||
impl $crate::syntax::parsing::ParseCall for $name {
|
|
||||||
type Meta = $meta;
|
|
||||||
|
|
||||||
fn parse(
|
|
||||||
#[allow(unused)] mut call: $crate::syntax::parsing::FuncCall,
|
|
||||||
#[allow(unused)] $state: &$crate::syntax::parsing::ParseState,
|
|
||||||
#[allow(unused)] $metadata: Self::Meta,
|
|
||||||
) -> $crate::Pass<Self>
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
let mut feedback = $crate::Feedback::new();
|
|
||||||
#[allow(unused)] let $header = &mut call.header;
|
|
||||||
#[allow(unused)] let $body = call.body;
|
|
||||||
#[allow(unused)] let $feedback = &mut feedback;
|
|
||||||
|
|
||||||
let func = $code;
|
|
||||||
|
|
||||||
for arg in call.header.args.pos.0 {
|
|
||||||
error!(@feedback, arg.span, "unexpected argument");
|
|
||||||
}
|
|
||||||
|
|
||||||
for arg in call.header.args.key.0 {
|
|
||||||
error!(@feedback, arg.span, "unexpected argument");
|
|
||||||
}
|
|
||||||
|
|
||||||
$crate::Pass::new(func, feedback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function!(@layout($name) $($r)*);
|
|
||||||
};
|
|
||||||
|
|
||||||
(@layout($name:ident) layout(
|
|
||||||
$this:ident,
|
|
||||||
$ctx:ident,
|
|
||||||
$feedback:ident
|
|
||||||
) $code:block) => {
|
|
||||||
impl $crate::layout::Layout for $name {
|
|
||||||
fn layout<'a, 'b, 't>(
|
|
||||||
#[allow(unused)] &'a $this,
|
|
||||||
#[allow(unused)] mut $ctx: $crate::layout::LayoutContext<'b>,
|
|
||||||
) -> $crate::DynFuture<'t, $crate::Pass<$crate::layout::Commands<'a>>>
|
|
||||||
where
|
|
||||||
'a: 't,
|
|
||||||
'b: 't,
|
|
||||||
Self: 't,
|
|
||||||
{
|
|
||||||
Box::pin(async move {
|
|
||||||
let mut feedback = $crate::Feedback::new();
|
|
||||||
#[allow(unused)] let $feedback = &mut feedback;
|
|
||||||
let commands = $code;
|
|
||||||
$crate::Pass::new(commands, feedback)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
@ -10,7 +10,9 @@ mod tree;
|
|||||||
/// Basic types used across the layouting engine.
|
/// Basic types used across the layouting engine.
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use super::primitive::*;
|
pub use super::primitive::*;
|
||||||
pub use super::layout;
|
pub use super::{
|
||||||
|
BoxLayout, layout, Layout, LayoutContext, LayoutSpace, MultiLayout,
|
||||||
|
};
|
||||||
pub use Dir::*;
|
pub use Dir::*;
|
||||||
pub use GenAlign::*;
|
pub use GenAlign::*;
|
||||||
pub use GenAxis::*;
|
pub use GenAxis::*;
|
||||||
@ -46,7 +48,7 @@ pub struct BoxLayout {
|
|||||||
pub elements: LayoutElements,
|
pub elements: LayoutElements,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Comamnd-based layout.
|
/// Command-based layouting.
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
pub trait Layout {
|
pub trait Layout {
|
||||||
/// Create a sequence of layouting commands to execute.
|
/// Create a sequence of layouting commands to execute.
|
||||||
|
17
src/lib.rs
17
src/lib.rs
@ -48,7 +48,7 @@ use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
|||||||
use crate::syntax::decoration::Decorations;
|
use crate::syntax::decoration::Decorations;
|
||||||
use crate::syntax::parsing::{parse, ParseState};
|
use crate::syntax::parsing::{parse, ParseState};
|
||||||
use crate::syntax::span::{Offset, Pos};
|
use crate::syntax::span::{Offset, Pos};
|
||||||
use crate::syntax::tree::SyntaxTree;
|
use crate::syntax::tree::{DynamicNode, SyntaxNode, SyntaxTree};
|
||||||
|
|
||||||
/// Transforms source code into typesetted layouts.
|
/// Transforms source code into typesetted layouts.
|
||||||
///
|
///
|
||||||
@ -68,7 +68,7 @@ impl Typesetter {
|
|||||||
Self {
|
Self {
|
||||||
loader,
|
loader,
|
||||||
style: LayoutStyle::default(),
|
style: LayoutStyle::default(),
|
||||||
parse_state: ParseState { scope: crate::library::std() },
|
parse_state: ParseState { scope: crate::library::_std() },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +90,6 @@ impl Typesetter {
|
|||||||
/// Layout a syntax tree and return the produced layout.
|
/// Layout a syntax tree and return the produced layout.
|
||||||
pub async fn layout(&self, tree: &SyntaxTree) -> Pass<MultiLayout> {
|
pub async fn layout(&self, tree: &SyntaxTree) -> Pass<MultiLayout> {
|
||||||
use crate::layout::prelude::*;
|
use crate::layout::prelude::*;
|
||||||
use crate::layout::{LayoutContext, LayoutSpace};
|
|
||||||
|
|
||||||
let margins = self.style.page.margins();
|
let margins = self.style.page.margins();
|
||||||
layout(
|
layout(
|
||||||
@ -141,6 +140,11 @@ impl<T> Pass<T> {
|
|||||||
Self { output, feedback }
|
Self { output, feedback }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new pass with empty feedback.
|
||||||
|
pub fn okay(output: T) -> Self {
|
||||||
|
Self { output, feedback: Feedback::new() }
|
||||||
|
}
|
||||||
|
|
||||||
/// Map the output type and keep the feedback data.
|
/// Map the output type and keep the feedback data.
|
||||||
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Pass<U> {
|
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Pass<U> {
|
||||||
Pass {
|
Pass {
|
||||||
@ -150,6 +154,13 @@ impl<T> Pass<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Pass<SyntaxNode> {
|
||||||
|
/// Create a new pass from an unboxed dynamic node and feedback data..
|
||||||
|
pub fn node<T: DynamicNode + 'static>(node: T, feedback: Feedback) -> Self {
|
||||||
|
Pass::new(SyntaxNode::boxed(node), feedback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Diagnostic and semantic syntax highlighting data.
|
/// Diagnostic and semantic syntax highlighting data.
|
||||||
#[derive(Debug, Default, Clone, Eq, PartialEq)]
|
#[derive(Debug, Default, Clone, Eq, PartialEq)]
|
||||||
pub struct Feedback {
|
pub struct Feedback {
|
||||||
|
76
src/library/align.rs
Normal file
76
src/library/align.rs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// `align`: Align content along the layouting axes.
|
||||||
|
///
|
||||||
|
/// # Positional arguments
|
||||||
|
/// - At most two of `left`, `right`, `top`, `bottom`, `center`.
|
||||||
|
///
|
||||||
|
/// # Keyword arguments
|
||||||
|
/// - `horizontal`: Any of `left`, `right` or `center`.
|
||||||
|
/// - `vertical`: Any of `top`, `bottom` or `center`.
|
||||||
|
///
|
||||||
|
/// There may not be two alignment specifications for the same axis.
|
||||||
|
pub fn align(call: FuncCall, state: &ParseState) -> Pass<SyntaxNode> {
|
||||||
|
let mut f = Feedback::new();
|
||||||
|
let mut args = call.header.args;
|
||||||
|
let node = AlignNode {
|
||||||
|
body: parse_body_maybe(call.body, state, &mut f),
|
||||||
|
aligns: args.pos.all::<Spanned<SpecAlign>>().collect(),
|
||||||
|
h: args.key.get::<Spanned<SpecAlign>>("horizontal", &mut f),
|
||||||
|
v: args.key.get::<Spanned<SpecAlign>>("vertical", &mut f),
|
||||||
|
};
|
||||||
|
drain_args(args, &mut f);
|
||||||
|
Pass::node(node, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
struct AlignNode {
|
||||||
|
body: Option<SyntaxTree>,
|
||||||
|
aligns: SpanVec<SpecAlign>,
|
||||||
|
h: Option<Spanned<SpecAlign>>,
|
||||||
|
v: Option<Spanned<SpecAlign>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait(?Send)]
|
||||||
|
impl Layout for AlignNode {
|
||||||
|
async fn layout<'a>(&'a self, mut ctx: LayoutContext<'_>) -> Pass<Commands<'a>> {
|
||||||
|
let mut f = Feedback::new();
|
||||||
|
|
||||||
|
ctx.base = ctx.spaces[0].size;
|
||||||
|
|
||||||
|
let axes = ctx.axes;
|
||||||
|
let all = self.aligns.iter()
|
||||||
|
.map(|align| {
|
||||||
|
let spec = align.v.axis().unwrap_or(axes.primary.axis());
|
||||||
|
(spec, align)
|
||||||
|
})
|
||||||
|
.chain(self.h.iter().map(|align| (Horizontal, align)))
|
||||||
|
.chain(self.v.iter().map(|align| (Vertical, align)));
|
||||||
|
|
||||||
|
let mut had = [false; 2];
|
||||||
|
for (axis, align) in all {
|
||||||
|
if align.v.axis().map(|a| a != axis).unwrap_or(false) {
|
||||||
|
error!(
|
||||||
|
@f, align.span,
|
||||||
|
"invalid alignment {} for {} axis", align.v, axis,
|
||||||
|
);
|
||||||
|
} else if had[axis as usize] {
|
||||||
|
error!(@f, align.span, "duplicate alignment for {} axis", axis);
|
||||||
|
} else {
|
||||||
|
had[axis as usize] = true;
|
||||||
|
let gen_axis = axis.to_generic(ctx.axes);
|
||||||
|
let gen_align = align.v.to_generic(ctx.axes);
|
||||||
|
*ctx.align.get_mut(gen_axis) = gen_align;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Pass::new(match &self.body {
|
||||||
|
Some(body) => {
|
||||||
|
let layouted = layout(body, ctx).await;
|
||||||
|
f.extend(layouted.feedback);
|
||||||
|
vec![AddMultiple(layouted.output)]
|
||||||
|
}
|
||||||
|
None => vec![SetAlignment(ctx.align)],
|
||||||
|
}, f)
|
||||||
|
}
|
||||||
|
}
|
53
src/library/boxed.rs
Normal file
53
src/library/boxed.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
use crate::length::ScaleLength;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// `box`: Layouts its contents into a box.
|
||||||
|
///
|
||||||
|
/// # Keyword arguments
|
||||||
|
/// - `width`: The width of the box (length of relative to parent's width).
|
||||||
|
/// - `height`: The height of the box (length of relative to parent's height).
|
||||||
|
pub fn boxed(call: FuncCall, state: &ParseState) -> Pass<SyntaxNode> {
|
||||||
|
let mut f = Feedback::new();
|
||||||
|
let mut args = call.header.args;
|
||||||
|
let node = BoxNode {
|
||||||
|
body: parse_body_maybe(call.body, state, &mut f).unwrap_or(SyntaxTree::new()),
|
||||||
|
width: args.key.get::<ScaleLength>("width", &mut f),
|
||||||
|
height: args.key.get::<ScaleLength>("height", &mut f),
|
||||||
|
};
|
||||||
|
drain_args(args, &mut f);
|
||||||
|
Pass::node(node, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
struct BoxNode {
|
||||||
|
body: SyntaxTree,
|
||||||
|
width: Option<ScaleLength>,
|
||||||
|
height: Option<ScaleLength>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait(?Send)]
|
||||||
|
impl Layout for BoxNode {
|
||||||
|
async fn layout<'a>(&'a self, mut ctx: LayoutContext<'_>) -> Pass<Commands<'a>> {
|
||||||
|
ctx.spaces.truncate(1);
|
||||||
|
ctx.repeat = false;
|
||||||
|
|
||||||
|
self.width.with(|v| {
|
||||||
|
let length = v.raw_scaled(ctx.base.x);
|
||||||
|
ctx.base.x = length;
|
||||||
|
ctx.spaces[0].size.x = length;
|
||||||
|
ctx.spaces[0].expansion.horizontal = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
self.height.with(|v| {
|
||||||
|
let length = v.raw_scaled(ctx.base.y);
|
||||||
|
ctx.base.y = length;
|
||||||
|
ctx.spaces[0].size.y = length;
|
||||||
|
ctx.spaces[0].expansion.vertical = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
layout(&self.body, ctx).await.map(|out| {
|
||||||
|
let layout = out.into_iter().next().unwrap();
|
||||||
|
vec![Add(layout)]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -3,53 +3,68 @@ use fontdock::{FontStyle, FontWeight, FontWidth};
|
|||||||
use crate::length::ScaleLength;
|
use crate::length::ScaleLength;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
function! {
|
/// `font`: Configure the font.
|
||||||
/// `font`: Configure the font.
|
///
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
/// # Positional arguments
|
||||||
pub struct FontFunc {
|
/// - The font size (optional, length or relative to previous font size).
|
||||||
body: Option<SyntaxTree>,
|
/// - A font family fallback list (optional, identifiers or strings).
|
||||||
size: Option<ScaleLength>,
|
///
|
||||||
style: Option<FontStyle>,
|
/// # Keyword arguments
|
||||||
weight: Option<FontWeight>,
|
/// - `style`: `normal`, `italic` or `oblique`.
|
||||||
width: Option<FontWidth>,
|
/// - `weight`: `100` - `900` or a name like `thin`.
|
||||||
list: Vec<String>,
|
/// - `width`: `1` - `9` or a name like `condensed`.
|
||||||
classes: Vec<(String, Vec<String>)>,
|
/// - Any other keyword argument whose value is a tuple of strings is a class
|
||||||
}
|
/// fallback definition like:
|
||||||
|
/// ```typst
|
||||||
|
/// serif = ("Source Serif Pro", "Noto Serif")
|
||||||
|
/// ```
|
||||||
|
pub fn font(call: FuncCall, state: &ParseState) -> Pass<SyntaxNode> {
|
||||||
|
let mut f = Feedback::new();
|
||||||
|
let mut args = call.header.args;
|
||||||
|
|
||||||
parse(header, body, state, f) {
|
let node = FontNode {
|
||||||
let size = header.args.pos.get::<ScaleLength>();
|
body: parse_body_maybe(call.body, state, &mut f),
|
||||||
let style = header.args.key.get::<FontStyle>("style", f);
|
size: args.pos.get::<ScaleLength>(),
|
||||||
let weight = header.args.key.get::<FontWeight>("weight", f);
|
style: args.key.get::<FontStyle>("style", &mut f),
|
||||||
let width = header.args.key.get::<FontWidth>("width", f);
|
weight: args.key.get::<FontWeight>("weight", &mut f),
|
||||||
|
width: args.key.get::<FontWidth>("width", &mut f),
|
||||||
|
list: {
|
||||||
|
args.pos.all::<StringLike>()
|
||||||
|
.map(|s| s.0.to_lowercase())
|
||||||
|
.collect()
|
||||||
|
},
|
||||||
|
classes: {
|
||||||
|
args.key.all::<Tuple>()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into_iter()
|
||||||
|
.map(|(class, mut tuple)| {
|
||||||
|
let fallback = tuple.all::<StringLike>()
|
||||||
|
.map(|s| s.0.to_lowercase())
|
||||||
|
.collect();
|
||||||
|
(class.v.0, fallback)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
let list = header.args.pos.all::<StringLike>()
|
drain_args(args, &mut f);
|
||||||
.map(|s| s.0.to_lowercase())
|
Pass::node(node, f)
|
||||||
.collect();
|
}
|
||||||
|
|
||||||
let classes = header.args.key
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
.all::<Tuple>()
|
struct FontNode {
|
||||||
.collect::<Vec<_>>()
|
body: Option<SyntaxTree>,
|
||||||
.into_iter()
|
size: Option<ScaleLength>,
|
||||||
.map(|(class, mut tuple)| {
|
style: Option<FontStyle>,
|
||||||
let fallback = tuple.all::<StringLike>()
|
weight: Option<FontWeight>,
|
||||||
.map(|s| s.0.to_lowercase())
|
width: Option<FontWidth>,
|
||||||
.collect();
|
list: Vec<String>,
|
||||||
(class.v.0, fallback)
|
classes: Vec<(String, Vec<String>)>,
|
||||||
})
|
}
|
||||||
.collect();
|
|
||||||
|
|
||||||
Self {
|
#[async_trait(?Send)]
|
||||||
body: parse_maybe_body(body, state, f),
|
impl Layout for FontNode {
|
||||||
size,
|
async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass<Commands<'a>> {
|
||||||
style,
|
|
||||||
weight,
|
|
||||||
width,
|
|
||||||
list,
|
|
||||||
classes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
layout(self, ctx, f) {
|
|
||||||
let mut text = ctx.style.text.clone();
|
let mut text = ctx.style.text.clone();
|
||||||
|
|
||||||
self.size.with(|s| match s {
|
self.size.with(|s| match s {
|
||||||
@ -76,13 +91,13 @@ function! {
|
|||||||
|
|
||||||
text.fallback.flatten();
|
text.fallback.flatten();
|
||||||
|
|
||||||
match &self.body {
|
Pass::okay(match &self.body {
|
||||||
Some(tree) => vec![
|
Some(tree) => vec![
|
||||||
SetTextStyle(text),
|
SetTextStyle(text),
|
||||||
LayoutSyntaxTree(tree),
|
LayoutSyntaxTree(tree),
|
||||||
SetTextStyle(ctx.style.text.clone()),
|
SetTextStyle(ctx.style.text.clone()),
|
||||||
],
|
],
|
||||||
None => vec![SetTextStyle(text)],
|
None => vec![SetTextStyle(text)],
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,104 +0,0 @@
|
|||||||
use crate::length::ScaleLength;
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
function! {
|
|
||||||
/// `box`: Layouts content into a box.
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct BoxFunc {
|
|
||||||
body: SyntaxTree,
|
|
||||||
width: Option<ScaleLength>,
|
|
||||||
height: Option<ScaleLength>,
|
|
||||||
}
|
|
||||||
|
|
||||||
parse(header, body, state, f) {
|
|
||||||
Self {
|
|
||||||
body: parse_maybe_body(body, state, f).unwrap_or(SyntaxTree::new()),
|
|
||||||
width: header.args.key.get::<ScaleLength>("width", f),
|
|
||||||
height: header.args.key.get::<ScaleLength>("height", f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
layout(self, ctx, f) {
|
|
||||||
ctx.spaces.truncate(1);
|
|
||||||
ctx.repeat = false;
|
|
||||||
|
|
||||||
self.width.with(|v| {
|
|
||||||
let length = v.raw_scaled(ctx.base.x);
|
|
||||||
ctx.base.x = length;
|
|
||||||
ctx.spaces[0].size.x = length;
|
|
||||||
ctx.spaces[0].expansion.horizontal = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
self.height.with(|v| {
|
|
||||||
let length = v.raw_scaled(ctx.base.y);
|
|
||||||
ctx.base.y = length;
|
|
||||||
ctx.spaces[0].size.y = length;
|
|
||||||
ctx.spaces[0].expansion.vertical = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
let layouted = layout(&self.body, ctx).await;
|
|
||||||
let layout = layouted.output.into_iter().next().unwrap();
|
|
||||||
f.extend(layouted.feedback);
|
|
||||||
|
|
||||||
vec![Add(layout)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function! {
|
|
||||||
/// `align`: Aligns content along the layouting axes.
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct AlignFunc {
|
|
||||||
body: Option<SyntaxTree>,
|
|
||||||
aligns: SpanVec<SpecAlign>,
|
|
||||||
h: Option<Spanned<SpecAlign>>,
|
|
||||||
v: Option<Spanned<SpecAlign>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
parse(header, body, state, f) {
|
|
||||||
Self {
|
|
||||||
body: parse_maybe_body(body, state, f),
|
|
||||||
aligns: header.args.pos.all::<Spanned<SpecAlign>>().collect(),
|
|
||||||
h: header.args.key.get::<Spanned<SpecAlign>>("horizontal", f),
|
|
||||||
v: header.args.key.get::<Spanned<SpecAlign>>("vertical", f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
layout(self, ctx, f) {
|
|
||||||
ctx.base = ctx.spaces[0].size;
|
|
||||||
|
|
||||||
let axes = ctx.axes;
|
|
||||||
let all = self.aligns.iter()
|
|
||||||
.map(|align| {
|
|
||||||
let spec = align.v.axis().unwrap_or(axes.primary.axis());
|
|
||||||
(spec, align)
|
|
||||||
})
|
|
||||||
.chain(self.h.iter().map(|align| (Horizontal, align)))
|
|
||||||
.chain(self.v.iter().map(|align| (Vertical, align)));
|
|
||||||
|
|
||||||
let mut had = [false; 2];
|
|
||||||
for (axis, align) in all {
|
|
||||||
if align.v.axis().map(|a| a != axis).unwrap_or(false) {
|
|
||||||
error!(
|
|
||||||
@f, align.span,
|
|
||||||
"invalid alignment {} for {} axis", align.v, axis,
|
|
||||||
);
|
|
||||||
} else if had[axis as usize] {
|
|
||||||
error!(@f, align.span, "duplicate alignment for {} axis", axis);
|
|
||||||
} else {
|
|
||||||
had[axis as usize] = true;
|
|
||||||
let gen_axis = axis.to_generic(ctx.axes);
|
|
||||||
let gen_align = align.v.to_generic(ctx.axes);
|
|
||||||
*ctx.align.get_mut(gen_axis) = gen_align;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match &self.body {
|
|
||||||
Some(body) => {
|
|
||||||
let layouted = layout(body, ctx).await;
|
|
||||||
f.extend(layouted.feedback);
|
|
||||||
vec![AddMultiple(layouted.output)]
|
|
||||||
}
|
|
||||||
None => vec![SetAlignment(ctx.align)],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,54 +1,34 @@
|
|||||||
//! The standard library.
|
//! The standard library.
|
||||||
|
|
||||||
|
mod align;
|
||||||
|
mod boxed;
|
||||||
mod font;
|
mod font;
|
||||||
mod layout;
|
|
||||||
mod page;
|
mod page;
|
||||||
mod spacing;
|
mod spacing;
|
||||||
|
mod val;
|
||||||
|
|
||||||
|
pub use align::*;
|
||||||
|
pub use boxed::*;
|
||||||
pub use font::*;
|
pub use font::*;
|
||||||
pub use layout::*;
|
|
||||||
pub use page::*;
|
pub use page::*;
|
||||||
pub use spacing::*;
|
pub use spacing::*;
|
||||||
|
pub use val::*;
|
||||||
|
|
||||||
use crate::func::prelude::*;
|
use crate::func::prelude::*;
|
||||||
use crate::syntax::scope::Scope;
|
use crate::syntax::scope::Scope;
|
||||||
|
|
||||||
/// Create a scope with all standard library functions.
|
/// Create a scope with all standard library functions.
|
||||||
pub fn std() -> Scope {
|
pub fn _std() -> Scope {
|
||||||
let mut std = Scope::new::<ValFunc>();
|
let mut std = Scope::new(Box::new(val));
|
||||||
|
|
||||||
std.add::<ValFunc>("val");
|
std.insert("val", Box::new(val));
|
||||||
std.add::<FontFunc>("font");
|
std.insert("font", Box::new(font));
|
||||||
std.add::<PageFunc>("page");
|
std.insert("page", Box::new(page));
|
||||||
std.add::<AlignFunc>("align");
|
std.insert("align", Box::new(align));
|
||||||
std.add::<BoxFunc>("box");
|
std.insert("box", Box::new(boxed));
|
||||||
std.add::<PageBreakFunc>("pagebreak");
|
std.insert("pagebreak", Box::new(pagebreak));
|
||||||
std.add_with_meta::<SpacingFunc>("h", Horizontal);
|
std.insert("h", Box::new(h));
|
||||||
std.add_with_meta::<SpacingFunc>("v", Vertical);
|
std.insert("v", Box::new(v));
|
||||||
|
|
||||||
std
|
std
|
||||||
}
|
}
|
||||||
|
|
||||||
function! {
|
|
||||||
/// `val`: Ignores all arguments and layouts the body flatly.
|
|
||||||
///
|
|
||||||
/// This is also the fallback function, which is used when a function name
|
|
||||||
/// could not be resolved.
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct ValFunc {
|
|
||||||
body: Option<SyntaxTree>,
|
|
||||||
}
|
|
||||||
|
|
||||||
parse(header, body, state, f) {
|
|
||||||
header.args.pos.0.clear();
|
|
||||||
header.args.key.0.clear();
|
|
||||||
Self { body: parse_maybe_body(body, state, f), }
|
|
||||||
}
|
|
||||||
|
|
||||||
layout(self, ctx, f) {
|
|
||||||
match &self.body {
|
|
||||||
Some(tree) => vec![LayoutSyntaxTree(tree)],
|
|
||||||
None => vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -2,37 +2,55 @@ use crate::length::{Length, ScaleLength};
|
|||||||
use crate::paper::{Paper, PaperClass};
|
use crate::paper::{Paper, PaperClass};
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
function! {
|
/// `page`: Configure pages.
|
||||||
/// `page`: Configure pages.
|
///
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
/// # Positional arguments
|
||||||
pub struct PageFunc {
|
/// - The name of a paper, e.g. `a4` (optional).
|
||||||
paper: Option<Paper>,
|
///
|
||||||
width: Option<Length>,
|
/// # Keyword arguments
|
||||||
height: Option<Length>,
|
/// - `width`: The width of pages (length).
|
||||||
margins: Option<ScaleLength>,
|
/// - `height`: The height of pages (length).
|
||||||
left: Option<ScaleLength>,
|
/// - `margins`: The margins for all sides (length or relative to side lengths).
|
||||||
right: Option<ScaleLength>,
|
/// - `left`: The left margin (length or relative to width).
|
||||||
top: Option<ScaleLength>,
|
/// - `right`: The right margin (length or relative to width).
|
||||||
bottom: Option<ScaleLength>,
|
/// - `top`: The top margin (length or relative to height).
|
||||||
flip: bool,
|
/// - `bottom`: The bottom margin (length or relative to height).
|
||||||
}
|
/// - `flip`: Flips custom or paper-defined width and height (boolean).
|
||||||
|
pub fn page(call: FuncCall, _: &ParseState) -> Pass<SyntaxNode> {
|
||||||
|
let mut f = Feedback::new();
|
||||||
|
let mut args = call.header.args;
|
||||||
|
expect_no_body(call.body, &mut f);
|
||||||
|
let node = PageNode {
|
||||||
|
paper: args.pos.get::<Paper>(),
|
||||||
|
width: args.key.get::<Length>("width", &mut f),
|
||||||
|
height: args.key.get::<Length>("height", &mut f),
|
||||||
|
margins: args.key.get::<ScaleLength>("margins", &mut f),
|
||||||
|
left: args.key.get::<ScaleLength>("left", &mut f),
|
||||||
|
right: args.key.get::<ScaleLength>("right", &mut f),
|
||||||
|
top: args.key.get::<ScaleLength>("top", &mut f),
|
||||||
|
bottom: args.key.get::<ScaleLength>("bottom", &mut f),
|
||||||
|
flip: args.key.get::<bool>("flip", &mut f).unwrap_or(false),
|
||||||
|
};
|
||||||
|
drain_args(args, &mut f);
|
||||||
|
Pass::node(node, f)
|
||||||
|
}
|
||||||
|
|
||||||
parse(header, body, state, f) {
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
expect_no_body(body, f);
|
struct PageNode {
|
||||||
Self {
|
paper: Option<Paper>,
|
||||||
paper: header.args.pos.get::<Paper>(),
|
width: Option<Length>,
|
||||||
width: header.args.key.get::<Length>("width", f),
|
height: Option<Length>,
|
||||||
height: header.args.key.get::<Length>("height", f),
|
margins: Option<ScaleLength>,
|
||||||
margins: header.args.key.get::<ScaleLength>("margins", f),
|
left: Option<ScaleLength>,
|
||||||
left: header.args.key.get::<ScaleLength>("left", f),
|
right: Option<ScaleLength>,
|
||||||
right: header.args.key.get::<ScaleLength>("right", f),
|
top: Option<ScaleLength>,
|
||||||
top: header.args.key.get::<ScaleLength>("top", f),
|
bottom: Option<ScaleLength>,
|
||||||
bottom: header.args.key.get::<ScaleLength>("bottom", f),
|
flip: bool,
|
||||||
flip: header.args.key.get::<bool>("flip", f).unwrap_or(false),
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
layout(self, ctx, f) {
|
#[async_trait(?Send)]
|
||||||
|
impl Layout for PageNode {
|
||||||
|
async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass<Commands<'a>> {
|
||||||
let mut style = ctx.style.page;
|
let mut style = ctx.style.page;
|
||||||
|
|
||||||
if let Some(paper) = self.paper {
|
if let Some(paper) = self.paper {
|
||||||
@ -54,15 +72,23 @@ function! {
|
|||||||
style.size.swap();
|
style.size.swap();
|
||||||
}
|
}
|
||||||
|
|
||||||
vec![SetPageStyle(style)]
|
Pass::okay(vec![SetPageStyle(style)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function! {
|
/// `pagebreak`: Ends the current page.
|
||||||
/// `pagebreak`: Ends the current page.
|
pub fn pagebreak(call: FuncCall, _: &ParseState) -> Pass<SyntaxNode> {
|
||||||
#[derive(Debug, Default, Clone, PartialEq)]
|
let mut f = Feedback::new();
|
||||||
pub struct PageBreakFunc;
|
drain_args(call.header.args, &mut f);
|
||||||
|
Pass::node(PageBreakNode, f)
|
||||||
parse(default)
|
}
|
||||||
layout(self, ctx, f) { vec![BreakPage] }
|
|
||||||
|
#[derive(Debug, Default, Clone, PartialEq)]
|
||||||
|
struct PageBreakNode;
|
||||||
|
|
||||||
|
#[async_trait(?Send)]
|
||||||
|
impl Layout for PageBreakNode {
|
||||||
|
async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass<Commands<'a>> {
|
||||||
|
Pass::okay(vec![BreakPage])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,31 +2,49 @@ use crate::layout::SpacingKind;
|
|||||||
use crate::length::ScaleLength;
|
use crate::length::ScaleLength;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
function! {
|
/// `h`: Add horizontal spacing.
|
||||||
/// `h` and `v`: Add horizontal or vertical spacing.
|
///
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
/// # Positional arguments
|
||||||
pub struct SpacingFunc {
|
/// - The spacing (length or relative to font size).
|
||||||
spacing: Option<(SpecAxis, ScaleLength)>,
|
pub fn h(call: FuncCall, _: &ParseState) -> Pass<SyntaxNode> {
|
||||||
}
|
spacing(call, Horizontal)
|
||||||
|
}
|
||||||
|
|
||||||
type Meta = SpecAxis;
|
/// `v`: Add vertical spacing.
|
||||||
|
///
|
||||||
|
/// # Positional arguments
|
||||||
|
/// - The spacing (length or relative to font size).
|
||||||
|
pub fn v(call: FuncCall, _: &ParseState) -> Pass<SyntaxNode> {
|
||||||
|
spacing(call, Vertical)
|
||||||
|
}
|
||||||
|
|
||||||
parse(header, body, state, f, meta) {
|
fn spacing(call: FuncCall, axis: SpecAxis) -> Pass<SyntaxNode> {
|
||||||
expect_no_body(body, f);
|
let mut f = Feedback::new();
|
||||||
Self {
|
let mut args = call.header.args;
|
||||||
spacing: header.args.pos.expect::<ScaleLength>(f)
|
expect_no_body(call.body, &mut f);
|
||||||
.map(|s| (meta, s))
|
let node = SpacingNode {
|
||||||
.or_missing(header.name.span, "spacing", f),
|
spacing: args.pos.expect::<ScaleLength>(&mut f)
|
||||||
}
|
.map(|s| (axis, s))
|
||||||
}
|
.or_missing(call.header.name.span, "spacing", &mut f),
|
||||||
|
};
|
||||||
|
drain_args(args, &mut f);
|
||||||
|
Pass::node(node, f)
|
||||||
|
}
|
||||||
|
|
||||||
layout(self, ctx, f) {
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
if let Some((axis, spacing)) = self.spacing {
|
struct SpacingNode {
|
||||||
|
spacing: Option<(SpecAxis, ScaleLength)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait(?Send)]
|
||||||
|
impl Layout for SpacingNode {
|
||||||
|
async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass<Commands<'a>> {
|
||||||
|
Pass::okay(if let Some((axis, spacing)) = self.spacing {
|
||||||
let axis = axis.to_generic(ctx.axes);
|
let axis = axis.to_generic(ctx.axes);
|
||||||
let spacing = spacing.raw_scaled(ctx.style.text.font_size());
|
let spacing = spacing.raw_scaled(ctx.style.text.font_size());
|
||||||
vec![AddSpacing(spacing, SpacingKind::Hard, axis)]
|
vec![AddSpacing(spacing, SpacingKind::Hard, axis)]
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
28
src/library/val.rs
Normal file
28
src/library/val.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// `val`: Ignores all arguments and layouts its body flatly.
|
||||||
|
///
|
||||||
|
/// This is also the fallback function, which is used when a function name
|
||||||
|
/// cannot be resolved.
|
||||||
|
pub fn val(call: FuncCall, state: &ParseState) -> Pass<SyntaxNode> {
|
||||||
|
let mut f = Feedback::new();
|
||||||
|
let node = ValNode {
|
||||||
|
body: parse_body_maybe(call.body, state, &mut f),
|
||||||
|
};
|
||||||
|
Pass::node(node, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
struct ValNode {
|
||||||
|
body: Option<SyntaxTree>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait(?Send)]
|
||||||
|
impl Layout for ValNode {
|
||||||
|
async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass<Commands<'a>> {
|
||||||
|
Pass::okay(match &self.body {
|
||||||
|
Some(tree) => vec![LayoutSyntaxTree(tree)],
|
||||||
|
None => vec![],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -12,11 +12,3 @@ pub mod span;
|
|||||||
pub mod tokens;
|
pub mod tokens;
|
||||||
pub mod tree;
|
pub mod tree;
|
||||||
pub mod value;
|
pub mod value;
|
||||||
|
|
||||||
/// Basic types used around the syntax side.
|
|
||||||
pub mod prelude {
|
|
||||||
pub use super::expr::*;
|
|
||||||
pub use super::span::{Span, SpanVec, Spanned};
|
|
||||||
pub use super::tree::{DynamicNode, SyntaxNode, SyntaxTree};
|
|
||||||
pub use super::value::*;
|
|
||||||
}
|
|
||||||
|
@ -8,36 +8,16 @@ use super::expr::*;
|
|||||||
use super::scope::Scope;
|
use super::scope::Scope;
|
||||||
use super::span::{Pos, Span, Spanned};
|
use super::span::{Pos, Span, Spanned};
|
||||||
use super::tokens::{is_newline_char, Token, TokenMode, Tokens};
|
use super::tokens::{is_newline_char, Token, TokenMode, Tokens};
|
||||||
use super::tree::{DynamicNode, SyntaxNode, SyntaxTree};
|
use super::tree::{SyntaxNode, SyntaxTree};
|
||||||
|
|
||||||
/// A function which parses a function call into a dynamic node.
|
/// A function which parses a function call into a dynamic node.
|
||||||
pub type CallParser = dyn Fn(FuncCall, &ParseState) -> Pass<Box<dyn DynamicNode>>;
|
pub type CallParser = dyn Fn(FuncCall, &ParseState) -> Pass<SyntaxNode>;
|
||||||
|
|
||||||
/// Parse a function call.
|
|
||||||
pub trait ParseCall {
|
|
||||||
/// Metadata whose value is passed to `parse`. This allows a single function
|
|
||||||
/// to do different things depending on the value that needs to be given
|
|
||||||
/// when inserting the function into a scope.
|
|
||||||
///
|
|
||||||
/// For example, the functions `h` and `v` are built on the same type.
|
|
||||||
type Meta: Clone;
|
|
||||||
|
|
||||||
/// Parse the function call.
|
|
||||||
fn parse(
|
|
||||||
call: FuncCall,
|
|
||||||
state: &ParseState,
|
|
||||||
metadata: Self::Meta,
|
|
||||||
) -> Pass<Self>
|
|
||||||
where
|
|
||||||
Self: Sized;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An invocation of a function.
|
/// An invocation of a function.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct FuncCall<'s> {
|
pub struct FuncCall<'s> {
|
||||||
pub header: FuncHeader,
|
pub header: FuncHeader,
|
||||||
/// The body as a raw string containing what's inside of the brackets.
|
pub body: FuncBody<'s>,
|
||||||
pub body: Option<Spanned<&'s str>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The parsed header of a function (everything in the first set of brackets).
|
/// The parsed header of a function (everything in the first set of brackets).
|
||||||
@ -47,6 +27,10 @@ pub struct FuncHeader {
|
|||||||
pub args: FuncArgs,
|
pub args: FuncArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The body of a function as a raw spanned string containing what's inside of
|
||||||
|
/// the brackets.
|
||||||
|
pub type FuncBody<'s> = Option<Spanned<&'s str>>;
|
||||||
|
|
||||||
/// The positional and keyword arguments passed to a function.
|
/// The positional and keyword arguments passed to a function.
|
||||||
#[derive(Debug, Default, Clone, PartialEq)]
|
#[derive(Debug, Default, Clone, PartialEq)]
|
||||||
pub struct FuncArgs {
|
pub struct FuncArgs {
|
||||||
@ -215,7 +199,7 @@ impl<'s> FuncParser<'s> {
|
|||||||
let call = FuncCall { header, body: self.body };
|
let call = FuncCall { header, body: self.body };
|
||||||
let parsed = parser(call, self.state);
|
let parsed = parser(call, self.state);
|
||||||
self.feedback.extend(parsed.feedback);
|
self.feedback.extend(parsed.feedback);
|
||||||
Pass::new(SyntaxNode::Dyn(parsed.output), self.feedback)
|
Pass::new(parsed.output, self.feedback)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_func_header(&mut self) -> Option<FuncHeader> {
|
fn parse_func_header(&mut self) -> Option<FuncHeader> {
|
||||||
@ -678,7 +662,7 @@ fn unescape_raw(raw: &str) -> Vec<String> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use crate::length::Length;
|
use crate::length::Length;
|
||||||
use crate::syntax::span::SpanVec;
|
use crate::syntax::span::SpanVec;
|
||||||
use crate::syntax::test::{check, DebugFn};
|
use crate::syntax::test::{check, debug_func, DebugNode};
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use Decoration::*;
|
use Decoration::*;
|
||||||
@ -697,11 +681,11 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
($source:expr => [$($tree:tt)*], [$($diagnostics:tt)*] $(, [$($decos:tt)*])? $(,)?) => {
|
($source:expr => [$($tree:tt)*], [$($diagnostics:tt)*] $(, [$($decos:tt)*])? $(,)?) => {
|
||||||
let mut scope = Scope::new::<DebugFn>();
|
let mut scope = Scope::new(Box::new(debug_func));
|
||||||
scope.add::<DebugFn>("f");
|
scope.insert("f", Box::new(debug_func));
|
||||||
scope.add::<DebugFn>("n");
|
scope.insert("n", Box::new(debug_func));
|
||||||
scope.add::<DebugFn>("box");
|
scope.insert("box", Box::new(debug_func));
|
||||||
scope.add::<DebugFn>("val");
|
scope.insert("val", Box::new(debug_func));
|
||||||
|
|
||||||
let state = ParseState { scope };
|
let state = ParseState { scope };
|
||||||
let pass = parse($source, Pos::ZERO, &state);
|
let pass = parse($source, Pos::ZERO, &state);
|
||||||
@ -798,13 +782,13 @@ mod tests {
|
|||||||
value: Z($value),
|
value: Z($value),
|
||||||
})));)*)?
|
})));)*)?
|
||||||
)?
|
)?
|
||||||
SyntaxNode::Dyn(Box::new(DebugFn {
|
SyntaxNode::boxed(DebugNode {
|
||||||
header: FuncHeader {
|
header: FuncHeader {
|
||||||
name: span_item!($name).map(|s| Ident(s.to_string())),
|
name: span_item!($name).map(|s| Ident(s.to_string())),
|
||||||
args,
|
args,
|
||||||
},
|
},
|
||||||
body: func!(@body $($($body)*)?),
|
body: func!(@body $($($body)*)?),
|
||||||
}))
|
})
|
||||||
}};
|
}};
|
||||||
(@body [$($body:tt)*]) => { Some(span_vec![$($body)*].0) };
|
(@body [$($body:tt)*]) => { Some(span_vec![$($body)*].0) };
|
||||||
(@body) => { None };
|
(@body) => { None };
|
||||||
|
@ -3,8 +3,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
use super::parsing::{CallParser, ParseCall};
|
use super::parsing::CallParser;
|
||||||
use super::tree::DynamicNode;
|
|
||||||
|
|
||||||
/// A map from identifiers to function parsers.
|
/// A map from identifiers to function parsers.
|
||||||
pub struct Scope {
|
pub struct Scope {
|
||||||
@ -15,31 +14,16 @@ pub struct Scope {
|
|||||||
impl Scope {
|
impl Scope {
|
||||||
/// Create a new empty scope with a fallback parser that is invoked when no
|
/// Create a new empty scope with a fallback parser that is invoked when no
|
||||||
/// match is found.
|
/// match is found.
|
||||||
pub fn new<F>() -> Self
|
pub fn new(fallback: Box<CallParser>) -> Self {
|
||||||
where
|
|
||||||
F: ParseCall<Meta = ()> + DynamicNode + 'static
|
|
||||||
{
|
|
||||||
Self {
|
Self {
|
||||||
parsers: HashMap::new(),
|
parsers: HashMap::new(),
|
||||||
fallback: make_parser::<F>(()),
|
fallback,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Associate the given function name with a dynamic node type.
|
/// Associate the given function name with a dynamic node type.
|
||||||
pub fn add<F>(&mut self, name: &str)
|
pub fn insert(&mut self, name: impl Into<String>, parser: Box<CallParser>) {
|
||||||
where
|
self.parsers.insert(name.into(), parser);
|
||||||
F: ParseCall<Meta = ()> + DynamicNode + 'static
|
|
||||||
{
|
|
||||||
self.add_with_meta::<F>(name, ());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a dynamic node type with additional metadata that is passed to the
|
|
||||||
/// parser.
|
|
||||||
pub fn add_with_meta<F>(&mut self, name: &str, metadata: <F as ParseCall>::Meta)
|
|
||||||
where
|
|
||||||
F: ParseCall + DynamicNode + 'static
|
|
||||||
{
|
|
||||||
self.parsers.insert(name.to_string(), make_parser::<F>(metadata));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the parser with the given name if there is one.
|
/// Return the parser with the given name if there is one.
|
||||||
@ -58,13 +42,3 @@ impl Debug for Scope {
|
|||||||
f.debug_set().entries(self.parsers.keys()).finish()
|
f.debug_set().entries(self.parsers.keys()).finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_parser<F>(metadata: <F as ParseCall>::Meta) -> Box<CallParser>
|
|
||||||
where
|
|
||||||
F: ParseCall + DynamicNode + 'static,
|
|
||||||
{
|
|
||||||
Box::new(move |f, s| {
|
|
||||||
F::parse(f, s, metadata.clone())
|
|
||||||
.map(|tree| Box::new(tree) as Box<dyn DynamicNode>)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use crate::func::parse_maybe_body;
|
use crate::func::prelude::*;
|
||||||
use super::decoration::Decoration;
|
use super::decoration::Decoration;
|
||||||
use super::expr::{Expr, Ident, NamedTuple, Object, Pair, Tuple};
|
use super::expr::{Expr, Ident, NamedTuple, Object, Pair, Tuple};
|
||||||
use super::parsing::{FuncArg, FuncArgs, FuncHeader};
|
use super::parsing::{FuncArg, FuncArgs, FuncHeader};
|
||||||
@ -58,26 +58,26 @@ macro_rules! span_item {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function! {
|
pub fn debug_func(call: FuncCall, state: &ParseState) -> Pass<SyntaxNode> {
|
||||||
/// Most functions in the tests are parsed into the debug function for easy
|
let mut f = Feedback::new();
|
||||||
/// inspection of arguments and body.
|
let node = DebugNode {
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
header: call.header,
|
||||||
pub struct DebugFn {
|
body: parse_body_maybe(call.body, state, &mut f),
|
||||||
pub header: FuncHeader,
|
};
|
||||||
pub body: Option<SyntaxTree>,
|
Pass::node(node, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(header, body, state, f) {
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
let cloned = header.clone();
|
pub struct DebugNode {
|
||||||
header.args.pos.0.clear();
|
pub header: FuncHeader,
|
||||||
header.args.key.0.clear();
|
pub body: Option<SyntaxTree>,
|
||||||
Self {
|
}
|
||||||
header: cloned,
|
|
||||||
body: parse_maybe_body(body, state, f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
layout(self, ctx, f) { vec![] }
|
#[async_trait(?Send)]
|
||||||
|
impl Layout for DebugNode {
|
||||||
|
async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass<Commands<'a>> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compares elements by only looking at values and ignoring spans.
|
/// Compares elements by only looking at values and ignoring spans.
|
||||||
@ -87,8 +87,8 @@ pub trait SpanlessEq<Rhs = Self> {
|
|||||||
|
|
||||||
impl SpanlessEq for SyntaxNode {
|
impl SpanlessEq for SyntaxNode {
|
||||||
fn spanless_eq(&self, other: &Self) -> bool {
|
fn spanless_eq(&self, other: &Self) -> bool {
|
||||||
fn downcast<'a>(func: &'a (dyn DynamicNode + 'static)) -> &'a DebugFn {
|
fn downcast<'a>(func: &'a (dyn DynamicNode + 'static)) -> &'a DebugNode {
|
||||||
func.downcast::<DebugFn>().expect("not a debug fn")
|
func.downcast::<DebugNode>().expect("not a debug node")
|
||||||
}
|
}
|
||||||
|
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
@ -101,7 +101,7 @@ impl SpanlessEq for SyntaxNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpanlessEq for DebugFn {
|
impl SpanlessEq for DebugNode {
|
||||||
fn spanless_eq(&self, other: &Self) -> bool {
|
fn spanless_eq(&self, other: &Self) -> bool {
|
||||||
self.header.spanless_eq(&other.header)
|
self.header.spanless_eq(&other.header)
|
||||||
&& self.body.spanless_eq(&other.body)
|
&& self.body.spanless_eq(&other.body)
|
||||||
|
@ -31,6 +31,13 @@ pub enum SyntaxNode {
|
|||||||
Dyn(Box<dyn DynamicNode>),
|
Dyn(Box<dyn DynamicNode>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SyntaxNode {
|
||||||
|
/// Create a `Dyn` variant from an unboxed dynamic node.
|
||||||
|
pub fn boxed<T: DynamicNode + 'static>(node: T) -> SyntaxNode {
|
||||||
|
SyntaxNode::Dyn(Box::new(node))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PartialEq for SyntaxNode {
|
impl PartialEq for SyntaxNode {
|
||||||
fn eq(&self, other: &SyntaxNode) -> bool {
|
fn eq(&self, other: &SyntaxNode) -> bool {
|
||||||
use SyntaxNode::*;
|
use SyntaxNode::*;
|
||||||
@ -65,10 +72,7 @@ pub trait DynamicNode: Debug + Layout {
|
|||||||
|
|
||||||
impl dyn DynamicNode {
|
impl dyn DynamicNode {
|
||||||
/// Downcast this dynamic node to a concrete node.
|
/// Downcast this dynamic node to a concrete node.
|
||||||
pub fn downcast<T>(&self) -> Option<&T>
|
pub fn downcast<T: DynamicNode + 'static>(&self) -> Option<&T> {
|
||||||
where
|
|
||||||
T: DynamicNode + 'static,
|
|
||||||
{
|
|
||||||
self.as_any().downcast_ref::<T>()
|
self.as_any().downcast_ref::<T>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use fontdock::{FontStyle, FontWeight, FontWidth};
|
use fontdock::{FontStyle, FontWeight, FontWidth};
|
||||||
|
|
||||||
use crate::layout::prelude::*;
|
use crate::layout::{Dir, SpecAlign};
|
||||||
use crate::length::{Length, ScaleLength};
|
use crate::length::{Length, ScaleLength};
|
||||||
use crate::paper::Paper;
|
use crate::paper::Paper;
|
||||||
use crate::Feedback;
|
use crate::Feedback;
|
||||||
@ -103,10 +103,10 @@ macro_rules! ident_value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ident_value!(Dir, "direction", |s| match s {
|
ident_value!(Dir, "direction", |s| match s {
|
||||||
"ltr" => Some(LTR),
|
"ltr" => Some(Self::LTR),
|
||||||
"rtl" => Some(RTL),
|
"rtl" => Some(Self::RTL),
|
||||||
"ttb" => Some(TTB),
|
"ttb" => Some(Self::TTB),
|
||||||
"btt" => Some(BTT),
|
"btt" => Some(Self::BTT),
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user