Move to non-fatal errors 🪂 [WIP]

- Dynamic models instead of SyntaxTrees
- No more ParseResult/LayoutResult
- Errors and Decorations which are propagated to parent contexts
- Models are finally clonable
This commit is contained in:
Laurenz 2020-01-19 21:50:20 +01:00
parent 277f2d2176
commit 95e6b078fe
19 changed files with 924 additions and 999 deletions

10
src/error.rs Normal file
View File

@ -0,0 +1,10 @@
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Error {
pub message: String,
}
impl Error {
pub fn new(message: impl Into<String>) -> Error {
Error { message: message.into() }
}
}

View File

@ -1,131 +1,76 @@
//! Helper types and macros for creating custom functions.
/// Defines function types concisely.
#[macro_export]
macro_rules! function {
// Parse a unit struct.
($(#[$outer:meta])* pub struct $type:ident; $($rest:tt)*) => {
$(#[$outer])* pub struct $type;
function!(@meta $type | $($rest)*);
// Entry point.
($(#[$outer:meta])* $v:vis $storage:ident $name:ident $($r:tt)*) => {
function!(@def($name) $(#[$outer])* $v $storage $name $($r)*);
};
(@def($name:ident) $definition:item $($r:tt)*) => {
$definition
function!(@meta($name) $($r)*);
};
// Parse a tuple struct.
($(#[$outer:meta])* pub struct $type:ident($($fields:tt)*); $($rest:tt)*) => {
$(#[$outer])* pub struct $type($($fields)*);
function!(@meta $type | $($rest)*);
// Metadata.
(@meta($name:ident) type Meta = $meta:ty; $($r:tt)*) => {
function!(@parse($name, $meta) $($r)*);
};
(@meta($name:ident) $($r:tt)*) => {
function!(@parse($name, ()) $($r)*);
};
// Parse a struct with fields.
($(#[$outer:meta])* pub struct $type:ident { $($fields:tt)* } $($rest:tt)*) => {
$(#[$outer])* pub struct $type { $($fields)* }
function!(@meta $type | $($rest)*);
// Parse trait.
(@parse($($a:tt)*) parse(default) $($r:tt)*) => {
function!(@parse($($a)*) parse() { Default::default() } $($r)*);
};
// Parse an enum.
($(#[$outer:meta])* pub enum $type:ident { $($fields:tt)* } $($rest:tt)*) => {
$(#[$outer])* pub enum $type { $($fields)* }
function!(@meta $type | $($rest)*);
(@parse($($a:tt)*) parse($h:ident, $b:ident, $c:ident, $e:ident, $d:ident) $($r:tt)* ) => {
function!(@parse($($a)*) parse($h, $b, $c, $e, $d, _metadata) $($r)*);
};
// Parse a metadata type definition.
(@meta $type:ident | type Meta = $meta:ty; $($rest:tt)*) => {
function!(@parse $type $meta | $($rest)*);
};
// Set the metadata to `()` if there is no type definition.
(@meta $type:ident | $($rest:tt)*) => {
function!(@parse $type () | $($rest)*);
};
// Parse a `parse(default)`.
(@parse $type:ident $meta:ty | parse(default) $($rest:tt)*) => {
function!(@parse $type $meta |
parse(_args, _body, _ctx, _meta) { Default::default() }
$($rest)*
);
};
// (0-arg) Parse a parse-definition without arguments.
(@parse $type:ident $meta:ty | parse() $code:block $($rest:tt)*) => {
function!(@parse $type $meta | parse(_args, _body, _ctx, _meta) $code $($rest)*);
};
// (1-arg) Parse a parse-definition with only the first argument.
(@parse $type:ident $meta:ty | parse($header:ident) $code:block $($rest:tt)*) => {
function!(@parse $type $meta | parse($header, _body, _ctx, _meta) $code $($rest)*);
};
// (2-arg) Parse a parse-definition with only the first two arguments.
(@parse $type:ident $meta:ty |
parse($header:ident, $body:pat) $code:block $($rest:tt)*
) => {
function!(@parse $type $meta | parse($header, $body, _ctx, _meta) $code $($rest)*);
};
// (3-arg) Parse a parse-definition with only the first three arguments.
(@parse $type:ident $meta:ty |
parse($header:ident, $body:pat, $ctx:pat) $code:block $($rest:tt)*
) => {
function!(@parse $type $meta | parse($header, $body, $ctx, _meta) $code $($rest)*);
};
// (4-arg) Parse a parse-definition with all four arguments.
(@parse $type:ident $meta:ty |
parse($header:ident, $body:pat, $ctx:pat, $metadata:pat) $code:block
$($rest:tt)*
) => {
impl $crate::func::ParseFunc for $type {
(@parse($name:ident, $meta:ty) parse(
$header:ident,
$body:ident,
$ctx:ident,
$errors:ident,
$decos:ident,
$metadata:ident
) $code:block $($r:tt)*) => {
impl $crate::func::Parse for $name {
type Meta = $meta;
fn parse(
header: $crate::syntax::FuncHeader,
$body: Option<&str>,
$ctx: $crate::syntax::ParseContext,
$metadata: Self::Meta,
) -> $crate::syntax::ParseResult<Self> where Self: Sized {
#[allow(unused_mut)]
let mut $header = header;
let val = $code;
if !$header.args.is_empty() {
return Err($crate::TypesetError::with_message("unexpected arguments"));
}
Ok(val)
#[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 output = $code;
$crate::syntax::Parsed { output, errors: $errors, decorations: $decos }
}
}
function!(@layout $type | $($rest)*);
function!(@layout($name) $($r)*);
};
// (0-arg) Parse a layout-definition without arguments.
(@layout $type:ident | layout() $code:block) => {
function!(@layout $type | layout(self, _ctx) $code);
};
// (1-arg) Parse a layout-definition with only the first argument.
(@layout $type:ident | layout($this:ident) $code:block) => {
function!(@layout $type | layout($this, _ctx) $code);
};
// (2-arg) Parse a layout-definition with all arguments.
(@layout $type:ident | layout($this:ident, $ctx:pat) $code:block) => {
impl $crate::func::LayoutFunc for $type {
fn layout<'a, 'life0, 'life1, 'async_trait>(
&'a $this,
$ctx: $crate::layout::LayoutContext<'life0, 'life1>
) -> std::pin::Pin<Box<dyn std::future::Future<
Output = $crate::layout::LayoutResult<
$crate::func::Commands<'a>>
> + 'async_trait
>>
(@layout($name:ident) layout($this:ident, $ctx:ident, $errors:ident) $code:block) => {
impl $crate::syntax::Model for $name {
fn layout<'a, 'b, 'c, 't>(
#[allow(unused)] &'a $this,
#[allow(unused)] $ctx: $crate::layout::LayoutContext<'b, 'c>,
) -> $crate::syntax::DynFuture<'t, $crate::layout::Layouted<$crate::func::Commands<'a>>>
where
'a: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
'a: 't,
'b: 't,
'c: 't,
Self: 't,
{
#[allow(unreachable_code)]
Box::pin(async move { Ok($code) })
Box::pin(async move {
#[allow(unused)] let mut $errors = vec![];
let output = $code;
$crate::layout::Layouted { output, errors: $errors }
})
}
}
};
@ -137,35 +82,30 @@ macro_rules! function {
/// - If the function can have a body, use `parse!(optional: body, ctx)`.
/// - If the function must have a body, use `parse!(expected: body, ctx)`.
#[macro_export]
macro_rules! parse {
(forbidden: $body:expr) => {
if $body.is_some() {
return Err($crate::TypesetError::with_message("unexpected body"));
macro_rules! body {
(opt: $body:expr, $ctx:expr, $errors:expr, $decos:expr) => ({
$body.map(|body| {
let parsed = $crate::syntax::parse(body.span.start, body.v, $ctx);
$errors.extend(parsed.errors);
$decos.extend(parsed.decorations);
parsed.output
})
});
(nope: $body:expr, $errors:expr) => {
if let Some(body) = $body {
$errors.push($crate::err!(body.span, "unexpected body"));
}
};
(optional: $body:expr, $ctx:expr) => (
if let Some(body) = $body {
Some($crate::syntax::parse(body, $ctx).0)
} else {
None
}
);
(expected: $body:expr, $ctx:expr) => (
if let Some(body) = $body {
$crate::syntax::parse(body, $ctx).0
} else {
Err($crate::TypesetError::with_message("unexpected body"))
}
)
}
/// Early-return with a formatted typesetting error or construct an error
/// expression.
/// Construct a spanned error.
#[macro_export]
macro_rules! error {
(@unexpected_argument) => (error!(@"unexpected argument"));
(@$($tts:tt)*) => ($crate::TypesetError::with_message(format!($($tts)*)));
($($tts:tt)*) => (return Err(error!(@$($tts)*)););
macro_rules! err {
($span:expr, $($args:tt)*) => {
$crate::syntax::Spanned {
v: $crate::error::Error::new(format!($($args)*)),
span: $span,
}
};
}

View File

@ -3,7 +3,6 @@
use std::any::Any;
use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter};
use async_trait::async_trait;
use self::prelude::*;
@ -12,81 +11,34 @@ mod macros;
/// Useful imports for creating your own functions.
pub mod prelude {
pub use crate::func::{Scope, ParseFunc, LayoutFunc, Command, Commands};
pub use super::{Scope, Parse, Command, Commands};
pub use crate::layout::prelude::*;
pub use crate::syntax::*;
pub use crate::syntax::prelude::*;
pub use crate::size::{Size, Size2D, SizeBox, ValueBox, ScaleSize, FSize, PSize};
pub use crate::style::{LayoutStyle, PageStyle, TextStyle};
pub use Command::*;
}
/// Types representing functions that are parsed from source code.
pub trait ParseFunc {
/// Parse a function from source code.
pub trait Parse {
type Meta: Clone;
/// Parse the header and body into this function given a context.
fn parse(
header: FuncHeader,
body: Option<&str>,
body: Option<Spanned<&str>>,
ctx: ParseContext,
metadata: Self::Meta,
) -> ParseResult<Self> where Self: Sized;
) -> Parsed<Self> where Self: Sized;
}
/// Function types which can be laid out in a layout context.
///
/// This trait is a supertrait of `[LayoutFuncBounds]` for technical reasons.
/// The trait `[LayoutFuncBounds]` is automatically implemented for types which
/// can be used as functions, that is, all types which fulfill the bounds `Debug
/// + PartialEq + 'static`.
#[async_trait(?Send)]
pub trait LayoutFunc: LayoutFuncBounds {
/// Layout this function in a given context.
///
/// Returns a sequence of layouting commands which describe what the
/// function is doing.
async fn layout<'a>(&'a self, ctx: LayoutContext<'_, '_>) -> LayoutResult<Commands<'a>>;
}
impl dyn LayoutFunc {
/// Downcast a function trait object to a concrete function type.
pub fn downcast<F>(&self) -> Option<&F> where F: LayoutFunc + 'static {
self.help_cast_as_any().downcast_ref::<F>()
}
}
impl PartialEq for dyn LayoutFunc {
fn eq(&self, other: &dyn LayoutFunc) -> bool {
self.help_eq(other)
}
}
/// A helper trait that describes requirements for types that can implement
/// [`Function`].
///
/// Automatically implemented for all types which fulfill to the bounds `Debug +
/// PartialEq + 'static`. There should be no need to implement this manually.
pub trait LayoutFuncBounds: Debug {
/// Cast self into `Any`.
fn help_cast_as_any(&self) -> &dyn Any;
/// Compare self with another function trait object.
fn help_eq(&self, other: &dyn LayoutFunc) -> bool;
}
impl<T> LayoutFuncBounds for T where T: Debug + PartialEq + 'static {
fn help_cast_as_any(&self) -> &dyn Any {
self
}
fn help_eq(&self, other: &dyn LayoutFunc) -> bool {
if let Some(other) = other.help_cast_as_any().downcast_ref::<Self>() {
self == other
} else {
false
}
}
}
/// A function which parses the source of a function into a model type which
/// implements [`Model`].
type Parser = dyn Fn(
FuncHeader,
Option<Spanned<&str>>,
ParseContext,
) -> Parsed<Box<dyn Model>>;
/// A sequence of layouting commands.
pub type Commands<'a> = Vec<Command<'a>>;
@ -94,7 +46,7 @@ pub type Commands<'a> = Vec<Command<'a>>;
/// Layouting commands from functions to the typesetting engine.
#[derive(Debug)]
pub enum Command<'a> {
LayoutTree(&'a SyntaxTree),
LayoutSyntaxModel(&'a SyntaxModel),
Add(Layout),
AddMultiple(MultiLayout),
@ -114,41 +66,17 @@ pub enum Command<'a> {
/// A map from identifiers to function parsers.
pub struct Scope {
parsers: HashMap<String, Box<Parser>>,
debug: Option<Box<Parser>>
}
/// A function which parses the source of a function into a function type which
/// implements [`LayoutFunc`].
type Parser = dyn Fn(
FuncHeader,
Option<&str>,
ParseContext
) -> ParseResult<Box<dyn LayoutFunc>>;
fn make_parser<F>(metadata: <F as ParseFunc>::Meta) -> Box<Parser>
where F: ParseFunc + LayoutFunc + 'static {
Box::new(move |a, b, c| {
F::parse(a, b, c, metadata.clone())
.map(|f| Box::new(f) as Box<dyn LayoutFunc>)
})
fallback: Box<Parser>
}
impl Scope {
/// Create a new empty scope.
pub fn new() -> Scope {
Scope {
parsers: HashMap::new(),
debug: None,
}
}
/// Create a new scope with a debug parser that is invoked if not other
/// Create a new empty scope with a fallback parser that is invoked when no
/// match is found.
pub fn with_debug<F>() -> Scope
where F: ParseFunc<Meta=()> + LayoutFunc + 'static {
pub fn new<F>() -> Scope
where F: Parse<Meta=()> + Model + 'static {
Scope {
parsers: HashMap::new(),
debug: Some(make_parser::<F>(())),
fallback: parser::<F>(()),
}
}
@ -159,24 +87,25 @@ 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: ParseFunc<Meta=()> + LayoutFunc + 'static {
where F: Parse<Meta=()> + Model + 'static {
self.add_with_metadata::<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 ParseFunc>::Meta)
where F: ParseFunc + LayoutFunc + 'static {
pub fn add_with_metadata<F>(&mut self, name: &str, metadata: <F as Parse>::Meta)
where F: Parse + Model + 'static {
self.parsers.insert(
name.to_owned(),
make_parser::<F>(metadata),
parser::<F>(metadata),
);
}
/// Return the parser with the given name if there is one.
pub(crate) fn get_parser(&self, name: &str) -> Option<&Parser> {
self.parsers.get(name).map(|x| &**x)
.or(self.debug.as_ref().map(|x| &**x))
pub(crate) fn get_parser(&self, name: &str) -> Result<&Parser, &Parser> {
self.parsers.get(name)
.map(|x| &**x)
.ok_or_else(|| &*self.fallback)
}
}
@ -186,3 +115,10 @@ impl Debug for Scope {
write!(f, "{:?}", self.parsers.keys())
}
}
fn parser<F>(metadata: <F as Parse>::Meta) -> Box<Parser> where F: Parse + Model + 'static {
Box::new(move |h, b, c| {
F::parse(h, b, c, metadata.clone())
.map(|model| Box::new(model) as Box<dyn Model>)
})
}

View File

@ -67,18 +67,21 @@ impl LineLayouter {
}
/// Add a layout to the run.
pub fn add(&mut self, layout: Layout) -> LayoutResult<()> {
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 self.stack.is_fitting_alignment(layout.alignment) {
self.finish_line()?;
// TODO: Issue warning for non-fitting alignment in
// non-repeating context.
let fitting = self.stack.is_fitting_alignment(layout.alignment);
if !fitting && self.ctx.repeat {
self.finish_space(true);
} else {
self.finish_space(true)?;
self.finish_line();
}
} else if layout.alignment.primary < alignment.primary {
self.finish_line()?;
self.finish_line();
} else if layout.alignment.primary > alignment.primary {
let mut rest_run = LineRun::new();
@ -92,7 +95,7 @@ impl LineLayouter {
rest_run.size.y = self.run.size.y;
self.finish_line()?;
self.finish_line();
self.stack.add_spacing(-rest_run.size.y, SpacingKind::Hard);
self.run = rest_run;
@ -105,16 +108,14 @@ impl LineLayouter {
let size = layout.dimensions.generalized(axes);
while !self.usable().fits(size) {
if !self.usable().fits(size) {
if !self.line_is_empty() {
self.finish_line()?;
} else {
if self.stack.space_is_last() && self.stack.space_is_empty() {
error!("cannot fit box of size {} into usable size of {}",
layout.dimensions, self.usable());
}
self.finish_line();
}
self.finish_space(true)?;
// TODO: Issue warning about overflow if there is overflow.
if !self.usable().fits(size) {
self.stack.skip_to_fitting_space(layout.dimensions);
}
}
@ -124,18 +125,15 @@ impl LineLayouter {
self.run.size.x += size.x;
self.run.size.y.max_eq(size.y);
self.run.last_spacing = LastSpacing::None;
Ok(())
}
/// Add multiple layouts to the run.
///
/// This function simply calls `add` repeatedly for each layout.
pub fn add_multiple(&mut self, layouts: MultiLayout) -> LayoutResult<()> {
pub fn add_multiple(&mut self, layouts: MultiLayout) {
for layout in layouts {
self.add(layout)?;
self.add(layout);
}
Ok(())
}
/// The remaining usable size in the run.
@ -180,20 +178,16 @@ impl LineLayouter {
}
/// Finish the run and add secondary spacing to the underlying stack.
pub fn add_secondary_spacing(
&mut self,
spacing: Size,
kind: SpacingKind
) -> LayoutResult<()> {
self.finish_line_if_not_empty()?;
Ok(self.stack.add_spacing(spacing, kind))
pub fn add_secondary_spacing(&mut self, spacing: Size, kind: SpacingKind) {
self.finish_line_if_not_empty();
self.stack.add_spacing(spacing, kind)
}
/// Change the layouting axes used by this layouter.
pub fn set_axes(&mut self, axes: LayoutAxes) -> LayoutResult<()> {
self.finish_line_if_not_empty()?;
pub fn set_axes(&mut self, axes: LayoutAxes) {
self.finish_line_if_not_empty();
self.ctx.axes = axes;
Ok(self.stack.set_axes(axes))
self.stack.set_axes(axes)
}
/// Change the layouting spaces to use.
@ -224,19 +218,19 @@ impl LineLayouter {
}
/// Finish the last line and compute the final multi-layout.
pub fn finish(mut self) -> LayoutResult<MultiLayout> {
self.finish_line_if_not_empty()?;
pub fn finish(mut self) -> MultiLayout {
self.finish_line_if_not_empty();
self.stack.finish()
}
/// Finish the currently active space and start a new one.
pub fn finish_space(&mut self, hard: bool) -> LayoutResult<()> {
self.finish_line_if_not_empty()?;
pub fn finish_space(&mut self, hard: bool) {
self.finish_line_if_not_empty();
self.stack.finish_space(hard)
}
/// Add the current line to the stack and start a new line.
pub fn finish_line(&mut self) -> LayoutResult<()> {
pub fn finish_line(&mut self) {
let mut actions = LayoutActions::new();
let layouts = std::mem::replace(&mut self.run.layouts, vec![]);
@ -257,21 +251,17 @@ impl LineLayouter {
alignment: self.run.alignment
.unwrap_or(LayoutAlignment::new(Origin, Origin)),
actions: actions.to_vec(),
})?;
});
self.run = LineRun::new();
self.stack.add_spacing(self.ctx.line_spacing, LINE_KIND);
Ok(())
self.stack.add_spacing(self.ctx.line_spacing, SpacingKind::LINE);
}
/// Finish the current line if it is not empty.
fn finish_line_if_not_empty(&mut self) -> LayoutResult<()> {
fn finish_line_if_not_empty(&mut self) {
if !self.line_is_empty() {
self.finish_line()
} else {
Ok(())
}
}
}

View File

@ -4,23 +4,20 @@ use std::io::{self, Write};
use smallvec::SmallVec;
use toddle::query::{SharedFontLoader, FontIndex};
use crate::error::Error;
use crate::syntax::SpanVec;
use crate::size::{Size, Size2D, SizeBox};
use crate::style::LayoutStyle;
mod actions;
mod tree;
mod model;
mod line;
mod stack;
mod text;
/// Common types for layouting.
pub mod prelude {
pub use super::{
layout, LayoutResult,
MultiLayout, Layout, LayoutContext, LayoutSpaces, LayoutSpace,
LayoutExpansion, LayoutAxes, GenericAxis, SpecificAxis, Direction,
LayoutAlignment, Alignment, SpacingKind,
};
pub use super::*;
pub use GenericAxis::*;
pub use SpecificAxis::*;
pub use Direction::*;
@ -29,7 +26,7 @@ pub mod prelude {
/// Different kinds of layouters (fully re-exported).
pub mod layouters {
pub use super::tree::layout;
pub use super::model::layout;
pub use super::line::{LineLayouter, LineContext};
pub use super::stack::{StackLayouter, StackContext};
pub use super::text::{layout_text, TextContext};
@ -40,8 +37,19 @@ pub use self::layouters::*;
pub use self::prelude::*;
/// The result type for layouting.
pub type LayoutResult<T> = crate::TypesetResult<T>;
pub struct Layouted<T> {
pub output: T,
pub errors: SpanVec<Error>,
}
impl<T> Layouted<T> {
pub fn map<F, U>(self, f: F) -> Layouted<U> where F: FnOnce(T) -> U {
Layouted {
output: f(self.output),
errors: self.errors,
}
}
}
/// A collection of layouts.
pub type MultiLayout = Vec<Layout>;
@ -361,14 +369,16 @@ pub enum SpacingKind {
Soft(u32),
}
/// The standard spacing kind used for paragraph spacing.
const PARAGRAPH_KIND: SpacingKind = SpacingKind::Soft(1);
impl SpacingKind {
/// The standard spacing kind used for paragraph spacing.
pub const PARAGRAPH: SpacingKind = SpacingKind::Soft(1);
/// The standard spacing kind used for line spacing.
const LINE_KIND: SpacingKind = SpacingKind::Soft(2);
/// The standard spacing kind used for line spacing.
pub const LINE: SpacingKind = SpacingKind::Soft(2);
/// The standard spacing kind used for word spacing.
const WORD_KIND: SpacingKind = SpacingKind::Soft(1);
/// The standard spacing kind used for word spacing.
pub const WORD: SpacingKind = SpacingKind::Soft(1);
}
/// The last appeared spacing.
#[derive(Debug, Copy, Clone, PartialEq)]

193
src/layout/model.rs Normal file
View File

@ -0,0 +1,193 @@
use std::pin::Pin;
use std::future::Future;
use smallvec::smallvec;
use crate::error::Error;
use crate::func::Command;
use crate::syntax::{Model, DynFuture, SyntaxModel, Node};
use crate::syntax::{SpanVec, Spanned, Span, offset_spans};
use super::*;
pub async fn layout(
model: &SyntaxModel,
ctx: LayoutContext<'_, '_>
) -> Layouted<MultiLayout> {
let mut layouter = ModelLayouter::new(ctx);
layouter.layout_syntax_model(model).await;
layouter.finish()
}
#[derive(Debug, Clone)]
struct ModelLayouter<'a, 'p> {
ctx: LayoutContext<'a, 'p>,
layouter: LineLayouter,
style: LayoutStyle,
errors: SpanVec<Error>,
}
impl<'a, 'p> ModelLayouter<'a, 'p> {
/// Create a new syntax tree layouter.
fn new(ctx: LayoutContext<'a, 'p>) -> ModelLayouter<'a, 'p> {
ModelLayouter {
layouter: LineLayouter::new(LineContext {
spaces: ctx.spaces.clone(),
axes: ctx.axes,
alignment: ctx.alignment,
repeat: ctx.repeat,
debug: ctx.debug,
line_spacing: ctx.style.text.line_spacing(),
}),
style: ctx.style.clone(),
ctx,
errors: vec![],
}
}
fn layout<'r>(
&'r mut self,
model: Spanned<&'r dyn Model>
) -> DynFuture<'r, ()> { Box::pin(async move {
let layouted = model.v.layout(LayoutContext {
style: &self.style,
spaces: self.layouter.remaining(),
nested: true,
debug: false,
.. self.ctx
}).await;
let commands = layouted.output;
self.errors.extend(offset_spans(layouted.errors, model.span.start));
for command in commands {
self.execute_command(command, model.span);
}
}) }
fn execute_command<'r>(
&'r mut self,
command: Command<'r>,
model_span: Span,
) -> DynFuture<'r, ()> { Box::pin(async move {
use Command::*;
match command {
LayoutSyntaxModel(model) => self.layout_syntax_model(model).await,
Add(layout) => self.layouter.add(layout),
AddMultiple(layouts) => self.layouter.add_multiple(layouts),
SpacingFunc(space, kind, axis) => match axis {
Primary => self.layouter.add_primary_spacing(space, kind),
Secondary => self.layouter.add_secondary_spacing(space, kind),
}
FinishLine => self.layouter.finish_line(),
FinishSpace => self.layouter.finish_space(true),
BreakParagraph => self.layout_paragraph(),
BreakPage => {
if self.ctx.nested {
self.errors.push(Spanned::new(
Error::new( "page break cannot be issued from nested context"),
model_span,
));
} else {
self.layouter.finish_space(true)
}
}
SetTextStyle(style) => {
self.layouter.set_line_spacing(style.line_spacing());
self.style.text = style;
}
SetPageStyle(style) => {
if self.ctx.nested {
self.errors.push(Spanned::new(
Error::new("page style cannot be changed from nested context"),
model_span,
));
} else {
self.style.page = style;
let margins = style.margins();
self.ctx.base = style.dimensions.unpadded(margins);
self.layouter.set_spaces(smallvec![
LayoutSpace {
dimensions: style.dimensions,
padding: margins,
expansion: LayoutExpansion::new(true, true),
}
], true);
}
}
SetAlignment(alignment) => self.ctx.alignment = alignment,
SetAxes(axes) => {
self.layouter.set_axes(axes);
self.ctx.axes = axes;
}
}
}) }
fn layout_syntax_model<'r>(
&'r mut self,
model: &'r SyntaxModel
) -> DynFuture<'r, ()> { Box::pin(async move {
use Node::*;
for node in &model.nodes {
match &node.v {
Space => self.layout_space(),
Newline => self.layout_paragraph(),
Text(text) => self.layout_text(text).await,
ToggleItalic => self.style.text.variant.style.toggle(),
ToggleBolder => {
let fac = if self.style.text.bolder { -1 } else { 1 };
self.style.text.variant.weight.0 += 300 * fac;
self.style.text.bolder = !self.style.text.bolder;
}
ToggleMonospace => {
let list = &mut self.style.text.fallback.list;
match list.get(0).map(|s| s.as_str()) {
Some("monospace") => { list.remove(0); },
_ => list.insert(0, "monospace".to_string()),
}
}
Node::Model(model) => {
self.layout(Spanned::new(model.as_ref(), node.span)).await;
}
}
}
}) }
async fn layout_text(&mut self, text: &str) {
self.layouter.add(layout_text(text, TextContext {
loader: &self.ctx.loader,
style: &self.style.text,
axes: self.ctx.axes,
alignment: self.ctx.alignment,
}).await)
}
fn layout_space(&mut self) {
self.layouter.add_primary_spacing(
self.style.text.word_spacing(),
SpacingKind::WORD,
);
}
fn layout_paragraph(&mut self) {
self.layouter.add_secondary_spacing(
self.style.text.paragraph_spacing(),
SpacingKind::PARAGRAPH,
);
}
fn finish(self) -> Layouted<MultiLayout> {
Layouted {
output: self.layouter.finish(),
errors: self.errors,
}
}
}

View File

@ -68,10 +68,12 @@ impl StackLayouter {
}
/// Add a layout to the stack.
pub fn add(&mut self, layout: Layout) -> LayoutResult<()> {
// If the alignment cannot be fit in this space, finish it.
if !self.update_rulers(layout.alignment) {
self.finish_space(true)?;
pub fn add(&mut self, layout: Layout) {
// 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 {
self.finish_space(true);
}
// Now, we add a possibly cached soft space. If the secondary alignment
@ -81,14 +83,9 @@ impl StackLayouter {
self.add_spacing(spacing, SpacingKind::Hard);
}
// Find the first space that fits the layout.
while !self.space.usable.fits(layout.dimensions) {
if self.space_is_last() && self.space_is_empty() {
error!("cannot fit box of size {} into usable size of {}",
layout.dimensions, self.space.usable);
}
self.finish_space(true)?;
// TODO: Issue warning about overflow if there is overflow.
if !self.space.usable.fits(layout.dimensions) && self.ctx.repeat {
self.skip_to_fitting_space(layout.dimensions);
}
// Change the usable space and size of the space.
@ -98,18 +95,15 @@ impl StackLayouter {
// again.
self.space.layouts.push((self.ctx.axes, layout));
self.space.last_spacing = LastSpacing::None;
Ok(())
}
/// Add multiple layouts to the stack.
///
/// This function simply calls `add` repeatedly for each layout.
pub fn add_multiple(&mut self, layouts: MultiLayout) -> LayoutResult<()> {
pub fn add_multiple(&mut self, layouts: MultiLayout) {
for layout in layouts {
self.add(layout)?;
self.add(layout);
}
Ok(())
}
/// Add secondary spacing to the stack.
@ -215,6 +209,19 @@ impl StackLayouter {
}
}
/// Move to the first space that can fit the given dimensions or do nothing
/// if no space is capable of that.
pub fn skip_to_fitting_space(&mut self, dimensions: Size2D) {
let start = self.next_space();
for (index, space) in self.ctx.spaces[start..].iter().enumerate() {
if space.usable().fits(dimensions) {
self.finish_space(true);
self.start_space(start + index, true);
return;
}
}
}
/// The remaining unpadded, unexpanding spaces. If a multi-layout is laid
/// out into these spaces, it will fit into this stack.
pub fn remaining(&self) -> LayoutSpaces {
@ -251,19 +258,15 @@ impl StackLayouter {
}
/// Compute the finished multi-layout.
pub fn finish(mut self) -> LayoutResult<MultiLayout> {
pub fn finish(mut self) -> MultiLayout {
if self.space.hard || !self.space_is_empty() {
self.finish_space(false)?;
self.finish_space(false);
}
Ok(self.layouts)
self.layouts
}
/// Finish the current space and start a new one.
pub fn finish_space(&mut self, hard: bool) -> LayoutResult<()> {
if !self.ctx.repeat && hard {
error!("cannot create new space in a non-repeating context");
}
pub fn finish_space(&mut self, hard: bool) {
let space = self.ctx.spaces[self.space.index];
// ------------------------------------------------------------------ //
@ -376,7 +379,7 @@ impl StackLayouter {
// ------------------------------------------------------------------ //
// Step 5: Start the next space.
Ok(self.start_space(self.next_space(), hard))
self.start_space(self.next_space(), hard)
}
/// Start a new space with the given index.

View File

@ -6,6 +6,14 @@ use crate::style::TextStyle;
use super::*;
/// Layouts text into a box.
///
/// There is no complex layout involved. The text is simply laid out left-
/// to-right using the correct font for each character.
pub async fn layout_text(text: &str, ctx: TextContext<'_, '_>) -> Layout {
TextLayouter::new(text, ctx).layout().await
}
/// The context for text layouting.
///
/// See [`LayoutContext`] for details about the fields.
@ -17,14 +25,6 @@ pub struct TextContext<'a, 'p> {
pub alignment: LayoutAlignment,
}
/// Layouts text into a box.
///
/// There is no complex layout involved. The text is simply laid out left-
/// to-right using the correct font for each character.
pub async fn layout_text(text: &str, ctx: TextContext<'_, '_>) -> LayoutResult<Layout> {
TextLayouter::new(text, ctx).layout().await
}
/// Layouts text into boxes.
struct TextLayouter<'a, 'p> {
ctx: TextContext<'a, 'p>,
@ -49,14 +49,14 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
}
/// Layout the text
async fn layout(mut self) -> LayoutResult<Layout> {
async fn layout(mut self) -> Layout {
if self.ctx.axes.primary.is_positive() {
for c in self.text.chars() {
self.layout_char(c).await?;
self.layout_char(c).await;
}
} else {
for c in self.text.chars().rev() {
self.layout_char(c).await?;
self.layout_char(c).await;
}
}
@ -64,16 +64,20 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
self.actions.add(LayoutAction::WriteText(self.buffer));
}
Ok(Layout {
Layout {
dimensions: Size2D::new(self.width, self.ctx.style.font_size()),
alignment: self.ctx.alignment,
actions: self.actions.to_vec(),
})
}
}
/// Layout an individual character.
async fn layout_char(&mut self, c: char) -> LayoutResult<()> {
let (index, char_width) = self.select_font(c).await?;
async fn layout_char(&mut self, c: char) {
let (index, char_width) = match self.select_font(c).await {
Some(selected) => selected,
// TODO: Issue warning about missing character.
None => return,
};
self.width += char_width;
@ -88,13 +92,11 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
}
self.buffer.push(c);
Ok(())
}
/// Select the best font for a character and return its index along with
/// the width of the char in the font.
async fn select_font(&mut self, c: char) -> LayoutResult<(FontIndex, Size)> {
async fn select_font(&mut self, c: char) -> Option<(FontIndex, Size)> {
let mut loader = self.ctx.loader.borrow_mut();
let query = FontQuery {
@ -104,26 +106,27 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
};
if let Some((font, index)) = loader.get(query).await {
let font_unit_ratio = 1.0 / (font.read_table::<Header>()?.units_per_em as f32);
let header = font.read_table::<Header>().ok()?;
let font_unit_ratio = 1.0 / (header.units_per_em as f32);
let font_unit_to_size = |x| Size::pt(font_unit_ratio * x);
let glyph = font
.read_table::<CharMap>()?
.get(c)
.expect("select_font: font should have char");
.read_table::<CharMap>()
.ok()?
.get(c)?;
let glyph_width = font
.read_table::<HorizontalMetrics>()?
.get(glyph)
.expect("select_font: font should have glyph")
.read_table::<HorizontalMetrics>()
.ok()?
.get(glyph)?
.advance_width as f32;
let char_width = font_unit_to_size(glyph_width)
* self.ctx.style.font_size().to_pt();
return Ok((index, char_width));
Some((index, char_width))
} else {
None
}
error!("no suitable font for character `{}`", c);
}
}

View File

@ -1,163 +0,0 @@
use std::pin::Pin;
use std::future::Future;
use smallvec::smallvec;
use crate::func::Command;
use crate::syntax::{SyntaxTree, Node, FuncCall};
use super::*;
type RecursiveResult<'a, T> = Pin<Box<dyn Future<Output=LayoutResult<T>> + 'a>>;
/// Layout a syntax tree into a multibox.
pub async fn layout(tree: &SyntaxTree, ctx: LayoutContext<'_, '_>) -> LayoutResult<MultiLayout> {
let mut layouter = TreeLayouter::new(ctx);
layouter.layout(tree).await?;
layouter.finish()
}
#[derive(Debug, Clone)]
struct TreeLayouter<'a, 'p> {
ctx: LayoutContext<'a, 'p>,
layouter: LineLayouter,
style: LayoutStyle,
}
impl<'a, 'p> TreeLayouter<'a, 'p> {
/// Create a new syntax tree layouter.
fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> {
TreeLayouter {
layouter: LineLayouter::new(LineContext {
spaces: ctx.spaces.clone(),
axes: ctx.axes,
alignment: ctx.alignment,
repeat: ctx.repeat,
debug: ctx.debug,
line_spacing: ctx.style.text.line_spacing(),
}),
style: ctx.style.clone(),
ctx,
}
}
fn layout<'b>(&'b mut self, tree: &'b SyntaxTree) -> RecursiveResult<'b, ()> {
Box::pin(async move {
for node in &tree.nodes {
match &node.v {
Node::Text(text) => self.layout_text(text).await?,
Node::Space => self.layout_space(),
Node::Newline => self.layout_paragraph()?,
Node::ToggleItalic => self.style.text.variant.style.toggle(),
Node::ToggleBolder => {
self.style.text.variant.weight.0 += 300 *
if self.style.text.bolder { -1 } else { 1 };
self.style.text.bolder = !self.style.text.bolder;
}
Node::ToggleMonospace => {
let list = &mut self.style.text.fallback.list;
match list.get(0).map(|s| s.as_str()) {
Some("monospace") => { list.remove(0); },
_ => list.insert(0, "monospace".to_string()),
}
}
Node::Func(func) => self.layout_func(func).await?,
}
}
Ok(())
})
}
async fn layout_text(&mut self, text: &str) -> LayoutResult<()> {
let layout = layout_text(text, TextContext {
loader: &self.ctx.loader,
style: &self.style.text,
axes: self.ctx.axes,
alignment: self.ctx.alignment,
}).await?;
self.layouter.add(layout)
}
fn layout_space(&mut self) {
self.layouter.add_primary_spacing(self.style.text.word_spacing(), WORD_KIND);
}
fn layout_paragraph(&mut self) -> LayoutResult<()> {
self.layouter.add_secondary_spacing(self.style.text.paragraph_spacing(), PARAGRAPH_KIND)
}
fn layout_func<'b>(&'b mut self, func: &'b FuncCall) -> RecursiveResult<'b, ()> {
Box::pin(async move {
let commands = func.0.layout(LayoutContext {
style: &self.style,
spaces: self.layouter.remaining(),
nested: true,
debug: false,
.. self.ctx
}).await?;
for command in commands {
use Command::*;
match command {
LayoutTree(tree) => self.layout(tree).await?,
Add(layout) => self.layouter.add(layout)?,
AddMultiple(layouts) => self.layouter.add_multiple(layouts)?,
SpacingFunc(space, kind, axis) => match axis {
Primary => self.layouter.add_primary_spacing(space, kind),
Secondary => self.layouter.add_secondary_spacing(space, kind)?,
}
FinishLine => self.layouter.finish_line()?,
FinishSpace => self.layouter.finish_space(true)?,
BreakParagraph => self.layout_paragraph()?,
BreakPage => {
if self.ctx.nested {
error!("page break cannot be issued from nested context");
}
self.layouter.finish_space(true)?
}
SetTextStyle(style) => {
self.layouter.set_line_spacing(style.line_spacing());
self.style.text = style;
}
SetPageStyle(style) => {
if self.ctx.nested {
error!("page style cannot be altered in nested context");
}
self.style.page = style;
let margins = style.margins();
self.ctx.base = style.dimensions.unpadded(margins);
self.layouter.set_spaces(smallvec![
LayoutSpace {
dimensions: style.dimensions,
padding: margins,
expansion: LayoutExpansion::new(true, true),
}
], true);
}
SetAlignment(alignment) => self.ctx.alignment = alignment,
SetAxes(axes) => {
self.layouter.set_axes(axes)?;
self.ctx.axes = axes;
}
}
}
Ok(())
})
}
fn finish(self) -> LayoutResult<MultiLayout> {
self.layouter.finish()
}
}

View File

@ -27,13 +27,14 @@ use toddle::query::{FontLoader, FontProvider, SharedFontLoader};
use toddle::Error as FontError;
use crate::func::Scope;
use crate::layout::{MultiLayout, LayoutResult};
use crate::syntax::{parse, SyntaxTree, Colorization, ErrorMap, ParseContext, Span};
use crate::layout::{Layouted, LayoutContext, MultiLayout};
use crate::syntax::{parse, ParseContext, Parsed, SyntaxModel, Position};
use crate::style::{LayoutStyle, PageStyle, TextStyle};
#[macro_use]
mod macros;
pub mod export;
pub mod error;
#[macro_use]
pub mod func;
pub mod layout;
@ -51,6 +52,8 @@ pub struct Typesetter<'p> {
loader: SharedFontLoader<'p>,
/// The base layouting style.
style: LayoutStyle,
/// The standard library scope.
scope: Scope,
}
impl<'p> Typesetter<'p> {
@ -59,6 +62,7 @@ impl<'p> Typesetter<'p> {
Typesetter {
loader: RefCell::new(FontLoader::new()),
style: LayoutStyle::default(),
scope: Scope::with_std(),
}
}
@ -84,17 +88,16 @@ impl<'p> Typesetter<'p> {
}
/// Parse source code into a syntax tree.
pub fn parse(&self, src: &str) -> (SyntaxTree, Colorization, ErrorMap) {
let scope = Scope::with_std();
parse(src, ParseContext { scope: &scope })
pub fn parse(&self, src: &str) -> Parsed<SyntaxModel> {
parse(Position::ZERO, src, ParseContext { scope: &self.scope })
}
/// Layout a syntax tree and return the produced layout.
pub async fn layout(&self, tree: &SyntaxTree) -> LayoutResult<MultiLayout> {
pub async fn layout(&self, model: &SyntaxModel) -> Layouted<MultiLayout> {
use crate::layout::prelude::*;
let margins = self.style.page.margins();
Ok(layout(
&tree,
layout(
&model,
LayoutContext {
loader: &self.loader,
style: &self.style,
@ -110,42 +113,12 @@ impl<'p> Typesetter<'p> {
nested: false,
debug: false,
},
).await?)
).await
}
/// Process source code directly into a layout.
pub async fn typeset(&self, src: &str) -> TypesetResult<MultiLayout> {
let tree = self.parse(src).0;
let layout = self.layout(&tree).await?;
Ok(layout)
/// Process source code directly into a collection of layouts.
pub async fn typeset(&self, src: &str) -> MultiLayout {
let tree = self.parse(src).output;
self.layout(&tree).await.output
}
}
/// The result type for typesetting.
pub type TypesetResult<T> = Result<T, TypesetError>;
/// The error type for typesetting.
pub struct TypesetError {
pub message: String,
pub span: Option<Span>,
}
impl TypesetError {
/// Create a new typesetting error.
pub fn with_message(message: impl Into<String>) -> TypesetError {
TypesetError { message: message.into(), span: None }
}
}
error_type! {
self: TypesetError,
show: f => {
write!(f, "{}", self.message)?;
if let Some(span) = self.span {
write!(f, " at {}", span)?;
}
Ok(())
},
from: (err: std::io::Error, TypesetError::with_message(err.to_string())),
from: (err: FontError, TypesetError::with_message(err.to_string())),
}

View File

@ -1,3 +1,4 @@
use crate::size::ScaleSize;
use super::*;
@ -126,6 +127,7 @@ impl Display for Object {
}
}
/// A key-value pair in an object.
#[derive(Clone, PartialEq)]
pub struct Pair {
pub key: Spanned<Ident>,
@ -144,7 +146,6 @@ debug_display!(Tuple);
debug_display!(Object);
debug_display!(Pair);
/// Kinds of expressions.
pub trait ExpressionKind: Sized {
/// The name of the expression in an `expected <name>` error.

125
src/syntax/func.rs Normal file
View File

@ -0,0 +1,125 @@
use super::*;
#[derive(Debug, Clone, PartialEq)]
pub struct FuncHeader {
pub name: Spanned<Ident>,
pub args: FuncArgs,
}
#[derive(Debug, Clone, PartialEq)]
pub struct FuncArgs {
pub pos: Tuple,
pub key: Object,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Arg {
Pos(Spanned<Expression>),
Key(Pair),
}
impl Arg {
/// The span or the value or combined span of key and value.
pub fn span(&self) -> Span {
match self {
Arg::Pos(item) => item.span,
Arg::Key(Pair { key, value }) => Span::merge(key.span, value.span),
}
}
}
impl FuncArgs {
pub fn new() -> FuncArgs {
FuncArgs {
pos: Tuple::new(),
key: Object::new(),
}
}
/// Add an argument.
pub fn add(&mut self, arg: Arg) {
match arg {
Arg::Pos(item) => self.add_pos(item),
Arg::Key(pair) => self.add_key_pair(pair),
}
}
/// Add a positional argument.
pub fn add_pos(&mut self, item: Spanned<Expression>) {
self.pos.add(item);
}
/// Add a keyword argument.
pub fn add_key(&mut self, key: Spanned<Ident>, value: Spanned<Expression>) {
self.key.add(key, value);
}
/// Add a keyword argument from an existing pair.
pub fn add_key_pair(&mut self, pair: Pair) {
self.key.add_pair(pair);
}
// /// Force-extract the first positional argument.
// pub fn get_pos<E: ExpressionKind>(&mut self) -> ParseResult<E> {
// expect(self.get_pos_opt())
// }
// /// Extract the first positional argument.
// pub fn get_pos_opt<E: ExpressionKind>(&mut self) -> ParseResult<Option<E>> {
// Ok(if !self.positional.items.is_empty() {
// let spanned = self.positional.items.remove(0);
// Some(E::from_expr(spanned)?)
// } else {
// None
// })
// }
// /// Force-extract a keyword argument.
// pub fn get_key<E: ExpressionKind>(&mut self, name: &str) -> ParseResult<E> {
// expect(self.get_key_opt(name))
// }
// /// Extract a keyword argument.
// pub fn get_key_opt<E: ExpressionKind>(&mut self, name: &str) -> ParseResult<Option<E>> {
// self.keyword.pairs.iter()
// .position(|p| p.key.v.0 == name)
// .map(|index| {
// let value = self.keyword.pairs.swap_remove(index).value;
// E::from_expr(value)
// })
// .transpose()
// }
// /// Iterator over positional arguments.
// pub fn iter_pos(&mut self) -> std::vec::IntoIter<Spanned<Expression>> {
// let tuple = std::mem::replace(&mut self.positional, Tuple::new());
// tuple.items.into_iter()
// }
// /// Iterator over all keyword arguments.
// pub fn iter_keys(&mut self) -> std::vec::IntoIter<Pair> {
// let object = std::mem::replace(&mut self.keyword, Object::new());
// object.pairs.into_iter()
// }
// /// Clear the argument lists.
// pub fn clear(&mut self) {
// self.positional.items.clear();
// self.keyword.pairs.clear();
// }
// /// Whether both the positional and keyword argument lists are empty.
// pub fn is_empty(&self) -> bool {
// self.positional.items.is_empty() && self.keyword.pairs.is_empty()
// }
}
// /// Extract the option expression kind from the option or return an error.
// fn expect<E: ExpressionKind>(opt: ParseResult<Option<E>>) -> ParseResult<E> {
// match opt {
// Ok(Some(spanned)) => Ok(spanned),
// Ok(None) => error!("expected {}", E::NAME),
// Err(e) => Err(e),
// }
// }

View File

@ -1,99 +1,125 @@
//! Tokenization and parsing of source code.
use std::fmt::{self, Display, Formatter};
use unicode_xid::UnicodeXID;
use std::any::Any;
use std::fmt::{self, Debug, Display, Formatter};
use std::future::Future;
use std::pin::Pin;
use serde::Serialize;
use crate::func::LayoutFunc;
use crate::size::{Size, ScaleSize};
pub type ParseResult<T> = crate::TypesetResult<T>;
use crate::error::Error;
use crate::func::{Commands, Command};
use crate::layout::{Layouted, LayoutContext};
use crate::size::Size;
pub_use_mod!(expr);
pub_use_mod!(func);
pub_use_mod!(tokens);
pub_use_mod!(parsing);
pub_use_mod!(span);
/// Common syntax types.
pub mod prelude {
pub use super::*;
}
/// A minimal semantic entity of source code.
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Token<'s> {
/// One or more whitespace characters. The contained `usize` denotes the
/// number of newlines that were contained in the whitespace.
Whitespace(usize),
/// A line comment with inner string contents `//<&'s str>\n`.
LineComment(&'s str),
/// A block comment with inner string contents `/*<&'s str>*/`. The comment
/// can contain nested block comments.
BlockComment(&'s str),
/// An erroneous `*/` without an opening block comment.
StarSlash,
pub struct Parsed<T> {
pub output: T,
pub errors: SpanVec<Error>,
pub decorations: SpanVec<Decoration>,
}
/// A left bracket: `[`.
LeftBracket,
/// A right bracket: `]`.
RightBracket,
impl<T> Parsed<T> {
pub fn map<F, U>(self, f: F) -> Parsed<U> where F: FnOnce(T) -> U {
Parsed {
output: f(self.output),
errors: self.errors,
decorations: self.decorations,
}
}
}
/// A left parenthesis in a function header: `(`.
LeftParen,
/// A right parenthesis in a function header: `)`.
RightParen,
/// A left brace in a function header: `{`.
LeftBrace,
/// A right brace in a function header: `}`.
RightBrace,
#[async_trait::async_trait(?Send)]
pub trait Model: Debug + ModelBounds {
async fn layout<'a>(
&'a self,
ctx: LayoutContext<'_, '_>
) -> Layouted<Commands<'a>>;
}
/// A colon in a function header: `:`.
Colon,
/// A comma in a function header: `:`.
Comma,
/// An equals sign in a function header: `=`.
Equals,
pub type DynFuture<'a, T> = Pin<Box<dyn Future<Output=T> + 'a>>;
/// An identifier in a function header: `center`.
ExprIdent(&'s str),
/// A quoted string in a function header: `"..."`.
ExprStr(&'s str),
/// A number in a function header: `3.14`.
ExprNumber(f64),
/// A size in a function header: `12pt`.
ExprSize(Size),
/// A boolean in a function header: `true | false`.
ExprBool(bool),
impl dyn Model {
pub fn downcast<T>(&self) -> Option<&T> where T: Model + 'static {
self.as_any().downcast_ref::<T>()
}
}
/// A star in body-text.
Star,
/// An underscore in body-text.
Underscore,
/// A backtick in body-text.
Backtick,
impl PartialEq for dyn Model {
fn eq(&self, other: &dyn Model) -> bool {
self.bound_eq(other)
}
}
/// Any other consecutive string.
Text(&'s str),
impl Clone for Box<dyn Model> {
fn clone(&self) -> Self {
self.bound_clone()
}
}
pub trait ModelBounds {
fn as_any(&self) -> &dyn Any;
fn bound_eq(&self, other: &dyn Model) -> bool;
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())
}
}
/// A tree representation of source code.
#[derive(Debug, PartialEq)]
pub struct SyntaxTree {
pub nodes: Vec<Spanned<Node>>,
#[derive(Debug, Clone, PartialEq)]
pub struct SyntaxModel {
pub nodes: SpanVec<Node>,
}
impl SyntaxTree {
/// Create an empty syntax tree.
pub fn new() -> SyntaxTree {
SyntaxTree { nodes: vec![] }
impl SyntaxModel {
/// Create an empty syntax model.
pub fn new() -> SyntaxModel {
SyntaxModel { nodes: vec![] }
}
/// Add a node to the tree.
/// Add a node to the model.
pub fn add(&mut self, node: Spanned<Node>) {
self.nodes.push(node);
}
}
#[async_trait::async_trait(?Send)]
impl Model for SyntaxModel {
async fn layout<'a>(&'a self, ctx: LayoutContext<'_, '_>) -> Layouted<Commands<'a>> {
Layouted {
output: vec![Command::LayoutSyntaxModel(self)],
errors: vec![],
}
}
}
/// A node in the syntax tree.
#[derive(PartialEq)]
#[derive(Debug, Clone)]
pub enum Node {
/// A number of whitespace characters containing less than two newlines.
Space,
@ -107,215 +133,29 @@ pub enum Node {
ToggleBolder,
/// Monospace enabled / disabled.
ToggleMonospace,
/// A function invocation.
Func(FuncCall),
/// A submodel.
Model(Box<dyn Model>),
}
impl Display for Node {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Node::Space => write!(f, "Space"),
Node::Newline => write!(f, "Newline"),
Node::Text(text) => write!(f, "{:?}", text),
Node::ToggleItalic => write!(f, "ToggleItalic"),
Node::ToggleBolder => write!(f, "ToggleBold"),
Node::ToggleMonospace => write!(f, "ToggleMonospace"),
Node::Func(func) => {
if f.alternate() {
write!(f, "{:#?}", func.0)
} else {
write!(f, "{:?}", func.0)
}
}
impl PartialEq for Node {
fn eq(&self, other: &Node) -> bool {
use Node::*;
match (self, other) {
(Space, Space) => true,
(Newline, Newline) => true,
(Text(a), Text(b)) => a == b,
(ToggleItalic, ToggleItalic) => true,
(ToggleBolder, ToggleBolder) => true,
(ToggleMonospace, ToggleMonospace) => true,
(Model(a), Model(b)) => a == b,
_ => false,
}
}
}
debug_display!(Node);
/// An invocation of a function.
#[derive(Debug)]
pub struct FuncCall(pub Box<dyn LayoutFunc>);
impl PartialEq for FuncCall {
fn eq(&self, other: &FuncCall) -> bool {
&self.0 == &other.0
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FuncHeader {
pub name: Spanned<Ident>,
pub args: FuncArgs,
}
#[derive(Debug, Clone, PartialEq)]
pub struct FuncArgs {
pub positional: Tuple,
pub keyword: Object,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Arg {
Pos(Spanned<Expression>),
Key(Pair),
}
impl Arg {
/// The span or the value or combined span of key and value.
pub fn span(&self) -> Span {
match self {
Arg::Pos(spanned) => spanned.span,
Arg::Key(Pair { key, value }) => Span::merge(key.span, value.span),
}
}
}
impl FuncArgs {
pub fn new() -> FuncArgs {
FuncArgs {
positional: Tuple::new(),
keyword: Object::new(),
}
}
/// Add a positional argument.
pub fn add_pos(&mut self, item: Spanned<Expression>) {
self.positional.add(item);
}
/// Force-extract the first positional argument.
pub fn get_pos<E: ExpressionKind>(&mut self) -> ParseResult<E> {
expect(self.get_pos_opt())
}
/// Extract the first positional argument.
pub fn get_pos_opt<E: ExpressionKind>(&mut self) -> ParseResult<Option<E>> {
Ok(if !self.positional.items.is_empty() {
let spanned = self.positional.items.remove(0);
Some(E::from_expr(spanned)?)
} else {
None
})
}
/// Add a keyword argument.
pub fn add_key(&mut self, key: Spanned<Ident>, value: Spanned<Expression>) {
self.keyword.add(key, value);
}
/// Add a keyword argument from an existing pair.
pub fn add_key_pair(&mut self, pair: Pair) {
self.keyword.add_pair(pair);
}
/// Force-extract a keyword argument.
pub fn get_key<E: ExpressionKind>(&mut self, name: &str) -> ParseResult<E> {
expect(self.get_key_opt(name))
}
/// Extract a keyword argument.
pub fn get_key_opt<E: ExpressionKind>(&mut self, name: &str) -> ParseResult<Option<E>> {
self.keyword.pairs.iter()
.position(|p| p.key.v.0 == name)
.map(|index| {
let value = self.keyword.pairs.swap_remove(index).value;
E::from_expr(value)
})
.transpose()
}
/// Iterator over positional arguments.
pub fn iter_pos(&mut self) -> std::vec::IntoIter<Spanned<Expression>> {
let tuple = std::mem::replace(&mut self.positional, Tuple::new());
tuple.items.into_iter()
}
/// Iterator over all keyword arguments.
pub fn iter_keys(&mut self) -> std::vec::IntoIter<Pair> {
let object = std::mem::replace(&mut self.keyword, Object::new());
object.pairs.into_iter()
}
/// Clear the argument lists.
pub fn clear(&mut self) {
self.positional.items.clear();
self.keyword.pairs.clear();
}
/// Whether both the positional and keyword argument lists are empty.
pub fn is_empty(&self) -> bool {
self.positional.items.is_empty() && self.keyword.pairs.is_empty()
}
}
/// Extract the option expression kind from the option or return an error.
fn expect<E: ExpressionKind>(opt: ParseResult<Option<E>>) -> ParseResult<E> {
match opt {
Ok(Some(spanned)) => Ok(spanned),
Ok(None) => error!("expected {}", E::NAME),
Err(e) => Err(e),
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
pub struct Colorization {
pub tokens: Vec<Spanned<ColorToken>>,
}
impl Colorization {
pub fn new() -> Colorization {
Colorization { tokens: vec![] }
}
pub fn add(&mut self, token: ColorToken, span: Span) {
self.tokens.push(Spanned { v: token, span });
}
pub fn replace_last(&mut self, token: ColorToken) {
self.tokens.last_mut().expect("replace_last: no token").v = token;
}
}
/// Entities which can be colored by syntax highlighting.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum ColorToken {
Comment,
Bracket,
FuncName,
Colon,
Key,
Equals,
Comma,
Paren,
Brace,
ExprIdent,
ExprStr,
ExprNumber,
ExprSize,
ExprBool,
Bold,
Italic,
Monospace,
Invalid,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
pub struct ErrorMap {
pub errors: Vec<Spanned<String>>,
}
impl ErrorMap {
pub fn new() -> ErrorMap {
ErrorMap { errors: vec![] }
}
pub fn add(&mut self, message: impl Into<String>, span: Span) {
self.errors.push(Spanned { v: message.into(), span });
}
pub fn add_at(&mut self, message: impl Into<String>, pos: Position) {
self.errors.push(Spanned { v: message.into(), span: Span::at(pos) })
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Decoration {
ValidFuncName,
InvalidFuncName,
ArgumentKey,
}

View File

@ -3,9 +3,8 @@ use super::*;
use Token::*;
/// Parses source code into a syntax tree given a context.
pub fn parse(src: &str, ctx: ParseContext) -> (SyntaxTree, Colorization, ErrorMap) {
Parser::new(src, ctx).parse()
pub fn parse(start: Position, src: &str, ctx: ParseContext) -> Parsed<SyntaxModel> {
Parser::new(start, src, ctx).parse()
}
/// The context for parsing.
@ -18,64 +17,68 @@ pub struct ParseContext<'a> {
struct Parser<'s> {
src: &'s str,
ctx: ParseContext<'s>,
colorization: Colorization,
error_map: ErrorMap,
tokens: Tokens<'s>,
peeked: Option<Option<Spanned<Token<'s>>>>,
position: Position,
last_position: Position,
errors: SpanVec<Error>,
decorations: SpanVec<Decoration>,
}
impl<'s> Parser<'s> {
fn new(src: &'s str, ctx: ParseContext<'s>) -> Parser<'s> {
fn new(start: Position, src: &'s str, ctx: ParseContext<'s>) -> Parser<'s> {
Parser {
src,
ctx,
error_map: ErrorMap::new(),
colorization: Colorization::new(),
tokens: Tokens::new(src),
tokens: tokenize(start, src),
peeked: None,
position: Position::ZERO,
last_position: Position::ZERO,
errors: vec![],
decorations: vec![],
}
}
/// The main parsing entrypoint.
fn parse(mut self) -> (SyntaxTree, Colorization, ErrorMap) {
let mut tree = SyntaxTree::new();
fn parse(mut self) -> Parsed<SyntaxModel> {
let mut model = SyntaxModel::new();
loop {
if let Some(spanned) = self.eat() {
match spanned.v {
LineComment(_) | BlockComment(_) => {}
while let Some(token) = self.eat() {
let mut span = token.span;
let node = match token.v {
LineComment(_) | BlockComment(_) => None,
Whitespace(newlines) => Some(if newlines >= 2 {
Node::Newline
} else {
Node::Space
}),
Whitespace(newlines) => {
tree.add(spanned.map_v(if newlines >= 2 {
Node::Newline
} else {
Node::Space
}));
}
LeftBracket => self.parse_func().map(|spanned| {
span = spanned.span;
spanned.v
}),
LeftBracket => {
if let Some(func) = self.parse_func() {
tree.add(func);
}
}
Star => Some(Node::ToggleBolder),
Underscore => Some(Node::ToggleItalic),
Backtick => Some(Node::ToggleMonospace),
Text(text) => Some(Node::Text(text.to_owned())),
Star => tree.add(spanned.map_v(Node::ToggleBolder)),
Underscore => tree.add(spanned.map_v(Node::ToggleItalic)),
Backtick => tree.add(spanned.map_v(Node::ToggleMonospace)),
Text(text) => tree.add(spanned.map_v(Node::Text(text.to_owned()))),
_ => self.unexpected(spanned),
_ => {
self.unexpected(token);
None
}
} else {
break;
};
if let Some(v) = node {
model.add(Spanned { v, span });
}
}
(tree, self.colorization, self.error_map)
Parsed {
output: model,
errors: self.errors,
decorations: self.decorations,
}
}
/// Parses a function including header and body with the cursor starting
@ -90,12 +93,55 @@ impl<'s> Parser<'s> {
self.expected_at("closing bracket", self.pos());
}
let call = self.parse_func_call(header)?;
let body = if self.peekv() == Some(LeftBracket) {
self.eat();
let start_index = self.tokens.index();
let start_position = self.tokens.pos();
let found = self.tokens.move_to_closing_bracket();
let end_index = self.tokens.index();
let end_position = self.tokens.pos();
let body = &self.src[start_index .. end_index];
self.position = end_position;
if found {
let next = self.eat().map(Spanned::value);
debug_assert_eq!(next, Some(RightBracket));
} else {
self.expected_at("closing bracket", self.pos());
}
Some(Spanned::new(body, Span::new(start_position, end_position)))
} else {
None
};
let header = header?;
let (parser, decoration) = match self.ctx.scope.get_parser(header.name.v.as_str()) {
Ok(parser) => (parser, Decoration::ValidFuncName),
Err(parser) => {
let error = Error::new(format!("unknown function: `{}`", header.name.v));
self.errors.push(Spanned::new(error, header.name.span));
(parser, Decoration::InvalidFuncName)
}
};
self.decorations.push(Spanned::new(decoration, header.name.span));
let parsed = parser(header, body, self.ctx);
self.errors.extend(offset_spans(parsed.errors, start));
self.decorations.extend(offset_spans(parsed.decorations, start));
let node = Node::Model(parsed.output);
let end = self.pos();
let span = Span { start, end };
Some(Spanned { v: Node::Func(call), span })
Some(Spanned { v: node, span })
}
/// Parses a function header including the closing bracket.
@ -125,7 +171,6 @@ impl<'s> Parser<'s> {
match self.peek() {
Some(Spanned { v: ExprIdent(ident), span }) => {
self.eat();
self.colorization.replace_last(ColorToken::FuncName);
return Some(Spanned { v: Ident(ident.to_string()), span });
}
other => self.expected_found_or_at("identifier", other, self.pos()),
@ -144,8 +189,7 @@ impl<'s> Parser<'s> {
match self.peekv() {
Some(RightBracket) | None => break,
_ => match self.parse_arg() {
Some(Arg::Pos(item)) => args.add_pos(item),
Some(Arg::Key(pair)) => args.add_key_pair(pair),
Some(arg) => args.add(arg),
None => {}
}
}
@ -165,11 +209,11 @@ impl<'s> Parser<'s> {
let ident = Ident(ident.to_string());
if let Some(Equals) = self.peekv() {
self.colorization.replace_last(ColorToken::Key);
self.eat();
self.skip_whitespace();
self.decorations.push(Spanned::new(Decoration::ArgumentKey, span));
self.parse_expr().map(|value| {
Arg::Key(Pair {
key: Spanned { v: ident, span },
@ -251,42 +295,6 @@ impl<'s> Parser<'s> {
Spanned { v: Expression::Object(Object::new()), span }
}
/// Parse the body of a function invocation.
fn parse_func_call(&mut self, header: Option<FuncHeader>) -> Option<FuncCall> {
let body = if self.peekv() == Some(LeftBracket) {
self.eat();
let start = self.tokens.index();
let found = self.tokens.move_to_closing_bracket();
let end = self.tokens.index();
self.last_position = self.position;
self.position = self.tokens.pos();
let body = &self.src[start .. end];
if found {
let next = self.eat().map(Spanned::value);
debug_assert_eq!(next, Some(RightBracket));
} else {
self.expected_at("closing bracket", self.pos());
}
Some(body)
} else {
None
};
let header = header?;
let parser = self.ctx.scope.get_parser(header.name.v.as_str()).or_else(|| {
let message = format!("unknown function: `{}`", header.name.v);
self.error_map.add(message, header.name.span);
None
})?;
parser(header, body, self.ctx).ok().map(|f| FuncCall(f))
}
/// Skip all whitespace/comment tokens.
fn skip_whitespace(&mut self) {
self.eat_until(|t|
@ -296,14 +304,16 @@ impl<'s> Parser<'s> {
/// Add an error about an `thing` which was expected but not found at the
/// given position.
fn expected_at(&mut self, thing: &str, pos: Position) {
self.error_map.add_at(format!("expected {}", thing), pos);
let error = Error::new(format!("expected {}", thing));
self.errors.push(Spanned::new(error, Span::at(pos)));
}
/// Add an error about an expected `thing` which was not found, showing
/// what was found instead.
fn expected_found(&mut self, thing: &str, found: Spanned<Token>) {
let message = format!("expected {}, found {}", thing, name(found.v));
self.error_map.add(message, found.span);
let error = Error::new(message);
self.errors.push(Spanned::new(error, found.span));
}
/// Add a found-error if `found` is some and a positional error, otherwise.
@ -321,7 +331,8 @@ impl<'s> Parser<'s> {
/// Add an error about an unexpected token `found`.
fn unexpected(&mut self, found: Spanned<Token>) {
self.error_map.add(format!("unexpected {}", name(found.v)), found.span);
let error = Error::new(format!("unexpected {}", name(found.v)));
self.errors.push(Spanned::new(error, found.span));
}
/// Consume tokens until the function returns true and only consume the last
@ -348,10 +359,6 @@ impl<'s> Parser<'s> {
.unwrap_or_else(|| self.tokens.next());
if let Some(token) = token {
if let Some(color) = color(token.v) {
self.colorization.add(color, token.span);
}
self.last_position = self.position;
self.position = token.span.end;
}
@ -407,23 +414,3 @@ fn name(token: Token) -> &'static str {
Text(_) => "invalid identifier",
}
}
/// The color token corresponding to a token.
fn color(token: Token) -> Option<ColorToken> {
Some(match token {
LineComment(_) | BlockComment(_) => ColorToken::Comment,
LeftBracket | RightBracket => ColorToken::Bracket,
LeftParen | RightParen => ColorToken::Paren,
LeftBrace | RightBrace => ColorToken::Brace,
Colon => ColorToken::Colon,
Comma => ColorToken::Comma,
Equals => ColorToken::Equals,
ExprIdent(_) => ColorToken::ExprIdent,
ExprStr(_) => ColorToken::ExprStr,
ExprNumber(_) => ColorToken::ExprNumber,
ExprSize(_) => ColorToken::ExprSize,
ExprBool(_) => ColorToken::ExprBool,
StarSlash => ColorToken::Invalid,
_ => return None,
})
}

View File

@ -1,6 +1,7 @@
//! Spans map elements to the part of source code they originate from.
use std::fmt::{self, Debug, Display, Formatter};
use std::ops::{Add, AddAssign};
use serde::Serialize;
@ -100,6 +101,30 @@ impl Position {
}
}
impl Add for Position {
type Output = Position;
fn add(self, rhs: Position) -> Position {
if rhs.line == 0 {
Position {
line: self.line,
column: self.column + rhs.column
}
} else {
Position {
line: self.line + rhs.line,
column: rhs.column,
}
}
}
}
impl AddAssign for Position {
fn add_assign(&mut self, other: Position) {
*self = *self + other;
}
}
impl Display for Position {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}:{}", self.line, self.column)
@ -107,3 +132,18 @@ impl Display for Position {
}
debug_display!(Position);
/// A vector of spanned things.
pub type SpanVec<T> = Vec<Spanned<T>>;
pub fn offset_spans<T>(
vec: SpanVec<T>,
start: Position,
) -> impl Iterator<Item=Spanned<T>> {
vec.into_iter()
.map(move |mut spanned| {
spanned.span.start += start;
spanned.span.end += start;
spanned
})
}

View File

@ -1,21 +1,80 @@
use std::iter::Peekable;
use std::str::Chars;
use unicode_xid::UnicodeXID;
use super::*;
use Token::*;
use State::*;
/// A minimal semantic entity of source code.
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Token<'s> {
/// One or more whitespace characters. The contained `usize` denotes the
/// number of newlines that were contained in the whitespace.
Whitespace(usize),
/// A line comment with inner string contents `//<&'s str>\n`.
LineComment(&'s str),
/// A block comment with inner string contents `/*<&'s str>*/`. The comment
/// can contain nested block comments.
BlockComment(&'s str),
/// An erroneous `*/` without an opening block comment.
StarSlash,
/// A left bracket: `[`.
LeftBracket,
/// A right bracket: `]`.
RightBracket,
/// A left parenthesis in a function header: `(`.
LeftParen,
/// A right parenthesis in a function header: `)`.
RightParen,
/// A left brace in a function header: `{`.
LeftBrace,
/// A right brace in a function header: `}`.
RightBrace,
/// A colon in a function header: `:`.
Colon,
/// A comma in a function header: `:`.
Comma,
/// An equals sign in a function header: `=`.
Equals,
/// An identifier in a function header: `center`.
ExprIdent(&'s str),
/// A quoted string in a function header: `"..."`.
ExprStr(&'s str),
/// A number in a function header: `3.14`.
ExprNumber(f64),
/// A size in a function header: `12pt`.
ExprSize(Size),
/// A boolean in a function header: `true | false`.
ExprBool(bool),
/// A star in body-text.
Star,
/// An underscore in body-text.
Underscore,
/// A backtick in body-text.
Backtick,
/// Any other consecutive string.
Text(&'s str),
}
/// Decomposes text into a sequence of semantic tokens.
pub fn tokenize(src: &str) -> Tokens {
Tokens::new(src)
pub fn tokenize(start: Position, src: &str) -> Tokens {
Tokens::new(start, src)
}
/// An iterator over the tokens of a string of source code.
pub struct Tokens<'s> {
src: &'s str,
state: State,
stack: Vec<State>,
stack: Vec<(State, Position)>,
iter: Peekable<Chars<'s>>,
position: Position,
index: usize,
@ -29,13 +88,13 @@ enum State {
}
impl<'s> Tokens<'s> {
pub fn new(src: &'s str) -> Tokens<'s> {
pub fn new(start: Position, src: &'s str) -> Tokens<'s> {
Tokens {
src,
state: State::Body,
stack: vec![],
iter: src.chars().peekable(),
position: Position::ZERO,
position: start,
index: 0,
}
}
@ -47,7 +106,7 @@ impl<'s> Tokens<'s> {
}
/// The line-colunn position in the source at which the last token ends and
/// next token will start.
/// next token will start. This position is
pub fn pos(&self) -> Position {
self.position
}
@ -101,11 +160,13 @@ impl<'s> Iterator for Tokens<'s> {
// Functions.
'[' => {
if self.state == Header || self.state == Body {
self.stack.push(self.state);
self.state = Header;
} else {
self.state = Body;
match self.state {
Header | Body => {
self.stack.push((self.state, start));
self.position = Position::new(0, '['.len_utf8());
self.state = Header;
}
StartBody => self.state = Body,
}
LeftBracket
@ -114,7 +175,12 @@ impl<'s> Iterator for Tokens<'s> {
if self.state == Header && self.peek() == Some('[') {
self.state = StartBody;
} else {
self.state = self.stack.pop().unwrap_or(Body);
if let Some((state, pos)) = self.stack.pop() {
self.state = state;
self.position = pos + self.position;
} else {
self.state = Body;
}
}
RightBracket

View File

@ -76,10 +76,7 @@ fn test(name: &str, src: &str) -> DynResult<()> {
let font_paths = provider.paths();
typesetter.add_font_provider(provider);
let layouts = match compile(&typesetter, src) {
Some(layouts) => layouts,
None => return Ok(()),
};
let layouts = compile(&typesetter, src);
// Compute the font's paths.
let mut fonts = HashMap::new();
@ -122,40 +119,32 @@ fn test(name: &str, src: &str) -> DynResult<()> {
}
/// Compile the source code with the typesetter.
fn compile(typesetter: &Typesetter, src: &str) -> Option<MultiLayout> {
fn compile(typesetter: &Typesetter, src: &str) -> MultiLayout {
#[cfg(not(debug_assertions))] {
use std::time::Instant;
// Warmup.
let warmup_start = Instant::now();
let is_ok = block_on(typesetter.typeset(&src)).is_ok();
block_on(typesetter.typeset(&src));
let warmup_end = Instant::now();
// Only continue if the typesetting was successful.
if is_ok {
let start = Instant::now();
let tree = typesetter.parse(&src).unwrap();
let mid = Instant::now();
block_on(typesetter.layout(&tree)).unwrap();
let end = Instant::now();
let start = Instant::now();
let tree = typesetter.parse(&src).output;
let mid = Instant::now();
let layouts = block_on(typesetter.layout(&tree)).output;
let end = Instant::now();
println!(" - cold start: {:?}", warmup_end - warmup_start);
println!(" - warmed up: {:?}", end - start);
println!(" - parsing: {:?}", mid - start);
println!(" - layouting: {:?}", end - mid);
println!();
}
};
println!(" - cold start: {:?}", warmup_end - warmup_start);
println!(" - warmed up: {:?}", end - start);
println!(" - parsing: {:?}", mid - start);
println!(" - layouting: {:?}", end - mid);
println!();
match block_on(typesetter.typeset(&src)) {
Ok(layouts) => Some(layouts),
Err(err) => {
println!(" - compilation failed: {}", err);
#[cfg(not(debug_assertions))]
println!();
None
}
layouts
}
#[cfg(debug_assertions)]
block_on(typesetter.typeset(&src))
}
/// Command line options.

View File

@ -107,23 +107,22 @@ macro_rules! case {
(ps $($rest:tt)*) => (case!(@parse PartialEq::eq, $($rest)*));
(@parse $cmp:expr, $src:expr, [$($e:tt)*]) => ({
let expected = SyntaxTree { nodes: list!(nodes [$($e)*]) };
let expected = SyntaxModel { nodes: list!(nodes [$($e)*]) };
let found = parse($src, ParseContext { scope: &scope() }).0;
($cmp(&found, &expected), expected, found)
});
(c $src:expr, [$($e:tt)*]) => ({
let expected = Colorization { tokens: list!(colors [$($e)*]) };
let expected = Colorization { tokens: list!(decorations [$($e)*]) };
let found = parse($src, ParseContext { scope: &scope() }).1;
(expected == found, expected, found)
});
(e $src:expr, [$($e:tt)*]) => ({
let errors = list!([$($e)*]).into_iter()
let expected = list!([$($e)*]).into_iter()
.map(|s| s.map(|m| m.to_string()))
.collect();
let expected = ErrorMap { errors };
let found = parse($src, ParseContext { scope: &scope() }).2;
(expected == found, expected, found)
});
@ -131,7 +130,7 @@ macro_rules! case {
/// A scope containing the `DebugFn` as a fallback.
fn scope() -> Scope {
Scope::with_debug::<DebugFn>()
Scope::with_fallback::<DebugFn>()
}
/// Parses possibly-spanned lists of token or node expressions.
@ -182,7 +181,7 @@ macro_rules! func {
$(positional = list!(expr [$($p)*]);)?
$(keyword = list!(expr [$($k)*]);)?
Node::Func(FuncCall(Box::new(DebugFn {
Node::Model(Box::new(DebugFn {
header: FuncHeader {
name: zspan(Ident($name.to_string())),
args: FuncArgs {
@ -191,10 +190,10 @@ macro_rules! func {
},
},
body: func!(@body $($b)*),
})))
}))
});
(@body Some($($b:tt)*)) => (Some(SyntaxTree { nodes: list!(nodes $($b)*) }));
(@body Some($($b:tt)*)) => (Some(SyntaxModel{ nodes: list!(nodes $($b)*) }));
(@body None) => (None);
}
@ -270,27 +269,8 @@ mod cuts {
}
}
pub mod colors {
pub use typstc::syntax::ColorToken::{
Comment as C,
Bracket as B,
FuncName as FN,
Colon as CL,
Key as K,
Equals as EQ,
Comma as CM,
Paren as P,
Brace as BR,
ExprIdent as ID,
ExprStr as STR,
ExprNumber as NUM,
ExprSize as SIZE,
ExprBool as BOOL,
Bold as BD,
Italic as IT,
Monospace as MS,
Invalid as INV,
};
pub mod decorations {
pub use typstc::syntax::Decoration::*;
}
pub mod expr {

View File

@ -13,15 +13,17 @@ impl SpanlessEq<Vec<Spanned<Token<'_>>>> for Vec<Spanned<Token<'_>>> {
}
}
impl SpanlessEq<SyntaxTree> for SyntaxTree {
fn spanless_eq(&self, other: &SyntaxTree) -> bool {
fn downcast(func: &FuncCall) -> &DebugFn {
func.0.downcast::<DebugFn>().expect("not a debug fn")
impl SpanlessEq<SyntaxModel> for SyntaxModel {
fn spanless_eq(&self, other: &SyntaxModel) -> bool {
fn downcast(func: &dyn Model) -> &DebugFn {
func.downcast::<DebugFn>().expect("not a debug fn")
}
self.nodes.len() == other.nodes.len()
&& self.nodes.iter().zip(&other.nodes).all(|(x, y)| match (&x.v, &y.v) {
(Node::Func(a), Node::Func(b)) => downcast(a).spanless_eq(downcast(b)),
(Node::Model(a), Node::Model(b)) => {
downcast(a.as_ref()).spanless_eq(downcast(b.as_ref()))
}
(a, b) => a == b,
})
}