Refactor argument parsing 🔬
This commit is contained in:
parent
2188ef6b89
commit
533374db14
90
src/func.rs
90
src/func.rs
@ -1,8 +1,8 @@
|
||||
//! Trait and prelude for custom functions.
|
||||
|
||||
use crate::Pass;
|
||||
use crate::syntax::ParseState;
|
||||
use crate::syntax::func::FuncCall;
|
||||
use crate::{Pass, Feedback};
|
||||
use crate::syntax::parsing::{FuncCall, ParseState};
|
||||
use crate::syntax::span::Span;
|
||||
|
||||
/// Types that are useful for creating your own functions.
|
||||
pub mod prelude {
|
||||
@ -10,10 +10,11 @@ pub mod prelude {
|
||||
pub use crate::layout::prelude::*;
|
||||
pub use crate::layout::Command::{self, *};
|
||||
pub use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
||||
pub use crate::syntax::SyntaxModel;
|
||||
pub use crate::syntax::expr::*;
|
||||
pub use crate::syntax::func::*;
|
||||
pub use crate::syntax::model::SyntaxModel;
|
||||
pub use crate::syntax::span::{Span, Spanned};
|
||||
pub use crate::syntax::value::*;
|
||||
pub use super::OptionExt;
|
||||
}
|
||||
|
||||
/// Parse a function from source code.
|
||||
@ -37,47 +38,34 @@ pub trait ParseFunc {
|
||||
) -> Pass<Self> where Self: Sized;
|
||||
}
|
||||
|
||||
/// Extra methods on [`Options`](Option) used for argument parsing.
|
||||
pub trait OptionExt<T>: Sized {
|
||||
/// Calls `f` with `val` if this is `Some(val)`.
|
||||
fn with(self, f: impl FnOnce(T));
|
||||
|
||||
/// Reports an error about a missing argument with the given name and span
|
||||
/// if the option is `None`.
|
||||
fn or_missing(self, span: Span, arg: &str, f: &mut Feedback) -> Self;
|
||||
}
|
||||
|
||||
impl<T> OptionExt<T> for Option<T> {
|
||||
fn with(self, f: impl FnOnce(T)) {
|
||||
if let Some(val) = self {
|
||||
f(val);
|
||||
}
|
||||
}
|
||||
|
||||
fn or_missing(self, span: Span, arg: &str, f: &mut Feedback) -> Self {
|
||||
if self.is_none() {
|
||||
error!(@f, span, "missing argument: {}", arg);
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows to implement a function type concisely.
|
||||
///
|
||||
/// # Example
|
||||
/// A function that hides its body depending on a boolean argument.
|
||||
/// ```
|
||||
/// use typstc::func::prelude::*;
|
||||
///
|
||||
/// function! {
|
||||
/// #[derive(Debug, Clone, PartialEq)]
|
||||
/// pub struct HiderFunc {
|
||||
/// body: Option<SyntaxModel>,
|
||||
/// }
|
||||
///
|
||||
/// parse(header, body, state, f) {
|
||||
/// let body = body!(opt: body, state, f);
|
||||
/// let hidden = header.args.pos.get::<bool>(&mut f.diagnostics)
|
||||
/// .or_missing(&mut f.diagnostics, header.name.span, "hidden")
|
||||
/// .unwrap_or(false);
|
||||
///
|
||||
/// HiderFunc { body: if hidden { None } else { body } }
|
||||
/// }
|
||||
///
|
||||
/// layout(self, ctx, f) {
|
||||
/// match &self.body {
|
||||
/// Some(model) => vec![LayoutSyntaxModel(model)],
|
||||
/// None => vec![],
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// This function can be used as follows:
|
||||
/// ```typst
|
||||
/// [hider: true][Hi, you.] => Nothing
|
||||
/// [hider: false][Hi, you.] => Text: "Hi, you."
|
||||
///
|
||||
/// [hider][Hi, you.] => Text: "Hi, you."
|
||||
/// ^^^^^
|
||||
/// missing argument: hidden
|
||||
/// ```
|
||||
///
|
||||
/// # More examples
|
||||
/// # Examples
|
||||
/// Look at the source code of the [`library`](crate::library) module for more
|
||||
/// examples on how the macro works.
|
||||
#[macro_export]
|
||||
@ -117,8 +105,8 @@ macro_rules! function {
|
||||
type Meta = $meta;
|
||||
|
||||
fn parse(
|
||||
#[allow(unused)] mut call: $crate::syntax::func::FuncCall,
|
||||
#[allow(unused)] $state: &$crate::syntax::ParseState,
|
||||
#[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();
|
||||
@ -128,7 +116,11 @@ macro_rules! function {
|
||||
|
||||
let func = $code;
|
||||
|
||||
for arg in call.header.args.into_iter() {
|
||||
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");
|
||||
}
|
||||
|
||||
@ -140,7 +132,7 @@ macro_rules! function {
|
||||
};
|
||||
|
||||
(@layout($name:ident) layout($this:ident, $ctx:ident, $feedback:ident) $code:block) => {
|
||||
impl $crate::syntax::Model for $name {
|
||||
impl $crate::syntax::model::Model for $name {
|
||||
fn layout<'a, 'b, 't>(
|
||||
#[allow(unused)] &'a $this,
|
||||
#[allow(unused)] mut $ctx: $crate::layout::LayoutContext<'b>,
|
||||
@ -177,7 +169,7 @@ macro_rules! function {
|
||||
macro_rules! body {
|
||||
(opt: $body:expr, $state:expr, $feedback:expr) => ({
|
||||
$body.map(|body| {
|
||||
let parsed = $crate::syntax::parse(body.v, body.span.start, $state);
|
||||
let parsed = $crate::syntax::parsing::parse(body.v, body.span.start, $state);
|
||||
$feedback.extend(parsed.feedback);
|
||||
parsed.output
|
||||
})
|
||||
|
34
src/geom.rs
34
src/geom.rs
@ -45,7 +45,7 @@ impl<T: Clone> Value2<T> {
|
||||
pub fn with_all(s: T) -> Value2<T> { Value2 { x: s.clone(), y: s } }
|
||||
|
||||
/// Get the specificed component.
|
||||
pub fn get(self, axis: SpecificAxis) -> T {
|
||||
pub fn get(self, axis: SpecAxis) -> T {
|
||||
match axis {
|
||||
Horizontal => self.x,
|
||||
Vertical => self.y,
|
||||
@ -53,7 +53,7 @@ impl<T: Clone> Value2<T> {
|
||||
}
|
||||
|
||||
/// Borrow the specificed component mutably.
|
||||
pub fn get_mut(&mut self, axis: SpecificAxis) -> &mut T {
|
||||
pub fn get_mut(&mut self, axis: SpecAxis) -> &mut T {
|
||||
match axis {
|
||||
Horizontal => &mut self.x,
|
||||
Vertical => &mut self.y,
|
||||
@ -148,19 +148,19 @@ impl Size {
|
||||
///
|
||||
/// This assumes the size to be generalized such that `x` corresponds to the
|
||||
/// primary axis.
|
||||
pub fn anchor(self, alignment: LayoutAlignment, axes: LayoutAxes) -> Size {
|
||||
pub fn anchor(self, align: LayoutAlign, axes: LayoutAxes) -> Size {
|
||||
Size {
|
||||
x: anchor(self.x, alignment.primary, axes.primary),
|
||||
y: anchor(self.x, alignment.secondary, axes.secondary),
|
||||
x: anchor(self.x, align.primary, axes.primary),
|
||||
y: anchor(self.y, align.secondary, axes.secondary),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn anchor(length: f64, alignment: Alignment, direction: Direction) -> f64 {
|
||||
match (direction.is_positive(), alignment) {
|
||||
(true, Origin) | (false, End) => 0.0,
|
||||
fn anchor(length: f64, align: GenAlign, dir: Dir) -> f64 {
|
||||
match (dir.is_positive(), align) {
|
||||
(true, Start) | (false, End) => 0.0,
|
||||
(_, Center) => length / 2.0,
|
||||
(true, End) | (false, Origin) => length,
|
||||
(true, End) | (false, Start) => length,
|
||||
}
|
||||
}
|
||||
|
||||
@ -208,16 +208,16 @@ impl<T: Clone> Value4<T> {
|
||||
/// alignment.
|
||||
///
|
||||
/// Center alignment is treated the same as origin alignment.
|
||||
pub fn get_mut(&mut self, mut direction: Direction, alignment: Alignment) -> &mut T {
|
||||
if alignment == End {
|
||||
direction = direction.inv();
|
||||
pub fn get_mut(&mut self, mut dir: Dir, align: GenAlign) -> &mut T {
|
||||
if align == End {
|
||||
dir = dir.inv();
|
||||
}
|
||||
|
||||
match direction {
|
||||
LeftToRight => &mut self.left,
|
||||
RightToLeft => &mut self.right,
|
||||
TopToBottom => &mut self.top,
|
||||
BottomToTop => &mut self.bottom,
|
||||
match dir {
|
||||
LTT => &mut self.left,
|
||||
RTL => &mut self.right,
|
||||
TTB => &mut self.top,
|
||||
BTT => &mut self.bottom,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ pub struct LineContext {
|
||||
pub axes: LayoutAxes,
|
||||
/// Which alignment to set on the resulting layout. This affects how it will
|
||||
/// be positioned in a parent box.
|
||||
pub alignment: LayoutAlignment,
|
||||
pub align: LayoutAlign,
|
||||
/// Whether to have repeated spaces or to use only the first and only once.
|
||||
pub repeat: bool,
|
||||
/// Whether to output a command which renders a debugging box showing the
|
||||
@ -56,7 +56,7 @@ struct LineRun {
|
||||
/// When a new run is created the alignment is yet to be determined. Once a
|
||||
/// layout is added, it is decided which alignment the run has and all
|
||||
/// further elements of the run must have this alignment.
|
||||
alignment: Option<LayoutAlignment>,
|
||||
align: Option<LayoutAlign>,
|
||||
/// If another line run with different alignment already took up some space
|
||||
/// of the line, this run has less space and how much is stored here.
|
||||
usable: Option<f64>,
|
||||
@ -71,7 +71,7 @@ impl LineLayouter {
|
||||
stack: StackLayouter::new(StackContext {
|
||||
spaces: ctx.spaces.clone(),
|
||||
axes: ctx.axes,
|
||||
alignment: ctx.alignment,
|
||||
align: ctx.align,
|
||||
repeat: ctx.repeat,
|
||||
debug: ctx.debug,
|
||||
}),
|
||||
@ -84,27 +84,27 @@ impl LineLayouter {
|
||||
pub fn add(&mut self, layout: Layout) {
|
||||
let axes = self.ctx.axes;
|
||||
|
||||
if let Some(alignment) = self.run.alignment {
|
||||
if layout.alignment.secondary != alignment.secondary {
|
||||
if let Some(align) = self.run.align {
|
||||
if layout.align.secondary != align.secondary {
|
||||
// TODO: Issue warning for non-fitting alignment in
|
||||
// non-repeating context.
|
||||
let fitting = self.stack.is_fitting_alignment(layout.alignment);
|
||||
let fitting = self.stack.is_fitting_alignment(layout.align);
|
||||
if !fitting && self.ctx.repeat {
|
||||
self.finish_space(true);
|
||||
} else {
|
||||
self.finish_line();
|
||||
}
|
||||
} else if layout.alignment.primary < alignment.primary {
|
||||
} else if layout.align.primary < align.primary {
|
||||
self.finish_line();
|
||||
|
||||
} else if layout.alignment.primary > alignment.primary {
|
||||
} else if layout.align.primary > align.primary {
|
||||
let mut rest_run = LineRun::new();
|
||||
|
||||
let usable = self.stack.usable().primary(axes);
|
||||
rest_run.usable = Some(match layout.alignment.primary {
|
||||
Alignment::Origin => unreachable!("origin > x"),
|
||||
Alignment::Center => usable - 2.0 * self.run.size.x,
|
||||
Alignment::End => usable - self.run.size.x,
|
||||
rest_run.usable = Some(match layout.align.primary {
|
||||
GenAlign::Start => unreachable!("start > x"),
|
||||
GenAlign::Center => usable - 2.0 * self.run.size.x,
|
||||
GenAlign::End => usable - self.run.size.x,
|
||||
});
|
||||
|
||||
rest_run.size.y = self.run.size.y;
|
||||
@ -133,7 +133,7 @@ impl LineLayouter {
|
||||
}
|
||||
}
|
||||
|
||||
self.run.alignment = Some(layout.alignment);
|
||||
self.run.align = Some(layout.align);
|
||||
self.run.layouts.push((self.run.size.x, layout));
|
||||
|
||||
self.run.size.x += size.x;
|
||||
@ -269,8 +269,8 @@ impl LineLayouter {
|
||||
|
||||
self.stack.add(Layout {
|
||||
dimensions: self.run.size.specialized(self.ctx.axes),
|
||||
alignment: self.run.alignment
|
||||
.unwrap_or(LayoutAlignment::new(Origin, Origin)),
|
||||
align: self.run.align
|
||||
.unwrap_or(LayoutAlign::new(Start, Start)),
|
||||
actions: actions.into_vec(),
|
||||
});
|
||||
|
||||
@ -292,7 +292,7 @@ impl LineRun {
|
||||
LineRun {
|
||||
layouts: vec![],
|
||||
size: Size::ZERO,
|
||||
alignment: None,
|
||||
align: None,
|
||||
usable: None,
|
||||
last_spacing: LastSpacing::Hard,
|
||||
}
|
||||
|
@ -11,20 +11,20 @@ use self::prelude::*;
|
||||
pub mod line;
|
||||
pub mod stack;
|
||||
pub mod text;
|
||||
|
||||
pub_use_mod!(actions);
|
||||
pub_use_mod!(model);
|
||||
|
||||
/// Basic types used across the layouting engine.
|
||||
pub mod prelude {
|
||||
pub use super::{
|
||||
LayoutContext, layout, LayoutSpace, Commands,
|
||||
LayoutAxes, LayoutAlignment, LayoutExpansion
|
||||
layout, LayoutContext, LayoutSpace, Command, Commands,
|
||||
LayoutAxes, LayoutAlign, LayoutExpansion,
|
||||
};
|
||||
pub use super::GenericAxis::{self, *};
|
||||
pub use super::SpecificAxis::{self, *};
|
||||
pub use super::Direction::{self, *};
|
||||
pub use super::Alignment::{self, *};
|
||||
pub use super::Dir::{self, *};
|
||||
pub use super::GenAxis::{self, *};
|
||||
pub use super::SpecAxis::{self, *};
|
||||
pub use super::GenAlign::{self, *};
|
||||
pub use super::SpecAlign::{self, *};
|
||||
}
|
||||
|
||||
/// A collection of layouts.
|
||||
@ -37,7 +37,7 @@ pub struct Layout {
|
||||
pub dimensions: Size,
|
||||
/// How to align this layout in a parent container.
|
||||
#[serde(skip)]
|
||||
pub alignment: LayoutAlignment,
|
||||
pub align: LayoutAlign,
|
||||
/// The actions composing this layout.
|
||||
pub actions: Vec<LayoutAction>,
|
||||
}
|
||||
@ -95,82 +95,31 @@ impl LayoutSpace {
|
||||
}
|
||||
}
|
||||
|
||||
/// The two generic layouting axes.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum GenericAxis {
|
||||
/// The primary axis along which words are laid out.
|
||||
Primary,
|
||||
/// The secondary axis along which lines and paragraphs are laid out.
|
||||
Secondary,
|
||||
}
|
||||
|
||||
impl GenericAxis {
|
||||
/// The specific version of this axis in the given system of axes.
|
||||
pub fn to_specific(self, axes: LayoutAxes) -> SpecificAxis {
|
||||
axes.get(self).axis()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for GenericAxis {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Primary => write!(f, "primary"),
|
||||
Secondary => write!(f, "secondary"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The two specific layouting axes.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum SpecificAxis {
|
||||
/// The horizontal layouting axis.
|
||||
Horizontal,
|
||||
/// The vertical layouting axis.
|
||||
Vertical,
|
||||
}
|
||||
|
||||
impl SpecificAxis {
|
||||
/// The generic version of this axis in the given system of axes.
|
||||
pub fn to_generic(self, axes: LayoutAxes) -> GenericAxis {
|
||||
if self == axes.primary.axis() { Primary } else { Secondary }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SpecificAxis {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Horizontal => write!(f, "horizontal"),
|
||||
Vertical => write!(f, "vertical"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Specifies along which directions content is laid out.
|
||||
/// Specifies along which axes content is laid out.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct LayoutAxes {
|
||||
/// The primary layouting direction.
|
||||
pub primary: Direction,
|
||||
pub primary: Dir,
|
||||
/// The secondary layouting direction.
|
||||
pub secondary: Direction,
|
||||
pub secondary: Dir,
|
||||
}
|
||||
|
||||
impl LayoutAxes {
|
||||
/// Create a new instance from the two values.
|
||||
///
|
||||
/// # Panics
|
||||
/// This function panics if the directions are aligned, that is, they are
|
||||
/// This function panics if the axes are aligned, that is, they are
|
||||
/// on the same axis.
|
||||
pub fn new(primary: Direction, secondary: Direction) -> LayoutAxes {
|
||||
pub fn new(primary: Dir, secondary: Dir) -> LayoutAxes {
|
||||
if primary.axis() == secondary.axis() {
|
||||
panic!("LayoutAxes::new: invalid aligned axes \
|
||||
{} and {}", primary, secondary);
|
||||
panic!("invalid aligned axes {} and {}", primary, secondary);
|
||||
}
|
||||
|
||||
LayoutAxes { primary, secondary }
|
||||
}
|
||||
|
||||
/// Return the direction of the specified generic axis.
|
||||
pub fn get(self, axis: GenericAxis) -> Direction {
|
||||
pub fn get(self, axis: GenAxis) -> Dir {
|
||||
match axis {
|
||||
Primary => self.primary,
|
||||
Secondary => self.secondary,
|
||||
@ -178,7 +127,7 @@ impl LayoutAxes {
|
||||
}
|
||||
|
||||
/// Borrow the direction of the specified generic axis mutably.
|
||||
pub fn get_mut(&mut self, axis: GenericAxis) -> &mut Direction {
|
||||
pub fn get_mut(&mut self, axis: GenAxis) -> &mut Dir {
|
||||
match axis {
|
||||
Primary => &mut self.primary,
|
||||
Secondary => &mut self.secondary,
|
||||
@ -188,30 +137,29 @@ impl LayoutAxes {
|
||||
|
||||
/// Directions along which content is laid out.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum Direction {
|
||||
LeftToRight,
|
||||
RightToLeft,
|
||||
TopToBottom,
|
||||
BottomToTop,
|
||||
pub enum Dir {
|
||||
LTT,
|
||||
RTL,
|
||||
TTB,
|
||||
BTT,
|
||||
}
|
||||
|
||||
impl Direction {
|
||||
impl Dir {
|
||||
/// The specific axis this direction belongs to.
|
||||
pub fn axis(self) -> SpecificAxis {
|
||||
pub fn axis(self) -> SpecAxis {
|
||||
match self {
|
||||
LeftToRight | RightToLeft => Horizontal,
|
||||
TopToBottom | BottomToTop => Vertical,
|
||||
LTT | RTL => Horizontal,
|
||||
TTB | BTT => Vertical,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this axis points into the positive coordinate direction.
|
||||
///
|
||||
/// The positive directions are left-to-right and top-to-bottom.
|
||||
/// The positive axes are left-to-right and top-to-bottom.
|
||||
pub fn is_positive(self) -> bool {
|
||||
match self {
|
||||
LeftToRight | TopToBottom => true,
|
||||
RightToLeft | BottomToTop => false,
|
||||
LTT | TTB => true,
|
||||
RTL | BTT => false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,44 +172,94 @@ impl Direction {
|
||||
}
|
||||
|
||||
/// The inverse axis.
|
||||
pub fn inv(self) -> Direction {
|
||||
pub fn inv(self) -> Dir {
|
||||
match self {
|
||||
LeftToRight => RightToLeft,
|
||||
RightToLeft => LeftToRight,
|
||||
TopToBottom => BottomToTop,
|
||||
BottomToTop => TopToBottom,
|
||||
LTT => RTL,
|
||||
RTL => LTT,
|
||||
TTB => BTT,
|
||||
BTT => TTB,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Direction {
|
||||
impl Display for Dir {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
LeftToRight => write!(f, "left-to-right"),
|
||||
RightToLeft => write!(f, "right-to-left"),
|
||||
TopToBottom => write!(f, "top-to-bottom"),
|
||||
BottomToTop => write!(f, "bottom-to-top"),
|
||||
f.pad(match self {
|
||||
LTT => "ltr",
|
||||
RTL => "rtl",
|
||||
TTB => "ttb",
|
||||
BTT => "btt",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The two generic layouting axes.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum GenAxis {
|
||||
/// The primary axis along which words are laid out.
|
||||
Primary,
|
||||
/// The secondary axis along which lines and paragraphs are laid out.
|
||||
Secondary,
|
||||
}
|
||||
|
||||
impl GenAxis {
|
||||
/// The specific version of this axis in the given system of axes.
|
||||
pub fn to_specific(self, axes: LayoutAxes) -> SpecAxis {
|
||||
axes.get(self).axis()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for GenAxis {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad(match self {
|
||||
Primary => "primary",
|
||||
Secondary => "secondary",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The two specific layouting axes.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum SpecAxis {
|
||||
/// The horizontal layouting axis.
|
||||
Horizontal,
|
||||
/// The vertical layouting axis.
|
||||
Vertical,
|
||||
}
|
||||
|
||||
impl SpecAxis {
|
||||
/// The generic version of this axis in the given system of axes.
|
||||
pub fn to_generic(self, axes: LayoutAxes) -> GenAxis {
|
||||
if self == axes.primary.axis() { Primary } else { Secondary }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SpecAxis {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad(match self {
|
||||
Horizontal => "horizontal",
|
||||
Vertical => "vertical",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Specifies where to align a layout in a parent container.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct LayoutAlignment {
|
||||
pub struct LayoutAlign {
|
||||
/// The alignment along the primary axis.
|
||||
pub primary: Alignment,
|
||||
pub primary: GenAlign,
|
||||
/// The alignment along the secondary axis.
|
||||
pub secondary: Alignment,
|
||||
pub secondary: GenAlign,
|
||||
}
|
||||
|
||||
impl LayoutAlignment {
|
||||
impl LayoutAlign {
|
||||
/// Create a new instance from the two values.
|
||||
pub fn new(primary: Alignment, secondary: Alignment) -> LayoutAlignment {
|
||||
LayoutAlignment { primary, secondary }
|
||||
pub fn new(primary: GenAlign, secondary: GenAlign) -> LayoutAlign {
|
||||
LayoutAlign { primary, secondary }
|
||||
}
|
||||
|
||||
/// Return the alignment of the specified generic axis.
|
||||
pub fn get(self, axis: GenericAxis) -> Alignment {
|
||||
pub fn get(self, axis: GenAxis) -> GenAlign {
|
||||
match axis {
|
||||
Primary => self.primary,
|
||||
Secondary => self.secondary,
|
||||
@ -269,7 +267,7 @@ impl LayoutAlignment {
|
||||
}
|
||||
|
||||
/// Borrow the alignment of the specified generic axis mutably.
|
||||
pub fn get_mut(&mut self, axis: GenericAxis) -> &mut Alignment {
|
||||
pub fn get_mut(&mut self, axis: GenAxis) -> &mut GenAlign {
|
||||
match axis {
|
||||
Primary => &mut self.primary,
|
||||
Secondary => &mut self.secondary,
|
||||
@ -277,28 +275,88 @@ impl LayoutAlignment {
|
||||
}
|
||||
}
|
||||
|
||||
/// Where to align content.
|
||||
/// Where to align content along a generic context.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub enum Alignment {
|
||||
/// Align content at the start of the axis.
|
||||
Origin,
|
||||
/// Align content centered on the axis.
|
||||
pub enum GenAlign {
|
||||
Start,
|
||||
Center,
|
||||
/// Align content at the end of the axis.
|
||||
End,
|
||||
}
|
||||
|
||||
impl Alignment {
|
||||
impl GenAlign {
|
||||
/// The inverse alignment.
|
||||
pub fn inv(self) -> Alignment {
|
||||
pub fn inv(self) -> GenAlign {
|
||||
match self {
|
||||
Origin => End,
|
||||
Start => End,
|
||||
Center => Center,
|
||||
End => Origin,
|
||||
End => Start,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for GenAlign {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad(match self {
|
||||
Start => "start",
|
||||
Center => "center",
|
||||
End => "end",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Where to align content in a specific context.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub enum SpecAlign {
|
||||
Left,
|
||||
Right,
|
||||
Top,
|
||||
Bottom,
|
||||
Center,
|
||||
}
|
||||
|
||||
impl SpecAlign {
|
||||
/// The specific axis this alignment refers to.
|
||||
///
|
||||
/// Returns `None` if this is center.
|
||||
pub fn axis(self) -> Option<SpecAxis> {
|
||||
match self {
|
||||
Self::Left => Some(Horizontal),
|
||||
Self::Right => Some(Horizontal),
|
||||
Self::Top => Some(Vertical),
|
||||
Self::Bottom => Some(Vertical),
|
||||
Self::Center => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert this to a generic alignment.
|
||||
pub fn to_generic(self, axes: LayoutAxes) -> GenAlign {
|
||||
let get = |spec: SpecAxis, align: GenAlign| {
|
||||
let axis = spec.to_generic(axes);
|
||||
if axes.get(axis).is_positive() { align } else { align.inv() }
|
||||
};
|
||||
|
||||
match self {
|
||||
Self::Left => get(Horizontal, Start),
|
||||
Self::Right => get(Horizontal, End),
|
||||
Self::Top => get(Vertical, Start),
|
||||
Self::Bottom => get(Vertical, End),
|
||||
Self::Center => GenAlign::Center,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SpecAlign {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad(match self {
|
||||
Self::Left => "left",
|
||||
Self::Right => "right",
|
||||
Self::Top => "top",
|
||||
Self::Bottom => "bottom",
|
||||
Self::Center => "center",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Specifies whether to expand a layout to the full size of the space it is
|
||||
/// laid out in or to shrink it to fit the content.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
@ -316,7 +374,7 @@ impl LayoutExpansion {
|
||||
}
|
||||
|
||||
/// Return the expansion value for the given specific axis.
|
||||
pub fn get(self, axis: SpecificAxis) -> bool {
|
||||
pub fn get(self, axis: SpecAxis) -> bool {
|
||||
match axis {
|
||||
Horizontal => self.horizontal,
|
||||
Vertical => self.vertical,
|
||||
@ -324,7 +382,7 @@ impl LayoutExpansion {
|
||||
}
|
||||
|
||||
/// Borrow the expansion value for the given specific axis mutably.
|
||||
pub fn get_mut(&mut self, axis: SpecificAxis) -> &mut bool {
|
||||
pub fn get_mut(&mut self, axis: SpecAxis) -> &mut bool {
|
||||
match axis {
|
||||
Horizontal => &mut self.horizontal,
|
||||
Vertical => &mut self.vertical,
|
||||
|
@ -10,7 +10,8 @@ use crate::{Pass, Feedback};
|
||||
use crate::SharedFontLoader;
|
||||
use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
||||
use crate::geom::Size;
|
||||
use crate::syntax::{Model, SyntaxModel, Node, Decoration};
|
||||
use crate::syntax::decoration::Decoration;
|
||||
use crate::syntax::model::{Model, SyntaxModel, Node};
|
||||
use crate::syntax::span::{Span, Spanned};
|
||||
use super::line::{LineLayouter, LineContext};
|
||||
use super::text::{layout_text, TextContext};
|
||||
@ -42,7 +43,7 @@ pub struct LayoutContext<'a> {
|
||||
/// The initial axes along which content is laid out.
|
||||
pub axes: LayoutAxes,
|
||||
/// The alignment of the finished layout.
|
||||
pub alignment: LayoutAlignment,
|
||||
pub align: LayoutAlign,
|
||||
/// Whether the layout that is to be created will be nested in a parent
|
||||
/// container.
|
||||
pub nested: bool,
|
||||
@ -74,7 +75,7 @@ pub enum Command<'a> {
|
||||
/// Add spacing of given [kind](super::SpacingKind) along the primary or
|
||||
/// secondary axis. The spacing kind defines how the spacing interacts with
|
||||
/// surrounding spacing.
|
||||
AddSpacing(f64, SpacingKind, GenericAxis),
|
||||
AddSpacing(f64, SpacingKind, GenAxis),
|
||||
|
||||
/// Start a new line.
|
||||
BreakLine,
|
||||
@ -90,9 +91,9 @@ pub enum Command<'a> {
|
||||
SetPageStyle(PageStyle),
|
||||
|
||||
/// Update the alignment for future boxes added to this layouting process.
|
||||
SetAlignment(LayoutAlignment),
|
||||
/// Update the layouting axes along which future boxes will be laid out.
|
||||
/// This finishes the current line.
|
||||
SetAlignment(LayoutAlign),
|
||||
/// Update the layouting axes along which future boxes will be laid
|
||||
/// out. This finishes the current line.
|
||||
SetAxes(LayoutAxes),
|
||||
}
|
||||
|
||||
@ -115,7 +116,7 @@ impl<'a> ModelLayouter<'a> {
|
||||
layouter: LineLayouter::new(LineContext {
|
||||
spaces: ctx.spaces.clone(),
|
||||
axes: ctx.axes,
|
||||
alignment: ctx.alignment,
|
||||
align: ctx.align,
|
||||
repeat: ctx.repeat,
|
||||
debug: ctx.debug && ctx.nested,
|
||||
line_spacing: ctx.style.text.line_spacing(),
|
||||
@ -127,10 +128,10 @@ impl<'a> ModelLayouter<'a> {
|
||||
}
|
||||
|
||||
/// Flatly layout a model into this layouting process.
|
||||
pub fn layout<'r>(
|
||||
pub async fn layout<'r>(
|
||||
&'r mut self,
|
||||
model: Spanned<&'r dyn Model>
|
||||
) -> DynFuture<'r, ()> { Box::pin(async move {
|
||||
) {
|
||||
// Execute the model's layout function which generates the commands.
|
||||
let layouted = model.v.layout(LayoutContext {
|
||||
style: &self.style,
|
||||
@ -145,14 +146,14 @@ impl<'a> ModelLayouter<'a> {
|
||||
for command in layouted.output {
|
||||
self.execute_command(command, model.span).await;
|
||||
}
|
||||
}) }
|
||||
}
|
||||
|
||||
/// Layout a syntax model by directly processing the nodes instead of using
|
||||
/// the command based architecture.
|
||||
pub fn layout_syntax_model<'r>(
|
||||
pub async fn layout_syntax_model<'r>(
|
||||
&'r mut self,
|
||||
model: &'r SyntaxModel
|
||||
) -> DynFuture<'r, ()> { Box::pin(async move {
|
||||
) {
|
||||
use Node::*;
|
||||
|
||||
for Spanned { v: node, span } in &model.nodes {
|
||||
@ -213,7 +214,7 @@ impl<'a> ModelLayouter<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}) }
|
||||
}
|
||||
|
||||
/// Compute the finished list of boxes.
|
||||
pub fn finish(self) -> Pass<MultiLayout> {
|
||||
@ -280,7 +281,7 @@ impl<'a> ModelLayouter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
SetAlignment(alignment) => self.ctx.alignment = alignment,
|
||||
SetAlignment(align) => self.ctx.align = align,
|
||||
SetAxes(axes) => {
|
||||
self.layouter.set_axes(axes);
|
||||
self.ctx.axes = axes;
|
||||
@ -294,7 +295,7 @@ impl<'a> ModelLayouter<'a> {
|
||||
loader: &self.ctx.loader,
|
||||
style: &self.style.text,
|
||||
axes: self.ctx.axes,
|
||||
alignment: self.ctx.alignment,
|
||||
align: self.ctx.align,
|
||||
}).await)
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ pub struct StackContext {
|
||||
pub axes: LayoutAxes,
|
||||
/// Which alignment to set on the resulting layout. This affects how it will
|
||||
/// be positioned in a parent box.
|
||||
pub alignment: LayoutAlignment,
|
||||
pub align: LayoutAlign,
|
||||
/// Whether to have repeated spaces or to use only the first and only once.
|
||||
pub repeat: bool,
|
||||
/// Whether to output a command which renders a debugging box showing the
|
||||
@ -72,7 +72,7 @@ struct Space {
|
||||
extra: Size,
|
||||
/// The rulers of a space dictate which alignments for new boxes are still
|
||||
/// allowed and which require a new space to be started.
|
||||
rulers: Value4<Alignment>,
|
||||
rulers: Value4<GenAlign>,
|
||||
/// The last added spacing if the last added thing was spacing.
|
||||
last_spacing: LastSpacing,
|
||||
}
|
||||
@ -93,7 +93,7 @@ impl StackLayouter {
|
||||
// If the alignment cannot be fitted in this space, finish it.
|
||||
// TODO: Issue warning for non-fitting alignment in
|
||||
// non-repeating context.
|
||||
if !self.update_rulers(layout.alignment) && self.ctx.repeat {
|
||||
if !self.update_rulers(layout.align) && self.ctx.repeat {
|
||||
self.finish_space(true);
|
||||
}
|
||||
|
||||
@ -139,7 +139,7 @@ impl StackLayouter {
|
||||
self.update_metrics(dimensions);
|
||||
self.space.layouts.push((self.ctx.axes, Layout {
|
||||
dimensions: dimensions.specialized(self.ctx.axes),
|
||||
alignment: LayoutAlignment::new(Origin, Origin),
|
||||
align: LayoutAlign::new(Start, Start),
|
||||
actions: vec![]
|
||||
}));
|
||||
|
||||
@ -183,26 +183,26 @@ impl StackLayouter {
|
||||
|
||||
/// Update the rulers to account for the new layout. Returns true if a
|
||||
/// space break is necessary.
|
||||
fn update_rulers(&mut self, alignment: LayoutAlignment) -> bool {
|
||||
let allowed = self.is_fitting_alignment(alignment);
|
||||
fn update_rulers(&mut self, align: LayoutAlign) -> bool {
|
||||
let allowed = self.is_fitting_alignment(align);
|
||||
if allowed {
|
||||
*self.space.rulers.get_mut(self.ctx.axes.secondary, Origin)
|
||||
= alignment.secondary;
|
||||
*self.space.rulers.get_mut(self.ctx.axes.secondary, Start)
|
||||
= align.secondary;
|
||||
}
|
||||
allowed
|
||||
}
|
||||
|
||||
/// Whether a layout with the given alignment can still be layouted in the
|
||||
/// active space.
|
||||
pub fn is_fitting_alignment(&mut self, alignment: LayoutAlignment) -> bool {
|
||||
self.is_fitting_axis(self.ctx.axes.primary, alignment.primary)
|
||||
&& self.is_fitting_axis(self.ctx.axes.secondary, alignment.secondary)
|
||||
pub fn is_fitting_alignment(&mut self, align: LayoutAlign) -> bool {
|
||||
self.is_fitting_axis(self.ctx.axes.primary, align.primary)
|
||||
&& self.is_fitting_axis(self.ctx.axes.secondary, align.secondary)
|
||||
}
|
||||
|
||||
/// Whether the given alignment is still allowed according to the rulers.
|
||||
fn is_fitting_axis(&mut self, direction: Direction, alignment: Alignment) -> bool {
|
||||
alignment >= *self.space.rulers.get_mut(direction, Origin)
|
||||
&& alignment <= self.space.rulers.get_mut(direction, End).inv()
|
||||
fn is_fitting_axis(&mut self, dir: Dir, align: GenAlign) -> bool {
|
||||
align >= *self.space.rulers.get_mut(dir, Start)
|
||||
&& align <= self.space.rulers.get_mut(dir, End).inv()
|
||||
}
|
||||
|
||||
/// Change the layouting axes used by this layouter.
|
||||
@ -326,7 +326,7 @@ impl StackLayouter {
|
||||
// layout uses up space from the origin to the end. Thus, it reduces
|
||||
// the usable space for following layouts at it's origin by its
|
||||
// extent along the secondary axis.
|
||||
*bound.get_mut(axes.secondary, Origin)
|
||||
*bound.get_mut(axes.secondary, Start)
|
||||
+= axes.secondary.factor() * layout.dimensions.secondary(*axes);
|
||||
}
|
||||
|
||||
@ -342,7 +342,7 @@ impl StackLayouter {
|
||||
for (bound, entry) in bounds.iter_mut().zip(&self.space.layouts).rev() {
|
||||
let (axes, layout) = entry;
|
||||
|
||||
// When the axes get rotated, the the maximal primary size
|
||||
// When the axes are rotated, the the maximal primary size
|
||||
// (`extent.x`) dictates how much secondary extent the whole run
|
||||
// had. This value is thus stored in `extent.y`. The primary extent
|
||||
// is reset for this new axis-aligned run.
|
||||
@ -377,7 +377,7 @@ impl StackLayouter {
|
||||
let layouts = std::mem::replace(&mut self.space.layouts, vec![]);
|
||||
for ((axes, layout), bound) in layouts.into_iter().zip(bounds) {
|
||||
let size = layout.dimensions.specialized(axes);
|
||||
let alignment = layout.alignment;
|
||||
let align = layout.align;
|
||||
|
||||
// The space in which this layout is aligned is given by the
|
||||
// distances between the borders of it's bounding box.
|
||||
@ -385,7 +385,7 @@ impl StackLayouter {
|
||||
Size::new(bound.right - bound.left, bound.bottom - bound.top)
|
||||
.generalized(axes);
|
||||
|
||||
let local = usable.anchor(alignment, axes) - size.anchor(alignment, axes);
|
||||
let local = usable.anchor(align, axes) - size.anchor(align, axes);
|
||||
let pos = Size::new(bound.left, bound.top) + local.specialized(axes);
|
||||
|
||||
actions.add_layout(pos, layout);
|
||||
@ -393,7 +393,7 @@ impl StackLayouter {
|
||||
|
||||
self.layouts.push(Layout {
|
||||
dimensions,
|
||||
alignment: self.ctx.alignment,
|
||||
align: self.ctx.align,
|
||||
actions: actions.into_vec(),
|
||||
});
|
||||
|
||||
@ -424,7 +424,7 @@ impl Space {
|
||||
size: Size::ZERO,
|
||||
usable,
|
||||
extra: Size::ZERO,
|
||||
rulers: Value4::with_all(Origin),
|
||||
rulers: Value4::with_all(Start),
|
||||
last_spacing: LastSpacing::Hard,
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ pub struct TextContext<'a> {
|
||||
/// primary-horizontal layouting is supported.
|
||||
pub axes: LayoutAxes,
|
||||
/// The alignment of the finished layout.
|
||||
pub alignment: LayoutAlignment,
|
||||
pub align: LayoutAlign,
|
||||
}
|
||||
|
||||
/// Layouts text into a box.
|
||||
@ -75,7 +75,7 @@ impl<'a> TextLayouter<'a> {
|
||||
|
||||
Layout {
|
||||
dimensions: Size::new(self.width, self.ctx.style.font_size()),
|
||||
alignment: self.ctx.alignment,
|
||||
align: self.ctx.align,
|
||||
actions: self.actions.into_vec(),
|
||||
}
|
||||
}
|
||||
|
@ -179,7 +179,6 @@ impl Display for ParseLengthError {
|
||||
|
||||
/// Either an absolute length or a factor of some entity.
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum ScaleLength {
|
||||
Absolute(Length),
|
||||
Scaled(f64),
|
||||
|
@ -23,7 +23,10 @@ use crate::diagnostic::Diagnostics;
|
||||
use crate::font::SharedFontLoader;
|
||||
use crate::layout::MultiLayout;
|
||||
use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
||||
use crate::syntax::{Decorations, SyntaxModel, Scope, ParseState, parse};
|
||||
use crate::syntax::decoration::Decorations;
|
||||
use crate::syntax::model::SyntaxModel;
|
||||
use crate::syntax::parsing::{parse, ParseState};
|
||||
use crate::syntax::scope::Scope;
|
||||
use crate::syntax::span::{Offset, Pos};
|
||||
|
||||
/// Declare a module and reexport all its contents.
|
||||
@ -112,8 +115,8 @@ impl Typesetter {
|
||||
expansion: LayoutExpansion::new(true, true),
|
||||
}],
|
||||
repeat: true,
|
||||
axes: LayoutAxes::new(LeftToRight, TopToBottom),
|
||||
alignment: LayoutAlignment::new(Origin, Origin),
|
||||
axes: LayoutAxes::new(LTT, TTB),
|
||||
align: LayoutAlign::new(Start, Start),
|
||||
nested: false,
|
||||
debug: self.debug,
|
||||
},
|
||||
|
@ -16,42 +16,41 @@ function! {
|
||||
}
|
||||
|
||||
parse(header, body, ctx, f) {
|
||||
let size = header.args.pos.get_first::<ScaleLength>(&mut f.diagnostics);
|
||||
let size = header.args.pos.get::<ScaleLength>();
|
||||
|
||||
let style = header.args.key.get::<FontStyle>(&mut f.diagnostics, "style");
|
||||
let weight = header.args.key.get::<FontWeight>(&mut f.diagnostics, "weight");
|
||||
let width = header.args.key.get::<FontWidth>(&mut f.diagnostics, "width");
|
||||
let style = header.args.key.get::<FontStyle>("style", f);
|
||||
let weight = header.args.key.get::<FontWeight>("weight", f);
|
||||
let width = header.args.key.get::<FontWidth>("width", f);
|
||||
|
||||
let list = header.args.pos.get_all::<StringLike>(&mut f.diagnostics)
|
||||
let list = header.args.pos.all::<StringLike>()
|
||||
.map(|s| s.0.to_lowercase())
|
||||
.collect();
|
||||
|
||||
let classes = header.args.key
|
||||
.get_all::<String, Tuple>(&mut f.diagnostics)
|
||||
.all::<Tuple>()
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.map(|(class, mut tuple)| {
|
||||
let fallback = tuple.get_all::<StringLike>(&mut f.diagnostics)
|
||||
let fallback = tuple.all::<StringLike>()
|
||||
.map(|s| s.0.to_lowercase())
|
||||
.collect();
|
||||
(class.to_lowercase(), fallback)
|
||||
(class.v.0, fallback)
|
||||
})
|
||||
.collect();
|
||||
|
||||
FontFunc {
|
||||
body: body!(opt: body, ctx, f),
|
||||
size,
|
||||
list,
|
||||
classes,
|
||||
style,
|
||||
weight,
|
||||
width,
|
||||
list,
|
||||
classes,
|
||||
}
|
||||
}
|
||||
|
||||
layout(self, ctx, f) {
|
||||
styled(&self.body, ctx, Some(()),
|
||||
|t, _| {
|
||||
styled(&self.body, ctx, Some(()), |t, _| {
|
||||
self.size.with(|s| match s {
|
||||
ScaleLength::Absolute(length) => {
|
||||
t.base_font_size = length.as_raw();
|
||||
@ -65,7 +64,9 @@ function! {
|
||||
self.width.with(|w| t.variant.width = w);
|
||||
|
||||
if !self.list.is_empty() {
|
||||
*t.fallback.list_mut() = self.list.clone();
|
||||
*t.fallback.list_mut() = self.list.iter()
|
||||
.map(|s| s.to_lowercase())
|
||||
.collect();
|
||||
}
|
||||
|
||||
for (class, fallback) in &self.classes {
|
||||
|
@ -1,116 +1,22 @@
|
||||
use crate::length::ScaleLength;
|
||||
use super::*;
|
||||
|
||||
function! {
|
||||
/// `align`: Aligns content along the layouting axes.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct AlignFunc {
|
||||
body: Option<SyntaxModel>,
|
||||
map: PosAxisMap<AlignmentValue>,
|
||||
}
|
||||
|
||||
parse(header, body, ctx, f) {
|
||||
AlignFunc {
|
||||
body: body!(opt: body, ctx, f),
|
||||
map: PosAxisMap::parse::<AxisKey>(&mut f.diagnostics, &mut header.args),
|
||||
}
|
||||
}
|
||||
|
||||
layout(self, ctx, f) {
|
||||
ctx.base = ctx.spaces[0].dimensions;
|
||||
|
||||
let map = self.map.dedup(&mut f.diagnostics, ctx.axes, |alignment| {
|
||||
alignment.axis().map(|s| s.to_generic(ctx.axes))
|
||||
});
|
||||
|
||||
for &axis in &[Primary, Secondary] {
|
||||
if let Some(Spanned { v: alignment, span }) = map.get_spanned(axis) {
|
||||
if let Some(generic) = alignment.to_generic(ctx.axes, axis) {
|
||||
*ctx.alignment.get_mut(axis) = generic;
|
||||
} else {
|
||||
error!(
|
||||
@f, span,
|
||||
"invalid alignment `{}` for {} axis", alignment, axis,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match &self.body {
|
||||
Some(body) => {
|
||||
let layouted = layout(body, ctx).await;
|
||||
f.extend(layouted.feedback);
|
||||
vec![AddMultiple(layouted.output)]
|
||||
}
|
||||
None => vec![SetAlignment(ctx.alignment)],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function! {
|
||||
/// `direction`: Sets the directions of the layouting axes.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct DirectionFunc {
|
||||
name_span: Span,
|
||||
body: Option<SyntaxModel>,
|
||||
map: PosAxisMap<Direction>,
|
||||
}
|
||||
|
||||
parse(header, body, ctx, f) {
|
||||
DirectionFunc {
|
||||
name_span: header.name.span,
|
||||
body: body!(opt: body, ctx, f),
|
||||
map: PosAxisMap::parse::<AxisKey>(&mut f.diagnostics, &mut header.args),
|
||||
}
|
||||
}
|
||||
|
||||
layout(self, ctx, f) {
|
||||
ctx.base = ctx.spaces[0].dimensions;
|
||||
|
||||
let map = self.map.dedup(&mut f.diagnostics, ctx.axes, |direction| {
|
||||
Some(direction.axis().to_generic(ctx.axes))
|
||||
});
|
||||
|
||||
let mut axes = ctx.axes;
|
||||
|
||||
map.with(Primary, |&dir| axes.primary = dir);
|
||||
map.with(Secondary, |&dir| axes.secondary = dir);
|
||||
|
||||
if axes.primary.axis() == axes.secondary.axis() {
|
||||
error!(
|
||||
@f, self.name_span,
|
||||
"invalid aligned primary and secondary axes: `{}`, `{}`",
|
||||
ctx.axes.primary, ctx.axes.secondary,
|
||||
);
|
||||
} else {
|
||||
ctx.axes = axes;
|
||||
}
|
||||
|
||||
match &self.body {
|
||||
Some(body) => {
|
||||
let layouted = layout(body, ctx).await;
|
||||
f.extend(layouted.feedback);
|
||||
vec![AddMultiple(layouted.output)]
|
||||
}
|
||||
None => vec![SetAxes(ctx.axes)],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function! {
|
||||
/// `box`: Layouts content into a box.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct BoxFunc {
|
||||
body: SyntaxModel,
|
||||
extents: AxisMap<ScaleLength>,
|
||||
width: Option<ScaleLength>,
|
||||
height: Option<ScaleLength>,
|
||||
debug: Option<bool>,
|
||||
}
|
||||
|
||||
parse(header, body, ctx, f) {
|
||||
BoxFunc {
|
||||
body: body!(opt: body, ctx, f).unwrap_or(SyntaxModel::new()),
|
||||
extents: AxisMap::parse::<ExtentKey>(&mut f.diagnostics, &mut header.args.key),
|
||||
debug: header.args.key.get::<bool>(&mut f.diagnostics, "debug"),
|
||||
width: header.args.key.get::<ScaleLength>("width", f),
|
||||
height: header.args.key.get::<ScaleLength>("height", f),
|
||||
debug: header.args.key.get::<bool>("debug", f),
|
||||
}
|
||||
}
|
||||
|
||||
@ -122,15 +28,19 @@ function! {
|
||||
ctx.debug = debug;
|
||||
}
|
||||
|
||||
let map = self.extents.dedup(&mut f.diagnostics, ctx.axes);
|
||||
for &axis in &[Horizontal, Vertical] {
|
||||
if let Some(scale) = map.get(axis) {
|
||||
let length = scale.raw_scaled(ctx.base.get(axis));
|
||||
*ctx.base.get_mut(axis) = length;
|
||||
*ctx.spaces[0].dimensions.get_mut(axis) = length;
|
||||
*ctx.spaces[0].expansion.get_mut(axis) = true;
|
||||
}
|
||||
}
|
||||
self.width.with(|v| {
|
||||
let length = v.raw_scaled(ctx.base.x);
|
||||
ctx.base.x = length;
|
||||
ctx.spaces[0].dimensions.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].dimensions.y = length;
|
||||
ctx.spaces[0].expansion.vertical = true;
|
||||
});
|
||||
|
||||
let layouted = layout(&self.body, ctx).await;
|
||||
let layout = layouted.output.into_iter().next().unwrap();
|
||||
@ -139,3 +49,62 @@ function! {
|
||||
vec![Add(layout)]
|
||||
}
|
||||
}
|
||||
|
||||
function! {
|
||||
/// `align`: Aligns content along the layouting axes.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct AlignFunc {
|
||||
body: Option<SyntaxModel>,
|
||||
aligns: Vec<Spanned<SpecAlign>>,
|
||||
h: Option<Spanned<SpecAlign>>,
|
||||
v: Option<Spanned<SpecAlign>>,
|
||||
}
|
||||
|
||||
parse(header, body, ctx, f) {
|
||||
AlignFunc {
|
||||
body: body!(opt: body, ctx, 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].dimensions;
|
||||
|
||||
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,6 +1,6 @@
|
||||
//! The _Typst_ standard library.
|
||||
|
||||
use crate::syntax::Scope;
|
||||
use crate::syntax::scope::Scope;
|
||||
use crate::func::prelude::*;
|
||||
|
||||
pub_use_mod!(font);
|
||||
@ -12,31 +12,15 @@ pub_use_mod!(spacing);
|
||||
pub fn std() -> Scope {
|
||||
let mut std = Scope::new::<ValFunc>();
|
||||
|
||||
// Basics
|
||||
std.add::<ValFunc>("val");
|
||||
|
||||
// Font setup
|
||||
std.add::<FontFunc>("font");
|
||||
std.add_with_meta::<ContentSpacingFunc>("word.spacing", ContentKind::Word);
|
||||
|
||||
// Layout
|
||||
std.add_with_meta::<ContentSpacingFunc>("line.spacing", ContentKind::Line);
|
||||
std.add_with_meta::<ContentSpacingFunc>("par.spacing", ContentKind::Paragraph);
|
||||
std.add::<AlignFunc>("align");
|
||||
std.add::<DirectionFunc>("direction");
|
||||
std.add::<BoxFunc>("box");
|
||||
|
||||
// Spacing
|
||||
std.add::<LineBreakFunc>("n");
|
||||
std.add::<LineBreakFunc>("line.break");
|
||||
std.add::<ParBreakFunc>("par.break");
|
||||
std.add::<PageBreakFunc>("page.break");
|
||||
std.add_with_meta::<SpacingFunc>("spacing", None);
|
||||
std.add_with_meta::<SpacingFunc>("h", Some(Horizontal));
|
||||
std.add_with_meta::<SpacingFunc>("v", Some(Vertical));
|
||||
|
||||
// Page setup
|
||||
std.add::<PageFunc>("page");
|
||||
std.add::<AlignFunc>("align");
|
||||
std.add::<BoxFunc>("box");
|
||||
std.add_with_meta::<SpacingFunc>("h", Horizontal);
|
||||
std.add_with_meta::<SpacingFunc>("v", Vertical);
|
||||
std.add::<ParBreakFunc>("parbreak");
|
||||
std.add::<PageBreakFunc>("pagebreak");
|
||||
|
||||
std
|
||||
}
|
||||
@ -49,8 +33,8 @@ function! {
|
||||
}
|
||||
|
||||
parse(header, body, state, f) {
|
||||
header.args.pos.items.clear();
|
||||
header.args.key.pairs.clear();
|
||||
header.args.pos.0.clear();
|
||||
header.args.key.0.clear();
|
||||
ValFunc { body: body!(opt: body, state, f) }
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::length::Length;
|
||||
use crate::length::{Length, ScaleLength};
|
||||
use crate::paper::{Paper, PaperClass};
|
||||
use super::*;
|
||||
|
||||
@ -7,18 +7,28 @@ function! {
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct PageFunc {
|
||||
paper: Option<Paper>,
|
||||
extents: AxisMap<Length>,
|
||||
padding: PaddingMap,
|
||||
width: Option<Length>,
|
||||
height: Option<Length>,
|
||||
margins: Option<ScaleLength>,
|
||||
left: Option<ScaleLength>,
|
||||
right: Option<ScaleLength>,
|
||||
top: Option<ScaleLength>,
|
||||
bottom: Option<ScaleLength>,
|
||||
flip: bool,
|
||||
}
|
||||
|
||||
parse(header, body, state, f) {
|
||||
body!(nope: body, f);
|
||||
PageFunc {
|
||||
paper: header.args.pos.get::<Paper>(&mut f.diagnostics),
|
||||
extents: AxisMap::parse::<ExtentKey>(&mut f.diagnostics, &mut header.args.key),
|
||||
padding: PaddingMap::parse(&mut f.diagnostics, &mut header.args),
|
||||
flip: header.args.key.get::<bool>(&mut f.diagnostics, "flip").unwrap_or(false),
|
||||
paper: header.args.pos.get::<Paper>(),
|
||||
width: header.args.key.get::<Length>("width", f),
|
||||
height: header.args.key.get::<Length>("height", f),
|
||||
margins: header.args.key.get::<ScaleLength>("margins", f),
|
||||
left: header.args.key.get::<ScaleLength>("left", f),
|
||||
right: header.args.key.get::<ScaleLength>("right", f),
|
||||
top: header.args.key.get::<ScaleLength>("top", f),
|
||||
bottom: header.args.key.get::<ScaleLength>("bottom", f),
|
||||
flip: header.args.key.get::<bool>("flip", f).unwrap_or(false),
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,20 +38,22 @@ function! {
|
||||
if let Some(paper) = self.paper {
|
||||
style.class = paper.class;
|
||||
style.dimensions = paper.size();
|
||||
} else {
|
||||
} else if self.width.is_some() || self.height.is_some() {
|
||||
style.class = PaperClass::Custom;
|
||||
}
|
||||
|
||||
let map = self.extents.dedup(&mut f.diagnostics, ctx.axes);
|
||||
map.with(Horizontal, |&width| style.dimensions.x = width.as_raw());
|
||||
map.with(Vertical, |&height| style.dimensions.y = height.as_raw());
|
||||
self.width.with(|v| style.dimensions.x = v.as_raw());
|
||||
self.height.with(|v| style.dimensions.y = v.as_raw());
|
||||
self.margins.with(|v| style.margins.set_all(Some(v)));
|
||||
self.left.with(|v| style.margins.left = Some(v));
|
||||
self.right.with(|v| style.margins.right = Some(v));
|
||||
self.top.with(|v| style.margins.top = Some(v));
|
||||
self.bottom.with(|v| style.margins.bottom = Some(v));
|
||||
|
||||
if self.flip {
|
||||
style.dimensions.swap();
|
||||
}
|
||||
|
||||
self.padding.apply(&mut f.diagnostics, ctx.axes, &mut style.margins);
|
||||
|
||||
vec![SetPageStyle(style)]
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,9 @@
|
||||
use crate::length::ScaleLength;
|
||||
use crate::layout::SpacingKind;
|
||||
|
||||
use super::*;
|
||||
use self::ContentKind::*;
|
||||
|
||||
function! {
|
||||
/// `line.break`, `n`: Ends the current line.
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct LineBreakFunc;
|
||||
|
||||
parse(default)
|
||||
layout(self, ctx, f) { vec![BreakLine] }
|
||||
}
|
||||
|
||||
function! {
|
||||
/// `par.break`: Ends the current paragraph.
|
||||
/// `parbreak`: Ends the current paragraph.
|
||||
///
|
||||
/// self has the same effect as two subsequent newlines.
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
@ -25,7 +14,7 @@ function! {
|
||||
}
|
||||
|
||||
function! {
|
||||
/// `page.break`: Ends the current page.
|
||||
/// `pagebreak`: Ends the current page.
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct PageBreakFunc;
|
||||
|
||||
@ -34,64 +23,20 @@ function! {
|
||||
}
|
||||
|
||||
function! {
|
||||
/// `word.spacing`, `line.spacing`, `par.spacing`: The spacing between
|
||||
/// words, lines or paragraphs as a multiple of the font size.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ContentSpacingFunc {
|
||||
body: Option<SyntaxModel>,
|
||||
content: ContentKind,
|
||||
spacing: Option<f64>,
|
||||
}
|
||||
|
||||
type Meta = ContentKind;
|
||||
|
||||
parse(header, body, state, f, meta) {
|
||||
ContentSpacingFunc {
|
||||
body: body!(opt: body, state, f),
|
||||
content: meta,
|
||||
spacing: header.args.pos.get::<f64>(&mut f.diagnostics)
|
||||
.map(|num| num as f64)
|
||||
.or_missing(&mut f.diagnostics, header.name.span, "spacing"),
|
||||
}
|
||||
}
|
||||
|
||||
layout(self, ctx, f) {
|
||||
styled(&self.body, ctx, self.spacing, |t, s| match self.content {
|
||||
Word => t.word_spacing_scale = s,
|
||||
Line => t.line_spacing_scale = s,
|
||||
Paragraph => t.paragraph_spacing_scale = s,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The different kinds of content that can be spaced. Used as a metadata type
|
||||
/// for the [`ContentSpacingFunc`].
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum ContentKind {
|
||||
Word,
|
||||
Line,
|
||||
Paragraph,
|
||||
}
|
||||
|
||||
function! {
|
||||
/// `spacing`, `h`, `v`: Adds spacing along an axis.
|
||||
/// `h` and `v`: Add spacing along an axis.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct SpacingFunc {
|
||||
spacing: Option<(AxisKey, ScaleLength)>,
|
||||
spacing: Option<(SpecAxis, ScaleLength)>,
|
||||
}
|
||||
|
||||
type Meta = Option<SpecificAxis>;
|
||||
type Meta = SpecAxis;
|
||||
|
||||
parse(header, body, state, f, meta) {
|
||||
body!(nope: body, f);
|
||||
SpacingFunc {
|
||||
spacing: if let Some(axis) = meta {
|
||||
header.args.pos.get::<ScaleLength>(&mut f.diagnostics)
|
||||
.map(|s| (AxisKey::Specific(axis), s))
|
||||
} else {
|
||||
header.args.key.get_with_key::<AxisKey, ScaleLength>(&mut f.diagnostics)
|
||||
}.or_missing(&mut f.diagnostics, header.name.span, "spacing"),
|
||||
spacing: header.args.pos.expect::<ScaleLength>(f)
|
||||
.map(|s| (meta, s))
|
||||
.or_missing(header.name.span, "spacing", f),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,6 @@ impl Paper {
|
||||
|
||||
/// Paper classes define default margins for a class of related papers.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum PaperClass {
|
||||
Custom,
|
||||
Base,
|
||||
|
41
src/syntax/decoration.rs
Normal file
41
src/syntax/decoration.rs
Normal file
@ -0,0 +1,41 @@
|
||||
//! Decorations for semantic syntax highlighting.
|
||||
|
||||
use serde::Serialize;
|
||||
use super::span::SpanVec;
|
||||
|
||||
/// A list of spanned decorations.
|
||||
pub type Decorations = SpanVec<Decoration>;
|
||||
|
||||
/// Decorations for semantic syntax highlighting.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Decoration {
|
||||
/// A valid function name.
|
||||
/// ```typst
|
||||
/// [box]
|
||||
/// ^^^
|
||||
/// ```
|
||||
ValidFuncName,
|
||||
/// An invalid function name.
|
||||
/// ```typst
|
||||
/// [blabla]
|
||||
/// ^^^^^^
|
||||
/// ```
|
||||
InvalidFuncName,
|
||||
/// A key of a keyword argument.
|
||||
/// ```typst
|
||||
/// [box: width=5cm]
|
||||
/// ^^^^^
|
||||
/// ```
|
||||
ArgumentKey,
|
||||
/// A key in an object.
|
||||
/// ```typst
|
||||
/// [box: padding={ left: 1cm, right: 2cm}]
|
||||
/// ^^^^ ^^^^^
|
||||
/// ```
|
||||
ObjectKey,
|
||||
/// An italic word.
|
||||
Italic,
|
||||
/// A bold word.
|
||||
Bold,
|
||||
}
|
@ -1,16 +1,15 @@
|
||||
//! Expressions in function headers.
|
||||
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::iter::FromIterator;
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use std::u8;
|
||||
|
||||
use crate::diagnostic::Diagnostics;
|
||||
use crate::Feedback;
|
||||
use crate::length::Length;
|
||||
use super::func::{Key, Value};
|
||||
use super::span::{Span, Spanned};
|
||||
use super::span::Spanned;
|
||||
use super::tokens::is_identifier;
|
||||
use super::value::Value;
|
||||
|
||||
/// An argument or return value.
|
||||
#[derive(Clone, PartialEq)]
|
||||
@ -238,103 +237,63 @@ impl fmt::Display for ParseColorError {
|
||||
/// (false, 12cm, "hi")
|
||||
/// ```
|
||||
#[derive(Default, Clone, PartialEq)]
|
||||
pub struct Tuple {
|
||||
/// The elements of the tuple.
|
||||
pub items: Vec<Spanned<Expr>>,
|
||||
}
|
||||
pub struct Tuple(pub Vec<Spanned<Expr>>);
|
||||
|
||||
impl Tuple {
|
||||
/// Create an empty tuple.
|
||||
pub fn new() -> Tuple {
|
||||
Tuple { items: vec![] }
|
||||
Tuple(vec![])
|
||||
}
|
||||
|
||||
/// Add an element.
|
||||
pub fn add(&mut self, item: Spanned<Expr>) {
|
||||
self.items.push(item);
|
||||
pub fn push(&mut self, item: Spanned<Expr>) {
|
||||
self.0.push(item);
|
||||
}
|
||||
|
||||
/// Extract (and remove) the first matching value and remove and generate
|
||||
/// diagnostics for all previous items that did not match.
|
||||
pub fn get<V: Value>(&mut self, diagnostics: &mut Diagnostics) -> Option<V> {
|
||||
while !self.items.is_empty() {
|
||||
let expr = self.items.remove(0);
|
||||
let span = expr.span;
|
||||
match V::parse(expr) {
|
||||
Ok(output) => return Some(output),
|
||||
Err(v) => diagnostics.push(Spanned { v, span }),
|
||||
/// Expect a specific value type and generate errors for every argument
|
||||
/// until an argument of the value type is found.
|
||||
pub fn expect<V: Value>(&mut self, f: &mut Feedback) -> Option<V> {
|
||||
while !self.0.is_empty() {
|
||||
let item = self.0.remove(0);
|
||||
if let Some(val) = V::parse(item, f) {
|
||||
return Some(val);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Extract (and remove) the first matching value without removing and
|
||||
/// generating diagnostics for all previous items that did not match.
|
||||
pub fn get_first<V: Value>(&mut self, _: &mut Diagnostics) -> Option<V> {
|
||||
/// Extract the first argument of the value type if there is any.
|
||||
pub fn get<V: Value>(&mut self) -> Option<V> {
|
||||
for (i, item) in self.0.iter().enumerate() {
|
||||
if let Some(val) = V::parse(item.clone(), &mut Feedback::new()) {
|
||||
self.0.remove(i);
|
||||
return Some(val);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Extract all arguments of the value type.
|
||||
pub fn all<'a, V: Value>(&'a mut self) -> impl Iterator<Item = V> + 'a {
|
||||
let mut i = 0;
|
||||
while i < self.items.len() {
|
||||
let expr = self.items[i].clone();
|
||||
match V::parse(expr) {
|
||||
Ok(output) => {
|
||||
self.items.remove(i);
|
||||
return Some(output)
|
||||
}
|
||||
Err(_) => {},
|
||||
}
|
||||
std::iter::from_fn(move || {
|
||||
while i < self.0.len() {
|
||||
let val = V::parse(self.0[i].clone(), &mut Feedback::new());
|
||||
if val.is_some() {
|
||||
self.0.remove(i);
|
||||
return val;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Extract and return an iterator over all values that match and generate
|
||||
/// diagnostics for all items that do not match.
|
||||
pub fn get_all<'a, V: Value>(&'a mut self, diagnostics: &'a mut Diagnostics)
|
||||
-> impl Iterator<Item=V> + 'a {
|
||||
self.items.drain(..).filter_map(move |expr| {
|
||||
let span = expr.span;
|
||||
match V::parse(expr) {
|
||||
Ok(output) => Some(output),
|
||||
Err(v) => { diagnostics.push(Spanned { v, span }); None }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterate over the items of this tuple.
|
||||
pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, Spanned<Expr>> {
|
||||
self.items.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Tuple {
|
||||
type Item = Spanned<Expr>;
|
||||
type IntoIter = std::vec::IntoIter<Spanned<Expr>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.items.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Tuple {
|
||||
type Item = &'a Spanned<Expr>;
|
||||
type IntoIter = std::slice::Iter<'a, Spanned<Expr>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<Spanned<Expr>> for Tuple {
|
||||
fn from_iter<I: IntoIterator<Item=Spanned<Expr>>>(iter: I) -> Self {
|
||||
Tuple { items: iter.into_iter().collect() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Tuple {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.debug_list()
|
||||
.entries(&self.items)
|
||||
.finish()
|
||||
f.debug_list().entries(&self.0).finish()
|
||||
}
|
||||
}
|
||||
|
||||
@ -374,10 +333,7 @@ impl Deref for NamedTuple {
|
||||
/// { fit: false, width: 12cm, items: (1, 2, 3) }
|
||||
/// ```
|
||||
#[derive(Default, Clone, PartialEq)]
|
||||
pub struct Object {
|
||||
/// The key-value pairs of the object.
|
||||
pub pairs: Vec<Spanned<Pair>>,
|
||||
}
|
||||
pub struct Object(pub Vec<Spanned<Pair>>);
|
||||
|
||||
/// A key-value pair in an object.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@ -399,126 +355,52 @@ pub struct Pair {
|
||||
impl Object {
|
||||
/// Create an empty object.
|
||||
pub fn new() -> Object {
|
||||
Object { pairs: vec![] }
|
||||
Object(vec![])
|
||||
}
|
||||
|
||||
/// Add a pair to object.
|
||||
pub fn add(&mut self, pair: Spanned<Pair>) {
|
||||
self.pairs.push(pair);
|
||||
pub fn push(&mut self, pair: Spanned<Pair>) {
|
||||
self.0.push(pair);
|
||||
}
|
||||
|
||||
/// Extract (and remove) a pair with the given key string and matching
|
||||
/// value.
|
||||
/// Extract an argument with the given key if there is any.
|
||||
///
|
||||
/// Inserts an error if the value does not match. If the key is not
|
||||
/// contained, no error is inserted.
|
||||
pub fn get<V: Value>(&mut self, diagnostics: &mut Diagnostics, key: &str) -> Option<V> {
|
||||
let index = self.pairs.iter().position(|pair| pair.v.key.v.as_str() == key)?;
|
||||
self.get_index::<V>(diagnostics, index)
|
||||
}
|
||||
|
||||
/// Extract (and remove) a pair with a matching key and value.
|
||||
///
|
||||
/// Inserts an error if the value does not match. If no matching key is
|
||||
/// found, no error is inserted.
|
||||
pub fn get_with_key<K: Key, V: Value>(
|
||||
&mut self,
|
||||
diagnostics: &mut Diagnostics,
|
||||
) -> Option<(K, V)> {
|
||||
for (index, pair) in self.pairs.iter().enumerate() {
|
||||
let key = Spanned { v: pair.v.key.v.as_str(), span: pair.v.key.span };
|
||||
if let Some(key) = K::parse(key) {
|
||||
return self.get_index::<V>(diagnostics, index).map(|value| (key, value));
|
||||
/// Generates an error if there is a matching key, but the value is of the
|
||||
/// wrong type.
|
||||
pub fn get<V: Value>(&mut self, key: &str, f: &mut Feedback) -> Option<V> {
|
||||
for (i, pair) in self.0.iter().enumerate() {
|
||||
if pair.v.key.v.as_str() == key {
|
||||
let pair = self.0.remove(i);
|
||||
return V::parse(pair.v.value, f);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Extract (and remove) all pairs with matching keys and values.
|
||||
///
|
||||
/// Inserts errors for values that do not match.
|
||||
pub fn get_all<'a, K: Key, V: Value>(
|
||||
&'a mut self,
|
||||
diagnostics: &'a mut Diagnostics,
|
||||
) -> impl Iterator<Item=(K, V)> + 'a {
|
||||
let mut index = 0;
|
||||
/// Extract all key-value pairs where the value is of the given type.
|
||||
pub fn all<'a, V: Value>(&'a mut self)
|
||||
-> impl Iterator<Item = (Spanned<Ident>, V)> + 'a
|
||||
{
|
||||
let mut i = 0;
|
||||
std::iter::from_fn(move || {
|
||||
if index < self.pairs.len() {
|
||||
let key = &self.pairs[index].v.key;
|
||||
let key = Spanned { v: key.v.as_str(), span: key.span };
|
||||
|
||||
Some(if let Some(key) = K::parse(key) {
|
||||
self.get_index::<V>(diagnostics, index).map(|v| (key, v))
|
||||
while i < self.0.len() {
|
||||
let val = V::parse(self.0[i].v.value.clone(), &mut Feedback::new());
|
||||
if let Some(val) = val {
|
||||
let pair = self.0.remove(i);
|
||||
return Some((pair.v.key, val));
|
||||
} else {
|
||||
index += 1;
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}).filter_map(|x| x)
|
||||
}
|
||||
|
||||
/// Extract all key value pairs with span information.
|
||||
///
|
||||
/// The spans are over both key and value, like so:
|
||||
/// ```typst
|
||||
/// { key: value }
|
||||
/// ^^^^^^^^^^
|
||||
/// ```
|
||||
pub fn get_all_spanned<'a, K: Key + 'a, V: Value + 'a>(
|
||||
&'a mut self,
|
||||
diagnostics: &'a mut Diagnostics,
|
||||
) -> impl Iterator<Item=Spanned<(K, V)>> + 'a {
|
||||
self.get_all::<Spanned<K>, Spanned<V>>(diagnostics)
|
||||
.map(|(k, v)| Spanned::new((k.v, v.v), Span::merge(k.span, v.span)))
|
||||
}
|
||||
|
||||
/// Extract the argument at the given index and insert an error if the value
|
||||
/// does not match.
|
||||
fn get_index<V: Value>(&mut self, diagnostics: &mut Diagnostics, index: usize) -> Option<V> {
|
||||
let expr = self.pairs.remove(index).v.value;
|
||||
let span = expr.span;
|
||||
match V::parse(expr) {
|
||||
Ok(output) => Some(output),
|
||||
Err(v) => { diagnostics.push(Spanned { v, span }); None }
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over the pairs of this object.
|
||||
pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, Spanned<Pair>> {
|
||||
self.pairs.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Object {
|
||||
type Item = Spanned<Pair>;
|
||||
type IntoIter = std::vec::IntoIter<Spanned<Pair>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.pairs.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Object {
|
||||
type Item = &'a Spanned<Pair>;
|
||||
type IntoIter = std::slice::Iter<'a, Spanned<Pair>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<Spanned<Pair>> for Object {
|
||||
fn from_iter<I: IntoIterator<Item=Spanned<Pair>>>(iter: I) -> Self {
|
||||
Object { pairs: iter.into_iter().collect() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Object {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.debug_map()
|
||||
.entries(self.pairs.iter().map(|p| (&p.v.key.v, &p.v.value.v)))
|
||||
.entries(self.0.iter().map(|p| (&p.v.key.v, &p.v.value.v)))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
@ -1,169 +0,0 @@
|
||||
//! Key types for identifying keyword arguments.
|
||||
|
||||
use crate::layout::prelude::*;
|
||||
use super::values::AlignmentValue::{self, *};
|
||||
use super::*;
|
||||
|
||||
use self::AxisKey::*;
|
||||
use self::PaddingKey::*;
|
||||
|
||||
/// Key types are used to extract keyword arguments from
|
||||
/// [`Objects`](crate::syntax::expr::Object). They represent the key part of a
|
||||
/// keyword argument.
|
||||
/// ```typst
|
||||
/// [func: key=value]
|
||||
/// ^^^
|
||||
/// ```
|
||||
///
|
||||
/// # Example implementation
|
||||
/// An implementation for the `AxisKey` that identifies layouting axes might
|
||||
/// look as follows:
|
||||
/// ```
|
||||
/// # use typstc::syntax::func::Key;
|
||||
/// # use typstc::syntax::span::Spanned;
|
||||
/// # #[derive(Eq, PartialEq)] enum Axis { Horizontal, Vertical, Primary, Secondary }
|
||||
/// # #[derive(Eq, PartialEq)] enum AxisKey { Specific(Axis), Generic(Axis) }
|
||||
/// # use Axis::*;
|
||||
/// # use AxisKey::*;
|
||||
/// impl Key for AxisKey {
|
||||
/// fn parse(key: Spanned<&str>) -> Option<Self> {
|
||||
/// match key.v {
|
||||
/// "horizontal" | "h" => Some(Specific(Horizontal)),
|
||||
/// "vertical" | "v" => Some(Specific(Vertical)),
|
||||
/// "primary" | "p" => Some(Generic(Primary)),
|
||||
/// "secondary" | "s" => Some(Generic(Secondary)),
|
||||
/// _ => None,
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait Key: Sized + Eq {
|
||||
/// Parse a key string into this type if it is valid for it.
|
||||
fn parse(key: Spanned<&str>) -> Option<Self>;
|
||||
}
|
||||
|
||||
impl Key for String {
|
||||
fn parse(key: Spanned<&str>) -> Option<Self> {
|
||||
Some(key.v.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Key> Key for Spanned<K> {
|
||||
fn parse(key: Spanned<&str>) -> Option<Self> {
|
||||
K::parse(key).map(|v| Spanned { v, span: key.span })
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements [`Key`] for types that just need to match on strings.
|
||||
macro_rules! key {
|
||||
($type:ty, $($($p:pat)|* => $r:expr),* $(,)?) => {
|
||||
impl Key for $type {
|
||||
fn parse(key: Spanned<&str>) -> Option<Self> {
|
||||
match key.v {
|
||||
$($($p)|* => Some($r)),*,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// A key which identifies a layouting axis.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum AxisKey {
|
||||
Generic(GenericAxis),
|
||||
Specific(SpecificAxis),
|
||||
}
|
||||
|
||||
impl AxisKey {
|
||||
/// The generic version of this axis key in the given system of axes.
|
||||
pub fn to_generic(self, axes: LayoutAxes) -> GenericAxis {
|
||||
match self {
|
||||
Generic(axis) => axis,
|
||||
Specific(axis) => axis.to_generic(axes),
|
||||
}
|
||||
}
|
||||
|
||||
/// The specific version of this axis key in the given system of axes.
|
||||
pub fn to_specific(self, axes: LayoutAxes) -> SpecificAxis {
|
||||
match self {
|
||||
Generic(axis) => axis.to_specific(axes),
|
||||
Specific(axis) => axis,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
key!(AxisKey,
|
||||
"horizontal" | "h" => Specific(Horizontal),
|
||||
"vertical" | "v" => Specific(Vertical),
|
||||
"primary" | "p" => Generic(Primary),
|
||||
"secondary" | "s" => Generic(Secondary),
|
||||
);
|
||||
|
||||
/// A key which is equivalent to a [`AxisKey`] but uses typical extent keywords
|
||||
/// instead of axis keywords, e.g. `width` instead of `horizontal`.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct ExtentKey(pub AxisKey);
|
||||
|
||||
key!(ExtentKey,
|
||||
"width" | "w" => ExtentKey(Specific(Horizontal)),
|
||||
"height" | "h" => ExtentKey(Specific(Vertical)),
|
||||
"primary-size" | "ps" => ExtentKey(Generic(Primary)),
|
||||
"secondary-size" | "ss" => ExtentKey(Generic(Secondary)),
|
||||
);
|
||||
|
||||
impl From<ExtentKey> for AxisKey {
|
||||
fn from(key: ExtentKey) -> AxisKey {
|
||||
key.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A key which identifies an axis, but alternatively allows for two positional
|
||||
/// arguments with unspecified axes.
|
||||
///
|
||||
/// This type does not implement `Key` in itself since it cannot be parsed from
|
||||
/// a string. Rather, [`AxisKeys`](AxisKey) and positional arguments should be
|
||||
/// parsed separately and mapped onto this key, as happening in the
|
||||
/// [`PosAxisMap`](super::maps::PosAxisMap).
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum PosAxisKey {
|
||||
/// The first positional argument.
|
||||
First,
|
||||
/// The second positional argument.
|
||||
Second,
|
||||
/// An axis keyword argument.
|
||||
Keyword(AxisKey),
|
||||
}
|
||||
|
||||
/// An argument key which identifies a margin or padding target.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum PaddingKey<Axis> {
|
||||
/// All four sides should have the specified padding.
|
||||
All,
|
||||
/// Both sides of the given axis should have the specified padding.
|
||||
Both(Axis),
|
||||
/// Only the given side of the given axis should have the specified padding.
|
||||
Side(Axis, AlignmentValue),
|
||||
}
|
||||
|
||||
key!(PaddingKey<AxisKey>,
|
||||
"horizontal" | "h" => Both(Specific(Horizontal)),
|
||||
"vertical" | "v" => Both(Specific(Vertical)),
|
||||
"primary" | "p" => Both(Generic(Primary)),
|
||||
"secondary" | "s" => Both(Generic(Secondary)),
|
||||
|
||||
"left" => Side(Specific(Horizontal), Left),
|
||||
"right" => Side(Specific(Horizontal), Right),
|
||||
"top" => Side(Specific(Vertical), Top),
|
||||
"bottom" => Side(Specific(Vertical), Bottom),
|
||||
|
||||
"primary-origin" => Side(Generic(Primary), Align(Origin)),
|
||||
"primary-end" => Side(Generic(Primary), Align(End)),
|
||||
"secondary-origin" => Side(Generic(Secondary), Align(Origin)),
|
||||
"secondary-end" => Side(Generic(Secondary), Align(End)),
|
||||
"horizontal-origin" => Side(Specific(Horizontal), Align(Origin)),
|
||||
"horizontal-end" => Side(Specific(Horizontal), Align(End)),
|
||||
"vertical-origin" => Side(Specific(Vertical), Align(Origin)),
|
||||
"vertical-end" => Side(Specific(Vertical), Align(End)),
|
||||
);
|
@ -1,233 +0,0 @@
|
||||
//! Deduplicating maps and keys for argument parsing.
|
||||
|
||||
use crate::diagnostic::Diagnostics;
|
||||
use crate::geom::Value4;
|
||||
use crate::layout::prelude::*;
|
||||
use crate::length::ScaleLength;
|
||||
use crate::syntax::span::Spanned;
|
||||
use super::keys::*;
|
||||
use super::values::*;
|
||||
use super::*;
|
||||
|
||||
/// A map which deduplicates redundant arguments.
|
||||
///
|
||||
/// Whenever a duplicate argument is inserted into the map, through the
|
||||
/// functions `from_iter`, `insert` or `extend` an diagnostics is added to the error
|
||||
/// list that needs to be passed to those functions.
|
||||
///
|
||||
/// All entries need to have span information to enable the error reporting.
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq)]
|
||||
pub struct DedupMap<K, V> where K: Eq {
|
||||
map: Vec<Spanned<(K, V)>>,
|
||||
}
|
||||
|
||||
impl<K, V> DedupMap<K, V> where K: Eq {
|
||||
/// Create a new deduplicating map.
|
||||
pub fn new() -> DedupMap<K, V> {
|
||||
DedupMap { map: vec![] }
|
||||
}
|
||||
|
||||
/// Create a new map from an iterator of spanned keys and values.
|
||||
pub fn from_iter<I>(diagnostics: &mut Diagnostics, iter: I) -> DedupMap<K, V>
|
||||
where I: IntoIterator<Item=Spanned<(K, V)>> {
|
||||
let mut map = DedupMap::new();
|
||||
map.extend(diagnostics, iter);
|
||||
map
|
||||
}
|
||||
|
||||
/// Add a spanned key-value pair.
|
||||
pub fn insert(&mut self, diagnostics: &mut Diagnostics, entry: Spanned<(K, V)>) {
|
||||
if self.map.iter().any(|e| e.v.0 == entry.v.0) {
|
||||
diagnostics.push(error!(entry.span, "duplicate argument"));
|
||||
} else {
|
||||
self.map.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/// Add multiple spanned key-value pairs.
|
||||
pub fn extend<I>(&mut self, diagnostics: &mut Diagnostics, items: I)
|
||||
where I: IntoIterator<Item=Spanned<(K, V)>> {
|
||||
for item in items.into_iter() {
|
||||
self.insert(diagnostics, item);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the value corresponding to a key if it is present.
|
||||
pub fn get(&self, key: K) -> Option<&V> {
|
||||
self.map.iter().find(|e| e.v.0 == key).map(|e| &e.v.1)
|
||||
}
|
||||
|
||||
/// Get the value and its span corresponding to a key if it is present.
|
||||
pub fn get_spanned(&self, key: K) -> Option<Spanned<&V>> {
|
||||
self.map.iter().find(|e| e.v.0 == key)
|
||||
.map(|e| Spanned { v: &e.v.1, span: e.span })
|
||||
}
|
||||
|
||||
/// Call a function with the value if the key is present.
|
||||
pub fn with<F>(&self, key: K, callback: F) where F: FnOnce(&V) {
|
||||
if let Some(value) = self.get(key) {
|
||||
callback(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new map where keys and values are mapped to new keys and
|
||||
/// values. When the mapping introduces new duplicates, diagnostics are
|
||||
/// generated.
|
||||
pub fn dedup<F, K2, V2>(&self, diagnostics: &mut Diagnostics, mut f: F) -> DedupMap<K2, V2>
|
||||
where F: FnMut(&K, &V) -> (K2, V2), K2: Eq {
|
||||
let mut map = DedupMap::new();
|
||||
|
||||
for Spanned { v: (key, value), span } in self.map.iter() {
|
||||
let (key, value) = f(key, value);
|
||||
map.insert(diagnostics, Spanned { v: (key, value), span: *span });
|
||||
}
|
||||
|
||||
map
|
||||
}
|
||||
|
||||
/// Iterate over the (key, value) pairs.
|
||||
pub fn iter(&self) -> impl Iterator<Item=&(K, V)> {
|
||||
self.map.iter().map(|e| &e.v)
|
||||
}
|
||||
}
|
||||
|
||||
/// A map for storing a value for axes given by keyword arguments.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct AxisMap<V>(DedupMap<AxisKey, V>);
|
||||
|
||||
impl<V: Value> AxisMap<V> {
|
||||
/// Parse an axis map from the object.
|
||||
pub fn parse<K>(
|
||||
diagnostics: &mut Diagnostics,
|
||||
object: &mut Object,
|
||||
) -> AxisMap<V> where K: Key + Into<AxisKey> {
|
||||
let values: Vec<_> = object
|
||||
.get_all_spanned::<K, V>(diagnostics)
|
||||
.map(|s| s.map(|(k, v)| (k.into(), v)))
|
||||
.collect();
|
||||
|
||||
AxisMap(DedupMap::from_iter(diagnostics, values))
|
||||
}
|
||||
|
||||
/// Deduplicate from specific or generic to just specific axes.
|
||||
pub fn dedup(&self, diagnostics: &mut Diagnostics, axes: LayoutAxes) -> DedupMap<SpecificAxis, V>
|
||||
where V: Clone {
|
||||
self.0.dedup(diagnostics, |key, val| (key.to_specific(axes), val.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
/// A map for storing values for axes that are given through a combination of
|
||||
/// (two) positional and keyword arguments.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct PosAxisMap<V>(DedupMap<PosAxisKey, V>);
|
||||
|
||||
impl<V: Value> PosAxisMap<V> {
|
||||
/// Parse a positional/axis map from the function arguments.
|
||||
pub fn parse<K>(
|
||||
diagnostics: &mut Diagnostics,
|
||||
args: &mut FuncArgs,
|
||||
) -> PosAxisMap<V> where K: Key + Into<AxisKey> {
|
||||
let mut map = DedupMap::new();
|
||||
|
||||
for &key in &[PosAxisKey::First, PosAxisKey::Second] {
|
||||
if let Some(Spanned { v, span }) = args.pos.get::<Spanned<V>>(diagnostics) {
|
||||
map.insert(diagnostics, Spanned { v: (key, v), span })
|
||||
}
|
||||
}
|
||||
|
||||
let keywords: Vec<_> = args.key
|
||||
.get_all_spanned::<K, V>(diagnostics)
|
||||
.map(|s| s.map(|(k, v)| (PosAxisKey::Keyword(k.into()), v)))
|
||||
.collect();
|
||||
|
||||
map.extend(diagnostics, keywords);
|
||||
|
||||
PosAxisMap(map)
|
||||
}
|
||||
|
||||
/// Deduplicate from positional arguments and keyword arguments for generic
|
||||
/// or specific axes to just generic axes.
|
||||
pub fn dedup<F>(
|
||||
&self,
|
||||
diagnostics: &mut Diagnostics,
|
||||
axes: LayoutAxes,
|
||||
mut f: F,
|
||||
) -> DedupMap<GenericAxis, V>
|
||||
where
|
||||
F: FnMut(&V) -> Option<GenericAxis>,
|
||||
V: Clone,
|
||||
{
|
||||
self.0.dedup(diagnostics, |key, val| {
|
||||
(match key {
|
||||
PosAxisKey::First => f(val).unwrap_or(GenericAxis::Primary),
|
||||
PosAxisKey::Second => f(val).unwrap_or(GenericAxis::Secondary),
|
||||
PosAxisKey::Keyword(AxisKey::Specific(axis)) => axis.to_generic(axes),
|
||||
PosAxisKey::Keyword(AxisKey::Generic(axis)) => *axis,
|
||||
}, val.clone())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A map for storing padding given for a combination of all sides, opposing
|
||||
/// sides or single sides.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct PaddingMap(DedupMap<PaddingKey<AxisKey>, Option<ScaleLength>>);
|
||||
|
||||
impl PaddingMap {
|
||||
/// Parse a padding map from the function arguments.
|
||||
pub fn parse(diagnostics: &mut Diagnostics, args: &mut FuncArgs) -> PaddingMap {
|
||||
let mut map = DedupMap::new();
|
||||
|
||||
let all = args.key.get::<Spanned<Defaultable<ScaleLength>>>(diagnostics, "margins");
|
||||
if let Some(Spanned { v, span }) = all {
|
||||
map.insert(diagnostics, Spanned { v: (PaddingKey::All, v.into()), span });
|
||||
}
|
||||
|
||||
let paddings: Vec<_> = args.key
|
||||
.get_all_spanned::<PaddingKey<AxisKey>, Defaultable<ScaleLength>>(diagnostics)
|
||||
.map(|s| s.map(|(k, v)| (k, v.into())))
|
||||
.collect();
|
||||
|
||||
map.extend(diagnostics, paddings);
|
||||
|
||||
PaddingMap(map)
|
||||
}
|
||||
|
||||
/// Apply the specified padding on a value box of optional, scalable sizes.
|
||||
pub fn apply(
|
||||
&self,
|
||||
diagnostics: &mut Diagnostics,
|
||||
axes: LayoutAxes,
|
||||
padding: &mut Value4<Option<ScaleLength>>
|
||||
) {
|
||||
use PaddingKey::*;
|
||||
|
||||
let map = self.0.dedup(diagnostics, |key, &val| {
|
||||
(match key {
|
||||
All => All,
|
||||
Both(axis) => Both(axis.to_specific(axes)),
|
||||
Side(axis, alignment) => {
|
||||
let generic = axis.to_generic(axes);
|
||||
let axis = axis.to_specific(axes);
|
||||
Side(axis, alignment.to_specific(axes, generic))
|
||||
}
|
||||
}, val)
|
||||
});
|
||||
|
||||
map.with(All, |&val| padding.set_all(val));
|
||||
map.with(Both(Horizontal), |&val| padding.set_horizontal(val));
|
||||
map.with(Both(Vertical), |&val| padding.set_vertical(val));
|
||||
|
||||
for &(key, val) in map.iter() {
|
||||
if let Side(_, alignment) = key {
|
||||
match alignment {
|
||||
AlignmentValue::Left => padding.left = val,
|
||||
AlignmentValue::Right => padding.right = val,
|
||||
AlignmentValue::Top => padding.top = val,
|
||||
AlignmentValue::Bottom => padding.bottom = val,
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
//! Primitives for argument parsing in library functions.
|
||||
|
||||
use std::iter::FromIterator;
|
||||
use crate::diagnostic::{Diagnostic, Diagnostics};
|
||||
use super::expr::{Expr, Ident, Tuple, Object, Pair};
|
||||
use super::span::{Span, Spanned};
|
||||
|
||||
pub_use_mod!(maps);
|
||||
pub_use_mod!(keys);
|
||||
pub_use_mod!(values);
|
||||
|
||||
/// An invocation of a function.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FuncCall<'s> {
|
||||
pub header: FuncHeader,
|
||||
/// The body as a raw string containing what's inside of the brackets.
|
||||
pub body: Option<Spanned<&'s str>>,
|
||||
}
|
||||
|
||||
/// The parsed header of a function (everything in the first set of brackets).
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FuncHeader {
|
||||
pub name: Spanned<Ident>,
|
||||
pub args: FuncArgs,
|
||||
}
|
||||
|
||||
/// The positional and keyword arguments passed to a function.
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct FuncArgs {
|
||||
/// The positional arguments.
|
||||
pub pos: Tuple,
|
||||
/// They keyword arguments.
|
||||
pub key: Object,
|
||||
}
|
||||
|
||||
impl FuncArgs {
|
||||
/// Create new empty function arguments.
|
||||
pub fn new() -> FuncArgs {
|
||||
FuncArgs {
|
||||
pos: Tuple::new(),
|
||||
key: Object::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an argument.
|
||||
pub fn add(&mut self, arg: Spanned<FuncArg>) {
|
||||
match arg.v {
|
||||
FuncArg::Pos(item) => self.pos.add(Spanned::new(item, arg.span)),
|
||||
FuncArg::Key(pair) => self.key.add(Spanned::new(pair, arg.span)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over all arguments.
|
||||
pub fn into_iter(self) -> impl Iterator<Item=Spanned<FuncArg>> {
|
||||
let pos = self.pos.items.into_iter()
|
||||
.map(|spanned| spanned.map(|item| FuncArg::Pos(item)));
|
||||
|
||||
let key = self.key.pairs.into_iter()
|
||||
.map(|spanned| spanned.map(|pair| FuncArg::Key(pair)));
|
||||
|
||||
pos.chain(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<Spanned<FuncArg>> for FuncArgs {
|
||||
fn from_iter<I: IntoIterator<Item=Spanned<FuncArg>>>(iter: I) -> Self {
|
||||
let mut args = FuncArgs::new();
|
||||
for item in iter.into_iter() {
|
||||
args.add(item);
|
||||
}
|
||||
args
|
||||
}
|
||||
}
|
||||
|
||||
/// Either a positional or keyword argument.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum FuncArg {
|
||||
/// A positional argument.
|
||||
Pos(Expr),
|
||||
/// A keyword argument.
|
||||
Key(Pair),
|
||||
}
|
||||
|
||||
/// Extra methods on [`Options`](Option) used for argument parsing.
|
||||
pub trait OptionExt<T>: Sized {
|
||||
/// Calls `f` with `val` if this is `Some(val)`.
|
||||
fn with(self, f: impl FnOnce(T));
|
||||
|
||||
/// Add an error about a missing argument `arg` with the given span if the
|
||||
/// option is `None`.
|
||||
fn or_missing(self, diagnostics: &mut Diagnostics, span: Span, arg: &str) -> Self;
|
||||
}
|
||||
|
||||
impl<T> OptionExt<T> for Option<T> {
|
||||
fn with(self, f: impl FnOnce(T)) {
|
||||
if let Some(val) = self {
|
||||
f(val);
|
||||
}
|
||||
}
|
||||
|
||||
fn or_missing(self, diagnostics: &mut Diagnostics, span: Span, arg: &str) -> Self {
|
||||
if self.is_none() {
|
||||
diagnostics.push(error!(span, "missing argument: {}", arg));
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
@ -1,301 +0,0 @@
|
||||
//! Value types for extracting function arguments.
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use fontdock::{FontStyle, FontWeight, FontWidth};
|
||||
|
||||
use crate::layout::prelude::*;
|
||||
use crate::length::{Length, ScaleLength};
|
||||
use crate::paper::Paper;
|
||||
use super::*;
|
||||
|
||||
use self::AlignmentValue::*;
|
||||
|
||||
/// Value types are used to extract the values of positional and keyword
|
||||
/// arguments from [`Tuples`](crate::syntax::expr::Tuple) and
|
||||
/// [`Objects`](crate::syntax::expr::Object). They represent the value part of
|
||||
/// an argument.
|
||||
/// ```typst
|
||||
/// [func: value, key=value]
|
||||
/// ^^^^^ ^^^^^
|
||||
/// ```
|
||||
///
|
||||
/// # Example implementation
|
||||
/// An implementation for `bool` might look as follows:
|
||||
/// ```
|
||||
/// # use typstc::error;
|
||||
/// # use typstc::diagnostic::Diagnostic;
|
||||
/// # use typstc::syntax::expr::Expr;
|
||||
/// # use typstc::syntax::func::Value;
|
||||
/// # use typstc::syntax::span::Spanned;
|
||||
/// # struct Bool; /*
|
||||
/// impl Value for bool {
|
||||
/// # */ impl Value for Bool {
|
||||
/// fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
|
||||
/// match expr.v {
|
||||
/// # /*
|
||||
/// Expr::Bool(b) => Ok(b),
|
||||
/// # */ Expr::Bool(_) => Ok(Bool),
|
||||
/// other => Err(error!("expected bool, found {}", other.name())),
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait Value: Sized {
|
||||
/// Parse an expression into this value or return an error if the expression
|
||||
/// is valid for this value type.
|
||||
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic>;
|
||||
}
|
||||
|
||||
impl<V: Value> Value for Spanned<V> {
|
||||
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
|
||||
let span = expr.span;
|
||||
V::parse(expr).map(|v| Spanned { v, span })
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements [`Value`] for types that just need to match on expressions.
|
||||
macro_rules! value {
|
||||
($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => {
|
||||
impl Value for $type {
|
||||
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
|
||||
#[allow(unreachable_patterns)]
|
||||
match expr.v {
|
||||
$($p => Ok($r)),*,
|
||||
other => Err(
|
||||
error!("expected {}, found {}", $name, other.name())
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
value!(Expr, "expression", e => e);
|
||||
|
||||
value!(Ident, "identifier", Expr::Ident(i) => i);
|
||||
value!(String, "string", Expr::Str(s) => s);
|
||||
value!(f64, "number", Expr::Number(n) => n);
|
||||
value!(bool, "bool", Expr::Bool(b) => b);
|
||||
value!(Length, "length", Expr::Length(l) => l);
|
||||
value!(Tuple, "tuple", Expr::Tuple(t) => t);
|
||||
value!(Object, "object", Expr::Object(o) => o);
|
||||
|
||||
value!(ScaleLength, "number or length",
|
||||
Expr::Length(length) => ScaleLength::Absolute(length),
|
||||
Expr::Number(scale) => ScaleLength::Scaled(scale),
|
||||
);
|
||||
|
||||
/// A value type that matches [`Expr::Ident`] and [`Expr::Str`] and implements
|
||||
/// `Into<String>`.
|
||||
pub struct StringLike(pub String);
|
||||
|
||||
value!(StringLike, "identifier or string",
|
||||
Expr::Ident(Ident(s)) => StringLike(s),
|
||||
Expr::Str(s) => StringLike(s),
|
||||
);
|
||||
|
||||
impl From<StringLike> for String {
|
||||
fn from(like: StringLike) -> String {
|
||||
like.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A value type that matches the identifier `default` or a value type `V` and
|
||||
/// implements `Into<Option>` yielding `Option::Some(V)` for a value and
|
||||
/// `Option::None` for `default`.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use typstc::syntax::func::{FuncArgs, Defaultable};
|
||||
/// # use typstc::length::Length;
|
||||
/// # let mut args = FuncArgs::new();
|
||||
/// # let mut errors = vec![];
|
||||
/// args.key.get::<Defaultable<Length>>(&mut errors, "length");
|
||||
/// ```
|
||||
/// This will yield.
|
||||
/// ```typst
|
||||
/// [func: length=default] => None
|
||||
/// [func: length=2cm] => Some(Length::cm(2.0))
|
||||
/// ```
|
||||
pub struct Defaultable<V>(pub Option<V>);
|
||||
|
||||
impl<V: Value> Value for Defaultable<V> {
|
||||
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
|
||||
Ok(Defaultable(match expr.v {
|
||||
Expr::Ident(ident) if ident.as_str() == "default" => None,
|
||||
_ => Some(V::parse(expr)?)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> From<Defaultable<V>> for Option<V> {
|
||||
fn from(defaultable: Defaultable<V>) -> Option<V> {
|
||||
defaultable.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Value for FontStyle {
|
||||
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
|
||||
FontStyle::from_name(Ident::parse(expr)?.as_str())
|
||||
.ok_or_else(|| error!("invalid font style"))
|
||||
}
|
||||
}
|
||||
|
||||
/// The additional boolean specifies whether a number was clamped into the range
|
||||
/// 100 - 900 to make it a valid font weight.
|
||||
impl Value for FontWeight {
|
||||
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
|
||||
match expr.v {
|
||||
Expr::Number(weight) => {
|
||||
let weight = weight.round();
|
||||
if weight >= 100.0 && weight <= 900.0 {
|
||||
Ok(FontWeight(weight as u16))
|
||||
} else {
|
||||
let clamped = weight.min(900.0).max(100.0);
|
||||
Ok(FontWeight(clamped as u16))
|
||||
}
|
||||
}
|
||||
Expr::Ident(id) => {
|
||||
FontWeight::from_name(id.as_str())
|
||||
.ok_or_else(|| error!("invalid font weight"))
|
||||
}
|
||||
other => Err(
|
||||
error!("expected identifier or number, found {}", other.name())
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The additional boolean specifies whether a number was clamped into the range
|
||||
/// 1 - 9 to make it a valid font width.
|
||||
impl Value for FontWidth {
|
||||
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
|
||||
match expr.v {
|
||||
Expr::Number(width) => {
|
||||
let width = width.round();
|
||||
if width >= 1.0 && width <= 9.0 {
|
||||
Ok(FontWidth::new(width as u16).unwrap())
|
||||
} else {
|
||||
let clamped = width.min(9.0).max(1.0);
|
||||
Ok(FontWidth::new(clamped as u16).unwrap())
|
||||
}
|
||||
}
|
||||
Expr::Ident(id) => {
|
||||
FontWidth::from_name(id.as_str())
|
||||
.ok_or_else(|| error!("invalid font width"))
|
||||
}
|
||||
other => Err(
|
||||
error!("expected identifier or number, found {}", other.name())
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Value for Paper {
|
||||
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
|
||||
Paper::from_name(Ident::parse(expr)?.as_str())
|
||||
.ok_or_else(|| error!("invalid paper type"))
|
||||
}
|
||||
}
|
||||
|
||||
impl Value for Direction {
|
||||
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
|
||||
Ok(match Ident::parse(expr)?.as_str() {
|
||||
"left-to-right" | "ltr" | "LTR" => LeftToRight,
|
||||
"right-to-left" | "rtl" | "RTL" => RightToLeft,
|
||||
"top-to-bottom" | "ttb" | "TTB" => TopToBottom,
|
||||
"bottom-to-top" | "btt" | "BTT" => BottomToTop,
|
||||
_ => return Err(error!("invalid direction"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A value type that matches identifiers that are valid alignments like
|
||||
/// `origin` or `right`.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum AlignmentValue {
|
||||
/// A generic alignment.
|
||||
Align(Alignment),
|
||||
Left,
|
||||
Top,
|
||||
Right,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
impl AlignmentValue {
|
||||
/// The specific axis this alignment corresponds to. `None` if the alignment
|
||||
/// is generic.
|
||||
pub fn axis(self) -> Option<SpecificAxis> {
|
||||
match self {
|
||||
Left | Right => Some(Horizontal),
|
||||
Top | Bottom => Some(Vertical),
|
||||
Align(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The generic version of this alignment on the given axis in the given
|
||||
/// system of layouting axes.
|
||||
///
|
||||
/// Returns `None` if the alignment is invalid for the given axis.
|
||||
pub fn to_generic(self, axes: LayoutAxes, axis: GenericAxis) -> Option<Alignment> {
|
||||
let specific = axis.to_specific(axes);
|
||||
let positive = axes.get(axis).is_positive();
|
||||
|
||||
// The alignment matching the origin of the positive coordinate direction.
|
||||
let start = if positive { Origin } else { End };
|
||||
|
||||
match (self, specific) {
|
||||
(Align(alignment), _) => Some(alignment),
|
||||
(Left, Horizontal) | (Top, Vertical) => Some(start),
|
||||
(Right, Horizontal) | (Bottom, Vertical) => Some(start.inv()),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
/// The specific version of this alignment on the given axis in the given
|
||||
/// system of layouting axes.
|
||||
pub fn to_specific(self, axes: LayoutAxes, axis: GenericAxis) -> AlignmentValue {
|
||||
let direction = axes.get(axis);
|
||||
if let Align(alignment) = self {
|
||||
match (direction, alignment) {
|
||||
(LeftToRight, Origin) | (RightToLeft, End) => Left,
|
||||
(LeftToRight, End) | (RightToLeft, Origin) => Right,
|
||||
(TopToBottom, Origin) | (BottomToTop, End) => Top,
|
||||
(TopToBottom, End) | (BottomToTop, Origin) => Bottom,
|
||||
(_, Center) => self,
|
||||
}
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Value for AlignmentValue {
|
||||
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
|
||||
Ok(match Ident::parse(expr)?.as_str() {
|
||||
"origin" => Align(Origin),
|
||||
"center" => Align(Center),
|
||||
"end" => Align(End),
|
||||
"left" => Left,
|
||||
"top" => Top,
|
||||
"right" => Right,
|
||||
"bottom" => Bottom,
|
||||
_ => return Err(error!("invalid alignment"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for AlignmentValue {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Align(Origin) => write!(f, "origin"),
|
||||
Align(Center) => write!(f, "center"),
|
||||
Align(End) => write!(f, "end"),
|
||||
Left => write!(f, "left"),
|
||||
Top => write!(f, "top"),
|
||||
Right => write!(f, "right"),
|
||||
Bottom => write!(f, "bottom"),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,183 +1,14 @@
|
||||
//! Syntax models, parsing and tokenization.
|
||||
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
use async_trait::async_trait;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{Pass, Feedback};
|
||||
use crate::layout::{LayoutContext, Commands, Command};
|
||||
use self::span::{Spanned, SpanVec};
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
mod test;
|
||||
|
||||
pub mod decoration;
|
||||
pub mod expr;
|
||||
pub mod func;
|
||||
pub mod model;
|
||||
pub mod parsing;
|
||||
pub mod span;
|
||||
pub_use_mod!(scope);
|
||||
pub_use_mod!(parsing);
|
||||
pub_use_mod!(tokens);
|
||||
|
||||
/// Represents a parsed piece of source that can be layouted and in the future
|
||||
/// also be queried for information used for refactorings, autocomplete, etc.
|
||||
#[async_trait(?Send)]
|
||||
pub trait Model: Debug + ModelBounds {
|
||||
/// Layout the model into a sequence of commands processed by a
|
||||
/// [`ModelLayouter`](crate::layout::ModelLayouter).
|
||||
async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass<Commands<'a>>;
|
||||
}
|
||||
|
||||
/// A tree representation of source code.
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct SyntaxModel {
|
||||
/// The syntactical elements making up this model.
|
||||
pub nodes: SpanVec<Node>,
|
||||
}
|
||||
|
||||
impl SyntaxModel {
|
||||
/// Create an empty syntax model.
|
||||
pub fn new() -> SyntaxModel {
|
||||
SyntaxModel { nodes: vec![] }
|
||||
}
|
||||
|
||||
/// Add a node to the model.
|
||||
pub fn add(&mut self, node: Spanned<Node>) {
|
||||
self.nodes.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl Model for SyntaxModel {
|
||||
async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass<Commands<'a>> {
|
||||
Pass::new(vec![Command::LayoutSyntaxModel(self)], Feedback::new())
|
||||
}
|
||||
}
|
||||
|
||||
/// A node in the [syntax model](SyntaxModel).
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Node {
|
||||
/// Whitespace containing less than two newlines.
|
||||
Space,
|
||||
/// Whitespace with more than two newlines.
|
||||
Parbreak,
|
||||
/// A forced line break.
|
||||
Linebreak,
|
||||
/// Plain text.
|
||||
Text(String),
|
||||
/// Lines of raw text.
|
||||
Raw(Vec<String>),
|
||||
/// Italics were enabled / disabled.
|
||||
ToggleItalic,
|
||||
/// Bolder was enabled / disabled.
|
||||
ToggleBolder,
|
||||
/// A submodel, typically a function invocation.
|
||||
Model(Box<dyn Model>),
|
||||
}
|
||||
|
||||
impl PartialEq for Node {
|
||||
fn eq(&self, other: &Node) -> bool {
|
||||
use Node::*;
|
||||
match (self, other) {
|
||||
(Space, Space) => true,
|
||||
(Parbreak, Parbreak) => true,
|
||||
(Linebreak, Linebreak) => true,
|
||||
(Text(a), Text(b)) => a == b,
|
||||
(Raw(a), Raw(b)) => a == b,
|
||||
(ToggleItalic, ToggleItalic) => true,
|
||||
(ToggleBolder, ToggleBolder) => true,
|
||||
(Model(a), Model(b)) => a == b,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A list of spanned decorations.
|
||||
pub type Decorations = SpanVec<Decoration>;
|
||||
|
||||
/// Decorations for semantic syntax highlighting.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Decoration {
|
||||
/// A valid function name:
|
||||
/// ```typst
|
||||
/// [box]
|
||||
/// ^^^
|
||||
/// ```
|
||||
ValidFuncName,
|
||||
/// An invalid function name:
|
||||
/// ```typst
|
||||
/// [blabla]
|
||||
/// ^^^^^^
|
||||
/// ```
|
||||
InvalidFuncName,
|
||||
/// A key of a keyword argument:
|
||||
/// ```typst
|
||||
/// [box: width=5cm]
|
||||
/// ^^^^^
|
||||
/// ```
|
||||
ArgumentKey,
|
||||
/// A key in an object.
|
||||
/// ```typst
|
||||
/// [box: padding={ left: 1cm, right: 2cm}]
|
||||
/// ^^^^ ^^^^^
|
||||
/// ```
|
||||
ObjectKey,
|
||||
/// An italic word.
|
||||
Italic,
|
||||
/// A bold word.
|
||||
Bold,
|
||||
}
|
||||
|
||||
impl dyn Model {
|
||||
/// Downcast this model to a concrete type implementing [`Model`].
|
||||
pub fn downcast<T>(&self) -> Option<&T> where T: Model + 'static {
|
||||
self.as_any().downcast_ref::<T>()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for dyn Model {
|
||||
fn eq(&self, other: &dyn Model) -> bool {
|
||||
self.bound_eq(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Box<dyn Model> {
|
||||
fn clone(&self) -> Self {
|
||||
self.bound_clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait describes bounds necessary for types implementing [`Model`]. It is
|
||||
/// automatically implemented for all types that are [`Model`], [`PartialEq`],
|
||||
/// [`Clone`] and `'static`.
|
||||
///
|
||||
/// It is necessary to make models comparable and clonable.
|
||||
pub trait ModelBounds {
|
||||
/// Convert into a `dyn Any`.
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
/// Check for equality with another model.
|
||||
fn bound_eq(&self, other: &dyn Model) -> bool;
|
||||
|
||||
/// Clone into a boxed model trait object.
|
||||
fn bound_clone(&self) -> Box<dyn Model>;
|
||||
}
|
||||
|
||||
impl<T> ModelBounds for T where T: Model + PartialEq + Clone + 'static {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn bound_eq(&self, other: &dyn Model) -> bool {
|
||||
match other.as_any().downcast_ref::<Self>() {
|
||||
Some(other) => self == other,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn bound_clone(&self) -> Box<dyn Model> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
pub mod scope;
|
||||
pub mod tokens;
|
||||
pub mod value;
|
||||
|
134
src/syntax/model.rs
Normal file
134
src/syntax/model.rs
Normal file
@ -0,0 +1,134 @@
|
||||
//! The syntax model.
|
||||
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::{Pass, Feedback};
|
||||
use crate::layout::{LayoutContext, Commands, Command};
|
||||
use super::span::{Spanned, SpanVec};
|
||||
|
||||
/// Represents a parsed piece of source that can be layouted and in the future
|
||||
/// also be queried for information used for refactorings, autocomplete, etc.
|
||||
#[async_trait(?Send)]
|
||||
pub trait Model: Debug + ModelBounds {
|
||||
/// Layout the model into a sequence of commands processed by a
|
||||
/// [`ModelLayouter`](crate::layout::ModelLayouter).
|
||||
async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass<Commands<'a>>;
|
||||
}
|
||||
|
||||
/// A tree representation of source code.
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct SyntaxModel {
|
||||
/// The syntactical elements making up this model.
|
||||
pub nodes: SpanVec<Node>,
|
||||
}
|
||||
|
||||
impl SyntaxModel {
|
||||
/// Create an empty syntax model.
|
||||
pub fn new() -> SyntaxModel {
|
||||
SyntaxModel { nodes: vec![] }
|
||||
}
|
||||
|
||||
/// Add a node to the model.
|
||||
pub fn add(&mut self, node: Spanned<Node>) {
|
||||
self.nodes.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl Model for SyntaxModel {
|
||||
async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass<Commands<'a>> {
|
||||
Pass::new(vec![Command::LayoutSyntaxModel(self)], Feedback::new())
|
||||
}
|
||||
}
|
||||
|
||||
/// A node in the [syntax model](SyntaxModel).
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Node {
|
||||
/// Whitespace containing less than two newlines.
|
||||
Space,
|
||||
/// Whitespace with more than two newlines.
|
||||
Parbreak,
|
||||
/// A forced line break.
|
||||
Linebreak,
|
||||
/// Plain text.
|
||||
Text(String),
|
||||
/// Lines of raw text.
|
||||
Raw(Vec<String>),
|
||||
/// Italics were enabled / disabled.
|
||||
ToggleItalic,
|
||||
/// Bolder was enabled / disabled.
|
||||
ToggleBolder,
|
||||
/// A submodel, typically a function invocation.
|
||||
Model(Box<dyn Model>),
|
||||
}
|
||||
|
||||
impl PartialEq for Node {
|
||||
fn eq(&self, other: &Node) -> bool {
|
||||
use Node::*;
|
||||
match (self, other) {
|
||||
(Space, Space) => true,
|
||||
(Parbreak, Parbreak) => true,
|
||||
(Linebreak, Linebreak) => true,
|
||||
(Text(a), Text(b)) => a == b,
|
||||
(Raw(a), Raw(b)) => a == b,
|
||||
(ToggleItalic, ToggleItalic) => true,
|
||||
(ToggleBolder, ToggleBolder) => true,
|
||||
(Model(a), Model(b)) => a == b,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl dyn Model {
|
||||
/// Downcast this model to a concrete type implementing [`Model`].
|
||||
pub fn downcast<T>(&self) -> Option<&T> where T: Model + 'static {
|
||||
self.as_any().downcast_ref::<T>()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for dyn Model {
|
||||
fn eq(&self, other: &dyn Model) -> bool {
|
||||
self.bound_eq(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Box<dyn Model> {
|
||||
fn clone(&self) -> Self {
|
||||
self.bound_clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait describes bounds necessary for types implementing [`Model`]. It is
|
||||
/// automatically implemented for all types that are [`Model`], [`PartialEq`],
|
||||
/// [`Clone`] and `'static`.
|
||||
///
|
||||
/// It is necessary to make models comparable and clonable.
|
||||
pub trait ModelBounds {
|
||||
/// Convert into a `dyn Any`.
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
/// Check for equality with another model.
|
||||
fn bound_eq(&self, other: &dyn Model) -> bool;
|
||||
|
||||
/// Clone into a boxed model trait object.
|
||||
fn bound_clone(&self) -> Box<dyn Model>;
|
||||
}
|
||||
|
||||
impl<T> ModelBounds for T where T: Model + PartialEq + Clone + 'static {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn bound_eq(&self, other: &dyn Model) -> bool {
|
||||
match other.as_any().downcast_ref::<Self>() {
|
||||
Some(other) => self == other,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn bound_clone(&self) -> Box<dyn Model> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
@ -2,14 +2,66 @@
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::{Pass, Feedback};
|
||||
use super::decoration::Decoration;
|
||||
use super::expr::*;
|
||||
use super::func::{FuncCall, FuncHeader, FuncArgs, FuncArg};
|
||||
use super::scope::Scope;
|
||||
use super::span::{Pos, Span, Spanned};
|
||||
use super::*;
|
||||
use super::tokens::{is_newline_char, Token, Tokens, TokenMode};
|
||||
use super::model::{SyntaxModel, Node, Model};
|
||||
|
||||
/// A function which parses a function call into a model.
|
||||
pub type CallParser = dyn Fn(FuncCall, &ParseState) -> Pass<Box<dyn Model>>;
|
||||
|
||||
/// An invocation of a function.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FuncCall<'s> {
|
||||
pub header: FuncHeader,
|
||||
/// The body as a raw string containing what's inside of the brackets.
|
||||
pub body: Option<Spanned<&'s str>>,
|
||||
}
|
||||
|
||||
/// The parsed header of a function (everything in the first set of brackets).
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FuncHeader {
|
||||
pub name: Spanned<Ident>,
|
||||
pub args: FuncArgs,
|
||||
}
|
||||
|
||||
/// The positional and keyword arguments passed to a function.
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct FuncArgs {
|
||||
pub pos: Tuple,
|
||||
pub key: Object,
|
||||
}
|
||||
|
||||
impl FuncArgs {
|
||||
/// Create new empty function arguments.
|
||||
pub fn new() -> FuncArgs {
|
||||
FuncArgs {
|
||||
pos: Tuple::new(),
|
||||
key: Object::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an argument.
|
||||
pub fn push(&mut self, arg: Spanned<FuncArg>) {
|
||||
match arg.v {
|
||||
FuncArg::Pos(item) => self.pos.push(Spanned::new(item, arg.span)),
|
||||
FuncArg::Key(pair) => self.key.push(Spanned::new(pair, arg.span)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Either a positional or keyword argument.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum FuncArg {
|
||||
/// A positional argument.
|
||||
Pos(Expr),
|
||||
/// A keyword argument.
|
||||
Key(Pair),
|
||||
}
|
||||
|
||||
/// The state which can influence how a string of source code is parsed.
|
||||
///
|
||||
/// Parsing is pure - when passed in the same state and source code, the output
|
||||
@ -216,7 +268,7 @@ impl<'s> FuncParser<'s> {
|
||||
};
|
||||
|
||||
let behind_arg = arg.span.end;
|
||||
args.add(arg);
|
||||
args.push(arg);
|
||||
|
||||
self.skip_white();
|
||||
if self.eof() {
|
||||
@ -348,7 +400,7 @@ impl FuncParser<'_> {
|
||||
let (tuple, coercable) = self.parse_tuple();
|
||||
Some(if coercable {
|
||||
tuple.map(|v| {
|
||||
v.into_iter().next().expect("tuple is coercable").v
|
||||
v.0.into_iter().next().expect("tuple is coercable").v
|
||||
})
|
||||
} else {
|
||||
tuple.map(|tup| Expr::Tuple(tup))
|
||||
@ -388,7 +440,7 @@ impl FuncParser<'_> {
|
||||
});
|
||||
|
||||
let behind_expr = expr.span.end;
|
||||
tuple.add(expr);
|
||||
tuple.push(expr);
|
||||
|
||||
self.skip_white();
|
||||
if self.eof() || self.check(Token::RightParen) {
|
||||
@ -401,7 +453,7 @@ impl FuncParser<'_> {
|
||||
|
||||
self.expect(Token::RightParen);
|
||||
let end = self.pos();
|
||||
let coercable = commaless && !tuple.items.is_empty();
|
||||
let coercable = commaless && !tuple.0.is_empty();
|
||||
|
||||
(Spanned::new(tuple, Span::new(start, end)), coercable)
|
||||
}
|
||||
@ -440,7 +492,7 @@ impl FuncParser<'_> {
|
||||
|
||||
let behind_value = value.span.end;
|
||||
let span = Span::merge(key.span, value.span);
|
||||
object.add(Spanned::new(Pair { key, value }, span));
|
||||
object.push(Spanned::new(Pair { key, value }, span));
|
||||
|
||||
self.skip_white();
|
||||
if self.eof() || self.check(Token::RightBrace) {
|
||||
@ -611,7 +663,6 @@ fn unescape_raw(raw: &str) -> Vec<String> {
|
||||
mod tests {
|
||||
use crate::length::Length;
|
||||
use super::super::test::{check, DebugFn};
|
||||
use super::super::func::Value;
|
||||
use super::*;
|
||||
|
||||
use Decoration::*;
|
||||
@ -682,7 +733,7 @@ mod tests {
|
||||
|
||||
macro_rules! tuple {
|
||||
($($tts:tt)*) => {
|
||||
Expr::Tuple(Tuple { items: span_vec![$($tts)*].0 })
|
||||
Expr::Tuple(Tuple(span_vec![$($tts)*].0))
|
||||
};
|
||||
}
|
||||
|
||||
@ -690,19 +741,17 @@ mod tests {
|
||||
($name:tt $(, $($tts:tt)*)?) => {
|
||||
Expr::NamedTuple(NamedTuple::new(
|
||||
span_item!($name).map(|n| Ident(n.to_string())),
|
||||
Z(Tuple { items: span_vec![$($($tts)*)?].0 })
|
||||
Z(Tuple(span_vec![$($($tts)*)?].0))
|
||||
))
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! object {
|
||||
($($key:tt => $value:expr),* $(,)?) => {
|
||||
Expr::Object(Object {
|
||||
pairs: vec![$(Z(Pair {
|
||||
Expr::Object(Object(vec![$(Z(Pair {
|
||||
key: span_item!($key).map(|k| Ident(k.to_string())),
|
||||
value: Z($value),
|
||||
})),*]
|
||||
})
|
||||
})),*]))
|
||||
};
|
||||
}
|
||||
|
||||
@ -713,11 +762,22 @@ mod tests {
|
||||
}
|
||||
|
||||
macro_rules! func {
|
||||
($name:tt $(: ($($pos:tt)*) $(, { $($key:tt)* })? )? $(; $($body:tt)*)?) => {{
|
||||
($name:tt
|
||||
$(: ($($pos:tt)*) $(, { $($key:tt => $value:expr),* })? )?
|
||||
$(; $($body:tt)*)?
|
||||
) => {{
|
||||
#[allow(unused_mut)]
|
||||
let mut args = FuncArgs::new();
|
||||
$(args.pos = Tuple::parse(Z(tuple!($($pos)*))).unwrap();
|
||||
$(args.key = Object::parse(Z(object! { $($key)* })).unwrap();)?)?
|
||||
$(
|
||||
let items: Vec<Spanned<Expr>> = span_vec![$($pos)*].0;
|
||||
for item in items {
|
||||
args.push(item.map(|v| FuncArg::Pos(v)));
|
||||
}
|
||||
$($(args.push(Z(FuncArg::Key(Pair {
|
||||
key: span_item!($key).map(|k| Ident(k.to_string())),
|
||||
value: Z($value),
|
||||
})));)*)?
|
||||
)?
|
||||
Node::Model(Box::new(DebugFn {
|
||||
header: FuncHeader {
|
||||
name: span_item!($name).map(|s| Ident(s.to_string())),
|
||||
@ -1101,7 +1161,7 @@ mod tests {
|
||||
[func!("val": (Len(Length::pt(12.0))), { "key" => Id("value") })], [],
|
||||
[(0:12, 0:15, ArgumentKey), (0:1, 0:4, ValidFuncName)],
|
||||
);
|
||||
pval!("a , x=\"b\" , c" => (Id("a"), Id("c")), { "x" => Str("b"), });
|
||||
pval!("a , x=\"b\" , c" => (Id("a"), Id("c")), { "x" => Str("b") });
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -5,7 +5,7 @@ use std::fmt::{self, Debug, Formatter};
|
||||
|
||||
use crate::func::ParseFunc;
|
||||
use super::parsing::CallParser;
|
||||
use super::Model;
|
||||
use super::model::Model;
|
||||
|
||||
/// A map from identifiers to function parsers.
|
||||
pub struct Scope {
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! Spans map elements to the part of source code they originate from.
|
||||
//! Mapping of values to the locations they originate from in source code.
|
||||
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::ops::{Add, Sub};
|
||||
|
@ -1,9 +1,11 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::func::FuncHeader;
|
||||
use super::decoration::Decoration;
|
||||
use super::expr::{Expr, Ident, Tuple, NamedTuple, Object, Pair};
|
||||
use super::parsing::{FuncHeader, FuncArgs, FuncArg};
|
||||
use super::span::Spanned;
|
||||
use super::expr::{Expr, Tuple, NamedTuple, Object};
|
||||
use super::*;
|
||||
use super::tokens::Token;
|
||||
use super::model::{SyntaxModel, Model, Node};
|
||||
|
||||
/// Check whether the expected and found results are the same.
|
||||
pub fn check<T>(src: &str, exp: T, found: T, cmp_spans: bool)
|
||||
@ -65,8 +67,8 @@ function! {
|
||||
|
||||
parse(header, body, state, f) {
|
||||
let cloned = header.clone();
|
||||
header.args.pos.items.clear();
|
||||
header.args.key.pairs.clear();
|
||||
header.args.pos.0.clear();
|
||||
header.args.key.0.clear();
|
||||
DebugFn {
|
||||
header: cloned,
|
||||
body: body!(opt: body, state, f),
|
||||
@ -104,9 +106,31 @@ impl SpanlessEq for Node {
|
||||
|
||||
impl SpanlessEq for DebugFn {
|
||||
fn spanless_eq(&self, other: &DebugFn) -> bool {
|
||||
self.header.name.v == other.header.name.v
|
||||
&& self.header.args.pos.spanless_eq(&other.header.args.pos)
|
||||
&& self.header.args.key.spanless_eq(&other.header.args.key)
|
||||
self.header.spanless_eq(&other.header)
|
||||
&& self.body.spanless_eq(&other.body)
|
||||
}
|
||||
}
|
||||
|
||||
impl SpanlessEq for FuncHeader {
|
||||
fn spanless_eq(&self, other: &Self) -> bool {
|
||||
self.name.spanless_eq(&other.name) && self.args.spanless_eq(&other.args)
|
||||
}
|
||||
}
|
||||
|
||||
impl SpanlessEq for FuncArgs {
|
||||
fn spanless_eq(&self, other: &Self) -> bool {
|
||||
self.key.spanless_eq(&other.key)
|
||||
&& self.pos.spanless_eq(&other.pos)
|
||||
}
|
||||
}
|
||||
|
||||
impl SpanlessEq for FuncArg {
|
||||
fn spanless_eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(FuncArg::Pos(a), FuncArg::Pos(b)) => a.spanless_eq(b),
|
||||
(FuncArg::Key(a), FuncArg::Key(b)) => a.spanless_eq(b),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,9 +152,7 @@ impl SpanlessEq for Expr {
|
||||
|
||||
impl SpanlessEq for Tuple {
|
||||
fn spanless_eq(&self, other: &Tuple) -> bool {
|
||||
self.items.len() == other.items.len()
|
||||
&& self.items.iter().zip(&other.items)
|
||||
.all(|(x, y)| x.v.spanless_eq(&y.v))
|
||||
self.0.spanless_eq(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,9 +165,13 @@ impl SpanlessEq for NamedTuple {
|
||||
|
||||
impl SpanlessEq for Object {
|
||||
fn spanless_eq(&self, other: &Object) -> bool {
|
||||
self.pairs.len() == other.pairs.len()
|
||||
&& self.pairs.iter().zip(&other.pairs)
|
||||
.all(|(x, y)| x.v.key.v == y.v.key.v && x.v.value.v.spanless_eq(&y.v.value.v))
|
||||
self.0.spanless_eq(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl SpanlessEq for Pair {
|
||||
fn spanless_eq(&self, other: &Self) -> bool {
|
||||
self.key.spanless_eq(&other.key) && self.value.spanless_eq(&other.value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,6 +194,16 @@ impl<T: SpanlessEq> SpanlessEq for Box<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: SpanlessEq> SpanlessEq for Option<T> {
|
||||
fn spanless_eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Some(a), Some(b)) => a.spanless_eq(b),
|
||||
(None, None) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_through_partial_eq {
|
||||
($type:ty) => {
|
||||
impl SpanlessEq for $type {
|
||||
@ -179,6 +215,7 @@ macro_rules! impl_through_partial_eq {
|
||||
}
|
||||
|
||||
impl_through_partial_eq!(Token<'_>);
|
||||
impl_through_partial_eq!(Ident);
|
||||
|
||||
// Implement for string and decoration to be able to compare feedback.
|
||||
impl_through_partial_eq!(String);
|
||||
|
@ -1,3 +1,5 @@
|
||||
//! Tokenization.
|
||||
|
||||
use std::iter::Peekable;
|
||||
use std::str::Chars;
|
||||
use unicode_xid::UnicodeXID;
|
||||
@ -161,7 +163,6 @@ pub struct Tokens<'s> {
|
||||
/// similar tokens or in body mode which yields text and star, underscore,
|
||||
/// backtick tokens.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum TokenMode {
|
||||
Header,
|
||||
Body,
|
||||
|
193
src/syntax/value.rs
Normal file
193
src/syntax/value.rs
Normal file
@ -0,0 +1,193 @@
|
||||
//! Value types for extracting function arguments.
|
||||
|
||||
use fontdock::{FontStyle, FontWeight, FontWidth};
|
||||
|
||||
use crate::Feedback;
|
||||
use crate::layout::prelude::*;
|
||||
use crate::length::{Length, ScaleLength};
|
||||
use crate::paper::Paper;
|
||||
use super::span::Spanned;
|
||||
use super::expr::*;
|
||||
|
||||
/// Value types are used to extract values from functions, tuples and
|
||||
/// objects. They represent the value part of an argument.
|
||||
/// ```typst
|
||||
/// [func: value, key=value]
|
||||
/// ^^^^^ ^^^^^
|
||||
/// ```
|
||||
pub trait Value: Sized {
|
||||
/// Try to parse this value from an expression.
|
||||
///
|
||||
/// Returns `None` and generates an appropriate error if the expression is
|
||||
/// not valid for this value type
|
||||
fn parse(expr: Spanned<Expr>, f: &mut Feedback) -> Option<Self>;
|
||||
}
|
||||
|
||||
impl<V: Value> Value for Spanned<V> {
|
||||
fn parse(expr: Spanned<Expr>, f: &mut Feedback) -> Option<Self> {
|
||||
let span = expr.span;
|
||||
V::parse(expr, f).map(|v| Spanned { v, span })
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! match_value {
|
||||
($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => {
|
||||
impl Value for $type {
|
||||
fn parse(expr: Spanned<Expr>, f: &mut Feedback) -> Option<Self> {
|
||||
#[allow(unreachable_patterns)]
|
||||
match expr.v {
|
||||
$($p => Some($r)),*,
|
||||
other => {
|
||||
error!(
|
||||
@f, expr.span,
|
||||
"expected {}, found {}", $name, other.name()
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
match_value!(Expr, "expression", e => e);
|
||||
match_value!(Ident, "identifier", Expr::Ident(i) => i);
|
||||
match_value!(String, "string", Expr::Str(s) => s);
|
||||
match_value!(f64, "number", Expr::Number(n) => n);
|
||||
match_value!(bool, "bool", Expr::Bool(b) => b);
|
||||
match_value!(Length, "length", Expr::Length(l) => l);
|
||||
match_value!(Tuple, "tuple", Expr::Tuple(t) => t);
|
||||
match_value!(Object, "object", Expr::Object(o) => o);
|
||||
match_value!(ScaleLength, "number or length",
|
||||
Expr::Length(length) => ScaleLength::Absolute(length),
|
||||
Expr::Number(scale) => ScaleLength::Scaled(scale),
|
||||
);
|
||||
|
||||
/// A value type that matches [`Expr::Ident`] and [`Expr::Str`] and implements
|
||||
/// `Into<String>`.
|
||||
pub struct StringLike(pub String);
|
||||
|
||||
impl From<StringLike> for String {
|
||||
fn from(like: StringLike) -> String {
|
||||
like.0
|
||||
}
|
||||
}
|
||||
|
||||
match_value!(StringLike, "identifier or string",
|
||||
Expr::Ident(Ident(s)) => StringLike(s),
|
||||
Expr::Str(s) => StringLike(s),
|
||||
);
|
||||
|
||||
macro_rules! ident_value {
|
||||
($type:ty, $name:expr, $parse:expr) => {
|
||||
impl Value for $type {
|
||||
fn parse(expr: Spanned<Expr>, f: &mut Feedback) -> Option<Self> {
|
||||
if let Expr::Ident(ident) = expr.v {
|
||||
let val = $parse(ident.as_str());
|
||||
if val.is_none() {
|
||||
error!(@f, expr.span, "invalid {}", $name);
|
||||
}
|
||||
val
|
||||
} else {
|
||||
error!(
|
||||
@f, expr.span,
|
||||
"expected {}, found {}", $name, expr.v.name()
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ident_value!(Dir, "direction", |s| match s {
|
||||
"ltr" => Some(LTT),
|
||||
"rtl" => Some(RTL),
|
||||
"ttb" => Some(TTB),
|
||||
"btt" => Some(BTT),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
ident_value!(SpecAlign, "alignment", |s| match s {
|
||||
"left" => Some(SpecAlign::Left),
|
||||
"right" => Some(SpecAlign::Right),
|
||||
"top" => Some(SpecAlign::Top),
|
||||
"bottom" => Some(SpecAlign::Bottom),
|
||||
"center" => Some(SpecAlign::Center),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
ident_value!(FontStyle, "font style", FontStyle::from_name);
|
||||
ident_value!(Paper, "paper", Paper::from_name);
|
||||
|
||||
impl Value for FontWeight {
|
||||
fn parse(expr: Spanned<Expr>, f: &mut Feedback) -> Option<Self> {
|
||||
match expr.v {
|
||||
Expr::Number(weight) => {
|
||||
const MIN: u16 = 100;
|
||||
const MAX: u16 = 900;
|
||||
|
||||
Some(FontWeight(if weight < MIN as f64 {
|
||||
error!(@f, expr.span, "the minimum font weight is {}", MIN);
|
||||
MIN
|
||||
} else if weight > MAX as f64 {
|
||||
error!(@f, expr.span, "the maximum font weight is {}", MAX);
|
||||
MAX
|
||||
} else {
|
||||
weight.round() as u16
|
||||
}))
|
||||
}
|
||||
Expr::Ident(ident) => {
|
||||
let weight = Self::from_name(ident.as_str());
|
||||
if weight.is_none() {
|
||||
error!(@f, expr.span, "invalid font weight");
|
||||
}
|
||||
weight
|
||||
}
|
||||
other => {
|
||||
error!(
|
||||
@f, expr.span,
|
||||
"expected font weight (name or number), found {}",
|
||||
other.name(),
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Value for FontWidth {
|
||||
fn parse(expr: Spanned<Expr>, f: &mut Feedback) -> Option<Self> {
|
||||
match expr.v {
|
||||
Expr::Number(width) => {
|
||||
const MIN: u16 = 1;
|
||||
const MAX: u16 = 9;
|
||||
|
||||
FontWidth::new(if width < MIN as f64 {
|
||||
error!(@f, expr.span, "the minimum font width is {}", MIN);
|
||||
MIN
|
||||
} else if width > MAX as f64 {
|
||||
error!(@f, expr.span, "the maximum font width is {}", MAX);
|
||||
MAX
|
||||
} else {
|
||||
width.round() as u16
|
||||
})
|
||||
}
|
||||
Expr::Ident(ident) => {
|
||||
let width = Self::from_name(ident.as_str());
|
||||
if width.is_none() {
|
||||
error!(@f, expr.span, "invalid font width");
|
||||
}
|
||||
width
|
||||
}
|
||||
other => {
|
||||
error!(
|
||||
@f, expr.span,
|
||||
"expected font width (name or number), found {}",
|
||||
other.name(),
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,16 @@
|
||||
[page: width=450pt, height=300pt, margins=1cm]
|
||||
|
||||
[box][
|
||||
*Technische Universität Berlin* [n]
|
||||
*Fakultät II, Institut for Mathematik* [n]
|
||||
Sekretariat MA [n]
|
||||
Dr. Max Mustermann [n]
|
||||
*Technische Universität Berlin* \
|
||||
*Fakultät II, Institut for Mathematik* \
|
||||
Sekretariat MA \
|
||||
Dr. Max Mustermann \
|
||||
Ola Nordmann, John Doe
|
||||
]
|
||||
[align: right][*WiSe 2019/2020* [n] Woche 3]
|
||||
[align: right][
|
||||
*WiSe 2019/2020* \
|
||||
Woche 3
|
||||
]
|
||||
|
||||
[v: 6mm]
|
||||
|
||||
|
@ -1,70 +0,0 @@
|
||||
[page: w=5cm, h=5cm, margins=0cm]
|
||||
|
||||
// Test 1
|
||||
[box: w=1, h=1, debug=false][
|
||||
[box][
|
||||
[align: center]
|
||||
[box: ps=3cm, ss=1cm]
|
||||
[direction: p=ttb, s=ltr]
|
||||
[box: ps=3cm, ss=1cm]
|
||||
[box: ps=1cm, ss=1cm]
|
||||
[box: ps=2cm, ss=1cm]
|
||||
[box: ps=1cm, ss=1cm]
|
||||
]
|
||||
]
|
||||
|
||||
// Test 2
|
||||
[box: w=1, h=1, debug=false][
|
||||
[align: s=top] Top
|
||||
[align: s=center] Center
|
||||
[align: s=bottom] Bottom
|
||||
[direction: p=ttb, s=ltr]
|
||||
[align: p=bottom, s=origin]
|
||||
[box: w=1cm, h=1cm]
|
||||
]
|
||||
|
||||
// Test 3
|
||||
[box: w=1, h=1, debug=false][
|
||||
[align: center][
|
||||
Some-long-spaceless-word!
|
||||
[align: left] Some
|
||||
[align: right] word!
|
||||
]
|
||||
]
|
||||
|
||||
// Test 4
|
||||
[box: w=1, h=1, debug=false][
|
||||
[direction: p=ltr, s=ttb]
|
||||
[align: center]
|
||||
[align: s=origin] [box: ps=1cm, ss=1cm]
|
||||
[align: s=center] [box: ps=3cm, ss=1cm] [box: ps=4cm, ss=0.5cm]
|
||||
[align: s=end] [box: ps=2cm, ss=1cm]
|
||||
]
|
||||
|
||||
// Test 5
|
||||
[box: w=1, h=1, debug=false][
|
||||
[direction: p=btt, s=ltr]
|
||||
[align: p=center, s=left]
|
||||
[box: h=2cm, w=1cm]
|
||||
|
||||
[direction: p=rtl, s=btt]
|
||||
[align: center]
|
||||
[align: v=origin] ORIGIN
|
||||
[align: v=center] CENTER
|
||||
[align: v=end] END
|
||||
]
|
||||
|
||||
// Test 6
|
||||
[box: w=1, h=1, debug=false][
|
||||
[box: w=4cm, h=1cm]
|
||||
|
||||
[align: p=right, s=center] CENTER
|
||||
|
||||
[direction: p=btt, s=rtl]
|
||||
[align: p=center, s=origin]
|
||||
[box: w=0.5cm, h=0.5cm]
|
||||
[box: w=0.5cm, h=1cm]
|
||||
[box: w=0.5cm, h=0.5cm]
|
||||
|
||||
[align: p=origin, s=end] END
|
||||
]
|
Loading…
x
Reference in New Issue
Block a user