mirror of
https://github.com/typst/typst
synced 2025-06-28 08:12:53 +08:00
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:
parent
277f2d2176
commit
95e6b078fe
10
src/error.rs
Normal file
10
src/error.rs
Normal 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() }
|
||||||
|
}
|
||||||
|
}
|
@ -1,131 +1,76 @@
|
|||||||
//! Helper types and macros for creating custom functions.
|
//! Helper types and macros for creating custom functions.
|
||||||
|
|
||||||
|
|
||||||
/// Defines function types concisely.
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! function {
|
macro_rules! function {
|
||||||
// Parse a unit struct.
|
// Entry point.
|
||||||
($(#[$outer:meta])* pub struct $type:ident; $($rest:tt)*) => {
|
($(#[$outer:meta])* $v:vis $storage:ident $name:ident $($r:tt)*) => {
|
||||||
$(#[$outer])* pub struct $type;
|
function!(@def($name) $(#[$outer])* $v $storage $name $($r)*);
|
||||||
function!(@meta $type | $($rest)*);
|
};
|
||||||
|
(@def($name:ident) $definition:item $($r:tt)*) => {
|
||||||
|
$definition
|
||||||
|
function!(@meta($name) $($r)*);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse a tuple struct.
|
// Metadata.
|
||||||
($(#[$outer:meta])* pub struct $type:ident($($fields:tt)*); $($rest:tt)*) => {
|
(@meta($name:ident) type Meta = $meta:ty; $($r:tt)*) => {
|
||||||
$(#[$outer])* pub struct $type($($fields)*);
|
function!(@parse($name, $meta) $($r)*);
|
||||||
function!(@meta $type | $($rest)*);
|
};
|
||||||
|
(@meta($name:ident) $($r:tt)*) => {
|
||||||
|
function!(@parse($name, ()) $($r)*);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse a struct with fields.
|
// Parse trait.
|
||||||
($(#[$outer:meta])* pub struct $type:ident { $($fields:tt)* } $($rest:tt)*) => {
|
(@parse($($a:tt)*) parse(default) $($r:tt)*) => {
|
||||||
$(#[$outer])* pub struct $type { $($fields)* }
|
function!(@parse($($a)*) parse() { Default::default() } $($r)*);
|
||||||
function!(@meta $type | $($rest)*);
|
|
||||||
};
|
};
|
||||||
|
(@parse($($a:tt)*) parse($h:ident, $b:ident, $c:ident, $e:ident, $d:ident) $($r:tt)* ) => {
|
||||||
// Parse an enum.
|
function!(@parse($($a)*) parse($h, $b, $c, $e, $d, _metadata) $($r)*);
|
||||||
($(#[$outer:meta])* pub enum $type:ident { $($fields:tt)* } $($rest:tt)*) => {
|
|
||||||
$(#[$outer])* pub enum $type { $($fields)* }
|
|
||||||
function!(@meta $type | $($rest)*);
|
|
||||||
};
|
};
|
||||||
|
(@parse($name:ident, $meta:ty) parse(
|
||||||
// Parse a metadata type definition.
|
$header:ident,
|
||||||
(@meta $type:ident | type Meta = $meta:ty; $($rest:tt)*) => {
|
$body:ident,
|
||||||
function!(@parse $type $meta | $($rest)*);
|
$ctx:ident,
|
||||||
};
|
$errors:ident,
|
||||||
|
$decos:ident,
|
||||||
// Set the metadata to `()` if there is no type definition.
|
$metadata:ident
|
||||||
(@meta $type:ident | $($rest:tt)*) => {
|
) $code:block $($r:tt)*) => {
|
||||||
function!(@parse $type () | $($rest)*);
|
impl $crate::func::Parse for $name {
|
||||||
};
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
type Meta = $meta;
|
type Meta = $meta;
|
||||||
|
|
||||||
fn parse(
|
fn parse(
|
||||||
header: $crate::syntax::FuncHeader,
|
#[allow(unused)] mut $header: FuncHeader,
|
||||||
$body: Option<&str>,
|
#[allow(unused)] $body: Option<Spanned<&str>>,
|
||||||
$ctx: $crate::syntax::ParseContext,
|
#[allow(unused)] $ctx: ParseContext,
|
||||||
$metadata: Self::Meta,
|
#[allow(unused)] $metadata: Self::Meta,
|
||||||
) -> $crate::syntax::ParseResult<Self> where Self: Sized {
|
) -> Parsed<Self> where Self: Sized {
|
||||||
#[allow(unused_mut)]
|
#[allow(unused)] let mut $errors = vec![];
|
||||||
let mut $header = header;
|
#[allow(unused)] let mut $decos = vec![];
|
||||||
let val = $code;
|
let output = $code;
|
||||||
if !$header.args.is_empty() {
|
$crate::syntax::Parsed { output, errors: $errors, decorations: $decos }
|
||||||
return Err($crate::TypesetError::with_message("unexpected arguments"));
|
|
||||||
}
|
|
||||||
Ok(val)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function!(@layout $type | $($rest)*);
|
function!(@layout($name) $($r)*);
|
||||||
};
|
};
|
||||||
|
|
||||||
// (0-arg) Parse a layout-definition without arguments.
|
(@layout($name:ident) layout($this:ident, $ctx:ident, $errors:ident) $code:block) => {
|
||||||
(@layout $type:ident | layout() $code:block) => {
|
impl $crate::syntax::Model for $name {
|
||||||
function!(@layout $type | layout(self, _ctx) $code);
|
fn layout<'a, 'b, 'c, 't>(
|
||||||
};
|
#[allow(unused)] &'a $this,
|
||||||
|
#[allow(unused)] $ctx: $crate::layout::LayoutContext<'b, 'c>,
|
||||||
// (1-arg) Parse a layout-definition with only the first argument.
|
) -> $crate::syntax::DynFuture<'t, $crate::layout::Layouted<$crate::func::Commands<'a>>>
|
||||||
(@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
|
|
||||||
>>
|
|
||||||
where
|
where
|
||||||
'a: 'async_trait,
|
'a: 't,
|
||||||
'life0: 'async_trait,
|
'b: 't,
|
||||||
'life1: 'async_trait,
|
'c: 't,
|
||||||
Self: 'async_trait,
|
Self: 't,
|
||||||
{
|
{
|
||||||
#[allow(unreachable_code)]
|
Box::pin(async move {
|
||||||
Box::pin(async move { Ok($code) })
|
#[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 can have a body, use `parse!(optional: body, ctx)`.
|
||||||
/// - If the function must have a body, use `parse!(expected: body, ctx)`.
|
/// - If the function must have a body, use `parse!(expected: body, ctx)`.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! parse {
|
macro_rules! body {
|
||||||
(forbidden: $body:expr) => {
|
(opt: $body:expr, $ctx:expr, $errors:expr, $decos:expr) => ({
|
||||||
if $body.is_some() {
|
$body.map(|body| {
|
||||||
return Err($crate::TypesetError::with_message("unexpected 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
|
/// Construct a spanned error.
|
||||||
/// expression.
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! error {
|
macro_rules! err {
|
||||||
(@unexpected_argument) => (error!(@"unexpected argument"));
|
($span:expr, $($args:tt)*) => {
|
||||||
(@$($tts:tt)*) => ($crate::TypesetError::with_message(format!($($tts)*)));
|
$crate::syntax::Spanned {
|
||||||
($($tts:tt)*) => (return Err(error!(@$($tts)*)););
|
v: $crate::error::Error::new(format!($($args)*)),
|
||||||
|
span: $span,
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
132
src/func/mod.rs
132
src/func/mod.rs
@ -3,7 +3,6 @@
|
|||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use async_trait::async_trait;
|
|
||||||
|
|
||||||
use self::prelude::*;
|
use self::prelude::*;
|
||||||
|
|
||||||
@ -12,81 +11,34 @@ mod macros;
|
|||||||
|
|
||||||
/// Useful imports for creating your own functions.
|
/// Useful imports for creating your own functions.
|
||||||
pub mod prelude {
|
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::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::size::{Size, Size2D, SizeBox, ValueBox, ScaleSize, FSize, PSize};
|
||||||
pub use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
pub use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
||||||
pub use Command::*;
|
pub use Command::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Types representing functions that are parsed from source code.
|
/// Parse a function from source code.
|
||||||
pub trait ParseFunc {
|
pub trait Parse {
|
||||||
type Meta: Clone;
|
type Meta: Clone;
|
||||||
|
|
||||||
/// Parse the header and body into this function given a context.
|
/// Parse the header and body into this function given a context.
|
||||||
fn parse(
|
fn parse(
|
||||||
header: FuncHeader,
|
header: FuncHeader,
|
||||||
body: Option<&str>,
|
body: Option<Spanned<&str>>,
|
||||||
ctx: ParseContext,
|
ctx: ParseContext,
|
||||||
metadata: Self::Meta,
|
metadata: Self::Meta,
|
||||||
) -> ParseResult<Self> where Self: Sized;
|
) -> Parsed<Self> where Self: Sized;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Function types which can be laid out in a layout context.
|
/// A function which parses the source of a function into a model type which
|
||||||
///
|
/// implements [`Model`].
|
||||||
/// This trait is a supertrait of `[LayoutFuncBounds]` for technical reasons.
|
type Parser = dyn Fn(
|
||||||
/// The trait `[LayoutFuncBounds]` is automatically implemented for types which
|
FuncHeader,
|
||||||
/// can be used as functions, that is, all types which fulfill the bounds `Debug
|
Option<Spanned<&str>>,
|
||||||
/// + PartialEq + 'static`.
|
ParseContext,
|
||||||
#[async_trait(?Send)]
|
) -> Parsed<Box<dyn Model>>;
|
||||||
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 sequence of layouting commands.
|
/// A sequence of layouting commands.
|
||||||
pub type Commands<'a> = Vec<Command<'a>>;
|
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.
|
/// Layouting commands from functions to the typesetting engine.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Command<'a> {
|
pub enum Command<'a> {
|
||||||
LayoutTree(&'a SyntaxTree),
|
LayoutSyntaxModel(&'a SyntaxModel),
|
||||||
|
|
||||||
Add(Layout),
|
Add(Layout),
|
||||||
AddMultiple(MultiLayout),
|
AddMultiple(MultiLayout),
|
||||||
@ -114,41 +66,17 @@ pub enum Command<'a> {
|
|||||||
/// A map from identifiers to function parsers.
|
/// A map from identifiers to function parsers.
|
||||||
pub struct Scope {
|
pub struct Scope {
|
||||||
parsers: HashMap<String, Box<Parser>>,
|
parsers: HashMap<String, Box<Parser>>,
|
||||||
debug: Option<Box<Parser>>
|
fallback: 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>)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scope {
|
impl Scope {
|
||||||
/// Create a new empty scope.
|
/// Create a new empty scope with a fallback parser that is invoked when no
|
||||||
pub fn new() -> Scope {
|
|
||||||
Scope {
|
|
||||||
parsers: HashMap::new(),
|
|
||||||
debug: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new scope with a debug parser that is invoked if not other
|
|
||||||
/// match is found.
|
/// match is found.
|
||||||
pub fn with_debug<F>() -> Scope
|
pub fn new<F>() -> Scope
|
||||||
where F: ParseFunc<Meta=()> + LayoutFunc + 'static {
|
where F: Parse<Meta=()> + Model + 'static {
|
||||||
Scope {
|
Scope {
|
||||||
parsers: HashMap::new(),
|
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.
|
/// Associate the given name with a type that is parseable into a function.
|
||||||
pub fn add<F>(&mut self, name: &str)
|
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, ());
|
self.add_with_metadata::<F>(name, ());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a parseable type with additional metadata that is given to the
|
/// Add a parseable type with additional metadata that is given to the
|
||||||
/// parser (other than the default of `()`).
|
/// parser (other than the default of `()`).
|
||||||
pub fn add_with_metadata<F>(&mut self, name: &str, metadata: <F as ParseFunc>::Meta)
|
pub fn add_with_metadata<F>(&mut self, name: &str, metadata: <F as Parse>::Meta)
|
||||||
where F: ParseFunc + LayoutFunc + 'static {
|
where F: Parse + Model + 'static {
|
||||||
self.parsers.insert(
|
self.parsers.insert(
|
||||||
name.to_owned(),
|
name.to_owned(),
|
||||||
make_parser::<F>(metadata),
|
parser::<F>(metadata),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the parser with the given name if there is one.
|
/// Return the parser with the given name if there is one.
|
||||||
pub(crate) fn get_parser(&self, name: &str) -> Option<&Parser> {
|
pub(crate) fn get_parser(&self, name: &str) -> Result<&Parser, &Parser> {
|
||||||
self.parsers.get(name).map(|x| &**x)
|
self.parsers.get(name)
|
||||||
.or(self.debug.as_ref().map(|x| &**x))
|
.map(|x| &**x)
|
||||||
|
.ok_or_else(|| &*self.fallback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,3 +115,10 @@ impl Debug for Scope {
|
|||||||
write!(f, "{:?}", self.parsers.keys())
|
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>)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -67,18 +67,21 @@ impl LineLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add a layout to the run.
|
/// 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;
|
let axes = self.ctx.axes;
|
||||||
|
|
||||||
if let Some(alignment) = self.run.alignment {
|
if let Some(alignment) = self.run.alignment {
|
||||||
if layout.alignment.secondary != alignment.secondary {
|
if layout.alignment.secondary != alignment.secondary {
|
||||||
if self.stack.is_fitting_alignment(layout.alignment) {
|
// TODO: Issue warning for non-fitting alignment in
|
||||||
self.finish_line()?;
|
// non-repeating context.
|
||||||
|
let fitting = self.stack.is_fitting_alignment(layout.alignment);
|
||||||
|
if !fitting && self.ctx.repeat {
|
||||||
|
self.finish_space(true);
|
||||||
} else {
|
} else {
|
||||||
self.finish_space(true)?;
|
self.finish_line();
|
||||||
}
|
}
|
||||||
} else if layout.alignment.primary < alignment.primary {
|
} else if layout.alignment.primary < alignment.primary {
|
||||||
self.finish_line()?;
|
self.finish_line();
|
||||||
|
|
||||||
} else if layout.alignment.primary > alignment.primary {
|
} else if layout.alignment.primary > alignment.primary {
|
||||||
let mut rest_run = LineRun::new();
|
let mut rest_run = LineRun::new();
|
||||||
@ -92,7 +95,7 @@ impl LineLayouter {
|
|||||||
|
|
||||||
rest_run.size.y = self.run.size.y;
|
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.stack.add_spacing(-rest_run.size.y, SpacingKind::Hard);
|
||||||
|
|
||||||
self.run = rest_run;
|
self.run = rest_run;
|
||||||
@ -105,16 +108,14 @@ impl LineLayouter {
|
|||||||
|
|
||||||
let size = layout.dimensions.generalized(axes);
|
let size = layout.dimensions.generalized(axes);
|
||||||
|
|
||||||
while !self.usable().fits(size) {
|
if !self.usable().fits(size) {
|
||||||
if !self.line_is_empty() {
|
if !self.line_is_empty() {
|
||||||
self.finish_line()?;
|
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_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.x += size.x;
|
||||||
self.run.size.y.max_eq(size.y);
|
self.run.size.y.max_eq(size.y);
|
||||||
self.run.last_spacing = LastSpacing::None;
|
self.run.last_spacing = LastSpacing::None;
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add multiple layouts to the run.
|
/// Add multiple layouts to the run.
|
||||||
///
|
///
|
||||||
/// This function simply calls `add` repeatedly for each layout.
|
/// 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 {
|
for layout in layouts {
|
||||||
self.add(layout)?;
|
self.add(layout);
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The remaining usable size in the run.
|
/// The remaining usable size in the run.
|
||||||
@ -180,20 +178,16 @@ impl LineLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Finish the run and add secondary spacing to the underlying stack.
|
/// Finish the run and add secondary spacing to the underlying stack.
|
||||||
pub fn add_secondary_spacing(
|
pub fn add_secondary_spacing(&mut self, spacing: Size, kind: SpacingKind) {
|
||||||
&mut self,
|
self.finish_line_if_not_empty();
|
||||||
spacing: Size,
|
self.stack.add_spacing(spacing, kind)
|
||||||
kind: SpacingKind
|
|
||||||
) -> LayoutResult<()> {
|
|
||||||
self.finish_line_if_not_empty()?;
|
|
||||||
Ok(self.stack.add_spacing(spacing, kind))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change the layouting axes used by this layouter.
|
/// Change the layouting axes used by this layouter.
|
||||||
pub fn set_axes(&mut self, axes: LayoutAxes) -> LayoutResult<()> {
|
pub fn set_axes(&mut self, axes: LayoutAxes) {
|
||||||
self.finish_line_if_not_empty()?;
|
self.finish_line_if_not_empty();
|
||||||
self.ctx.axes = axes;
|
self.ctx.axes = axes;
|
||||||
Ok(self.stack.set_axes(axes))
|
self.stack.set_axes(axes)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change the layouting spaces to use.
|
/// Change the layouting spaces to use.
|
||||||
@ -224,19 +218,19 @@ impl LineLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Finish the last line and compute the final multi-layout.
|
/// Finish the last line and compute the final multi-layout.
|
||||||
pub fn finish(mut self) -> LayoutResult<MultiLayout> {
|
pub fn finish(mut self) -> MultiLayout {
|
||||||
self.finish_line_if_not_empty()?;
|
self.finish_line_if_not_empty();
|
||||||
self.stack.finish()
|
self.stack.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish the currently active space and start a new one.
|
/// Finish the currently active space and start a new one.
|
||||||
pub fn finish_space(&mut self, hard: bool) -> LayoutResult<()> {
|
pub fn finish_space(&mut self, hard: bool) {
|
||||||
self.finish_line_if_not_empty()?;
|
self.finish_line_if_not_empty();
|
||||||
self.stack.finish_space(hard)
|
self.stack.finish_space(hard)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add the current line to the stack and start a new line.
|
/// 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 mut actions = LayoutActions::new();
|
||||||
|
|
||||||
let layouts = std::mem::replace(&mut self.run.layouts, vec![]);
|
let layouts = std::mem::replace(&mut self.run.layouts, vec![]);
|
||||||
@ -257,21 +251,17 @@ impl LineLayouter {
|
|||||||
alignment: self.run.alignment
|
alignment: self.run.alignment
|
||||||
.unwrap_or(LayoutAlignment::new(Origin, Origin)),
|
.unwrap_or(LayoutAlignment::new(Origin, Origin)),
|
||||||
actions: actions.to_vec(),
|
actions: actions.to_vec(),
|
||||||
})?;
|
});
|
||||||
|
|
||||||
self.run = LineRun::new();
|
self.run = LineRun::new();
|
||||||
|
|
||||||
self.stack.add_spacing(self.ctx.line_spacing, LINE_KIND);
|
self.stack.add_spacing(self.ctx.line_spacing, SpacingKind::LINE);
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish the current line if it is not empty.
|
/// 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() {
|
if !self.line_is_empty() {
|
||||||
self.finish_line()
|
self.finish_line()
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,23 +4,20 @@ use std::io::{self, Write};
|
|||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use toddle::query::{SharedFontLoader, FontIndex};
|
use toddle::query::{SharedFontLoader, FontIndex};
|
||||||
|
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::syntax::SpanVec;
|
||||||
use crate::size::{Size, Size2D, SizeBox};
|
use crate::size::{Size, Size2D, SizeBox};
|
||||||
use crate::style::LayoutStyle;
|
use crate::style::LayoutStyle;
|
||||||
|
|
||||||
mod actions;
|
mod actions;
|
||||||
mod tree;
|
mod model;
|
||||||
mod line;
|
mod line;
|
||||||
mod stack;
|
mod stack;
|
||||||
mod text;
|
mod text;
|
||||||
|
|
||||||
/// Common types for layouting.
|
/// Common types for layouting.
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use super::{
|
pub use super::*;
|
||||||
layout, LayoutResult,
|
|
||||||
MultiLayout, Layout, LayoutContext, LayoutSpaces, LayoutSpace,
|
|
||||||
LayoutExpansion, LayoutAxes, GenericAxis, SpecificAxis, Direction,
|
|
||||||
LayoutAlignment, Alignment, SpacingKind,
|
|
||||||
};
|
|
||||||
pub use GenericAxis::*;
|
pub use GenericAxis::*;
|
||||||
pub use SpecificAxis::*;
|
pub use SpecificAxis::*;
|
||||||
pub use Direction::*;
|
pub use Direction::*;
|
||||||
@ -29,7 +26,7 @@ pub mod prelude {
|
|||||||
|
|
||||||
/// Different kinds of layouters (fully re-exported).
|
/// Different kinds of layouters (fully re-exported).
|
||||||
pub mod layouters {
|
pub mod layouters {
|
||||||
pub use super::tree::layout;
|
pub use super::model::layout;
|
||||||
pub use super::line::{LineLayouter, LineContext};
|
pub use super::line::{LineLayouter, LineContext};
|
||||||
pub use super::stack::{StackLayouter, StackContext};
|
pub use super::stack::{StackLayouter, StackContext};
|
||||||
pub use super::text::{layout_text, TextContext};
|
pub use super::text::{layout_text, TextContext};
|
||||||
@ -40,8 +37,19 @@ pub use self::layouters::*;
|
|||||||
pub use self::prelude::*;
|
pub use self::prelude::*;
|
||||||
|
|
||||||
|
|
||||||
/// The result type for layouting.
|
pub struct Layouted<T> {
|
||||||
pub type LayoutResult<T> = crate::TypesetResult<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.
|
/// A collection of layouts.
|
||||||
pub type MultiLayout = Vec<Layout>;
|
pub type MultiLayout = Vec<Layout>;
|
||||||
@ -361,14 +369,16 @@ pub enum SpacingKind {
|
|||||||
Soft(u32),
|
Soft(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The standard spacing kind used for paragraph spacing.
|
impl SpacingKind {
|
||||||
const PARAGRAPH_KIND: SpacingKind = SpacingKind::Soft(1);
|
/// The standard spacing kind used for paragraph spacing.
|
||||||
|
pub const PARAGRAPH: SpacingKind = SpacingKind::Soft(1);
|
||||||
|
|
||||||
/// The standard spacing kind used for line spacing.
|
/// The standard spacing kind used for line spacing.
|
||||||
const LINE_KIND: SpacingKind = SpacingKind::Soft(2);
|
pub const LINE: SpacingKind = SpacingKind::Soft(2);
|
||||||
|
|
||||||
/// The standard spacing kind used for word spacing.
|
/// The standard spacing kind used for word spacing.
|
||||||
const WORD_KIND: SpacingKind = SpacingKind::Soft(1);
|
pub const WORD: SpacingKind = SpacingKind::Soft(1);
|
||||||
|
}
|
||||||
|
|
||||||
/// The last appeared spacing.
|
/// The last appeared spacing.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
|
193
src/layout/model.rs
Normal file
193
src/layout/model.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -68,10 +68,12 @@ impl StackLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add a layout to the stack.
|
/// Add a layout to the stack.
|
||||||
pub fn add(&mut self, layout: Layout) -> LayoutResult<()> {
|
pub fn add(&mut self, layout: Layout) {
|
||||||
// If the alignment cannot be fit in this space, finish it.
|
// If the alignment cannot be fitted in this space, finish it.
|
||||||
if !self.update_rulers(layout.alignment) {
|
// TODO: Issue warning for non-fitting alignment in
|
||||||
self.finish_space(true)?;
|
// 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
|
// Now, we add a possibly cached soft space. If the secondary alignment
|
||||||
@ -81,14 +83,9 @@ impl StackLayouter {
|
|||||||
self.add_spacing(spacing, SpacingKind::Hard);
|
self.add_spacing(spacing, SpacingKind::Hard);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the first space that fits the layout.
|
// TODO: Issue warning about overflow if there is overflow.
|
||||||
while !self.space.usable.fits(layout.dimensions) {
|
if !self.space.usable.fits(layout.dimensions) && self.ctx.repeat {
|
||||||
if self.space_is_last() && self.space_is_empty() {
|
self.skip_to_fitting_space(layout.dimensions);
|
||||||
error!("cannot fit box of size {} into usable size of {}",
|
|
||||||
layout.dimensions, self.space.usable);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.finish_space(true)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change the usable space and size of the space.
|
// Change the usable space and size of the space.
|
||||||
@ -98,18 +95,15 @@ impl StackLayouter {
|
|||||||
// again.
|
// again.
|
||||||
self.space.layouts.push((self.ctx.axes, layout));
|
self.space.layouts.push((self.ctx.axes, layout));
|
||||||
self.space.last_spacing = LastSpacing::None;
|
self.space.last_spacing = LastSpacing::None;
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add multiple layouts to the stack.
|
/// Add multiple layouts to the stack.
|
||||||
///
|
///
|
||||||
/// This function simply calls `add` repeatedly for each layout.
|
/// 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 {
|
for layout in layouts {
|
||||||
self.add(layout)?;
|
self.add(layout);
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add secondary spacing to the stack.
|
/// 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
|
/// The remaining unpadded, unexpanding spaces. If a multi-layout is laid
|
||||||
/// out into these spaces, it will fit into this stack.
|
/// out into these spaces, it will fit into this stack.
|
||||||
pub fn remaining(&self) -> LayoutSpaces {
|
pub fn remaining(&self) -> LayoutSpaces {
|
||||||
@ -251,19 +258,15 @@ impl StackLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the finished multi-layout.
|
/// 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() {
|
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.
|
/// Finish the current space and start a new one.
|
||||||
pub fn finish_space(&mut self, hard: bool) -> LayoutResult<()> {
|
pub fn finish_space(&mut self, hard: bool) {
|
||||||
if !self.ctx.repeat && hard {
|
|
||||||
error!("cannot create new space in a non-repeating context");
|
|
||||||
}
|
|
||||||
|
|
||||||
let space = self.ctx.spaces[self.space.index];
|
let space = self.ctx.spaces[self.space.index];
|
||||||
|
|
||||||
// ------------------------------------------------------------------ //
|
// ------------------------------------------------------------------ //
|
||||||
@ -376,7 +379,7 @@ impl StackLayouter {
|
|||||||
// ------------------------------------------------------------------ //
|
// ------------------------------------------------------------------ //
|
||||||
// Step 5: Start the next space.
|
// 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.
|
/// Start a new space with the given index.
|
||||||
|
@ -6,6 +6,14 @@ use crate::style::TextStyle;
|
|||||||
use super::*;
|
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.
|
/// The context for text layouting.
|
||||||
///
|
///
|
||||||
/// See [`LayoutContext`] for details about the fields.
|
/// See [`LayoutContext`] for details about the fields.
|
||||||
@ -17,14 +25,6 @@ pub struct TextContext<'a, 'p> {
|
|||||||
pub alignment: LayoutAlignment,
|
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.
|
/// Layouts text into boxes.
|
||||||
struct TextLayouter<'a, 'p> {
|
struct TextLayouter<'a, 'p> {
|
||||||
ctx: TextContext<'a, 'p>,
|
ctx: TextContext<'a, 'p>,
|
||||||
@ -49,14 +49,14 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Layout the text
|
/// Layout the text
|
||||||
async fn layout(mut self) -> LayoutResult<Layout> {
|
async fn layout(mut self) -> Layout {
|
||||||
if self.ctx.axes.primary.is_positive() {
|
if self.ctx.axes.primary.is_positive() {
|
||||||
for c in self.text.chars() {
|
for c in self.text.chars() {
|
||||||
self.layout_char(c).await?;
|
self.layout_char(c).await;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for c in self.text.chars().rev() {
|
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));
|
self.actions.add(LayoutAction::WriteText(self.buffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Layout {
|
Layout {
|
||||||
dimensions: Size2D::new(self.width, self.ctx.style.font_size()),
|
dimensions: Size2D::new(self.width, self.ctx.style.font_size()),
|
||||||
alignment: self.ctx.alignment,
|
alignment: self.ctx.alignment,
|
||||||
actions: self.actions.to_vec(),
|
actions: self.actions.to_vec(),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout an individual character.
|
/// Layout an individual character.
|
||||||
async fn layout_char(&mut self, c: char) -> LayoutResult<()> {
|
async fn layout_char(&mut self, c: char) {
|
||||||
let (index, char_width) = self.select_font(c).await?;
|
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;
|
self.width += char_width;
|
||||||
|
|
||||||
@ -88,13 +92,11 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.buffer.push(c);
|
self.buffer.push(c);
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Select the best font for a character and return its index along with
|
/// Select the best font for a character and return its index along with
|
||||||
/// the width of the char in the font.
|
/// 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 mut loader = self.ctx.loader.borrow_mut();
|
||||||
|
|
||||||
let query = FontQuery {
|
let query = FontQuery {
|
||||||
@ -104,26 +106,27 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some((font, index)) = loader.get(query).await {
|
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 font_unit_to_size = |x| Size::pt(font_unit_ratio * x);
|
||||||
|
|
||||||
let glyph = font
|
let glyph = font
|
||||||
.read_table::<CharMap>()?
|
.read_table::<CharMap>()
|
||||||
.get(c)
|
.ok()?
|
||||||
.expect("select_font: font should have char");
|
.get(c)?;
|
||||||
|
|
||||||
let glyph_width = font
|
let glyph_width = font
|
||||||
.read_table::<HorizontalMetrics>()?
|
.read_table::<HorizontalMetrics>()
|
||||||
.get(glyph)
|
.ok()?
|
||||||
.expect("select_font: font should have glyph")
|
.get(glyph)?
|
||||||
.advance_width as f32;
|
.advance_width as f32;
|
||||||
|
|
||||||
let char_width = font_unit_to_size(glyph_width)
|
let char_width = font_unit_to_size(glyph_width)
|
||||||
* self.ctx.style.font_size().to_pt();
|
* 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
59
src/lib.rs
59
src/lib.rs
@ -27,13 +27,14 @@ use toddle::query::{FontLoader, FontProvider, SharedFontLoader};
|
|||||||
use toddle::Error as FontError;
|
use toddle::Error as FontError;
|
||||||
|
|
||||||
use crate::func::Scope;
|
use crate::func::Scope;
|
||||||
use crate::layout::{MultiLayout, LayoutResult};
|
use crate::layout::{Layouted, LayoutContext, MultiLayout};
|
||||||
use crate::syntax::{parse, SyntaxTree, Colorization, ErrorMap, ParseContext, Span};
|
use crate::syntax::{parse, ParseContext, Parsed, SyntaxModel, Position};
|
||||||
use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
pub mod export;
|
pub mod export;
|
||||||
|
pub mod error;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod func;
|
pub mod func;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
@ -51,6 +52,8 @@ pub struct Typesetter<'p> {
|
|||||||
loader: SharedFontLoader<'p>,
|
loader: SharedFontLoader<'p>,
|
||||||
/// The base layouting style.
|
/// The base layouting style.
|
||||||
style: LayoutStyle,
|
style: LayoutStyle,
|
||||||
|
/// The standard library scope.
|
||||||
|
scope: Scope,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'p> Typesetter<'p> {
|
impl<'p> Typesetter<'p> {
|
||||||
@ -59,6 +62,7 @@ impl<'p> Typesetter<'p> {
|
|||||||
Typesetter {
|
Typesetter {
|
||||||
loader: RefCell::new(FontLoader::new()),
|
loader: RefCell::new(FontLoader::new()),
|
||||||
style: LayoutStyle::default(),
|
style: LayoutStyle::default(),
|
||||||
|
scope: Scope::with_std(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,17 +88,16 @@ impl<'p> Typesetter<'p> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse source code into a syntax tree.
|
/// Parse source code into a syntax tree.
|
||||||
pub fn parse(&self, src: &str) -> (SyntaxTree, Colorization, ErrorMap) {
|
pub fn parse(&self, src: &str) -> Parsed<SyntaxModel> {
|
||||||
let scope = Scope::with_std();
|
parse(Position::ZERO, src, ParseContext { scope: &self.scope })
|
||||||
parse(src, ParseContext { scope: &scope })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout a syntax tree and return the produced layout.
|
/// Layout a syntax tree and return the produced layout.
|
||||||
pub async fn layout(&self, tree: &SyntaxTree) -> LayoutResult<MultiLayout> {
|
pub async fn layout(&self, model: &SyntaxModel) -> Layouted<MultiLayout> {
|
||||||
use crate::layout::prelude::*;
|
use crate::layout::prelude::*;
|
||||||
let margins = self.style.page.margins();
|
let margins = self.style.page.margins();
|
||||||
Ok(layout(
|
layout(
|
||||||
&tree,
|
&model,
|
||||||
LayoutContext {
|
LayoutContext {
|
||||||
loader: &self.loader,
|
loader: &self.loader,
|
||||||
style: &self.style,
|
style: &self.style,
|
||||||
@ -110,42 +113,12 @@ impl<'p> Typesetter<'p> {
|
|||||||
nested: false,
|
nested: false,
|
||||||
debug: false,
|
debug: false,
|
||||||
},
|
},
|
||||||
).await?)
|
).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Process source code directly into a layout.
|
/// Process source code directly into a collection of layouts.
|
||||||
pub async fn typeset(&self, src: &str) -> TypesetResult<MultiLayout> {
|
pub async fn typeset(&self, src: &str) -> MultiLayout {
|
||||||
let tree = self.parse(src).0;
|
let tree = self.parse(src).output;
|
||||||
let layout = self.layout(&tree).await?;
|
self.layout(&tree).await.output
|
||||||
Ok(layout)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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())),
|
|
||||||
}
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::size::ScaleSize;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
||||||
@ -126,6 +127,7 @@ impl Display for Object {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A key-value pair in an object.
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct Pair {
|
pub struct Pair {
|
||||||
pub key: Spanned<Ident>,
|
pub key: Spanned<Ident>,
|
||||||
@ -144,7 +146,6 @@ debug_display!(Tuple);
|
|||||||
debug_display!(Object);
|
debug_display!(Object);
|
||||||
debug_display!(Pair);
|
debug_display!(Pair);
|
||||||
|
|
||||||
|
|
||||||
/// Kinds of expressions.
|
/// Kinds of expressions.
|
||||||
pub trait ExpressionKind: Sized {
|
pub trait ExpressionKind: Sized {
|
||||||
/// The name of the expression in an `expected <name>` error.
|
/// The name of the expression in an `expected <name>` error.
|
||||||
|
125
src/syntax/func.rs
Normal file
125
src/syntax/func.rs
Normal 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),
|
||||||
|
// }
|
||||||
|
// }
|
@ -1,99 +1,125 @@
|
|||||||
//! Tokenization and parsing of source code.
|
//! Tokenization and parsing of source code.
|
||||||
|
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::any::Any;
|
||||||
use unicode_xid::UnicodeXID;
|
use std::fmt::{self, Debug, Display, Formatter};
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::func::LayoutFunc;
|
use crate::error::Error;
|
||||||
use crate::size::{Size, ScaleSize};
|
use crate::func::{Commands, Command};
|
||||||
|
use crate::layout::{Layouted, LayoutContext};
|
||||||
|
use crate::size::Size;
|
||||||
pub type ParseResult<T> = crate::TypesetResult<T>;
|
|
||||||
|
|
||||||
pub_use_mod!(expr);
|
pub_use_mod!(expr);
|
||||||
|
pub_use_mod!(func);
|
||||||
pub_use_mod!(tokens);
|
pub_use_mod!(tokens);
|
||||||
pub_use_mod!(parsing);
|
pub_use_mod!(parsing);
|
||||||
pub_use_mod!(span);
|
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`.
|
pub struct Parsed<T> {
|
||||||
LineComment(&'s str),
|
pub output: T,
|
||||||
/// A block comment with inner string contents `/*<&'s str>*/`. The comment
|
pub errors: SpanVec<Error>,
|
||||||
/// can contain nested block comments.
|
pub decorations: SpanVec<Decoration>,
|
||||||
BlockComment(&'s str),
|
}
|
||||||
/// An erroneous `*/` without an opening block comment.
|
|
||||||
StarSlash,
|
|
||||||
|
|
||||||
/// A left bracket: `[`.
|
impl<T> Parsed<T> {
|
||||||
LeftBracket,
|
pub fn map<F, U>(self, f: F) -> Parsed<U> where F: FnOnce(T) -> U {
|
||||||
/// A right bracket: `]`.
|
Parsed {
|
||||||
RightBracket,
|
output: f(self.output),
|
||||||
|
errors: self.errors,
|
||||||
|
decorations: self.decorations,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A left parenthesis in a function header: `(`.
|
#[async_trait::async_trait(?Send)]
|
||||||
LeftParen,
|
pub trait Model: Debug + ModelBounds {
|
||||||
/// A right parenthesis in a function header: `)`.
|
async fn layout<'a>(
|
||||||
RightParen,
|
&'a self,
|
||||||
/// A left brace in a function header: `{`.
|
ctx: LayoutContext<'_, '_>
|
||||||
LeftBrace,
|
) -> Layouted<Commands<'a>>;
|
||||||
/// A right brace in a function header: `}`.
|
}
|
||||||
RightBrace,
|
|
||||||
|
|
||||||
/// A colon in a function header: `:`.
|
pub type DynFuture<'a, T> = Pin<Box<dyn Future<Output=T> + 'a>>;
|
||||||
Colon,
|
|
||||||
/// A comma in a function header: `:`.
|
|
||||||
Comma,
|
|
||||||
/// An equals sign in a function header: `=`.
|
|
||||||
Equals,
|
|
||||||
|
|
||||||
/// An identifier in a function header: `center`.
|
impl dyn Model {
|
||||||
ExprIdent(&'s str),
|
pub fn downcast<T>(&self) -> Option<&T> where T: Model + 'static {
|
||||||
/// A quoted string in a function header: `"..."`.
|
self.as_any().downcast_ref::<T>()
|
||||||
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.
|
impl PartialEq for dyn Model {
|
||||||
Star,
|
fn eq(&self, other: &dyn Model) -> bool {
|
||||||
/// An underscore in body-text.
|
self.bound_eq(other)
|
||||||
Underscore,
|
}
|
||||||
/// A backtick in body-text.
|
}
|
||||||
Backtick,
|
|
||||||
|
|
||||||
/// Any other consecutive string.
|
impl Clone for Box<dyn Model> {
|
||||||
Text(&'s str),
|
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.
|
/// A tree representation of source code.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct SyntaxTree {
|
pub struct SyntaxModel {
|
||||||
pub nodes: Vec<Spanned<Node>>,
|
pub nodes: SpanVec<Node>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyntaxTree {
|
impl SyntaxModel {
|
||||||
/// Create an empty syntax tree.
|
/// Create an empty syntax model.
|
||||||
pub fn new() -> SyntaxTree {
|
pub fn new() -> SyntaxModel {
|
||||||
SyntaxTree { nodes: vec![] }
|
SyntaxModel { nodes: vec![] }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a node to the tree.
|
/// Add a node to the model.
|
||||||
pub fn add(&mut self, node: Spanned<Node>) {
|
pub fn add(&mut self, node: Spanned<Node>) {
|
||||||
self.nodes.push(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.
|
/// A node in the syntax tree.
|
||||||
#[derive(PartialEq)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Node {
|
pub enum Node {
|
||||||
/// A number of whitespace characters containing less than two newlines.
|
/// A number of whitespace characters containing less than two newlines.
|
||||||
Space,
|
Space,
|
||||||
@ -107,215 +133,29 @@ pub enum Node {
|
|||||||
ToggleBolder,
|
ToggleBolder,
|
||||||
/// Monospace enabled / disabled.
|
/// Monospace enabled / disabled.
|
||||||
ToggleMonospace,
|
ToggleMonospace,
|
||||||
/// A function invocation.
|
/// A submodel.
|
||||||
Func(FuncCall),
|
Model(Box<dyn Model>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Node {
|
impl PartialEq for Node {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn eq(&self, other: &Node) -> bool {
|
||||||
match self {
|
use Node::*;
|
||||||
Node::Space => write!(f, "Space"),
|
match (self, other) {
|
||||||
Node::Newline => write!(f, "Newline"),
|
(Space, Space) => true,
|
||||||
Node::Text(text) => write!(f, "{:?}", text),
|
(Newline, Newline) => true,
|
||||||
Node::ToggleItalic => write!(f, "ToggleItalic"),
|
(Text(a), Text(b)) => a == b,
|
||||||
Node::ToggleBolder => write!(f, "ToggleBold"),
|
(ToggleItalic, ToggleItalic) => true,
|
||||||
Node::ToggleMonospace => write!(f, "ToggleMonospace"),
|
(ToggleBolder, ToggleBolder) => true,
|
||||||
Node::Func(func) => {
|
(ToggleMonospace, ToggleMonospace) => true,
|
||||||
if f.alternate() {
|
(Model(a), Model(b)) => a == b,
|
||||||
write!(f, "{:#?}", func.0)
|
_ => false,
|
||||||
} else {
|
|
||||||
write!(f, "{:?}", func.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug_display!(Node);
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub enum Decoration {
|
||||||
/// An invocation of a function.
|
ValidFuncName,
|
||||||
#[derive(Debug)]
|
InvalidFuncName,
|
||||||
pub struct FuncCall(pub Box<dyn LayoutFunc>);
|
ArgumentKey,
|
||||||
|
|
||||||
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) })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,8 @@ use super::*;
|
|||||||
use Token::*;
|
use Token::*;
|
||||||
|
|
||||||
|
|
||||||
/// Parses source code into a syntax tree given a context.
|
pub fn parse(start: Position, src: &str, ctx: ParseContext) -> Parsed<SyntaxModel> {
|
||||||
pub fn parse(src: &str, ctx: ParseContext) -> (SyntaxTree, Colorization, ErrorMap) {
|
Parser::new(start, src, ctx).parse()
|
||||||
Parser::new(src, ctx).parse()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The context for parsing.
|
/// The context for parsing.
|
||||||
@ -18,64 +17,68 @@ pub struct ParseContext<'a> {
|
|||||||
struct Parser<'s> {
|
struct Parser<'s> {
|
||||||
src: &'s str,
|
src: &'s str,
|
||||||
ctx: ParseContext<'s>,
|
ctx: ParseContext<'s>,
|
||||||
colorization: Colorization,
|
|
||||||
error_map: ErrorMap,
|
|
||||||
tokens: Tokens<'s>,
|
tokens: Tokens<'s>,
|
||||||
peeked: Option<Option<Spanned<Token<'s>>>>,
|
peeked: Option<Option<Spanned<Token<'s>>>>,
|
||||||
position: Position,
|
position: Position,
|
||||||
last_position: Position,
|
last_position: Position,
|
||||||
|
errors: SpanVec<Error>,
|
||||||
|
decorations: SpanVec<Decoration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Parser<'s> {
|
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 {
|
Parser {
|
||||||
src,
|
src,
|
||||||
ctx,
|
ctx,
|
||||||
error_map: ErrorMap::new(),
|
tokens: tokenize(start, src),
|
||||||
colorization: Colorization::new(),
|
|
||||||
tokens: Tokens::new(src),
|
|
||||||
peeked: None,
|
peeked: None,
|
||||||
position: Position::ZERO,
|
position: Position::ZERO,
|
||||||
last_position: Position::ZERO,
|
last_position: Position::ZERO,
|
||||||
|
errors: vec![],
|
||||||
|
decorations: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The main parsing entrypoint.
|
/// The main parsing entrypoint.
|
||||||
fn parse(mut self) -> (SyntaxTree, Colorization, ErrorMap) {
|
fn parse(mut self) -> Parsed<SyntaxModel> {
|
||||||
let mut tree = SyntaxTree::new();
|
let mut model = SyntaxModel::new();
|
||||||
|
|
||||||
loop {
|
while let Some(token) = self.eat() {
|
||||||
if let Some(spanned) = self.eat() {
|
let mut span = token.span;
|
||||||
match spanned.v {
|
let node = match token.v {
|
||||||
LineComment(_) | BlockComment(_) => {}
|
LineComment(_) | BlockComment(_) => None,
|
||||||
|
Whitespace(newlines) => Some(if newlines >= 2 {
|
||||||
|
Node::Newline
|
||||||
|
} else {
|
||||||
|
Node::Space
|
||||||
|
}),
|
||||||
|
|
||||||
Whitespace(newlines) => {
|
LeftBracket => self.parse_func().map(|spanned| {
|
||||||
tree.add(spanned.map_v(if newlines >= 2 {
|
span = spanned.span;
|
||||||
Node::Newline
|
spanned.v
|
||||||
} else {
|
}),
|
||||||
Node::Space
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
LeftBracket => {
|
Star => Some(Node::ToggleBolder),
|
||||||
if let Some(func) = self.parse_func() {
|
Underscore => Some(Node::ToggleItalic),
|
||||||
tree.add(func);
|
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)),
|
self.unexpected(token);
|
||||||
Backtick => tree.add(spanned.map_v(Node::ToggleMonospace)),
|
None
|
||||||
Text(text) => tree.add(spanned.map_v(Node::Text(text.to_owned()))),
|
|
||||||
|
|
||||||
_ => self.unexpected(spanned),
|
|
||||||
}
|
}
|
||||||
} 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
|
/// 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());
|
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 end = self.pos();
|
||||||
let span = Span { start, end };
|
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.
|
/// Parses a function header including the closing bracket.
|
||||||
@ -125,7 +171,6 @@ impl<'s> Parser<'s> {
|
|||||||
match self.peek() {
|
match self.peek() {
|
||||||
Some(Spanned { v: ExprIdent(ident), span }) => {
|
Some(Spanned { v: ExprIdent(ident), span }) => {
|
||||||
self.eat();
|
self.eat();
|
||||||
self.colorization.replace_last(ColorToken::FuncName);
|
|
||||||
return Some(Spanned { v: Ident(ident.to_string()), span });
|
return Some(Spanned { v: Ident(ident.to_string()), span });
|
||||||
}
|
}
|
||||||
other => self.expected_found_or_at("identifier", other, self.pos()),
|
other => self.expected_found_or_at("identifier", other, self.pos()),
|
||||||
@ -144,8 +189,7 @@ impl<'s> Parser<'s> {
|
|||||||
match self.peekv() {
|
match self.peekv() {
|
||||||
Some(RightBracket) | None => break,
|
Some(RightBracket) | None => break,
|
||||||
_ => match self.parse_arg() {
|
_ => match self.parse_arg() {
|
||||||
Some(Arg::Pos(item)) => args.add_pos(item),
|
Some(arg) => args.add(arg),
|
||||||
Some(Arg::Key(pair)) => args.add_key_pair(pair),
|
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -165,11 +209,11 @@ impl<'s> Parser<'s> {
|
|||||||
|
|
||||||
let ident = Ident(ident.to_string());
|
let ident = Ident(ident.to_string());
|
||||||
if let Some(Equals) = self.peekv() {
|
if let Some(Equals) = self.peekv() {
|
||||||
self.colorization.replace_last(ColorToken::Key);
|
|
||||||
|
|
||||||
self.eat();
|
self.eat();
|
||||||
self.skip_whitespace();
|
self.skip_whitespace();
|
||||||
|
|
||||||
|
self.decorations.push(Spanned::new(Decoration::ArgumentKey, span));
|
||||||
|
|
||||||
self.parse_expr().map(|value| {
|
self.parse_expr().map(|value| {
|
||||||
Arg::Key(Pair {
|
Arg::Key(Pair {
|
||||||
key: Spanned { v: ident, span },
|
key: Spanned { v: ident, span },
|
||||||
@ -251,42 +295,6 @@ impl<'s> Parser<'s> {
|
|||||||
Spanned { v: Expression::Object(Object::new()), span }
|
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.
|
/// Skip all whitespace/comment tokens.
|
||||||
fn skip_whitespace(&mut self) {
|
fn skip_whitespace(&mut self) {
|
||||||
self.eat_until(|t|
|
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
|
/// Add an error about an `thing` which was expected but not found at the
|
||||||
/// given position.
|
/// given position.
|
||||||
fn expected_at(&mut self, thing: &str, pos: 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
|
/// Add an error about an expected `thing` which was not found, showing
|
||||||
/// what was found instead.
|
/// what was found instead.
|
||||||
fn expected_found(&mut self, thing: &str, found: Spanned<Token>) {
|
fn expected_found(&mut self, thing: &str, found: Spanned<Token>) {
|
||||||
let message = format!("expected {}, found {}", thing, name(found.v));
|
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.
|
/// 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`.
|
/// Add an error about an unexpected token `found`.
|
||||||
fn unexpected(&mut self, found: Spanned<Token>) {
|
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
|
/// 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());
|
.unwrap_or_else(|| self.tokens.next());
|
||||||
|
|
||||||
if let Some(token) = token {
|
if let Some(token) = token {
|
||||||
if let Some(color) = color(token.v) {
|
|
||||||
self.colorization.add(color, token.span);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.last_position = self.position;
|
self.last_position = self.position;
|
||||||
self.position = token.span.end;
|
self.position = token.span.end;
|
||||||
}
|
}
|
||||||
@ -407,23 +414,3 @@ fn name(token: Token) -> &'static str {
|
|||||||
Text(_) => "invalid identifier",
|
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
//! Spans map elements to the part of source code they originate from.
|
//! Spans map elements to the part of source code they originate from.
|
||||||
|
|
||||||
use std::fmt::{self, Debug, Display, Formatter};
|
use std::fmt::{self, Debug, Display, Formatter};
|
||||||
|
use std::ops::{Add, AddAssign};
|
||||||
use serde::Serialize;
|
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 {
|
impl Display for Position {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
write!(f, "{}:{}", self.line, self.column)
|
write!(f, "{}:{}", self.line, self.column)
|
||||||
@ -107,3 +132,18 @@ impl Display for Position {
|
|||||||
}
|
}
|
||||||
|
|
||||||
debug_display!(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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -1,21 +1,80 @@
|
|||||||
use std::iter::Peekable;
|
use std::iter::Peekable;
|
||||||
use std::str::Chars;
|
use std::str::Chars;
|
||||||
|
use unicode_xid::UnicodeXID;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use Token::*;
|
use Token::*;
|
||||||
use State::*;
|
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.
|
/// Decomposes text into a sequence of semantic tokens.
|
||||||
pub fn tokenize(src: &str) -> Tokens {
|
pub fn tokenize(start: Position, src: &str) -> Tokens {
|
||||||
Tokens::new(src)
|
Tokens::new(start, src)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An iterator over the tokens of a string of source code.
|
/// An iterator over the tokens of a string of source code.
|
||||||
pub struct Tokens<'s> {
|
pub struct Tokens<'s> {
|
||||||
src: &'s str,
|
src: &'s str,
|
||||||
state: State,
|
state: State,
|
||||||
stack: Vec<State>,
|
stack: Vec<(State, Position)>,
|
||||||
iter: Peekable<Chars<'s>>,
|
iter: Peekable<Chars<'s>>,
|
||||||
position: Position,
|
position: Position,
|
||||||
index: usize,
|
index: usize,
|
||||||
@ -29,13 +88,13 @@ enum State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Tokens<'s> {
|
impl<'s> Tokens<'s> {
|
||||||
pub fn new(src: &'s str) -> Tokens<'s> {
|
pub fn new(start: Position, src: &'s str) -> Tokens<'s> {
|
||||||
Tokens {
|
Tokens {
|
||||||
src,
|
src,
|
||||||
state: State::Body,
|
state: State::Body,
|
||||||
stack: vec![],
|
stack: vec![],
|
||||||
iter: src.chars().peekable(),
|
iter: src.chars().peekable(),
|
||||||
position: Position::ZERO,
|
position: start,
|
||||||
index: 0,
|
index: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,7 +106,7 @@ impl<'s> Tokens<'s> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The line-colunn position in the source at which the last token ends and
|
/// 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 {
|
pub fn pos(&self) -> Position {
|
||||||
self.position
|
self.position
|
||||||
}
|
}
|
||||||
@ -101,11 +160,13 @@ impl<'s> Iterator for Tokens<'s> {
|
|||||||
|
|
||||||
// Functions.
|
// Functions.
|
||||||
'[' => {
|
'[' => {
|
||||||
if self.state == Header || self.state == Body {
|
match self.state {
|
||||||
self.stack.push(self.state);
|
Header | Body => {
|
||||||
self.state = Header;
|
self.stack.push((self.state, start));
|
||||||
} else {
|
self.position = Position::new(0, '['.len_utf8());
|
||||||
self.state = Body;
|
self.state = Header;
|
||||||
|
}
|
||||||
|
StartBody => self.state = Body,
|
||||||
}
|
}
|
||||||
|
|
||||||
LeftBracket
|
LeftBracket
|
||||||
@ -114,7 +175,12 @@ impl<'s> Iterator for Tokens<'s> {
|
|||||||
if self.state == Header && self.peek() == Some('[') {
|
if self.state == Header && self.peek() == Some('[') {
|
||||||
self.state = StartBody;
|
self.state = StartBody;
|
||||||
} else {
|
} 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
|
RightBracket
|
||||||
|
@ -76,10 +76,7 @@ fn test(name: &str, src: &str) -> DynResult<()> {
|
|||||||
let font_paths = provider.paths();
|
let font_paths = provider.paths();
|
||||||
typesetter.add_font_provider(provider);
|
typesetter.add_font_provider(provider);
|
||||||
|
|
||||||
let layouts = match compile(&typesetter, src) {
|
let layouts = compile(&typesetter, src);
|
||||||
Some(layouts) => layouts,
|
|
||||||
None => return Ok(()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Compute the font's paths.
|
// Compute the font's paths.
|
||||||
let mut fonts = HashMap::new();
|
let mut fonts = HashMap::new();
|
||||||
@ -122,40 +119,32 @@ fn test(name: &str, src: &str) -> DynResult<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Compile the source code with the typesetter.
|
/// 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))] {
|
#[cfg(not(debug_assertions))] {
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
// Warmup.
|
// Warmup.
|
||||||
let warmup_start = Instant::now();
|
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();
|
let warmup_end = Instant::now();
|
||||||
|
|
||||||
// Only continue if the typesetting was successful.
|
let start = Instant::now();
|
||||||
if is_ok {
|
let tree = typesetter.parse(&src).output;
|
||||||
let start = Instant::now();
|
let mid = Instant::now();
|
||||||
let tree = typesetter.parse(&src).unwrap();
|
let layouts = block_on(typesetter.layout(&tree)).output;
|
||||||
let mid = Instant::now();
|
let end = Instant::now();
|
||||||
block_on(typesetter.layout(&tree)).unwrap();
|
|
||||||
let end = Instant::now();
|
|
||||||
|
|
||||||
println!(" - cold start: {:?}", warmup_end - warmup_start);
|
println!(" - cold start: {:?}", warmup_end - warmup_start);
|
||||||
println!(" - warmed up: {:?}", end - start);
|
println!(" - warmed up: {:?}", end - start);
|
||||||
println!(" - parsing: {:?}", mid - start);
|
println!(" - parsing: {:?}", mid - start);
|
||||||
println!(" - layouting: {:?}", end - mid);
|
println!(" - layouting: {:?}", end - mid);
|
||||||
println!();
|
println!();
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match block_on(typesetter.typeset(&src)) {
|
layouts
|
||||||
Ok(layouts) => Some(layouts),
|
|
||||||
Err(err) => {
|
|
||||||
println!(" - compilation failed: {}", err);
|
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
println!();
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
block_on(typesetter.typeset(&src))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Command line options.
|
/// Command line options.
|
||||||
|
@ -107,23 +107,22 @@ macro_rules! case {
|
|||||||
(ps $($rest:tt)*) => (case!(@parse PartialEq::eq, $($rest)*));
|
(ps $($rest:tt)*) => (case!(@parse PartialEq::eq, $($rest)*));
|
||||||
|
|
||||||
(@parse $cmp:expr, $src:expr, [$($e:tt)*]) => ({
|
(@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;
|
let found = parse($src, ParseContext { scope: &scope() }).0;
|
||||||
($cmp(&found, &expected), expected, found)
|
($cmp(&found, &expected), expected, found)
|
||||||
});
|
});
|
||||||
|
|
||||||
(c $src:expr, [$($e:tt)*]) => ({
|
(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;
|
let found = parse($src, ParseContext { scope: &scope() }).1;
|
||||||
(expected == found, expected, found)
|
(expected == found, expected, found)
|
||||||
});
|
});
|
||||||
|
|
||||||
(e $src:expr, [$($e:tt)*]) => ({
|
(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()))
|
.map(|s| s.map(|m| m.to_string()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let expected = ErrorMap { errors };
|
|
||||||
let found = parse($src, ParseContext { scope: &scope() }).2;
|
let found = parse($src, ParseContext { scope: &scope() }).2;
|
||||||
(expected == found, expected, found)
|
(expected == found, expected, found)
|
||||||
});
|
});
|
||||||
@ -131,7 +130,7 @@ macro_rules! case {
|
|||||||
|
|
||||||
/// A scope containing the `DebugFn` as a fallback.
|
/// A scope containing the `DebugFn` as a fallback.
|
||||||
fn scope() -> Scope {
|
fn scope() -> Scope {
|
||||||
Scope::with_debug::<DebugFn>()
|
Scope::with_fallback::<DebugFn>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses possibly-spanned lists of token or node expressions.
|
/// Parses possibly-spanned lists of token or node expressions.
|
||||||
@ -182,7 +181,7 @@ macro_rules! func {
|
|||||||
$(positional = list!(expr [$($p)*]);)?
|
$(positional = list!(expr [$($p)*]);)?
|
||||||
$(keyword = list!(expr [$($k)*]);)?
|
$(keyword = list!(expr [$($k)*]);)?
|
||||||
|
|
||||||
Node::Func(FuncCall(Box::new(DebugFn {
|
Node::Model(Box::new(DebugFn {
|
||||||
header: FuncHeader {
|
header: FuncHeader {
|
||||||
name: zspan(Ident($name.to_string())),
|
name: zspan(Ident($name.to_string())),
|
||||||
args: FuncArgs {
|
args: FuncArgs {
|
||||||
@ -191,10 +190,10 @@ macro_rules! func {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
body: func!(@body $($b)*),
|
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);
|
(@body None) => (None);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,27 +269,8 @@ mod cuts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod colors {
|
pub mod decorations {
|
||||||
pub use typstc::syntax::ColorToken::{
|
pub use typstc::syntax::Decoration::*;
|
||||||
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 expr {
|
pub mod expr {
|
||||||
|
@ -13,15 +13,17 @@ impl SpanlessEq<Vec<Spanned<Token<'_>>>> for Vec<Spanned<Token<'_>>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpanlessEq<SyntaxTree> for SyntaxTree {
|
impl SpanlessEq<SyntaxModel> for SyntaxModel {
|
||||||
fn spanless_eq(&self, other: &SyntaxTree) -> bool {
|
fn spanless_eq(&self, other: &SyntaxModel) -> bool {
|
||||||
fn downcast(func: &FuncCall) -> &DebugFn {
|
fn downcast(func: &dyn Model) -> &DebugFn {
|
||||||
func.0.downcast::<DebugFn>().expect("not a debug fn")
|
func.downcast::<DebugFn>().expect("not a debug fn")
|
||||||
}
|
}
|
||||||
|
|
||||||
self.nodes.len() == other.nodes.len()
|
self.nodes.len() == other.nodes.len()
|
||||||
&& self.nodes.iter().zip(&other.nodes).all(|(x, y)| match (&x.v, &y.v) {
|
&& 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,
|
(a, b) => a == b,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user