Spans for all parts of functions

This commit is contained in:
Laurenz 2019-10-31 20:26:21 +01:00
parent 65ec3764e5
commit 110e4b9cb9
12 changed files with 232 additions and 178 deletions

View File

@ -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 {

View File

@ -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.

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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.

View File

@ -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);

View File

@ -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)
} }
} }
}; );
} }

View File

@ -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);

View File

@ -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.

View File

@ -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");
} }

View File

@ -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.