Non-fatal argument parsing 🌋

This commit is contained in:
Laurenz 2020-01-24 12:44:04 +01:00
parent 78da2bdd5d
commit 03fddaf3ae
26 changed files with 1241 additions and 1036 deletions

View File

@ -1,6 +1,9 @@
use serde::Serialize;
use crate::syntax::SpanVec;
pub type Errors = SpanVec<Error>;
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
pub struct Error {
pub message: String,

View File

@ -39,15 +39,23 @@ macro_rules! function {
type Meta = $meta;
fn parse(
#[allow(unused)] mut $header: FuncHeader,
#[allow(unused)] $body: Option<(Position, &str)>,
#[allow(unused)] mut header: FuncHeader,
#[allow(unused)] $body: Option<Spanned<&str>>,
#[allow(unused)] $ctx: ParseContext,
#[allow(unused)] $metadata: Self::Meta,
) -> Parsed<Self> where Self: Sized {
#[allow(unused)] let mut $errors = vec![];
#[allow(unused)] let mut $decos = vec![];
let mut errors = vec![];
let mut decorations = vec![];
#[allow(unused)] let $header = &mut header;
#[allow(unused)] let $errors = &mut errors;
#[allow(unused)] let $decos = &mut decorations;
let output = $code;
$crate::syntax::Parsed { output, errors: $errors, decorations: $decos }
for arg in header.args.into_iter() {
errors.push(err!(arg.span(); "unexpected argument"));
}
$crate::syntax::Parsed { output, errors, decorations }
}
}
@ -58,7 +66,7 @@ macro_rules! function {
impl $crate::syntax::Model for $name {
fn layout<'a, 'b, 'c, 't>(
#[allow(unused)] &'a $this,
#[allow(unused)] $ctx: $crate::layout::LayoutContext<'b, 'c>,
#[allow(unused)] mut $ctx: $crate::layout::LayoutContext<'b, 'c>,
) -> $crate::syntax::DynFuture<'t, $crate::layout::Layouted<$crate::func::Commands<'a>>>
where
'a: 't,
@ -67,9 +75,10 @@ macro_rules! function {
Self: 't,
{
Box::pin(async move {
#[allow(unused)] let mut $errors = vec![];
let mut errors = vec![];
#[allow(unused)] let $errors = &mut errors;
let output = $code;
$crate::layout::Layouted { output, errors: $errors }
$crate::layout::Layouted { output, errors }
})
}
}
@ -78,14 +87,17 @@ macro_rules! function {
/// Parse the body of a function.
///
/// - If the function does not expect a body, use `parse!(forbidden: body)`.
/// - If the function can have a body, use `parse!(optional: body, ctx)`.
/// - If the function must have a body, use `parse!(expected: body, ctx)`.
/// - If the function does not expect a body, use `parse!(nope: body, errors)`.
/// - If the function can have a body, use `parse!(opt: body, ctx, errors, decos)`.
#[macro_export]
macro_rules! body {
(opt: $body:expr, $ctx:expr, $errors:expr, $decos:expr) => ({
$body.map(|body| {
let parsed = $crate::syntax::parse(body.0, body.1, $ctx);
// Since the body span starts at the opening bracket of the body, we
// need to add 1 column to find out the start position of body
// content.
let start = body.span.start + Position::new(0, 1);
let parsed = $crate::syntax::parse(start, body.v, $ctx);
$errors.extend(parsed.errors);
$decos.extend(parsed.decorations);
parsed.output
@ -94,12 +106,19 @@ macro_rules! body {
(nope: $body:expr, $errors:expr) => {
if let Some(body) = $body {
$errors.push($crate::err!(body.span, "unexpected body"));
$errors.push($crate::err!(body.span; "unexpected body"));
}
};
}
/// Construct an error with an optional span.
/// Construct an error with optional severity and span.
///
/// # Examples
/// ```
/// err!(span; "the wrong {}", value);
/// err!(@Warning: span; "non-fatal!");
/// err!("no spans here ...");
/// ```
#[macro_export]
macro_rules! err {
(@$severity:ident: $span:expr; $($args:tt)*) => {

View File

@ -26,7 +26,7 @@ pub trait Parse {
/// Parse the header and body into this function given a context.
fn parse(
header: FuncHeader,
body: Option<(Position, &str)>,
body: Option<Spanned<&str>>,
ctx: ParseContext,
metadata: Self::Meta,
) -> Parsed<Self> where Self: Sized;
@ -36,7 +36,7 @@ pub trait Parse {
/// implements [`Model`].
type Parser = dyn Fn(
FuncHeader,
Option<(Position, &str)>,
Option<Spanned<&str>>,
ParseContext,
) -> Parsed<Box<dyn Model>>;
@ -50,7 +50,7 @@ pub enum Command<'a> {
Add(Layout),
AddMultiple(MultiLayout),
SpacingFunc(Size, SpacingKind, GenericAxis),
AddSpacing(Size, SpacingKind, GenericAxis),
FinishLine,
FinishSpace,
@ -88,12 +88,12 @@ impl Scope {
/// Associate the given name with a type that is parseable into a function.
pub fn add<F>(&mut self, name: &str)
where F: Parse<Meta=()> + Model + 'static {
self.add_with_metadata::<F>(name, ());
self.add_with_meta::<F>(name, ());
}
/// Add a parseable type with additional metadata that is given to the
/// parser (other than the default of `()`).
pub fn add_with_metadata<F>(&mut self, name: &str, metadata: <F as Parse>::Meta)
pub fn add_with_meta<F>(&mut self, name: &str, metadata: <F as Parse>::Meta)
where F: Parse + Model + 'static {
self.parsers.insert(
name.to_owned(),

View File

@ -1,10 +1,11 @@
//! The core layouting engine.
use std::io::{self, Write};
use std::fmt::{self, Display, Formatter};
use smallvec::SmallVec;
use toddle::query::{SharedFontLoader, FontIndex};
use crate::error::Error;
use crate::error::Errors;
use crate::syntax::{SyntaxModel, SpanVec};
use crate::size::{Size, Size2D, SizeBox};
use crate::style::LayoutStyle;
@ -93,7 +94,7 @@ pub struct LayoutContext<'a, 'p> {
pub struct Layouted<T> {
pub output: T,
pub errors: SpanVec<Error>,
pub errors: Errors,
}
impl<T> Layouted<T> {
@ -231,6 +232,15 @@ impl GenericAxis {
}
}
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 {
@ -253,6 +263,15 @@ impl SpecificAxis {
}
}
impl Display for SpecificAxis {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Horizontal => write!(f, "horizontal"),
Vertical => write!(f, "vertical"),
}
}
}
/// Directions along which content is laid out.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Direction {
@ -298,6 +317,17 @@ impl Direction {
}
}
impl Display for Direction {
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"),
}
}
}
/// Where to align a layout in a container.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct LayoutAlignment {

View File

@ -1,6 +1,6 @@
use smallvec::smallvec;
use crate::error::Error;
use crate::error::Errors;
use crate::func::Command;
use crate::syntax::{Model, DynFuture, SyntaxModel, Node};
use crate::syntax::{SpanVec, Spanned, Span, offset_spans};
@ -12,7 +12,7 @@ pub struct ModelLayouter<'a, 'p> {
ctx: LayoutContext<'a, 'p>,
layouter: LineLayouter,
style: LayoutStyle,
errors: SpanVec<Error>,
errors: Errors,
}
impl<'a, 'p> ModelLayouter<'a, 'p> {
@ -105,7 +105,7 @@ impl<'a, 'p> ModelLayouter<'a, 'p> {
Add(layout) => self.layouter.add(layout),
AddMultiple(layouts) => self.layouter.add_multiple(layouts),
SpacingFunc(space, kind, axis) => match axis {
AddSpacing(space, kind, axis) => match axis {
Primary => self.layouter.add_primary_spacing(space, kind),
Secondary => self.layouter.add_secondary_spacing(space, kind),
}

View File

@ -1,35 +0,0 @@
use crate::func::prelude::*;
use super::maps::{PosAxisMap, AlignmentKey};
function! {
/// `align`: Aligns content along the layouting axes.
#[derive(Debug, PartialEq)]
pub struct AlignFunc {
body: Option<SyntaxTree>,
map: PosAxisMap<AlignmentKey>,
}
parse(header, body, ctx) {
AlignFunc {
body: parse!(optional: body, ctx),
map: PosAxisMap::new(&mut header.args)?,
}
}
layout(self, mut ctx) {
ctx.base = ctx.spaces[0].dimensions;
let map = self.map.dedup(ctx.axes, |alignment| alignment.axis(ctx.axes))?;
for &axis in &[Primary, Secondary] {
if let Some(alignment) = map.get(axis) {
*ctx.alignment.get_mut(axis) = alignment.to_generic(ctx.axes, axis)?;
}
}
match &self.body {
Some(body) => vec![AddMultiple(layout(&body, ctx).await?)],
None => vec![SetAlignment(ctx.alignment)],
}
}
}

View File

@ -1,57 +0,0 @@
use smallvec::smallvec;
use crate::func::prelude::*;
use super::maps::ExtentMap;
function! {
/// `box`: Layouts content into a box.
#[derive(Debug, PartialEq)]
pub struct BoxFunc {
body: SyntaxTree,
map: ExtentMap<PSize>,
debug: Option<bool>,
}
parse(header, body, ctx) {
BoxFunc {
body: parse!(optional: body, ctx).unwrap_or(SyntaxTree::new()),
map: ExtentMap::new(&mut header.args, false)?,
debug: header.args.get_key_opt::<bool>("debug")?,
}
}
layout(self, mut ctx) {
ctx.repeat = false;
if let Some(debug) = self.debug {
ctx.debug = debug;
}
let map = self.map.dedup(ctx.axes)?;
// Try to layout this box in all spaces until it fits into some space.
let mut error = None;
for &(mut space) in &ctx.spaces {
let mut ctx = ctx.clone();
for &axis in &[Horizontal, Vertical] {
if let Some(psize) = map.get(axis) {
let size = psize.scaled(ctx.base.get(axis));
*ctx.base.get_mut(axis) = size;
*space.dimensions.get_mut(axis) = size;
*space.expansion.get_mut(axis) = true;
}
}
ctx.spaces = smallvec![space];
match layout(&self.body, ctx).await {
Ok(layouts) => return Ok(vec![AddMultiple(layouts)]),
Err(err) => error = Some(err),
}
}
return Err(error.expect("expected at least one space"));
}
}

View File

@ -1,43 +0,0 @@
use crate::func::prelude::*;
use super::maps::PosAxisMap;
function! {
/// `direction`: Sets the directions of the layouting axes.
#[derive(Debug, PartialEq)]
pub struct DirectionFunc {
body: Option<SyntaxTree>,
map: PosAxisMap<Direction>,
}
parse(header, body, ctx) {
DirectionFunc {
body: parse!(optional: body, ctx),
map: PosAxisMap::new(&mut header.args)?,
}
}
layout(self, mut ctx) {
ctx.base = ctx.spaces[0].dimensions;
let map = self.map.dedup(ctx.axes, |direction| {
Some(direction.axis().to_generic(ctx.axes))
})?;
map.with(Primary, |&dir| ctx.axes.primary = dir);
map.with(Secondary, |&dir| ctx.axes.secondary = dir);
if ctx.axes.primary.axis() == ctx.axes.secondary.axis() {
error!(
"invalid aligned primary and secondary axes: `{}`, `{}`",
format!("{:?}", ctx.axes.primary).to_lowercase(),
format!("{:?}", ctx.axes.secondary).to_lowercase(),
);
}
match &self.body {
Some(body) => vec![AddMultiple(layout(&body, ctx).await?)],
None => vec![Command::SetAxes(ctx.axes)],
}
}
}

104
src/library/font.rs Normal file
View File

@ -0,0 +1,104 @@
use toddle::query::{FontWeight, FontStyle};
use super::*;
function! {
/// `font.family`: Set the font family.
#[derive(Debug, Clone, PartialEq)]
pub struct FontFamilyFunc {
body: Option<SyntaxModel>,
list: Vec<String>,
}
parse(header, body, ctx, errors, decos) {
FontFamilyFunc {
body: body!(opt: body, ctx, errors, decos),
list: header.args.pos.get_all::<StringLike>(errors).collect(),
}
}
layout(self, ctx, errors) {
styled(&self.body, ctx, Some(&self.list),
|s, l| s.fallback.list = l.clone())
}
}
function! {
/// `font.style`: Set the font style (normal / italic).
#[derive(Debug, Clone, PartialEq)]
pub struct FontStyleFunc {
body: Option<SyntaxModel>,
style: Option<FontStyle>,
}
parse(header, body, ctx, errors, decos) {
FontStyleFunc {
body: body!(opt: body, ctx, errors, decos),
style: header.args.pos.get::<FontStyle>(errors)
.or_missing(errors, header.name.span, "style"),
}
}
layout(self, ctx, errors) {
styled(&self.body, ctx, self.style, |t, s| t.variant.style = s)
}
}
function! {
/// `font.weight`: Set text with a given weight.
#[derive(Debug, Clone, PartialEq)]
pub struct FontWeightFunc {
body: Option<SyntaxModel>,
weight: Option<FontWeight>,
}
parse(header, body, ctx, errors, decos) {
let body = body!(opt: body, ctx, errors, decos);
let weight = header.args.pos.get::<Spanned<FontWeight>>(errors)
.map(|Spanned { v: (weight, is_clamped), span }| {
if is_clamped {
errors.push(err!(@Warning: span;
"weight should be between \
100 and 900, clamped to {}", weight.0));
}
weight
})
.or_missing(errors, header.name.span, "weight");
FontWeightFunc { body, weight }
}
layout(self, ctx, errors) {
styled(&self.body, ctx, self.weight, |t, w| t.variant.weight = w)
}
}
function! {
/// `font.size`: Sets the font size.
#[derive(Debug, Clone, PartialEq)]
pub struct FontSizeFunc {
body: Option<SyntaxModel>,
size: Option<ScaleSize>,
}
parse(header, body, ctx, errors, decos) {
FontSizeFunc {
body: body!(opt: body, ctx, errors, decos),
size: header.args.pos.get::<FSize>(errors)
.or_missing(errors, header.name.span, "size")
}
}
layout(self, ctx, errors) {
styled(&self.body, ctx, self.size, |t, s| {
match s {
ScaleSize::Absolute(size) => {
t.base_font_size = size;
t.font_scale = 1.0;
}
ScaleSize::Scaled(scale) => t.font_scale = scale,
}
})
}
}

135
src/library/layout.rs Normal file
View File

@ -0,0 +1,135 @@
use smallvec::smallvec;
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, errors, decos) {
AlignFunc {
body: body!(opt: body, ctx, errors, decos),
map: PosAxisMap::parse::<AxisKey, AlignmentValue>(errors, &mut header.args),
}
}
layout(self, ctx, errors) {
ctx.base = ctx.spaces[0].dimensions;
let map = self.map.dedup(errors, ctx.axes, |alignment| alignment.axis(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 {
errors.push(err!(span;
"invalid alignment `{}` for {} axis", alignment, axis));
}
}
}
match &self.body {
Some(body) => {
let layouted = layout(body, ctx).await;
errors.extend(layouted.errors);
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, errors, decos) {
DirectionFunc {
name_span: header.name.span,
body: body!(opt: body, ctx, errors, decos),
map: PosAxisMap::parse::<AxisKey, Direction>(errors, &mut header.args),
}
}
layout(self, ctx, errors) {
ctx.base = ctx.spaces[0].dimensions;
let map = self.map.dedup(errors, 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() {
errors.push(err!(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;
errors.extend(layouted.errors);
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<PSize>,
debug: Option<bool>,
}
parse(header, body, ctx, errors, decos) {
BoxFunc {
body: body!(opt: body, ctx, errors, decos).unwrap_or(SyntaxModel::new()),
extents: AxisMap::parse::<ExtentKey, PSize>(errors, &mut header.args.key),
debug: header.args.key.get::<bool>(errors, "debug"),
}
}
layout(self, ctx, errors) {
ctx.repeat = false;
ctx.spaces.truncate(1);
if let Some(debug) = self.debug {
ctx.debug = debug;
}
let map = self.extents.dedup(errors, ctx.axes);
for &axis in &[Horizontal, Vertical] {
if let Some(psize) = map.get(axis) {
let size = psize.scaled(ctx.base.get(axis));
*ctx.base.get_mut(axis) = size;
*ctx.spaces[0].dimensions.get_mut(axis) = size;
*ctx.spaces[0].expansion.get_mut(axis) = true;
}
}
let layouted = layout(&self.body, ctx).await;
let layout = layouted.output.into_iter().next().unwrap();
errors.extend(layouted.errors);
vec![Add(layout)]
}
}

View File

@ -1,77 +0,0 @@
use super::*;
use AlignmentKey::*;
/// An argument key which describes a target alignment.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum AlignmentKey {
Align(Alignment),
Left,
Top,
Right,
Bottom,
}
impl AlignmentKey {
/// The generic axis this alignment key corresponds to in the given system
/// of layouting axes. `None` if the alignment is generic.
pub fn axis(self, axes: LayoutAxes) -> Option<GenericAxis> {
match self {
Left | Right => Some(Horizontal.to_generic(axes)),
Top | Bottom => Some(Vertical.to_generic(axes)),
Align(_) => None,
}
}
/// The generic version of this alignment in the given system of layouting
/// axes.
///
/// Returns an error if the alignment is invalid for the given axis.
pub fn to_generic(self, axes: LayoutAxes, axis: GenericAxis) -> LayoutResult<Alignment> {
let specific = axis.to_specific(axes);
let start = match axes.get(axis).is_positive() {
true => Origin,
false => End,
};
Ok(match (self, specific) {
(Align(alignment), _) => alignment,
(Left, Horizontal) | (Top, Vertical) => start,
(Right, Horizontal) | (Bottom, Vertical) => start.inv(),
_ => error!(
"invalid alignment `{}` for {} axis",
format!("{:?}", self).to_lowercase(),
format!("{:?}", axis).to_lowercase()
)
})
}
/// The specific version of this alignment in the given system of layouting
/// axes.
pub fn to_specific(self, axes: LayoutAxes, axis: SpecificAxis) -> AlignmentKey {
let direction = axes.get_specific(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
}
}
}
key!(AlignmentKey, "alignment",
"origin" => Align(Origin),
"center" => Align(Center),
"end" => Align(End),
"left" => Left,
"top" => Top,
"right" => Right,
"bottom" => Bottom,
);

View File

@ -1,130 +0,0 @@
use super::*;
use AxisKey::*;
/// An argument key which identifies a layouting axis.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
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, "axis",
"horizontal" | "h" => Specific(Horizontal),
"vertical" | "v" => Specific(Vertical),
"primary" | "p" => Generic(Primary),
"secondary" | "s" => Generic(Secondary),
);
key!(Direction, "direction",
"left-to-right" | "ltr" => LeftToRight,
"right-to-left" | "rtl" => RightToLeft,
"top-to-bottom" | "ttb" => TopToBottom,
"bottom-to-top" | "btt" => BottomToTop,
);
/// A map for storing extents along axes.
#[derive(Debug, Clone, PartialEq)]
pub struct ExtentMap<E: ExpressionKind + Copy>(ConsistentMap<AxisKey, E>);
impl<E: ExpressionKind + Copy> ExtentMap<E> {
/// Parse an extent map from the function args.
///
/// If `all` is true other arguments will create an error, otherwise
/// they are left intact.
pub fn new(args: &mut FuncArgs, all: bool) -> ParseResult<ExtentMap<E>> {
let mut map = ConsistentMap::new();
for arg in args.iter_keys() {
let key = match arg.key.v.as_str() {
"width" | "w" => AxisKey::Specific(Horizontal),
"height" | "h" => AxisKey::Specific(Vertical),
"primary-size" | "ps" => AxisKey::Generic(Primary),
"secondary-size" | "ss" => AxisKey::Generic(Secondary),
_ => if all {
error!("expected dimension")
} else {
args.add_key_pair(arg);
continue;
}
};
let e = E::from_expr(arg.value)?;
map.add(key, e)?;
}
Ok(ExtentMap(map))
}
/// Deduplicate from generic to specific axes.
pub fn dedup(&self, axes: LayoutAxes) -> LayoutResult<ConsistentMap<SpecificAxis, E>> {
self.0.dedup(|key, &val| Ok((key.to_specific(axes), val)))
}
}
/// An argument key which identifies an axis, but allows for positional
/// arguments with unspecified axes.
#[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),
}
/// A map for storing some data for via keyword or positionally given axes.
#[derive(Debug, Clone, PartialEq)]
pub struct PosAxisMap<E: ExpressionKind + Copy>(ConsistentMap<PosAxisKey, E>);
impl<E: ExpressionKind + Copy> PosAxisMap<E> {
/// Parse a positional axis map from the function args.
pub fn new(args: &mut FuncArgs) -> ParseResult<PosAxisMap<E>> {
let mut map = ConsistentMap::new();
map.add_opt(PosAxisKey::First, args.get_pos_opt::<E>()?)?;
map.add_opt(PosAxisKey::Second, args.get_pos_opt::<E>()?)?;
for arg in args.iter_keys() {
let axis = AxisKey::from_ident(&arg.key)?;
let value = E::from_expr(arg.value)?;
map.add(PosAxisKey::Keyword(axis), value)?;
}
Ok(PosAxisMap(map))
}
/// Deduplicate from positional or specific to generic axes.
pub fn dedup<F>(&self, axes: LayoutAxes, f: F) -> LayoutResult<ConsistentMap<GenericAxis, E>>
where F: Fn(E) -> Option<GenericAxis> {
self.0.dedup(|key, &e| {
Ok((match key {
PosAxisKey::First => f(e).unwrap_or(Primary),
PosAxisKey::Second => f(e).unwrap_or(Secondary),
PosAxisKey::Keyword(AxisKey::Specific(axis)) => axis.to_generic(axes),
PosAxisKey::Keyword(AxisKey::Generic(axis)) => *axis,
}, e))
})
}
}

View File

@ -1,128 +0,0 @@
//! Deduplicating maps and keys for argument parsing.
use std::collections::HashMap;
use std::hash::Hash;
use crate::func::prelude::*;
macro_rules! key {
($type:ty, $name:expr, $($patterns:tt)*) => {
impl $type {
/// Parse this key from an identifier.
pub fn from_ident(ident: &Spanned<Ident>) -> ParseResult<Self> {
Ok(match ident.v.as_str() {
$($patterns)*
_ => error!("expected {}", <Self as ExpressionKind>::NAME),
})
}
}
impl ExpressionKind for $type {
const NAME: &'static str = $name;
fn from_expr(expr: Spanned<Expr>) -> ParseResult<Self> {
if let Expr::Ident(ident) = expr.v {
Self::from_ident(&Spanned::new(ident, expr.span))
} else {
error!("expected {}", Self::NAME);
}
}
}
};
}
pub_use_mod!(axis);
pub_use_mod!(alignment);
pub_use_mod!(padding);
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum DefaultKey<T> {
Some(T),
None,
}
impl<T> Into<Option<T>> for DefaultKey<T> {
fn into(self) -> Option<T> {
match self {
DefaultKey::Some(v) => Some(v),
DefaultKey::None => None,
}
}
}
impl<T> ExpressionKind for DefaultKey<T> where T: ExpressionKind {
const NAME: &'static str = T::NAME;
fn from_expr(expr: Spanned<Expr>) -> ParseResult<DefaultKey<T>> {
if let Expr::Ident(ident) = &expr.v {
match ident.as_str() {
"default" => return Ok(DefaultKey::None),
_ => {},
}
}
T::from_expr(expr).map(|v| DefaultKey::Some(v))
}
}
/// A deduplicating map type useful for storing possibly redundant arguments.
#[derive(Debug, Clone, PartialEq)]
pub struct ConsistentMap<K, V> where K: Hash + Eq {
map: HashMap<K, V>,
}
impl<K, V> ConsistentMap<K, V> where K: Hash + Eq {
pub fn new() -> ConsistentMap<K, V> {
ConsistentMap { map: HashMap::new() }
}
/// Add a key-value pair.
pub fn add(&mut self, key: K, value: V) -> ParseResult<()> {
match self.map.insert(key, value) {
Some(_) => error!("duplicate argument"),
None => Ok(())
}
}
/// Add a key-value pair if the value is not `None`.
pub fn add_opt(&mut self, key: K, value: Option<V>) -> ParseResult<()> {
Ok(if let Some(value) = value {
self.add(key, value)?;
})
}
/// Get the value at a key if it is present.
pub fn get(&self, key: K) -> Option<&V> {
self.map.get(&key)
}
/// 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.map.get(&key) {
callback(value);
}
}
/// Create a new consistent map where keys and values are mapped to new keys
/// and values.
///
/// Returns an error if a new key is duplicate.
pub fn dedup<F, K2, V2>(&self, f: F) -> LayoutResult<ConsistentMap<K2, V2>>
where F: Fn(&K, &V) -> ParseResult<(K2, V2)>, K2: Hash + Eq {
let mut map = ConsistentMap::new();
for (key, value) in self.map.iter() {
let (key, value) = f(key, value)?;
map.add(key, value)?;
}
Ok(map)
}
/// Iterate over the (key, value) pairs.
pub fn iter(&self) -> std::collections::hash_map::Iter<'_, K, V> {
self.map.iter()
}
}

View File

@ -1,95 +0,0 @@
use super::*;
use AxisKey::*;
use AlignmentKey::*;
use PaddingKey::*;
/// An argument key which identifies a margin or padding target.
///
/// A is the used axis type.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum PaddingKey<A> {
/// All four sides should have the specified padding.
All,
/// Both sides of the given axis should have the specified padding.
Both(A),
/// Only the given side of the given axis should have the specified padding.
Side(A, AlignmentKey),
}
key!(PaddingKey<AxisKey>, "axis or side",
"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)),
);
/// A map for storing padding at sides.
#[derive(Debug, Clone, PartialEq)]
pub struct PaddingMap(ConsistentMap<PaddingKey<AxisKey>, Option<PSize>>);
impl PaddingMap {
/// Parse a padding map from the function args.
pub fn new(args: &mut FuncArgs) -> ParseResult<PaddingMap> {
let mut map = ConsistentMap::new();
map.add_opt(PaddingKey::All,
args.get_pos_opt::<DefaultKey<PSize>>()?.map(Into::into))?;
for arg in args.iter_keys() {
let key = PaddingKey::from_ident(&arg.key)?;
let size = DefaultKey::<PSize>::from_expr(arg.value)?.into();
map.add(key, size)?;
}
Ok(PaddingMap(map))
}
/// Apply the specified padding on the size box.
pub fn apply(&self, axes: LayoutAxes, padding: &mut ValueBox<Option<PSize>>)
-> LayoutResult<()> {
use PaddingKey::*;
let map = self.0.dedup(|key, &val| {
Ok((match key {
All => All,
Both(axis) => Both(axis.to_specific(axes)),
Side(axis, alignment) => {
let axis = axis.to_specific(axes);
Side(axis, alignment.to_specific(axes, axis))
}
}, 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 {
AlignmentKey::Left => padding.left = val,
AlignmentKey::Right => padding.right = val,
AlignmentKey::Top => padding.top = val,
AlignmentKey::Bottom => padding.bottom = val,
_ => {},
}
}
}
Ok(())
}
}

View File

@ -1,34 +1,32 @@
//! The standard library.
use toddle::query::{FontWeight, FontStyle};
use crate::func::prelude::*;
use crate::style::{Paper, PaperClass};
use self::maps::{ExtentMap, PaddingMap, AxisKey};
pub mod maps;
pub_use_mod!(align);
pub_use_mod!(boxed);
pub_use_mod!(direction);
pub_use_mod!(font);
pub_use_mod!(layout);
pub_use_mod!(page);
pub_use_mod!(spacing);
/// Create a scope with all standard functions.
pub fn std() -> Scope {
let mut std = Scope::new();
let mut std = Scope::new::<ValFunc>();
// Basics
std.add::<ValFunc>("val");
// Font setup
std.add::<FontFamilyFunc>("font.family");
std.add::<FontStyleFunc>("font.style");
std.add::<FontWeightFunc>("font.weight");
std.add::<FontSizeFunc>("font.size");
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_with_metadata::<ContentSpacingFunc>("par.spacing", ContentKind::Paragraph);
std.add_with_metadata::<ContentSpacingFunc>("word.spacing", ContentKind::Word);
std.add_with_metadata::<ContentSpacingFunc>("line.spacing", ContentKind::Line);
std.add::<BoxFunc>("box");
// Spacing
@ -36,9 +34,9 @@ pub fn std() -> Scope {
std.add::<LineBreakFunc>("line.break");
std.add::<ParBreakFunc>("par.break");
std.add::<PageBreakFunc>("page.break");
std.add_with_metadata::<SpacingFunc>("spacing", None);
std.add_with_metadata::<SpacingFunc>("h", Some(Horizontal));
std.add_with_metadata::<SpacingFunc>("v", Some(Vertical));
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::<PageSizeFunc>("page.size");
@ -47,331 +45,45 @@ pub fn std() -> Scope {
std
}
// -------------------------------------------------------------------------- //
// Font setup
function! {
/// `font.family`: Set the font family.
#[derive(Debug, PartialEq)]
pub struct FontFamilyFunc {
body: Option<SyntaxTree>,
list: Vec<String>,
/// `val`: Layouts the body with no special effect.
#[derive(Debug, Clone, PartialEq)]
pub struct ValFunc {
body: Option<SyntaxModel>,
}
parse(header, body, ctx) {
FontFamilyFunc {
body: parse!(optional: body, ctx),
list: {
header.args.iter_pos().map(|arg| match arg.v {
Expr::Str(s) |
Expr::Ident(Ident(s)) => Ok(s.to_lowercase()),
_ => error!("expected identifier or string"),
}).collect::<LayoutResult<Vec<_>>>()?
}
parse(header, body, ctx, errors, decos) {
ValFunc { body: body!(opt: body, ctx, errors, decos) }
}
layout(self, ctx, errors) {
match &self.body {
Some(model) => vec![LayoutSyntaxModel(model)],
None => vec![],
}
}
}
layout(self, ctx) {
/// Layout an optional body with a change of the text style.
fn styled<'a, T, F>(
body: &'a Option<SyntaxModel>,
ctx: LayoutContext,
data: Option<T>,
f: F,
) -> Commands<'a> where F: FnOnce(&mut TextStyle, T) {
if let Some(data) = data {
let mut style = ctx.style.text.clone();
style.fallback.list = self.list.clone();
styled(&self.body, &ctx, style)
}
}
f(&mut style, data);
function! {
/// `font.style`: Set the font style (normal / italic).
#[derive(Debug, PartialEq)]
pub struct FontStyleFunc {
body: Option<SyntaxTree>,
style: FontStyle,
}
parse(header, body, ctx) {
FontStyleFunc {
body: parse!(optional: body, ctx),
style: {
let s = header.args.get_pos::<String>()?;
match FontStyle::from_str(&s) {
Some(style) => style,
None => error!("invalid font style: `{}`", s),
}
}
match body {
Some(model) => vec![
SetTextStyle(style),
LayoutSyntaxModel(model),
SetTextStyle(ctx.style.text.clone()),
],
None => vec![SetTextStyle(style)],
}
}
layout(self, ctx) {
let mut style = ctx.style.text.clone();
style.variant.style = self.style;
styled(&self.body, &ctx, style)
}
}
function! {
/// `font.weight`: Set text with a given weight.
#[derive(Debug, PartialEq)]
pub struct FontWeightFunc {
body: Option<SyntaxTree>,
weight: FontWeight,
}
parse(header, body, ctx) {
FontWeightFunc {
body: parse!(optional: body, ctx),
weight: match header.args.get_pos::<Expr>()? {
Expr::Number(weight) => {
let weight = weight.round() as i16;
FontWeight(
if weight < 100 { 100 }
else if weight <= 900 { weight }
else { 900 }
)
}
Expr::Ident(Ident(s)) => {
match FontWeight::from_str(&s) {
Some(weight) => weight,
None => error!("invalid font weight: `{}`", s),
}
}
_ => error!("expected identifier or number"),
},
}
}
layout(self, ctx) {
let mut style = ctx.style.text.clone();
style.variant.weight = self.weight;
styled(&self.body, &ctx, style)
}
}
function! {
/// `font.size`: Sets the font size.
#[derive(Debug, PartialEq)]
pub struct FontSizeFunc {
body: Option<SyntaxTree>,
size: ScaleSize,
}
parse(header, body, ctx) {
FontSizeFunc {
body: parse!(optional: body, ctx),
size: header.args.get_pos::<ScaleSize>()?,
}
}
layout(self, ctx) {
let mut style = ctx.style.text.clone();
match self.size {
ScaleSize::Absolute(size) => {
style.base_font_size = size;
style.font_scale = 1.0;
}
ScaleSize::Scaled(scale) => style.font_scale = scale,
}
styled(&self.body, &ctx, style)
}
}
// -------------------------------------------------------------------------- //
// Layout
function! {
/// `word.spacing`, `line.spacing`, `par.spacing`: The spacing between
/// words, lines or paragraphs as a multiple of the font size.
#[derive(Debug, PartialEq)]
pub struct ContentSpacingFunc {
body: Option<SyntaxTree>,
content: ContentKind,
spacing: f32,
}
type Meta = ContentKind;
parse(header, body, ctx, meta) {
ContentSpacingFunc {
body: parse!(optional: body, ctx),
content: meta,
spacing: header.args.get_pos::<f64>()? as f32,
}
}
layout(self, ctx) {
let mut style = ctx.style.text.clone();
match self.content {
ContentKind::Word => style.word_spacing_scale = self.spacing,
ContentKind::Line => style.line_spacing_scale = self.spacing,
ContentKind::Paragraph => style.paragraph_spacing_scale = self.spacing,
}
styled(&self.body, &ctx, style)
}
}
/// The different kinds of content that can be spaced.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ContentKind {
Word,
Line,
Paragraph,
}
// -------------------------------------------------------------------------- //
// Spacing
function! {
/// `line.break`, `n`: Ends the current line.
#[derive(Debug, Default, PartialEq)]
pub struct LineBreakFunc;
parse(default)
layout() { vec![FinishLine] }
}
function! {
/// `par.break`: Ends the current paragraph.
///
/// self has the same effect as two subsequent newlines.
#[derive(Debug, Default, PartialEq)]
pub struct ParBreakFunc;
parse(default)
layout() { vec![BreakParagraph] }
}
function! {
/// `page.break`: Ends the current page.
#[derive(Debug, Default, PartialEq)]
pub struct PageBreakFunc;
parse(default)
layout() { vec![BreakPage] }
}
function! {
/// `spacing`, `h`, `v`: Adds spacing along an axis.
#[derive(Debug, PartialEq)]
pub struct SpacingFunc {
axis: AxisKey,
spacing: FSize,
}
type Meta = Option<SpecificAxis>;
parse(header, body, _, meta) {
parse!(forbidden: body);
if let Some(axis) = meta {
SpacingFunc {
axis: AxisKey::Specific(axis),
spacing: FSize::from_expr(
header.args.get_pos::<Spanned<Expr>>()?
)?,
}
} else {
for arg in header.args.iter_keys() {
let axis = AxisKey::from_ident(&arg.key)
.map_err(|_| error!(@unexpected_argument))?;
let spacing = FSize::from_expr(arg.value)?;
return Ok(SpacingFunc { axis, spacing });
}
error!("expected axis and spacing")
}
}
layout(self, ctx) {
let axis = self.axis.to_generic(ctx.axes);
let spacing = self.spacing.scaled(ctx.style.text.font_size());
vec![SpacingFunc(spacing, SpacingKind::Hard, axis)]
}
}
// -------------------------------------------------------------------------- //
// Page setup
function! {
/// `page.size`: Set the size of pages.
#[derive(Debug, PartialEq)]
pub enum PageSizeFunc {
Paper(Paper, bool),
Custom(ExtentMap<PSize>),
}
parse(header, body) {
parse!(forbidden: body);
if let Some(name) = header.args.get_pos_opt::<Ident>()? {
let flip = header.args.get_key_opt::<bool>("flip")?.unwrap_or(false);
let paper = Paper::from_name(name.as_str())
.ok_or_else(|| error!(@"invalid paper name: `{}`", name))?;
PageSizeFunc::Paper(paper, flip)
} else {
PageSizeFunc::Custom(ExtentMap::new(&mut header.args, true)?)
}
}
layout(self, ctx) {
let mut style = ctx.style.page;
match self {
PageSizeFunc::Paper(paper, flip) => {
style.class = paper.class;
style.dimensions = paper.dimensions;
if *flip {
style.dimensions.swap();
}
}
PageSizeFunc::Custom(map) => {
style.class = PaperClass::Custom;
let map = map.dedup(ctx.axes)?;
let dims = &mut style.dimensions;
map.with(Horizontal, |&psize| dims.x = psize.scaled(dims.x));
map.with(Vertical, |&psize| dims.y = psize.scaled(dims.y));
}
}
vec![SetPageStyle(style)]
}
}
function! {
/// `page.margins`: Sets the page margins.
#[derive(Debug, PartialEq)]
pub struct PageMarginsFunc {
map: PaddingMap,
}
parse(header, body) {
parse!(forbidden: body);
PageMarginsFunc {
map: PaddingMap::new(&mut header.args)?,
}
}
layout(self, ctx) {
let mut style = ctx.style.page;
self.map.apply(ctx.axes, &mut style.margins)?;
vec![SetPageStyle(style)]
}
}
// -------------------------------------------------------------------------- //
// Helpers
/// Layout the body with the style or update the style if there is no body.
fn styled<'a>(
body: &'a Option<SyntaxTree>,
ctx: &LayoutContext,
style: TextStyle
) -> Commands<'a> {
match &body {
Some(body) => vec![
SetTextStyle(style),
LayoutTree(body),
SetTextStyle(ctx.style.text.clone()),
],
None => vec![SetTextStyle(style)]
} else {
vec![]
}
}

64
src/library/page.rs Normal file
View File

@ -0,0 +1,64 @@
use crate::style::{Paper, PaperClass};
use super::*;
function! {
/// `page.size`: Set the size of pages.
#[derive(Debug, Clone, PartialEq)]
pub struct PageSizeFunc {
paper: Option<Paper>,
extents: AxisMap<Size>,
flip: bool,
}
parse(header, body, ctx, errors, decos) {
body!(nope: body, errors);
PageSizeFunc {
paper: header.args.pos.get::<Paper>(errors),
extents: AxisMap::parse::<ExtentKey, Size>(errors, &mut header.args.key),
flip: header.args.key.get::<bool>(errors, "flip").unwrap_or(false),
}
}
layout(self, ctx, errors) {
let mut style = ctx.style.page;
if let Some(paper) = self.paper {
style.class = paper.class;
style.dimensions = paper.dimensions;
} else {
style.class = PaperClass::Custom;
}
let map = self.extents.dedup(errors, ctx.axes);
map.with(Horizontal, |&width| style.dimensions.x = width);
map.with(Vertical, |&height| style.dimensions.y = height);
if self.flip {
style.dimensions.swap();
}
vec![SetPageStyle(style)]
}
}
function! {
/// `page.margins`: Sets the page margins.
#[derive(Debug, Clone, PartialEq)]
pub struct PageMarginsFunc {
padding: PaddingMap,
}
parse(header, body, ctx, errors, decos) {
body!(nope: body, errors);
PageMarginsFunc {
padding: PaddingMap::parse(errors, &mut header.args),
}
}
layout(self, ctx, errors) {
let mut style = ctx.style.page;
self.padding.apply(errors, ctx.axes, &mut style.margins);
vec![SetPageStyle(style)]
}
}

103
src/library/spacing.rs Normal file
View File

@ -0,0 +1,103 @@
use super::*;
use ContentKind::*;
function! {
/// `line.break`, `n`: Ends the current line.
#[derive(Debug, Default, Clone, PartialEq)]
pub struct LineBreakFunc;
parse(default)
layout(self, ctx, errors) { vec![FinishLine] }
}
function! {
/// `par.break`: Ends the current paragraph.
///
/// self has the same effect as two subsequent newlines.
#[derive(Debug, Default, Clone, PartialEq)]
pub struct ParBreakFunc;
parse(default)
layout(self, ctx, errors) { vec![BreakParagraph] }
}
function! {
/// `page.break`: Ends the current page.
#[derive(Debug, Default, Clone, PartialEq)]
pub struct PageBreakFunc;
parse(default)
layout(self, ctx, errors) { vec![BreakPage] }
}
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<f32>,
}
type Meta = ContentKind;
parse(header, body, ctx, errors, decos, meta) {
ContentSpacingFunc {
body: body!(opt: body, ctx, errors, decos),
content: meta,
spacing: header.args.pos.get::<f64>(errors)
.map(|num| num as f32)
.or_missing(errors, header.name.span, "spacing"),
}
}
layout(self, ctx, errors) {
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.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ContentKind {
Word,
Line,
Paragraph,
}
function! {
/// `spacing`, `h`, `v`: Adds spacing along an axis.
#[derive(Debug, Clone, PartialEq)]
pub struct SpacingFunc {
spacing: Option<(AxisKey, FSize)>,
}
type Meta = Option<SpecificAxis>;
parse(header, body, ctx, errors, decos, meta) {
body!(nope: body, errors);
SpacingFunc {
spacing: if let Some(axis) = meta {
header.args.pos.get::<FSize>(errors)
.map(|s| (AxisKey::Specific(axis), s))
} else {
header.args.key.get_with_key::<AxisKey, FSize>(errors)
}.or_missing(errors, header.name.span, "spacing"),
}
}
layout(self, ctx, errors) {
if let Some((axis, spacing)) = self.spacing {
let axis = axis.to_generic(ctx.axes);
let spacing = spacing.scaled(ctx.style.text.font_size());
vec![AddSpacing(spacing, SpacingKind::Hard, axis)]
} else {
vec![]
}
}
}

View File

@ -156,7 +156,7 @@ pub struct Paper {
impl Paper {
/// The paper with the given name.
pub fn from_name(name: &str) -> Option<Paper> {
pub fn from_str(name: &str) -> Option<Paper> {
parse_paper(name)
}
}

View File

@ -1,4 +1,3 @@
use crate::size::ScaleSize;
use super::*;
@ -22,30 +21,13 @@ impl Expr {
Str(_) => "string",
Number(_) => "number",
Size(_) => "size",
Bool(_) => "boolean",
Bool(_) => "bool",
Tuple(_) => "tuple",
Object(_) => "object",
}
}
}
impl Display for Expr {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
use Expr::*;
match self {
Ident(i) => write!(f, "{}", i),
Str(s) => write!(f, "{:?}", s),
Number(n) => write!(f, "{}", n),
Size(s) => write!(f, "{}", s),
Bool(b) => write!(f, "{}", b),
Tuple(t) => write!(f, "{}", t),
Object(o) => write!(f, "{}", o),
}
}
}
debug_display!(Expr);
/// An identifier.
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Ident(pub String);
@ -64,17 +46,6 @@ impl Ident {
}
}
impl Display for Ident {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
debug_display!(Ident);
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct StringLike(pub String);
/// A sequence of expressions.
#[derive(Clone, PartialEq)]
pub struct Tuple {
@ -89,27 +60,31 @@ impl Tuple {
pub fn add(&mut self, item: Spanned<Expr>) {
self.items.push(item);
}
}
impl Display for Tuple {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "(")?;
let mut first = true;
for item in &self.items {
if !first {
write!(f, ", ")?;
pub fn get<V: Value>(&mut self, errors: &mut Errors) -> Option<V::Output> {
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(err) => errors.push(Spanned { v: err, span }),
}
write!(f, "{}", item.v)?;
first = false;
}
None
}
write!(f, ")")
pub fn get_all<'a, V: Value>(&'a mut self, errors: &'a mut Errors)
-> impl Iterator<Item=V::Output> + 'a {
self.items.drain(..).filter_map(move |expr| {
let span = expr.span;
match V::parse(expr) {
Ok(output) => Some(output),
Err(err) => { errors.push(Spanned { v: err, span }); None }
}
})
}
}
debug_display!(Tuple);
/// A key-value collection of identifiers and associated expressions.
#[derive(Clone, PartialEq)]
pub struct Object {
@ -128,6 +103,108 @@ impl Object {
pub fn add_pair(&mut self, pair: Pair) {
self.pairs.push(pair);
}
pub fn get<V: Value>(&mut self, errors: &mut Errors, key: &str) -> Option<V::Output> {
let index = self.pairs.iter().position(|pair| pair.key.v.as_str() == key)?;
self.get_index::<V>(errors, index)
}
pub fn get_with_key<K: Key, V: Value>(
&mut self,
errors: &mut Errors,
) -> Option<(K::Output, V::Output)> {
for (index, pair) in self.pairs.iter().enumerate() {
let key = Spanned { v: pair.key.v.as_str(), span: pair.key.span };
if let Some(key) = K::parse(key) {
return self.get_index::<V>(errors, index).map(|value| (key, value));
}
}
None
}
pub fn get_all<'a, K: Key, V: Value>(
&'a mut self,
errors: &'a mut Errors,
) -> impl Iterator<Item=(K::Output, V::Output)> + 'a {
let mut index = 0;
std::iter::from_fn(move || {
if index < self.pairs.len() {
let key = &self.pairs[index].key;
let key = Spanned { v: key.v.as_str(), span: key.span };
Some(if let Some(key) = K::parse(key) {
self.get_index::<V>(errors, index).map(|v| (key, v))
} else {
index += 1;
None
})
} else {
None
}
}).filter_map(|x| x)
}
pub fn get_all_spanned<'a, K: Key + 'a, V: Value + 'a>(
&'a mut self,
errors: &'a mut Errors,
) -> impl Iterator<Item=Spanned<(K::Output, V::Output)>> + 'a {
self.get_all::<Spanned<K>, Spanned<V>>(errors)
.map(|(k, v)| Spanned::new((k.v, v.v), Span::merge(k.span, v.span)))
}
fn get_index<V: Value>(&mut self, errors: &mut Errors, index: usize) -> Option<V::Output> {
let expr = self.pairs.remove(index).value;
let span = expr.span;
match V::parse(expr) {
Ok(output) => Some(output),
Err(err) => { errors.push(Spanned { v: err, span }); None }
}
}
}
/// A key-value pair in an object.
#[derive(Clone, PartialEq)]
pub struct Pair {
pub key: Spanned<Ident>,
pub value: Spanned<Expr>,
}
impl Display for Expr {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
use Expr::*;
match self {
Ident(i) => write!(f, "{}", i),
Str(s) => write!(f, "{:?}", s),
Number(n) => write!(f, "{}", n),
Size(s) => write!(f, "{}", s),
Bool(b) => write!(f, "{}", b),
Tuple(t) => write!(f, "{}", t),
Object(o) => write!(f, "{}", o),
}
}
}
impl Display for Ident {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl Display for Tuple {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "(")?;
let mut first = true;
for item in &self.items {
if !first {
write!(f, ", ")?;
}
write!(f, "{}", item.v)?;
first = false;
}
write!(f, ")")
}
}
impl Display for Object {
@ -151,71 +228,14 @@ impl Display for Object {
}
}
debug_display!(Object);
/// A key-value pair in an object.
#[derive(Clone, PartialEq)]
pub struct Pair {
pub key: Spanned<Ident>,
pub value: Spanned<Expr>,
}
impl Display for Pair {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}: {}", self.key.v, self.value.v)
}
}
debug_display!(Expr);
debug_display!(Ident);
debug_display!(Tuple);
debug_display!(Object);
debug_display!(Pair);
pub trait ExprKind: Sized {
/// The name of the expression in an `expected <name>` error.
const NAME: &'static str;
/// Create from expression.
fn from_expr(expr: Spanned<Expr>) -> Result<Self, Error>;
}
impl<T> ExprKind for Spanned<T> where T: ExprKind {
const NAME: &'static str = T::NAME;
fn from_expr(expr: Spanned<Expr>) -> Result<Self, Error> {
let span = expr.span;
T::from_expr(expr).map(|v| Spanned { v, span })
}
}
/// Implements the expression kind trait for a type.
macro_rules! kind {
($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => {
impl ExprKind for $type {
const NAME: &'static str = $name;
fn from_expr(expr: Spanned<Expr>) -> Result<Self, Error> {
#[allow(unreachable_patterns)]
Ok(match expr.v {
$($p => $r),*,
_ => return Err(
err!("expected {}, found {}", Self::NAME, expr.v.name())
),
})
}
}
};
}
kind!(Expr, "expression", e => e);
kind!(Ident, "identifier", Expr::Ident(i) => i);
kind!(String, "string", Expr::Str(s) => s);
kind!(f64, "number", Expr::Number(n) => n);
kind!(bool, "boolean", Expr::Bool(b) => b);
kind!(Size, "size", Expr::Size(s) => s);
kind!(Tuple, "tuple", Expr::Tuple(t) => t);
kind!(Object, "object", Expr::Object(o) => o);
kind!(ScaleSize, "number or size",
Expr::Size(size) => ScaleSize::Absolute(size),
Expr::Number(scale) => ScaleSize::Scaled(scale as f32),
);
kind!(StringLike, "identifier or string",
Expr::Ident(Ident(s)) => StringLike(s),
Expr::Str(s) => StringLike(s),
);

121
src/syntax/func/keys.rs Normal file
View File

@ -0,0 +1,121 @@
use crate::layout::prelude::*;
use super::*;
use AxisKey::*;
use PaddingKey::*;
use AlignmentValue::*;
pub trait Key {
type Output: Eq;
fn parse(key: Spanned<&str>) -> Option<Self::Output>;
}
impl<K: Key> Key for Spanned<K> {
type Output = Spanned<K::Output>;
fn parse(key: Spanned<&str>) -> Option<Self::Output> {
K::parse(key).map(|v| Spanned { v, span: key.span })
}
}
macro_rules! key {
($type:ty, $output:ty, $($($p:pat)|* => $r:expr),* $(,)?) => {
impl Key for $type {
type Output = $output;
fn parse(key: Spanned<&str>) -> Option<Self::Output> {
match key.v {
$($($p)|* => Some($r)),*,
other => None,
}
}
}
};
}
/// An argument key which identifies a layouting axis.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
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, Self,
"horizontal" | "h" => Specific(Horizontal),
"vertical" | "v" => Specific(Vertical),
"primary" | "p" => Generic(Primary),
"secondary" | "s" => Generic(Secondary),
);
pub struct ExtentKey;
key!(ExtentKey, AxisKey,
"width" | "w" => Specific(Horizontal),
"height" | "h" => Specific(Vertical),
"primary-size" | "ps" => Generic(Primary),
"secondary-size" | "ss" => Generic(Secondary),
);
/// An argument key which identifies an axis, but allows for positional
/// arguments with unspecified axes.
#[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>, Self,
"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)),
);

211
src/syntax/func/maps.rs Normal file
View File

@ -0,0 +1,211 @@
//! Deduplicating maps and keys for argument parsing.
use std::collections::HashMap;
use std::hash::Hash;
use crate::layout::{LayoutAxes, SpecificAxis, GenericAxis};
use crate::size::{PSize, ValueBox};
use super::*;
/// A deduplicating map type useful for storing possibly redundant arguments.
#[derive(Debug, Clone, PartialEq)]
pub struct DedupMap<K, V> where K: Eq {
map: Vec<Spanned<(K, V)>>,
}
impl<K, V> DedupMap<K, V> where K: Eq {
pub fn new() -> DedupMap<K, V> {
DedupMap { map: vec![] }
}
pub fn from_iter<I>(errors: &mut Errors, iter: I) -> DedupMap<K, V>
where I: IntoIterator<Item=Spanned<(K, V)>> {
let mut map = DedupMap::new();
for Spanned { v: (key, value), span } in iter.into_iter() {
map.insert(errors, key, value, span);
}
map
}
/// Add a key-value pair.
pub fn insert(&mut self, errors: &mut Errors, key: K, value: V, span: Span) {
if self.map.iter().any(|e| e.v.0 == key) {
errors.push(err!(span; "duplicate argument"));
} else {
self.map.push(Spanned { v: (key, value), span });
}
}
/// Add multiple key-value pairs.
pub fn extend<I>(&mut self, errors: &mut Errors, items: I)
where I: IntoIterator<Item=Spanned<(K, V)>> {
for Spanned { v: (k, v), span } in items.into_iter() {
self.insert(errors, k, v, span);
}
}
/// 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.
///
/// Returns an error if a new key is duplicate.
pub fn dedup<F, K2, V2>(&self, errors: &mut Errors, 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(errors, key, value, *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 two axes given by keyword arguments.
#[derive(Debug, Clone, PartialEq)]
pub struct AxisMap<V>(DedupMap<AxisKey, V>);
impl<V: Clone> AxisMap<V> {
pub fn parse<KT: Key<Output=AxisKey>, VT: Value<Output=V>>(
errors: &mut Errors,
object: &mut Object,
) -> AxisMap<V> {
let values: Vec<_> = object.get_all_spanned::<KT, VT>(errors).collect();
AxisMap(DedupMap::from_iter(errors, values))
}
/// Deduplicate from specific or generic to just specific axes.
pub fn dedup(&self, errors: &mut Errors, axes: LayoutAxes) -> DedupMap<SpecificAxis, V> {
self.0.dedup(errors, |key, val| (key.to_specific(axes), val.clone()))
}
}
/// A map for extracting values for two axes that are given through two
/// positional or keyword arguments.
#[derive(Debug, Clone, PartialEq)]
pub struct PosAxisMap<V>(DedupMap<PosAxisKey, V>);
impl<V: Clone> PosAxisMap<V> {
pub fn parse<KT: Key<Output=AxisKey>, VT: Value<Output=V>>(
errors: &mut Errors,
args: &mut FuncArgs,
) -> PosAxisMap<V> {
let mut map = DedupMap::new();
for &key in &[PosAxisKey::First, PosAxisKey::Second] {
if let Some(value) = args.pos.get::<Spanned<VT>>(errors) {
map.insert(errors, key, value.v, value.span);
}
}
let keywords: Vec<_> = args.key
.get_all_spanned::<KT, VT>(errors)
.map(|s| s.map(|(k, v)| (PosAxisKey::Keyword(k), v)))
.collect();
map.extend(errors, keywords);
PosAxisMap(map)
}
/// Deduplicate from positional or specific to generic axes.
pub fn dedup<F>(
&self,
errors: &mut Errors,
axes: LayoutAxes,
mut f: F,
) -> DedupMap<GenericAxis, V> where F: FnMut(&V) -> Option<GenericAxis> {
self.0.dedup(errors, |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 extracting padding for a set of specifications given for all
/// sides, opposing sides or single sides.
#[derive(Debug, Clone, PartialEq)]
pub struct PaddingMap(DedupMap<PaddingKey<AxisKey>, Option<PSize>>);
impl PaddingMap {
pub fn parse(errors: &mut Errors, args: &mut FuncArgs) -> PaddingMap {
let mut map = DedupMap::new();
if let Some(psize) = args.pos.get::<Spanned<Defaultable<PSize>>>(errors) {
map.insert(errors, PaddingKey::All, psize.v, psize.span);
}
let paddings: Vec<_> = args.key
.get_all_spanned::<PaddingKey<AxisKey>, Defaultable<PSize>>(errors)
.collect();
map.extend(errors, paddings);
PaddingMap(map)
}
/// Apply the specified padding on a value box of optional, scalable sizes.
pub fn apply(
&self,
errors: &mut Errors,
axes: LayoutAxes,
padding: &mut ValueBox<Option<PSize>>
) {
use PaddingKey::*;
use SpecificAxis::*;
let map = self.0.dedup(errors, |key, &val| {
(match key {
All => All,
Both(axis) => Both(axis.to_specific(axes)),
Side(axis, alignment) => {
let axis = axis.to_specific(axes);
Side(axis, alignment.to_specific(axes, axis))
}
}, 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,
_ => {},
}
}
}
}
}

View File

@ -1,5 +1,9 @@
use super::*;
pub_use_mod!(maps);
pub_use_mod!(keys);
pub_use_mod!(values);
#[derive(Debug, Clone, PartialEq)]
pub struct FuncHeader {
@ -60,6 +64,11 @@ impl FuncArgs {
self.key.add_pair(pair);
}
pub fn into_iter(self) -> impl Iterator<Item=Arg> {
self.pos.items.into_iter().map(|item| Arg::Pos(item))
.chain(self.key.pairs.into_iter().map(|pair| Arg::Key(pair)))
}
// /// Force-extract the first positional argument.
// pub fn get_pos<E: ExpressionKind>(&mut self) -> ParseResult<E> {
// expect(self.get_pos_opt())
@ -123,3 +132,16 @@ impl FuncArgs {
// Err(e) => Err(e),
// }
// }
pub trait OptionExt: Sized {
fn or_missing(self, errors: &mut Errors, span: Span, what: &str) -> Self;
}
impl<T> OptionExt for Option<T> {
fn or_missing(self, errors: &mut Errors, span: Span, what: &str) -> Self {
if self.is_none() {
errors.push(err!(span; "missing argument: {}", what));
}
self
}
}

223
src/syntax/func/values.rs Normal file
View File

@ -0,0 +1,223 @@
use std::marker::PhantomData;
use toddle::query::{FontStyle, FontWeight};
use crate::layout::prelude::*;
use crate::size::ScaleSize;
use crate::style::Paper;
use super::*;
use AlignmentValue::*;
pub trait Value {
type Output;
fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error>;
}
impl<V: Value> Value for Spanned<V> {
type Output = Spanned<V::Output>;
fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
let span = expr.span;
V::parse(expr).map(|v| Spanned { v, span })
}
}
macro_rules! value {
($type:ty, $output:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => {
impl Value for $type {
type Output = $output;
fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
#[allow(unreachable_patterns)]
match expr.v {
$($p => Ok($r)),*,
other => Err(err!("expected {}, found {}",
$name, other.name())),
}
}
}
};
}
value!(Expr, Self, "expression", e => e);
value!(Ident, Self, "identifier", Expr::Ident(i) => i);
value!(String, Self, "string", Expr::Str(s) => s);
value!(f64, Self, "number", Expr::Number(n) => n);
value!(bool, Self, "bool", Expr::Bool(b) => b);
value!(Size, Self, "size", Expr::Size(s) => s);
value!(Tuple, Self, "tuple", Expr::Tuple(t) => t);
value!(Object, Self, "object", Expr::Object(o) => o);
value!(ScaleSize, Self, "number or size",
Expr::Size(size) => ScaleSize::Absolute(size),
Expr::Number(scale) => ScaleSize::Scaled(scale as f32),
);
pub struct StringLike;
value!(StringLike, String, "identifier or string",
Expr::Ident(Ident(s)) => s,
Expr::Str(s) => s,
);
pub struct Defaultable<T>(PhantomData<T>);
impl<T: Value> Value for Defaultable<T> {
type Output = Option<T::Output>;
fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
match expr.v {
Expr::Ident(ident) if ident.as_str() == "default" => Ok(None),
_ => T::parse(expr).map(Some)
}
}
}
impl Value for Direction {
type Output = Self;
fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
Ok(match Ident::parse(expr)?.as_str() {
"left-to-right" | "ltr" | "LTR" => Direction::LeftToRight,
"right-to-left" | "rtl" | "RTL" => Direction::RightToLeft,
"top-to-bottom" | "ttb" | "TTB" => Direction::TopToBottom,
"bottom-to-top" | "btt" | "BTT" => Direction::BottomToTop,
other => return Err(err!("invalid direction"))
})
}
}
impl Value for FontStyle {
type Output = Self;
fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
FontStyle::from_str(Ident::parse(expr)?.as_str())
.ok_or_else(|| err!("invalid font style"))
}
}
impl Value for FontWeight {
type Output = (Self, bool);
fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
match expr.v {
Expr::Number(weight) => {
let weight = weight.round();
if weight >= 100.0 && weight <= 900.0 {
Ok((FontWeight(weight as i16), false))
} else {
let clamped = weight.min(900.0).max(100.0) as i16;
Ok((FontWeight(clamped), true))
}
}
Expr::Ident(id) => {
FontWeight::from_str(id.as_str())
.ok_or_else(|| err!("invalid font weight"))
.map(|weight| (weight, false))
}
other => Err(err!("expected identifier or number, \
found {}", other.name())),
}
}
}
impl Value for Paper {
type Output = Self;
fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
Paper::from_str(Ident::parse(expr)?.as_str())
.ok_or_else(|| err!("invalid paper type"))
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum AlignmentValue {
Align(Alignment),
Left,
Top,
Right,
Bottom,
}
impl AlignmentValue {
/// The generic axis this alignment corresponds to in the given system of
/// layouting axes. `None` if the alignment is generic.
pub fn axis(self, axes: LayoutAxes) -> Option<GenericAxis> {
match self {
Left | Right => Some(Horizontal.to_generic(axes)),
Top | Bottom => Some(Vertical.to_generic(axes)),
Align(_) => None,
}
}
/// The generic version of this alignment 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 start = match axes.get(axis).is_positive() {
true => Origin,
false => 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 in the given system of layouting
/// axes.
pub fn to_specific(self, axes: LayoutAxes, axis: SpecificAxis) -> AlignmentValue {
let direction = axes.get_specific(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 {
type Output = Self;
fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
Ok(match Ident::parse(expr)?.as_str() {
"origin" => Align(Origin),
"center" => Align(Center),
"end" => Align(End),
"left" => Left,
"top" => Top,
"right" => Right,
"bottom" => Bottom,
other => return Err(err!("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"),
}
}
}

View File

@ -6,7 +6,7 @@ use std::future::Future;
use std::pin::Pin;
use serde::Serialize;
use crate::error::Error;
use crate::error::{Error, Errors};
use crate::func::{Commands, Command};
use crate::layout::{Layouted, LayoutContext};
use crate::size::Size;

View File

@ -12,7 +12,7 @@ pub struct ParseContext<'a> {
pub struct Parsed<T> {
pub output: T,
pub errors: SpanVec<Error>,
pub errors: Errors,
pub decorations: SpanVec<Decoration>,
}
@ -77,17 +77,17 @@ pub fn parse(start: Position, src: &str, ctx: ParseContext) -> Parsed<SyntaxMode
struct FuncParser<'s> {
ctx: ParseContext<'s>,
errors: SpanVec<Error>,
errors: Errors,
decorations: SpanVec<Decoration>,
tokens: Tokens<'s>,
peeked: Option<Option<Spanned<Token<'s>>>>,
body: Option<(Position, &'s str)>,
body: Option<Spanned<&'s str>>,
}
impl<'s> FuncParser<'s> {
fn new(
header: &'s str,
body: Option<(Position, &'s str)>,
body: Option<Spanned<&'s str>>,
ctx: ParseContext<'s>
) -> FuncParser<'s> {
FuncParser {

View File

@ -23,7 +23,7 @@ pub enum Token<'s> {
/// A function invocation `[<header>][<body>]`.
Function {
header: &'s str,
body: Option<(Position, &'s str)>,
body: Option<Spanned<&'s str>>,
terminated: bool,
},
@ -222,13 +222,16 @@ impl<'s> Tokens<'s> {
return Function { header, body: None, terminated };
}
let body_start = self.pos() - start;
self.eat();
let offset = self.pos() - start;
let (body, terminated) = self.read_function_part();
self.eat();
Function { header, body: Some((offset, body)), terminated }
let body_end = self.pos();
let span = Span::new(body_start, body_end);
Function { header, body: Some(Spanned { v: body, span }), terminated }
}
fn read_function_part(&mut self) -> (&'s str, bool) {