mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Spans for all parts of functions ✅
This commit is contained in:
parent
65ec3764e5
commit
110e4b9cb9
@ -5,16 +5,16 @@ use super::prelude::*;
|
|||||||
/// Implement the function trait more concisely.
|
/// Implement the function trait more concisely.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! function {
|
macro_rules! function {
|
||||||
(data: $ident:ident, $($tts:tt)*) => {
|
(data: $ident:ident, $($tts:tt)*) => (
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use $crate::func::prelude::*;
|
use $crate::func::prelude::*;
|
||||||
|
|
||||||
impl Function for $ident {
|
impl Function for $ident {
|
||||||
function!(@parse $ident, $($tts)*);
|
function!(@parse $ident, $($tts)*);
|
||||||
}
|
}
|
||||||
};
|
);
|
||||||
|
|
||||||
(@parse $ident:ident, parse: plain, $($tts:tt)*) => {
|
(@parse $ident:ident, parse: plain, $($tts:tt)*) => (
|
||||||
fn parse(header: &FuncHeader, body: Option<&str>, _: ParseContext)
|
fn parse(header: &FuncHeader, body: Option<&str>, _: ParseContext)
|
||||||
-> ParseResult<Self> where Self: Sized
|
-> ParseResult<Self> where Self: Sized
|
||||||
{
|
{
|
||||||
@ -25,14 +25,14 @@ macro_rules! function {
|
|||||||
Ok($ident)
|
Ok($ident)
|
||||||
}
|
}
|
||||||
function!(@layout $($tts)*);
|
function!(@layout $($tts)*);
|
||||||
};
|
);
|
||||||
|
|
||||||
(
|
(
|
||||||
@parse $ident:ident,
|
@parse $ident:ident,
|
||||||
parse($args:ident, $body:ident, $ctx:ident)
|
parse($args:ident, $body:ident, $ctx:ident)
|
||||||
$block:block
|
$block:block
|
||||||
$($tts:tt)*
|
$($tts:tt)*
|
||||||
) => {
|
) => (
|
||||||
fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext)
|
fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext)
|
||||||
-> ParseResult<Self> where Self: Sized
|
-> ParseResult<Self> where Self: Sized
|
||||||
{
|
{
|
||||||
@ -42,15 +42,15 @@ macro_rules! function {
|
|||||||
$block
|
$block
|
||||||
}
|
}
|
||||||
function!(@layout $($tts)*);
|
function!(@layout $($tts)*);
|
||||||
};
|
);
|
||||||
|
|
||||||
(@layout layout($this:pat, $ctx:pat) $block:block) => {
|
(@layout layout($this:pat, $ctx:pat) $block:block) => (
|
||||||
fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList> {
|
fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList> {
|
||||||
let $ctx = ctx;
|
let $ctx = ctx;
|
||||||
let $this = self;
|
let $this = self;
|
||||||
$block
|
$block
|
||||||
}
|
}
|
||||||
};
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the body of a function.
|
/// Parse the body of a function.
|
||||||
@ -65,56 +65,56 @@ macro_rules! parse {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
(optional: $body:expr, $ctx:expr) => {
|
(optional: $body:expr, $ctx:expr) => (
|
||||||
if let Some(body) = $body {
|
if let Some(body) = $body {
|
||||||
Some($crate::syntax::parse(body, $ctx)?)
|
Some($crate::syntax::parse(body, $ctx)?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
};
|
);
|
||||||
|
|
||||||
(required: $body:expr, $ctx:expr) => {
|
(required: $body:expr, $ctx:expr) => (
|
||||||
if let Some(body) = $body {
|
if let Some(body) = $body {
|
||||||
$crate::syntax::parse(body, $ctx)?
|
$crate::syntax::parse(body, $ctx)?
|
||||||
} else {
|
} else {
|
||||||
err!("expected body");
|
err!("expected body");
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a formatted parsing error.
|
/// Return a formatted parsing error.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! err {
|
macro_rules! err {
|
||||||
($($tts:tt)*) => {
|
(@$($tts:tt)*) => ($crate::syntax::ParseError::new(format!($($tts)*)));
|
||||||
return Err($crate::syntax::ParseError::new(format!($($tts)*)));
|
($($tts:tt)*) => (return Err(err!(@$($tts)*)););
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenient interface for parsing function arguments.
|
/// Convenient interface for parsing function arguments.
|
||||||
pub struct Arguments<'a> {
|
pub struct Arguments<'a> {
|
||||||
args: Peekable<Iter<'a, Expression>>,
|
args: Peekable<Iter<'a, Spanned<Expression>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Arguments<'a> {
|
impl<'a> Arguments<'a> {
|
||||||
pub fn new(header: &'a FuncHeader) -> Arguments<'a> {
|
pub fn new(header: &'a FuncHeader) -> Arguments<'a> {
|
||||||
Arguments {
|
Arguments {
|
||||||
args: header.args.iter().peekable()
|
args: header.args.positional.iter().peekable()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_expr(&mut self) -> ParseResult<&'a Expression> {
|
pub fn get_expr(&mut self) -> ParseResult<&'a Spanned<Expression>> {
|
||||||
self.args.next()
|
self.args.next()
|
||||||
.ok_or_else(|| ParseError::new("expected expression"))
|
.ok_or_else(|| ParseError::new("expected expression"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_ident(&mut self) -> ParseResult<&'a str> {
|
pub fn get_ident(&mut self) -> ParseResult<Spanned<&'a str>> {
|
||||||
match self.get_expr()? {
|
let expr = self.get_expr()?;
|
||||||
Expression::Ident(s) => Ok(s.as_str()),
|
match &expr.val {
|
||||||
_ => Err(ParseError::new("expected identifier")),
|
Expression::Ident(s) => Ok(Spanned::new(s.as_str(), expr.span)),
|
||||||
|
_ => err!("expected identifier"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_ident_if_present(&mut self) -> ParseResult<Option<&'a str>> {
|
pub fn get_ident_if_present(&mut self) -> ParseResult<Option<Spanned<&'a str>>> {
|
||||||
if self.args.peek().is_some() {
|
if self.args.peek().is_some() {
|
||||||
self.get_ident().map(|s| Some(s))
|
self.get_ident().map(|s| Some(s))
|
||||||
} else {
|
} else {
|
||||||
|
@ -15,7 +15,7 @@ pub mod prelude {
|
|||||||
pub use crate::func::{Command, CommandList, Function};
|
pub use crate::func::{Command, CommandList, Function};
|
||||||
pub use crate::layout::{layout_tree, Layout, LayoutContext, MultiLayout};
|
pub use crate::layout::{layout_tree, Layout, LayoutContext, MultiLayout};
|
||||||
pub use crate::layout::{Flow, Alignment, LayoutError, LayoutResult};
|
pub use crate::layout::{Flow, Alignment, LayoutError, LayoutResult};
|
||||||
pub use crate::syntax::{Expression, FuncHeader, SyntaxTree};
|
pub use crate::syntax::{SyntaxTree, FuncHeader, FuncArgs, Expression, Spanned, Span};
|
||||||
pub use crate::syntax::{parse, ParseContext, ParseError, ParseResult};
|
pub use crate::syntax::{parse, ParseContext, ParseError, ParseResult};
|
||||||
pub use crate::size::{Size, Size2D, SizeBox};
|
pub use crate::size::{Size, Size2D, SizeBox};
|
||||||
pub use crate::style::{PageStyle, TextStyle};
|
pub use crate::style::{PageStyle, TextStyle};
|
||||||
@ -144,9 +144,9 @@ pub enum Command<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! commands {
|
macro_rules! commands {
|
||||||
($($x:expr),*$(,)*) => ({
|
($($x:expr),*$(,)*) => (
|
||||||
$crate::func::CommandList::from_vec(vec![$($x,)*])
|
$crate::func::CommandList::from_vec(vec![$($x,)*])
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A map from identifiers to functions.
|
/// A map from identifiers to functions.
|
||||||
|
@ -42,7 +42,7 @@ pub struct FlexContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! reuse {
|
macro_rules! reuse {
|
||||||
($ctx:expr, $flex_spacing:expr) => {
|
($ctx:expr, $flex_spacing:expr) => (
|
||||||
FlexContext {
|
FlexContext {
|
||||||
flex_spacing: $flex_spacing,
|
flex_spacing: $flex_spacing,
|
||||||
alignment: $ctx.alignment,
|
alignment: $ctx.alignment,
|
||||||
@ -50,7 +50,7 @@ macro_rules! reuse {
|
|||||||
followup_spaces: $ctx.followup_spaces,
|
followup_spaces: $ctx.followup_spaces,
|
||||||
shrink_to_fit: $ctx.shrink_to_fit,
|
shrink_to_fit: $ctx.shrink_to_fit,
|
||||||
}
|
}
|
||||||
};
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FlexContext {
|
impl FlexContext {
|
||||||
|
@ -30,7 +30,7 @@ pub struct StackContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! reuse {
|
macro_rules! reuse {
|
||||||
($ctx:expr, $flow:expr) => {
|
($ctx:expr, $flow:expr) => (
|
||||||
StackContext {
|
StackContext {
|
||||||
alignment: $ctx.alignment,
|
alignment: $ctx.alignment,
|
||||||
space: $ctx.space,
|
space: $ctx.space,
|
||||||
@ -38,7 +38,7 @@ macro_rules! reuse {
|
|||||||
shrink_to_fit: $ctx.shrink_to_fit,
|
shrink_to_fit: $ctx.shrink_to_fit,
|
||||||
flow: $flow
|
flow: $flow
|
||||||
}
|
}
|
||||||
};
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StackContext {
|
impl StackContext {
|
||||||
|
@ -104,7 +104,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
|
|||||||
*space = space.usable_space();
|
*space = space.usable_space();
|
||||||
}
|
}
|
||||||
|
|
||||||
let commands = func.body.layout(ctx)?;
|
let commands = func.body.val.layout(ctx)?;
|
||||||
|
|
||||||
for command in commands {
|
for command in commands {
|
||||||
match command {
|
match command {
|
||||||
|
@ -38,7 +38,8 @@ function! {
|
|||||||
|
|
||||||
parse(args, body, ctx) {
|
parse(args, body, ctx) {
|
||||||
let body = parse!(optional: body, ctx);
|
let body = parse!(optional: body, ctx);
|
||||||
let alignment = match args.get_ident()? {
|
let arg = args.get_ident()?;
|
||||||
|
let alignment = match arg.val {
|
||||||
"left" => Alignment::Left,
|
"left" => Alignment::Left,
|
||||||
"right" => Alignment::Right,
|
"right" => Alignment::Right,
|
||||||
"center" => Alignment::Center,
|
"center" => Alignment::Center,
|
||||||
@ -80,7 +81,7 @@ function! {
|
|||||||
|
|
||||||
let mut flow = Flow::Vertical;
|
let mut flow = Flow::Vertical;
|
||||||
if let Some(ident) = args.get_ident_if_present()? {
|
if let Some(ident) = args.get_ident_if_present()? {
|
||||||
flow = match ident {
|
flow = match ident.val {
|
||||||
"vertical" => Flow::Vertical,
|
"vertical" => Flow::Vertical,
|
||||||
"horizontal" => Flow::Horizontal,
|
"horizontal" => Flow::Horizontal,
|
||||||
f => err!("invalid flow specifier: {}", f),
|
f => err!("invalid flow specifier: {}", f),
|
||||||
@ -105,7 +106,7 @@ function! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! spacefunc {
|
macro_rules! spacefunc {
|
||||||
($ident:ident, $name:expr, $var:ident => $command:expr) => {
|
($ident:ident, $name:expr, $var:ident => $command:expr) => (
|
||||||
/// Adds whitespace.
|
/// Adds whitespace.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct $ident(Spacing);
|
pub struct $ident(Spacing);
|
||||||
@ -116,9 +117,10 @@ macro_rules! spacefunc {
|
|||||||
parse(args, body, _ctx) {
|
parse(args, body, _ctx) {
|
||||||
parse!(forbidden: body);
|
parse!(forbidden: body);
|
||||||
|
|
||||||
let spacing = match args.get_expr()? {
|
let arg = args.get_expr()?;
|
||||||
Expression::Size(s) => Spacing::Absolute(*s),
|
let spacing = match arg.val {
|
||||||
Expression::Number(f) => Spacing::Relative(*f as f32),
|
Expression::Size(s) => Spacing::Absolute(s),
|
||||||
|
Expression::Number(f) => Spacing::Relative(f as f32),
|
||||||
_ => err!("invalid spacing, expected size or number"),
|
_ => err!("invalid spacing, expected size or number"),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -134,7 +136,7 @@ macro_rules! spacefunc {
|
|||||||
Ok(commands![$command])
|
Ok(commands![$command])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Absolute or font-relative spacing.
|
/// Absolute or font-relative spacing.
|
||||||
|
@ -2,7 +2,7 @@ use crate::func::prelude::*;
|
|||||||
use toddle::query::FontClass;
|
use toddle::query::FontClass;
|
||||||
|
|
||||||
macro_rules! stylefunc {
|
macro_rules! stylefunc {
|
||||||
($ident:ident) => {
|
($ident:ident) => (
|
||||||
/// Styles text.
|
/// Styles text.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct $ident {
|
pub struct $ident {
|
||||||
@ -31,7 +31,7 @@ macro_rules! stylefunc {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
stylefunc!(Italic);
|
stylefunc!(Italic);
|
||||||
|
@ -42,11 +42,11 @@ macro_rules! error_type {
|
|||||||
|
|
||||||
/// Create a `Debug` implementation from a `Display` implementation.
|
/// Create a `Debug` implementation from a `Display` implementation.
|
||||||
macro_rules! debug_display {
|
macro_rules! debug_display {
|
||||||
($type:ident) => {
|
($type:ident) => (
|
||||||
impl std::fmt::Debug for $type {
|
impl std::fmt::Debug for $type {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
std::fmt::Display::fmt(self, f)
|
std::fmt::Display::fmt(self, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
);
|
||||||
}
|
}
|
||||||
|
24
src/size.rs
24
src/size.rs
@ -252,7 +252,7 @@ impl Sum for Size {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_reflexive {
|
macro_rules! impl_reflexive {
|
||||||
($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident) => {
|
($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident) => (
|
||||||
impl $trait for Size {
|
impl $trait for Size {
|
||||||
type Output = Size;
|
type Output = Size;
|
||||||
|
|
||||||
@ -270,11 +270,11 @@ macro_rules! impl_reflexive {
|
|||||||
$assign_trait::$assign_func(&mut self.points, other.points);
|
$assign_trait::$assign_func(&mut self.points, other.points);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_num_back {
|
macro_rules! impl_num_back {
|
||||||
($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident, $ty:ty) => {
|
($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident, $ty:ty) => (
|
||||||
impl $trait<$ty> for Size {
|
impl $trait<$ty> for Size {
|
||||||
type Output = Size;
|
type Output = Size;
|
||||||
|
|
||||||
@ -292,11 +292,11 @@ macro_rules! impl_num_back {
|
|||||||
$assign_trait::$assign_func(&mut self.points, other as f32);
|
$assign_trait::$assign_func(&mut self.points, other as f32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_num_both {
|
macro_rules! impl_num_both {
|
||||||
($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident, $ty:ty) => {
|
($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident, $ty:ty) => (
|
||||||
impl_num_back!($trait, $func, $assign_trait, $assign_func, $ty);
|
impl_num_back!($trait, $func, $assign_trait, $assign_func, $ty);
|
||||||
|
|
||||||
impl $trait<Size> for $ty {
|
impl $trait<Size> for $ty {
|
||||||
@ -309,7 +309,7 @@ macro_rules! impl_num_both {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_reflexive!(Add, add, AddAssign, add_assign);
|
impl_reflexive!(Add, add, AddAssign, add_assign);
|
||||||
@ -342,7 +342,7 @@ impl Neg for Size2D {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_reflexive2d {
|
macro_rules! impl_reflexive2d {
|
||||||
($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident) => {
|
($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident) => (
|
||||||
impl $trait for Size2D {
|
impl $trait for Size2D {
|
||||||
type Output = Size2D;
|
type Output = Size2D;
|
||||||
|
|
||||||
@ -362,11 +362,11 @@ macro_rules! impl_reflexive2d {
|
|||||||
$assign_trait::$assign_func(&mut self.y, other.y);
|
$assign_trait::$assign_func(&mut self.y, other.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_num_back2d {
|
macro_rules! impl_num_back2d {
|
||||||
($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident, $ty:ty) => {
|
($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident, $ty:ty) => (
|
||||||
impl $trait<$ty> for Size2D {
|
impl $trait<$ty> for Size2D {
|
||||||
type Output = Size2D;
|
type Output = Size2D;
|
||||||
|
|
||||||
@ -386,11 +386,11 @@ macro_rules! impl_num_back2d {
|
|||||||
$assign_trait::$assign_func(&mut self.y, other as f32);
|
$assign_trait::$assign_func(&mut self.y, other as f32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_num_both2d {
|
macro_rules! impl_num_both2d {
|
||||||
($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident, $ty:ty) => {
|
($trait:ident, $func:ident, $assign_trait:ident, $assign_func:ident, $ty:ty) => (
|
||||||
impl_num_back2d!($trait, $func, $assign_trait, $assign_func, $ty);
|
impl_num_back2d!($trait, $func, $assign_trait, $assign_func, $ty);
|
||||||
|
|
||||||
impl $trait<Size2D> for $ty {
|
impl $trait<Size2D> for $ty {
|
||||||
@ -404,7 +404,7 @@ macro_rules! impl_num_both2d {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_reflexive2d!(Add, add, AddAssign, add_assign);
|
impl_reflexive2d!(Add, add, AddAssign, add_assign);
|
||||||
|
@ -27,13 +27,13 @@ pub enum Token<'s> {
|
|||||||
///
|
///
|
||||||
/// If a colon occurs outside of a function header, it will be tokenized as
|
/// If a colon occurs outside of a function header, it will be tokenized as
|
||||||
/// [Text](Token::Text), just like the other tokens annotated with
|
/// [Text](Token::Text), just like the other tokens annotated with
|
||||||
/// _Function header only_.
|
/// _Header only_.
|
||||||
Colon,
|
Colon,
|
||||||
/// An equals (`=`) sign assigning a function argument a value (Function header only).
|
/// An equals (`=`) sign assigning a function argument a value (Header only).
|
||||||
Equals,
|
Equals,
|
||||||
/// A comma (`,`) separating two function arguments (Function header only).
|
/// A comma (`,`) separating two function arguments (Header only).
|
||||||
Comma,
|
Comma,
|
||||||
/// Quoted text as a string value (Function header only).
|
/// Quoted text as a string value (Header only).
|
||||||
Quoted(&'s str),
|
Quoted(&'s str),
|
||||||
/// An underscore, indicating text in italics (Body only).
|
/// An underscore, indicating text in italics (Body only).
|
||||||
Underscore,
|
Underscore,
|
||||||
@ -47,9 +47,9 @@ pub enum Token<'s> {
|
|||||||
BlockComment(&'s str),
|
BlockComment(&'s str),
|
||||||
/// A star followed by a slash unexpectedly ending a block comment
|
/// A star followed by a slash unexpectedly ending a block comment
|
||||||
/// (the comment was not started before, otherwise a
|
/// (the comment was not started before, otherwise a
|
||||||
/// [BlockComment](Token::BlockComment would be returned).
|
/// [BlockComment](Token::BlockComment) would be returned).
|
||||||
StarSlash,
|
StarSlash,
|
||||||
/// A unit of Plain text.
|
/// Any consecutive string which does not contain markup.
|
||||||
Text(&'s str),
|
Text(&'s str),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,16 +88,32 @@ pub enum Node {
|
|||||||
/// A function invocation, consisting of header and a dynamically parsed body.
|
/// A function invocation, consisting of header and a dynamically parsed body.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FuncCall {
|
pub struct FuncCall {
|
||||||
pub header: FuncHeader,
|
pub header: Spanned<FuncHeader>,
|
||||||
pub body: Box<dyn Function>,
|
pub body: Spanned<Box<dyn Function>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contains header information of a function invocation.
|
/// Contains header information of a function invocation.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct FuncHeader {
|
pub struct FuncHeader {
|
||||||
pub name: String,
|
pub name: Spanned<String>,
|
||||||
pub args: Vec<Expression>,
|
pub args: FuncArgs,
|
||||||
pub kwargs: Vec<(String, Expression)>,
|
}
|
||||||
|
|
||||||
|
/// The arguments passed to a function.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct FuncArgs {
|
||||||
|
pub positional: Vec<Spanned<Expression>>,
|
||||||
|
pub keyword: Vec<Spanned<(Spanned<String>, Spanned<Expression>)>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FuncArgs {
|
||||||
|
/// Create an empty collection of arguments.
|
||||||
|
fn new() -> FuncArgs {
|
||||||
|
FuncArgs {
|
||||||
|
positional: vec![],
|
||||||
|
keyword: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An argument or return value.
|
/// An argument or return value.
|
||||||
@ -140,6 +156,14 @@ impl<T> Spanned<T> {
|
|||||||
pub fn new(val: T, span: Span) -> Spanned<T> {
|
pub fn new(val: T, span: Span) -> Spanned<T> {
|
||||||
Spanned { val, span }
|
Spanned { val, span }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn value(&self) -> &T {
|
||||||
|
&self.val
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn span_map<F, U>(self, f: F) -> Spanned<U> where F: FnOnce(T) -> U {
|
||||||
|
Spanned::new(f(self.val), self.span)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes a slice of source code.
|
/// Describes a slice of source code.
|
||||||
|
@ -99,7 +99,7 @@ impl<'s> Parser<'s> {
|
|||||||
let mut span = token.span;
|
let mut span = token.span;
|
||||||
|
|
||||||
let header = self.parse_func_header()?;
|
let header = self.parse_func_header()?;
|
||||||
let body = self.parse_func_body(&header)?;
|
let body = self.parse_func_body(&header.val)?;
|
||||||
|
|
||||||
span.end = self.tokens.string_index();
|
span.end = self.tokens.string_index();
|
||||||
|
|
||||||
@ -110,51 +110,41 @@ impl<'s> Parser<'s> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a function header.
|
/// Parse a function header.
|
||||||
fn parse_func_header(&mut self) -> ParseResult<FuncHeader> {
|
fn parse_func_header(&mut self) -> ParseResult<Spanned<FuncHeader>> {
|
||||||
|
let start = self.tokens.string_index() - 1;
|
||||||
|
|
||||||
self.skip_white();
|
self.skip_white();
|
||||||
|
|
||||||
let name = match self.tokens.next().map(|token| token.val) {
|
let name = match self.tokens.next() {
|
||||||
Some(Token::Text(word)) => {
|
Some(Spanned { val: Token::Text(word), span }) => {
|
||||||
if is_identifier(word) {
|
if is_identifier(word) {
|
||||||
Ok(word.to_owned())
|
Ok(Spanned::new(word.to_owned(), span))
|
||||||
} else {
|
} else {
|
||||||
Err(ParseError::new(format!("invalid identifier: '{}'", word)))
|
err!("invalid identifier: '{}'", word);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => Err(ParseError::new("expected identifier")),
|
_ => err!("expected identifier"),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
let mut header = FuncHeader {
|
|
||||||
name,
|
|
||||||
args: vec![],
|
|
||||||
kwargs: vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
self.skip_white();
|
self.skip_white();
|
||||||
|
|
||||||
// Check for arguments
|
// Check for arguments
|
||||||
match self.tokens.next().map(|token| token.val) {
|
let args = match self.tokens.next().map(|token| token.val) {
|
||||||
Some(Token::RightBracket) => {}
|
Some(Token::RightBracket) => FuncArgs::new(),
|
||||||
Some(Token::Colon) => {
|
Some(Token::Colon) => self.parse_func_args()?,
|
||||||
let (args, kwargs) = self.parse_func_args()?;
|
_ => err!("expected arguments or closing bracket"),
|
||||||
header.args = args;
|
};
|
||||||
header.kwargs = kwargs;
|
|
||||||
}
|
let end = self.tokens.string_index();
|
||||||
_ => {
|
|
||||||
return Err(ParseError::new(
|
|
||||||
"expected function arguments or closing bracket",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the header information of the function invocation.
|
// Store the header information of the function invocation.
|
||||||
Ok(header)
|
Ok(Spanned::new(FuncHeader { name, args }, Span::new(start, end)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the arguments to a function.
|
/// Parse the arguments to a function.
|
||||||
fn parse_func_args(&mut self) -> ParseResult<(Vec<Expression>, Vec<(String, Expression)>)> {
|
fn parse_func_args(&mut self) -> ParseResult<FuncArgs> {
|
||||||
let mut args = Vec::new();
|
let mut positional = Vec::new();
|
||||||
let kwargs = Vec::new();
|
let keyword = Vec::new();
|
||||||
|
|
||||||
let mut comma = false;
|
let mut comma = false;
|
||||||
loop {
|
loop {
|
||||||
@ -162,7 +152,7 @@ impl<'s> Parser<'s> {
|
|||||||
|
|
||||||
match self.tokens.peek().map(|token| token.val) {
|
match self.tokens.peek().map(|token| token.val) {
|
||||||
Some(Token::Text(_)) | Some(Token::Quoted(_)) if !comma => {
|
Some(Token::Text(_)) | Some(Token::Quoted(_)) if !comma => {
|
||||||
args.push(self.parse_expression()?);
|
positional.push(self.parse_expression()?);
|
||||||
comma = true;
|
comma = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,50 +165,53 @@ impl<'s> Parser<'s> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ if comma => return Err(ParseError::new("expected comma or closing bracket")),
|
_ if comma => err!("expected comma or closing bracket"),
|
||||||
_ => return Err(ParseError::new("expected closing bracket")),
|
_ => err!("expected closing bracket"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((args, kwargs))
|
Ok( FuncArgs { positional, keyword })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse an expression.
|
/// Parse an expression.
|
||||||
fn parse_expression(&mut self) -> ParseResult<Expression> {
|
fn parse_expression(&mut self) -> ParseResult<Spanned<Expression>> {
|
||||||
Ok(match self.tokens.next().map(|token| token.val) {
|
if let Some(token) = self.tokens.next() {
|
||||||
Some(Token::Quoted(text)) => Expression::Str(text.to_owned()),
|
Ok(Spanned::new(match token.val {
|
||||||
Some(Token::Text(text)) => {
|
Token::Quoted(text) => Expression::Str(text.to_owned()),
|
||||||
if let Ok(b) = text.parse::<bool>() {
|
Token::Text(text) => {
|
||||||
Expression::Bool(b)
|
if let Ok(b) = text.parse::<bool>() {
|
||||||
} else if let Ok(num) = text.parse::<f64>() {
|
Expression::Bool(b)
|
||||||
Expression::Number(num)
|
} else if let Ok(num) = text.parse::<f64>() {
|
||||||
} else if let Ok(size) = text.parse::<Size>() {
|
Expression::Number(num)
|
||||||
Expression::Size(size)
|
} else if let Ok(size) = text.parse::<Size>() {
|
||||||
} else {
|
Expression::Size(size)
|
||||||
Expression::Ident(text.to_owned())
|
} else {
|
||||||
|
Expression::Ident(text.to_owned())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_ => return Err(ParseError::new("expected expression")),
|
_ => err!("expected expression"),
|
||||||
})
|
}, token.span))
|
||||||
|
} else {
|
||||||
|
err!("expected expression");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the body of a function.
|
/// Parse the body of a function.
|
||||||
fn parse_func_body(&mut self, header: &FuncHeader) -> ParseResult<Box<dyn Function>> {
|
fn parse_func_body(&mut self, header: &FuncHeader) -> ParseResult<Spanned<Box<dyn Function>>> {
|
||||||
// Whether the function has a body.
|
|
||||||
let has_body = self.tokens.peek().map(|token| token.val) == Some(Token::LeftBracket);
|
|
||||||
if has_body {
|
|
||||||
self.advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we want to parse this function dynamically.
|
// Now we want to parse this function dynamically.
|
||||||
let parser = self
|
let parser = self
|
||||||
.ctx
|
.ctx
|
||||||
.scope
|
.scope
|
||||||
.get_parser(&header.name)
|
.get_parser(&header.name.val)
|
||||||
.ok_or_else(|| ParseError::new(format!("unknown function: '{}'", &header.name)))?;
|
.ok_or_else(|| err!(@"unknown function: '{}'", &header.name.val))?;
|
||||||
|
|
||||||
|
let has_body = self.tokens.peek().map(|token| token.val) == Some(Token::LeftBracket);
|
||||||
|
|
||||||
// Do the parsing dependent on whether the function has a body.
|
// Do the parsing dependent on whether the function has a body.
|
||||||
Ok(if has_body {
|
Ok(if has_body {
|
||||||
|
self.advance();
|
||||||
|
|
||||||
// Find out the string which makes the body of this function.
|
// Find out the string which makes the body of this function.
|
||||||
let start = self.tokens.string_index();
|
let start = self.tokens.string_index();
|
||||||
let end = find_closing_bracket(&self.src[start..])
|
let end = find_closing_bracket(&self.src[start..])
|
||||||
@ -236,9 +229,10 @@ impl<'s> Parser<'s> {
|
|||||||
let token = self.tokens.next().expect("parse_func_body: expected token");
|
let token = self.tokens.next().expect("parse_func_body: expected token");
|
||||||
assert!(token.val == Token::RightBracket);
|
assert!(token.val == Token::RightBracket);
|
||||||
|
|
||||||
body
|
Spanned::new(body, Span::new(start - 1, end + 1))
|
||||||
} else {
|
} else {
|
||||||
parser(&header, None, self.ctx)?
|
let body = parser(&header, None, self.ctx)?;
|
||||||
|
Spanned::new(body, Span::new(0, 0))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,8 +258,8 @@ impl<'s> Parser<'s> {
|
|||||||
NewlineState::Zero => state = NewlineState::One(token.span),
|
NewlineState::Zero => state = NewlineState::One(token.span),
|
||||||
NewlineState::One(mut span) => {
|
NewlineState::One(mut span) => {
|
||||||
span.expand(token.span);
|
span.expand(token.span);
|
||||||
state = NewlineState::TwoOrMore;
|
|
||||||
self.append(Node::Newline, span);
|
self.append(Node::Newline, span);
|
||||||
|
state = NewlineState::TwoOrMore;
|
||||||
},
|
},
|
||||||
NewlineState::TwoOrMore => self.append_space(token.span),
|
NewlineState::TwoOrMore => self.append_space(token.span),
|
||||||
}
|
}
|
||||||
@ -273,7 +267,7 @@ impl<'s> Parser<'s> {
|
|||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
if let NewlineState::One(span) = state {
|
if let NewlineState::One(span) = state {
|
||||||
self.append_space(span);
|
self.append_space(Span::new(span.start, token.span.start));
|
||||||
}
|
}
|
||||||
|
|
||||||
state = NewlineState::Zero;
|
state = NewlineState::Zero;
|
||||||
@ -369,7 +363,10 @@ impl<'s> PeekableTokens<'s> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn string_index(&mut self) -> usize {
|
fn string_index(&mut self) -> usize {
|
||||||
self.tokens.string_index()
|
match self.peeked {
|
||||||
|
Some(Some(peeked)) => peeked.span.start,
|
||||||
|
_ => self.tokens.string_index(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_string_index(&mut self, index: usize) {
|
fn set_string_index(&mut self, index: usize) {
|
||||||
@ -428,6 +425,8 @@ error_type! {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::func::{CommandList, Function, Scope};
|
use crate::func::{CommandList, Function, Scope};
|
||||||
use crate::layout::{LayoutContext, LayoutResult};
|
use crate::layout::{LayoutContext, LayoutResult};
|
||||||
@ -451,7 +450,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for TreeFn {
|
impl PartialEq for TreeFn {
|
||||||
fn eq(&self, other: &TreeFn) -> bool { tree_equal(&self.0, &other.0) }
|
fn eq(&self, other: &TreeFn) -> bool {
|
||||||
|
assert_tree_equal(&self.0, &other.0);
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A testing function without a body.
|
/// A testing function without a body.
|
||||||
@ -470,8 +472,24 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tree_equal(a: &SyntaxTree, b: &SyntaxTree) -> bool {
|
/// Asserts that two syntax trees are equal except for all spans inside them.
|
||||||
a.nodes.iter().zip(&b.nodes).all(|(x, y)| x.val == y.val)
|
fn assert_tree_equal(a: &SyntaxTree, b: &SyntaxTree) {
|
||||||
|
for (x, y) in a.nodes.iter().zip(&b.nodes) {
|
||||||
|
let equal = match (x, y) {
|
||||||
|
(Spanned { val: F(x), .. }, Spanned { val: F(y), .. }) => {
|
||||||
|
x.header.val.name.val == y.header.val.name.val
|
||||||
|
&& x.header.val.args.positional.iter().map(Spanned::value)
|
||||||
|
.eq(y.header.val.args.positional.iter().map(Spanned::value))
|
||||||
|
&& x.header.val.args.keyword.iter().map(|s| (&s.val.0.val, &s.val.1.val))
|
||||||
|
.eq(y.header.val.args.keyword.iter().map(|s| (&s.val.0.val, &s.val.1.val)))
|
||||||
|
}
|
||||||
|
_ => x.val == y.val
|
||||||
|
};
|
||||||
|
|
||||||
|
if !equal {
|
||||||
|
panic!("assert_tree_equal: ({:?}) != ({:?})", x.val, y.val);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test if the source code parses into the syntax tree.
|
/// Test if the source code parses into the syntax tree.
|
||||||
@ -479,13 +497,13 @@ mod tests {
|
|||||||
let ctx = ParseContext {
|
let ctx = ParseContext {
|
||||||
scope: &Scope::new(),
|
scope: &Scope::new(),
|
||||||
};
|
};
|
||||||
assert!(tree_equal(&parse(src, ctx).unwrap(), &tree));
|
assert_tree_equal(&parse(src, ctx).unwrap(), &tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test with a scope containing function definitions.
|
/// Test with a scope containing function definitions.
|
||||||
fn test_scoped(scope: &Scope, src: &str, tree: SyntaxTree) {
|
fn test_scoped(scope: &Scope, src: &str, tree: SyntaxTree) {
|
||||||
let ctx = ParseContext { scope };
|
let ctx = ParseContext { scope };
|
||||||
assert!(tree_equal(&parse(src, ctx).unwrap(), &tree));
|
assert_tree_equal(&parse(src, ctx).unwrap(), &tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test if the source parses into the error.
|
/// Test if the source parses into the error.
|
||||||
@ -503,18 +521,21 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a text node.
|
/// Create a text node.
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn T(s: &str) -> Node {
|
fn T(s: &str) -> Node {
|
||||||
Node::Text(s.to_owned())
|
Node::Text(s.to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn zerospan<T>(val: T) -> Spanned<T> {
|
||||||
|
Spanned::new(val, Span::new(0, 0))
|
||||||
|
}
|
||||||
|
|
||||||
/// Shortcut macro to create a syntax tree. Is `vec`-like and the elements
|
/// Shortcut macro to create a syntax tree. Is `vec`-like and the elements
|
||||||
/// are the nodes without spans.
|
/// are the nodes without spans.
|
||||||
macro_rules! tree {
|
macro_rules! tree {
|
||||||
($($x:expr),*) => ({
|
($($x:expr),*) => ({
|
||||||
#[allow(unused_mut)] let mut nodes = vec![];
|
#[allow(unused_mut)] let mut nodes = vec![];
|
||||||
$(
|
$(
|
||||||
nodes.push(Spanned::new($x, Span::new(0, 0)));
|
nodes.push(zerospan($x));
|
||||||
)*
|
)*
|
||||||
SyntaxTree { nodes }
|
SyntaxTree { nodes }
|
||||||
});
|
});
|
||||||
@ -523,22 +544,21 @@ mod tests {
|
|||||||
|
|
||||||
/// Shortcut macro to create a function.
|
/// Shortcut macro to create a function.
|
||||||
macro_rules! func {
|
macro_rules! func {
|
||||||
(name => $name:expr, body => None $(,)*) => {
|
(name => $name:expr) => (
|
||||||
func!(@$name, Box::new(BodylessFn))
|
func!(@$name, Box::new(BodylessFn), FuncArgs::new())
|
||||||
};
|
);
|
||||||
(name => $name:expr, body => $tree:expr $(,)*) => {
|
(name => $name:expr, body => $tree:expr $(,)*) => (
|
||||||
func!(@$name, Box::new(TreeFn($tree)))
|
func!(@$name, Box::new(TreeFn($tree)), FuncArgs::new())
|
||||||
};
|
);
|
||||||
(@$name:expr, $body:expr) => {
|
(@$name:expr, $body:expr, $args:expr) => (
|
||||||
FuncCall {
|
FuncCall {
|
||||||
header: FuncHeader {
|
header: zerospan(FuncHeader {
|
||||||
name: $name.to_string(),
|
name: zerospan($name.to_string()),
|
||||||
args: vec![],
|
args: $args,
|
||||||
kwargs: vec![],
|
}),
|
||||||
},
|
body: zerospan($body),
|
||||||
body: $body,
|
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the basic cases.
|
/// Parse the basic cases.
|
||||||
@ -573,8 +593,8 @@ mod tests {
|
|||||||
scope.add::<TreeFn>("modifier");
|
scope.add::<TreeFn>("modifier");
|
||||||
scope.add::<TreeFn>("func");
|
scope.add::<TreeFn>("func");
|
||||||
|
|
||||||
test_scoped(&scope,"[test]", tree! [ F(func! { name => "test", body => None }) ]);
|
test_scoped(&scope,"[test]", tree! [ F(func! { name => "test" }) ]);
|
||||||
test_scoped(&scope,"[ test]", tree! [ F(func! { name => "test", body => None }) ]);
|
test_scoped(&scope,"[ test]", tree! [ F(func! { name => "test" }) ]);
|
||||||
test_scoped(&scope, "This is an [modifier][example] of a function invocation.", tree! [
|
test_scoped(&scope, "This is an [modifier][example] of a function invocation.", tree! [
|
||||||
T("This"), S, T("is"), S, T("an"), S,
|
T("This"), S, T("is"), S, T("an"), S,
|
||||||
F(func! { name => "modifier", body => tree! [ T("example") ] }), S,
|
F(func! { name => "modifier", body => tree! [ T("example") ] }), S,
|
||||||
@ -583,7 +603,7 @@ mod tests {
|
|||||||
test_scoped(&scope, "[func][Hello][modifier][Here][end]", tree! [
|
test_scoped(&scope, "[func][Hello][modifier][Here][end]", tree! [
|
||||||
F(func! { name => "func", body => tree! [ T("Hello") ] }),
|
F(func! { name => "func", body => tree! [ T("Hello") ] }),
|
||||||
F(func! { name => "modifier", body => tree! [ T("Here") ] }),
|
F(func! { name => "modifier", body => tree! [ T("Here") ] }),
|
||||||
F(func! { name => "end", body => None }),
|
F(func! { name => "end" }),
|
||||||
]);
|
]);
|
||||||
test_scoped(&scope, "[func][]", tree! [ F(func! { name => "func", body => tree! [] }) ]);
|
test_scoped(&scope, "[func][]", tree! [ F(func! { name => "func", body => tree! [] }) ]);
|
||||||
test_scoped(&scope, "[modifier][[func][call]] outside", tree! [
|
test_scoped(&scope, "[modifier][[func][call]] outside", tree! [
|
||||||
@ -602,22 +622,15 @@ mod tests {
|
|||||||
fn parse_function_args() {
|
fn parse_function_args() {
|
||||||
use Expression::{Number as N, Size as Z, Bool as B};
|
use Expression::{Number as N, Size as Z, Bool as B};
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn S(string: &str) -> Expression { Expression::Str(string.to_owned()) }
|
fn S(string: &str) -> Expression { Expression::Str(string.to_owned()) }
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn I(string: &str) -> Expression { Expression::Ident(string.to_owned()) }
|
fn I(string: &str) -> Expression { Expression::Ident(string.to_owned()) }
|
||||||
|
|
||||||
fn func(name: &str, args: Vec<Expression>) -> SyntaxTree {
|
fn func(name: &str, positional: Vec<Expression>) -> SyntaxTree {
|
||||||
tree! [
|
let args = FuncArgs {
|
||||||
F(FuncCall {
|
positional: positional.into_iter().map(zerospan).collect(),
|
||||||
header: FuncHeader {
|
keyword: vec![]
|
||||||
name: name.to_string(),
|
};
|
||||||
args,
|
tree! [ F(func!(@name, Box::new(BodylessFn), args)) ]
|
||||||
kwargs: vec![],
|
|
||||||
},
|
|
||||||
body: Box::new(BodylessFn)
|
|
||||||
})
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
@ -646,9 +659,9 @@ mod tests {
|
|||||||
test_scoped(&scope, "Text\n// Comment\n More text",
|
test_scoped(&scope, "Text\n// Comment\n More text",
|
||||||
tree! [ T("Text"), S, T("More"), S, T("text") ]);
|
tree! [ T("Text"), S, T("More"), S, T("text") ]);
|
||||||
test_scoped(&scope, "[test/*world*/]",
|
test_scoped(&scope, "[test/*world*/]",
|
||||||
tree! [ F(func! { name => "test", body => None }) ]);
|
tree! [ F(func! { name => "test" }) ]);
|
||||||
test_scoped(&scope, "[test/*]*/]",
|
test_scoped(&scope, "[test/*]*/]",
|
||||||
tree! [ F(func! { name => "test", body => None }) ]);
|
tree! [ F(func! { name => "test" }) ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test if escaped, but unbalanced parens are correctly parsed.
|
/// Test if escaped, but unbalanced parens are correctly parsed.
|
||||||
@ -687,10 +700,7 @@ mod tests {
|
|||||||
scope.add::<TreeFn>("bold");
|
scope.add::<TreeFn>("bold");
|
||||||
|
|
||||||
test_scoped(&scope, "[func] ⺐.", tree! [
|
test_scoped(&scope, "[func] ⺐.", tree! [
|
||||||
F(func! {
|
F(func! { name => "func" }),
|
||||||
name => "func",
|
|
||||||
body => None,
|
|
||||||
}),
|
|
||||||
S, T("⺐.")
|
S, T("⺐.")
|
||||||
]);
|
]);
|
||||||
test_scoped(&scope, "[bold][Hello 🌍!]", tree! [
|
test_scoped(&scope, "[bold][Hello 🌍!]", tree! [
|
||||||
@ -719,13 +729,23 @@ mod tests {
|
|||||||
let tree = parse("p1\n \np2");
|
let tree = parse("p1\n \np2");
|
||||||
assert_eq!(tree[1].span.pair(), (2, 5));
|
assert_eq!(tree[1].span.pair(), (2, 5));
|
||||||
|
|
||||||
let tree = parse("func [hello: pos, other][body _🌍_]");
|
let tree = parse("p1\n p2");
|
||||||
|
assert_eq!(tree[1].span.pair(), (2, 4));
|
||||||
|
|
||||||
|
let src = "func [hello: pos, other][body _🌍_]";
|
||||||
|
let tree = parse(src);
|
||||||
assert_eq!(tree[0].span.pair(), (0, 4));
|
assert_eq!(tree[0].span.pair(), (0, 4));
|
||||||
assert_eq!(tree[1].span.pair(), (4, 5));
|
assert_eq!(tree[1].span.pair(), (4, 5));
|
||||||
assert_eq!(tree[2].span.pair(), (5, 37));
|
assert_eq!(tree[2].span.pair(), (5, 37));
|
||||||
|
|
||||||
let func = if let Node::Func(f) = &tree[2].val { f } else { panic!() };
|
let func = if let Node::Func(f) = &tree[2].val { f } else { panic!() };
|
||||||
let body = &func.body.downcast::<TreeFn>().unwrap().0.nodes;
|
assert_eq!(func.header.span.pair(), (5, 24));
|
||||||
|
assert_eq!(func.header.val.name.span.pair(), (6, 11));
|
||||||
|
assert_eq!(func.header.val.args.positional[0].span.pair(), (13, 16));
|
||||||
|
assert_eq!(func.header.val.args.positional[1].span.pair(), (18, 23));
|
||||||
|
|
||||||
|
let body = &func.body.val.downcast::<TreeFn>().unwrap().0.nodes;
|
||||||
|
assert_eq!(func.body.span.pair(), (24, 37));
|
||||||
assert_eq!(body[0].span.pair(), (0, 4));
|
assert_eq!(body[0].span.pair(), (0, 4));
|
||||||
assert_eq!(body[1].span.pair(), (4, 5));
|
assert_eq!(body[1].span.pair(), (4, 5));
|
||||||
assert_eq!(body[2].span.pair(), (5, 6));
|
assert_eq!(body[2].span.pair(), (5, 6));
|
||||||
@ -742,7 +762,7 @@ mod tests {
|
|||||||
|
|
||||||
test_err("No functions here]", "unexpected closing bracket");
|
test_err("No functions here]", "unexpected closing bracket");
|
||||||
test_err_scoped(&scope, "[hello][world", "expected closing bracket");
|
test_err_scoped(&scope, "[hello][world", "expected closing bracket");
|
||||||
test_err("[hello world", "expected function arguments or closing bracket");
|
test_err("[hello world", "expected arguments or closing bracket");
|
||||||
test_err("[ no-name][Why?]", "invalid identifier: 'no-name'");
|
test_err("[ no-name][Why?]", "invalid identifier: 'no-name'");
|
||||||
test_err("Hello */", "unexpected end of block comment");
|
test_err("Hello */", "unexpected end of block comment");
|
||||||
}
|
}
|
||||||
|
@ -74,15 +74,23 @@ fn test(name: &str, src: &str) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let start = Instant::now();
|
// Make run warm.
|
||||||
|
let warmup_start = Instant::now();
|
||||||
|
typesetter.typeset(&src).unwrap();
|
||||||
|
let warmup_end = Instant::now();
|
||||||
|
|
||||||
// Layout into box layout.
|
// Layout into box layout.
|
||||||
|
let start = Instant::now();
|
||||||
let tree = typesetter.parse(&src).unwrap();
|
let tree = typesetter.parse(&src).unwrap();
|
||||||
|
let mid = Instant::now();
|
||||||
let layouts = typesetter.layout(&tree).unwrap();
|
let layouts = typesetter.layout(&tree).unwrap();
|
||||||
|
|
||||||
let end = Instant::now();
|
let end = Instant::now();
|
||||||
let duration = end - start;
|
|
||||||
println!(" => {:?}", duration);
|
// Print measurements.
|
||||||
|
println!(" - cold start: {:?}", warmup_end - warmup_start);
|
||||||
|
println!(" - warmed up: {:?}", end - start);
|
||||||
|
println!(" - parsing: {:?}", mid - start);
|
||||||
|
println!(" - layouting: {:?}", end - mid);
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
// Write the serialed layout file.
|
// Write the serialed layout file.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user