Unify parsing and typesetting functions. 🗳

This commit is contained in:
Laurenz 2019-05-20 11:36:31 +02:00
parent 5c66bac689
commit e3215fa3b9
8 changed files with 303 additions and 285 deletions

View File

@ -46,7 +46,7 @@ fn run() -> Result<(), Box<Error>> {
]));
// Compile the source code with the compiler.
let document = compiler.typeset(&src)?;
let document = compiler.compile(&src)?;
// Export the document into a PDF file.

179
src/engine/loader.rs Normal file
View File

@ -0,0 +1,179 @@
//! Loading of fonts by queries.
use std::fmt::{self, Debug, Formatter};
use std::cell::{RefCell, Ref};
use std::collections::HashMap;
use crate::font::{Font, FontProvider, FontFamily, FontInfo};
/// Serves matching fonts given a query.
pub struct FontLoader<'p> {
/// The font providers.
providers: &'p [Box<dyn FontProvider + 'p>],
/// All available fonts indexed by provider.
provider_fonts: Vec<&'p [FontInfo]>,
/// The internal state.
state: RefCell<FontLoaderState<'p>>,
}
/// Internal state of the font loader (wrapped in a RefCell).
struct FontLoaderState<'p> {
/// The loaded fonts along with their external indices.
fonts: Vec<(Option<usize>, Font)>,
/// Allows to retrieve cached results for queries.
query_cache: HashMap<FontQuery<'p>, usize>,
/// Allows to lookup fonts by their infos.
info_cache: HashMap<&'p FontInfo, usize>,
/// Indexed by outside and indices maps to internal indices.
inner_index: Vec<usize>,
}
impl<'p> FontLoader<'p> {
/// Create a new font loader.
pub fn new(providers: &'p [Box<dyn FontProvider + 'p>]) -> FontLoader {
let provider_fonts = providers.iter()
.map(|prov| prov.available()).collect();
FontLoader {
providers,
provider_fonts,
state: RefCell::new(FontLoaderState {
query_cache: HashMap::new(),
info_cache: HashMap::new(),
inner_index: vec![],
fonts: vec![],
}),
}
}
/// Return the best matching font and it's index (if there is any) given the query.
pub fn get(&self, query: FontQuery<'p>) -> Option<(usize, Ref<Font>)> {
// Check if we had the exact same query before.
let state = self.state.borrow();
if let Some(&index) = state.query_cache.get(&query) {
// That this is the query cache means it must has an index as we've served it before.
let extern_index = state.fonts[index].0.unwrap();
let font = Ref::map(state, |s| &s.fonts[index].1);
return Some((extern_index, font));
}
drop(state);
// Go over all font infos from all font providers that match the query.
for family in query.families {
for (provider, infos) in self.providers.iter().zip(&self.provider_fonts) {
for info in infos.iter() {
// Check whether this info matches the query.
if Self::matches(query, family, info) {
let mut state = self.state.borrow_mut();
// Check if we have already loaded this font before.
// Otherwise we'll fetch the font from the provider.
let index = if let Some(&index) = state.info_cache.get(info) {
index
} else if let Some(mut source) = provider.get(info) {
// Read the font program into a vec.
let mut program = Vec::new();
source.read_to_end(&mut program).ok()?;
// Create a font from it.
let font = Font::new(program).ok()?;
// Insert it into the storage.
let index = state.fonts.len();
state.info_cache.insert(info, index);
state.fonts.push((None, font));
index
} else {
continue;
};
// Check whether this font has the character we need.
let has_char = state.fonts[index].1.mapping.contains_key(&query.character);
if has_char {
// We can take this font, so we store the query.
state.query_cache.insert(query, index);
// Now we have to find out the external index of it, or assign a new
// one if it has not already one.
let maybe_extern_index = state.fonts[index].0;
let extern_index = maybe_extern_index.unwrap_or_else(|| {
// We have to assign an external index before serving.
let extern_index = state.inner_index.len();
state.inner_index.push(index);
state.fonts[index].0 = Some(extern_index);
extern_index
});
// Release the mutable borrow and borrow immutably.
drop(state);
let font = Ref::map(self.state.borrow(), |s| &s.fonts[index].1);
// Finally we can return it.
return Some((extern_index, font));
}
}
}
}
}
None
}
/// Return a loaded font at an index. Panics if the index is out of bounds.
pub fn get_with_index(&self, index: usize) -> Ref<Font> {
let state = self.state.borrow();
let internal = state.inner_index[index];
Ref::map(state, |s| &s.fonts[internal].1)
}
/// Return the list of fonts.
pub fn into_fonts(self) -> Vec<Font> {
// Sort the fonts by external key so that they are in the correct order.
let mut fonts = self.state.into_inner().fonts;
fonts.sort_by_key(|&(maybe_index, _)| match maybe_index {
Some(index) => index as isize,
None => -1,
});
// Remove the fonts that are not used from the outside
fonts.into_iter().filter_map(|(maybe_index, font)| {
maybe_index.map(|_| font)
}).collect()
}
/// Check whether the query and the current family match the info.
fn matches(query: FontQuery, family: &FontFamily, info: &FontInfo) -> bool {
info.families.contains(family)
&& info.italic == query.italic && info.bold == query.bold
}
}
impl Debug for FontLoader<'_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let state = self.state.borrow();
f.debug_struct("FontLoader")
.field("providers", &self.providers.len())
.field("provider_fonts", &self.provider_fonts)
.field("fonts", &state.fonts)
.field("query_cache", &state.query_cache)
.field("info_cache", &state.info_cache)
.field("inner_index", &state.inner_index)
.finish()
}
}
/// A query for a font with specific properties.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct FontQuery<'p> {
/// A fallback list of font families to accept. The first family in this list, that also
/// satisfies the other conditions, shall be returned.
pub families: &'p [FontFamily],
/// Whether the font shall be in italics.
pub italic: bool,
/// Whether the font shall be in boldface.
pub bold: bool,
/// Which character we need.
pub character: char,
}

View File

@ -1,25 +1,32 @@
//! Core typesetting engine.
use std::cell::{RefCell, Ref};
use std::collections::HashMap;
use std::cell::Ref;
use std::mem::swap;
use smallvec::SmallVec;
use crate::syntax::{SyntaxTree, Node};
use crate::doc::{Document, Page, Text, TextCommand};
use crate::font::{Font, FontFamily, FontInfo, FontError};
use crate::Context;
use crate::font::{Font, FontFamily, FontProvider, FontError};
use crate::syntax::{SyntaxTree, Node};
use loader::{FontLoader, FontQuery};
mod size;
mod loader;
pub use size::Size;
/// Typeset a parsed syntax tree.
pub fn typeset<'p>(tree: &SyntaxTree, style: &Style, font_providers: &[Box<dyn FontProvider + 'p>])
-> TypesetResult<Document> {
Engine::new(tree, style, font_providers).typeset()
}
/// The core typesetting engine, transforming an abstract syntax tree into a document.
pub struct Engine<'a> {
struct Engine<'a> {
// Input
tree: &'a SyntaxTree,
ctx: &'a Context<'a>,
style: &'a Style,
// Internal
font_loader: FontLoader<'a>,
@ -38,12 +45,15 @@ pub struct Engine<'a> {
impl<'a> Engine<'a> {
/// Create a new generator from a syntax tree.
#[inline]
pub fn new(tree: &'a SyntaxTree, context: &'a Context<'a>) -> Engine<'a> {
fn new(
tree: &'a SyntaxTree,
style: &'a Style,
font_providers: &'a [Box<dyn FontProvider + 'a>]
) -> Engine<'a> {
Engine {
tree,
ctx: context,
font_loader: FontLoader::new(context),
style,
font_loader: FontLoader::new(font_providers),
text_commands: vec![],
active_font: std::usize::MAX,
current_text: String::new(),
@ -55,7 +65,7 @@ impl<'a> Engine<'a> {
}
/// Generate the abstract document.
pub fn typeset(mut self) -> TypesetResult<Document> {
fn typeset(mut self) -> TypesetResult<Document> {
// Start by moving to a suitable position.
self.move_start();
@ -66,7 +76,7 @@ impl<'a> Engine<'a> {
Node::Space => self.write_space()?,
Node::Newline => {
self.write_buffered_text();
self.move_newline(self.ctx.style.paragraph_spacing);
self.move_newline(self.style.paragraph_spacing);
},
Node::ToggleItalics => self.italic = !self.italic,
@ -83,8 +93,8 @@ impl<'a> Engine<'a> {
// Create a document with one page from the contents.
Ok(Document {
pages: vec![Page {
width: self.ctx.style.width,
height: self.ctx.style.height,
width: self.style.width,
height: self.style.height,
text: vec![Text {
commands: self.text_commands,
}],
@ -152,8 +162,8 @@ impl<'a> Engine<'a> {
fn move_start(&mut self) {
// Move cursor to top-left position
self.text_commands.push(TextCommand::Move(
self.ctx.style.margin_left,
self.ctx.style.height - self.ctx.style.margin_top
self.style.margin_left,
self.style.height - self.style.margin_top
));
}
@ -166,8 +176,8 @@ impl<'a> Engine<'a> {
let vertical_move = if self.current_max_vertical_move == Size::zero() {
// If max vertical move is still zero, the line is empty and we take the
// font size from the previous line.
self.ctx.style.font_size
* self.ctx.style.line_spacing
self.style.font_size
* self.style.line_spacing
* self.get_font_at(self.active_font).metrics.ascender
* factor
} else {
@ -181,21 +191,21 @@ impl<'a> Engine<'a> {
/// Set the current font.
fn set_font(&mut self, index: usize) {
self.text_commands.push(TextCommand::SetFont(index, self.ctx.style.font_size));
self.text_commands.push(TextCommand::SetFont(index, self.style.font_size));
self.active_font = index;
}
/// Whether the current line plus the extra `width` would overflow the line.
fn would_overflow(&self, width: Size) -> bool {
let max_width = self.ctx.style.width
- self.ctx.style.margin_left - self.ctx.style.margin_right;
let max_width = self.style.width
- self.style.margin_left - self.style.margin_right;
self.current_line_width + width > max_width
}
/// Load a font that has the character we need.
fn get_font_for(&self, character: char) -> TypesetResult<(usize, Ref<Font>)> {
self.font_loader.get(FontQuery {
families: &self.ctx.style.font_families,
families: &self.style.font_families,
italic: self.italic,
bold: self.bold,
character,
@ -209,167 +219,13 @@ impl<'a> Engine<'a> {
/// The width of a char in a specific font.
fn char_width(&self, character: char, font: &Font) -> Size {
font.widths[font.map(character) as usize] * self.ctx.style.font_size
font.widths[font.map(character) as usize] * self.style.font_size
}
}
/// Serves matching fonts given a query.
struct FontLoader<'a> {
/// The context containing the used font providers.
context: &'a Context<'a>,
/// All available fonts indexed by provider.
provider_fonts: Vec<&'a [FontInfo]>,
/// The internal state.
state: RefCell<FontLoaderState<'a>>,
}
/// Internal state of the font loader (wrapped in a RefCell).
struct FontLoaderState<'a> {
/// The loaded fonts along with their external indices.
fonts: Vec<(Option<usize>, Font)>,
/// Allows to retrieve cached results for queries.
query_cache: HashMap<FontQuery<'a>, usize>,
/// Allows to lookup fonts by their infos.
info_cache: HashMap<&'a FontInfo, usize>,
/// Indexed by outside and indices maps to internal indices.
inner_index: Vec<usize>,
}
impl<'a> FontLoader<'a> {
/// Create a new font loader.
pub fn new(context: &'a Context<'a>) -> FontLoader {
let provider_fonts = context.font_providers.iter()
.map(|prov| prov.available()).collect();
FontLoader {
context,
provider_fonts,
state: RefCell::new(FontLoaderState {
query_cache: HashMap::new(),
info_cache: HashMap::new(),
inner_index: vec![],
fonts: vec![],
}),
}
}
/// Return the best matching font and it's index (if there is any) given the query.
pub fn get(&self, query: FontQuery<'a>) -> Option<(usize, Ref<Font>)> {
// Check if we had the exact same query before.
let state = self.state.borrow();
if let Some(&index) = state.query_cache.get(&query) {
// That this is the query cache means it must has an index as we've served it before.
let extern_index = state.fonts[index].0.unwrap();
let font = Ref::map(state, |s| &s.fonts[index].1);
return Some((extern_index, font));
}
drop(state);
// Go over all font infos from all font providers that match the query.
for family in query.families {
for (provider, infos) in self.context.font_providers.iter().zip(&self.provider_fonts) {
for info in infos.iter() {
// Check whether this info matches the query.
if Self::matches(query, family, info) {
let mut state = self.state.borrow_mut();
// Check if we have already loaded this font before.
// Otherwise we'll fetch the font from the provider.
let index = if let Some(&index) = state.info_cache.get(info) {
index
} else if let Some(mut source) = provider.get(info) {
// Read the font program into a vec.
let mut program = Vec::new();
source.read_to_end(&mut program).ok()?;
// Create a font from it.
let font = Font::new(program).ok()?;
// Insert it into the storage.
let index = state.fonts.len();
state.info_cache.insert(info, index);
state.fonts.push((None, font));
index
} else {
continue;
};
// Check whether this font has the character we need.
let has_char = state.fonts[index].1.mapping.contains_key(&query.character);
if has_char {
// We can take this font, so we store the query.
state.query_cache.insert(query, index);
// Now we have to find out the external index of it, or assign a new
// one if it has not already one.
let maybe_extern_index = state.fonts[index].0;
let extern_index = maybe_extern_index.unwrap_or_else(|| {
// We have to assign an external index before serving.
let extern_index = state.inner_index.len();
state.inner_index.push(index);
state.fonts[index].0 = Some(extern_index);
extern_index
});
// Release the mutable borrow and borrow immutably.
drop(state);
let font = Ref::map(self.state.borrow(), |s| &s.fonts[index].1);
// Finally we can return it.
return Some((extern_index, font));
}
}
}
}
}
None
}
/// Return a loaded font at an index. Panics if the index is out of bounds.
pub fn get_with_index(&self, index: usize) -> Ref<Font> {
let state = self.state.borrow();
let internal = state.inner_index[index];
Ref::map(state, |s| &s.fonts[internal].1)
}
/// Return the list of fonts.
pub fn into_fonts(self) -> Vec<Font> {
// Sort the fonts by external key so that they are in the correct order.
let mut fonts = self.state.into_inner().fonts;
fonts.sort_by_key(|&(maybe_index, _)| match maybe_index {
Some(index) => index as isize,
None => -1,
});
// Remove the fonts that are not used from the outside
fonts.into_iter().filter_map(|(maybe_index, font)| {
maybe_index.map(|_| font)
}).collect()
}
/// Check whether the query and the current family match the info.
fn matches(query: FontQuery, family: &FontFamily, info: &FontInfo) -> bool {
info.families.contains(family)
&& info.italic == query.italic && info.bold == query.bold
}
}
/// A query for a font with specific properties.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
struct FontQuery<'a> {
/// A fallback list of font families to accept. The first family in this list, that also
/// satisfies the other conditions, shall be returned.
families: &'a [FontFamily],
/// Whether the font shall be in italics.
italic: bool,
/// Whether the font shall be in boldface.
bold: bool,
/// Which character we need.
character: char,
}
/// The context for typesetting a function.
#[derive(Debug)]
pub struct TypesetContext {}
/// Default styles for typesetting.
#[derive(Debug, Clone, PartialEq)]

View File

@ -209,7 +209,7 @@ pub struct FontInfo {
/// A macro to create [FontInfos](crate::font::FontInfo) easily.
///
/// Accepts first a bracketed (ordered) list of font families. Allowed are string expressions
/// aswell as the three base families `SansSerif`, `Serif` and `Monospace`.
/// as well as the three base families `SansSerif`, `Serif` and `Monospace`.
///
/// Then there may follow (separated by commas) the keywords `italic` and/or `bold`.
///

View File

@ -4,8 +4,9 @@ use std::any::Any;
use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter};
use crate::syntax::{FuncHeader, Expression};
use crate::parsing::{FuncContext, ParseResult};
use crate::syntax::FuncHeader;
use crate::parsing::{ParseContext, ParseResult};
use crate::engine::{TypesetContext, TypesetResult};
/// Types that act as functions.
@ -17,10 +18,11 @@ use crate::parsing::{FuncContext, ParseResult};
/// used as functions, that is they fulfill the bounds `Debug + PartialEq + 'static`.
pub trait Function: FunctionBounds {
/// Parse the tokens of the context with the given header and scope into self.
fn parse(context: FuncContext) -> ParseResult<Self> where Self: Sized;
fn parse(header: &FuncHeader, body: Option<&str>, ctx: &ParseContext)
-> ParseResult<Self> where Self: Sized;
/// Execute the function and optionally yield a return value.
fn typeset(&self, header: &FuncHeader) -> Option<Expression>;
fn typeset(&self, ctx: &TypesetContext) -> TypesetResult<()>;
}
impl PartialEq for dyn Function {
@ -61,7 +63,8 @@ pub struct Scope {
}
/// A function which transforms a parsing context into a boxed function.
type ParseFunc = dyn Fn(FuncContext) -> ParseResult<Box<dyn Function>>;
type ParseFunc = dyn Fn(&FuncHeader, Option<&str>, &ParseContext)
-> ParseResult<Box<dyn Function>>;
impl Scope {
/// Create a new empty scope.
@ -69,12 +72,17 @@ impl Scope {
Scope { parsers: HashMap::new() }
}
/// Create a new scope with the standard functions contained.
pub fn with_std() -> Scope {
Scope::new()
}
/// Add a function type to the scope with a given name.
pub fn add<F: Function + 'static>(&mut self, name: &str) {
self.parsers.insert(
name.to_owned(),
Box::new(|context| {
F::parse(context).map(|func| Box::new(func) as Box<dyn Function>)
Box::new(|h, b, c| {
F::parse(h, b, c).map(|func| Box::new(func) as Box<dyn Function>)
})
);
}

View File

@ -31,8 +31,8 @@
//! ("NotoEmoji-Regular.ttf", font_info!(["NotoEmoji", "Noto", SansSerif, Serif, Monospace])),
//! ]));
//!
//! // Compile the source code with the compiler.
//! let document = compiler.typeset(src).unwrap();
//! // Compile the source code into a document with the compiler.
//! let document = compiler.compile(src).unwrap();
//!
//! // Export the document into a PDF file.
//! # /*
@ -43,10 +43,8 @@
//! exporter.export(&document, file).unwrap();
//! ```
use std::fmt::{self, Debug, Formatter};
use crate::doc::Document;
use crate::engine::{Engine, Style, TypesetError};
use crate::engine::{typeset, Style, TypesetResult, TypesetError};
use crate::func::Scope;
use crate::font::FontProvider;
use crate::parsing::{parse, ParseResult, ParseError};
@ -68,7 +66,10 @@ pub mod syntax;
///
/// Holds the compilation context, which can be configured through various methods.
pub struct Compiler<'p> {
context: Context<'p>,
/// Style for typesetting.
style: Style,
/// Font providers.
font_providers: Vec<Box<dyn FontProvider + 'p>>,
}
impl<'p> Compiler<'p> {
@ -76,55 +77,42 @@ impl<'p> Compiler<'p> {
#[inline]
pub fn new() -> Compiler<'p> {
Compiler {
context: Context {
style: Style::default(),
font_providers: vec![],
}
style: Style::default(),
font_providers: vec![],
}
}
/// Set the default style for the document.
#[inline]
pub fn set_style(&mut self, style: Style) {
self.context.style = style;
self.style = style;
}
/// Add a font provider to the context of this compiler.
#[inline]
pub fn add_font_provider<P: 'p>(&mut self, provider: P) where P: FontProvider {
self.context.font_providers.push(Box::new(provider));
self.font_providers.push(Box::new(provider));
}
/// Parse source code into a syntax tree.
#[inline]
pub fn parse(&self, src: &str) -> ParseResult<SyntaxTree> {
let scope = Scope::new();
let scope = Scope::with_std();
parse(src, &scope)
}
/// Typeset a parsed syntax tree into a document.
#[inline]
pub fn typeset(&self, tree: &SyntaxTree) -> TypesetResult<Document> {
typeset(&tree, &self.style, &self.font_providers).map_err(Into::into)
}
/// Compile a portable typesetted document from source code.
#[inline]
pub fn typeset(&self, src: &str) -> CompileResult<Document> {
pub fn compile(&self, src: &str) -> Result<Document, CompileError> {
let tree = self.parse(src)?;
let engine = Engine::new(&tree, &self.context);
engine.typeset().map_err(Into::into)
}
}
/// Holds the compilation context.
pub struct Context<'p> {
/// Style for typesetting.
style: Style,
/// Font providers.
font_providers: Vec<Box<dyn FontProvider + 'p>>,
}
impl Debug for Context<'_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("Context")
.field("style", &self.style)
.field("font_providers", &self.font_providers.len())
.finish()
let document = self.typeset(&tree)?;
Ok(document)
}
}
@ -132,26 +120,23 @@ impl Debug for Context<'_> {
pub enum CompileError {
/// An error that occured while transforming source code into
/// an abstract syntax tree.
Parse(ParseError),
ParseErr(ParseError),
/// An error that occured while typesetting into an abstract document.
Typeset(TypesetError),
TypesetErr(TypesetError),
}
/// The result type for compilation.
pub type CompileResult<T> = Result<T, CompileError>;
error_type! {
err: CompileError,
show: f => match err {
CompileError::Parse(e) => write!(f, "parse error: {}", e),
CompileError::Typeset(e) => write!(f, "typeset error: {}", e),
CompileError::ParseErr(e) => write!(f, "parse error: {}", e),
CompileError::TypesetErr(e) => write!(f, "typeset error: {}", e),
},
source: match err {
CompileError::Parse(e) => Some(e),
CompileError::Typeset(e) => Some(e),
CompileError::ParseErr(e) => Some(e),
CompileError::TypesetErr(e) => Some(e),
},
from: (ParseError, CompileError::Parse(err)),
from: (TypesetError, CompileError::Typeset(err)),
from: (ParseError, CompileError::ParseErr(err)),
from: (TypesetError, CompileError::TypesetErr(err)),
}
@ -177,7 +162,7 @@ mod test {
]));
// Compile into document
let document = compiler.typeset(src).unwrap();
let document = compiler.compile(src).unwrap();
// Write to file
let path = format!("../target/typeset-unit-{}.pdf", name);
@ -187,38 +172,30 @@ mod test {
}
#[test]
fn simple() {
test("parentheses", "Text with ) and ( or (enclosed) works.");
test("multiline-lorem","
fn features() {
test("features", r"
**FEATURES TEST PAGE**
__Simple multiline:__
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
clita kasd gubergren, no sea takimata sanctus est.
__Parentheses:__ Text with ) and ( or (enclosed) works.
__Composite character:__
__Unicode:__ mbeed font with Unicode!
__Emoji:__ Hello World 🌍!
__Styles:__ This is **bold** and that is __great__!
");
}
#[test]
fn composite_glyph() {
test("composite-glyph", "Composite character‼");
}
#[test]
fn unicode() {
test("unicode", "∑mbe∂∂ed font with Unicode!");
test("mixed-emoji", "Hello World 🌍!")
}
#[test]
fn styled() {
test("styled", "
**Hello World**.
That's __great__!
");
}
#[test]
fn long_wikipedia() {
fn wikipedia() {
test("wikipedia", r#"
Typesetting is the composition of text by means of arranging physical types or the
digital equivalents. Stored letters and other symbols (called sorts in mechanical

View File

@ -457,6 +457,10 @@ impl<'s> Parser<'s> {
let parser = self.scope.get_parser(&header.name)
.ok_or_else(|| ParseError::new(format!("unknown function: '{}'", &header.name)))?;
let parse_context = ParseContext {
scope: &self.scope,
};
// Do the parsing dependent on whether the function has a body.
Ok(if has_body {
// Find out the string which makes the body of this function.
@ -467,11 +471,7 @@ impl<'s> Parser<'s> {
// Parse the body.
let body_string = &self.src[start .. end];
let body = parser(FuncContext {
header: &header,
body: Some(body_string),
scope: &self.scope,
})?;
let body = parser(&header, Some(body_string), &parse_context)?;
// Skip to the end of the function in the token stream.
self.tokens.goto(end);
@ -481,11 +481,7 @@ impl<'s> Parser<'s> {
body
} else {
parser(FuncContext {
header: &header,
body: None,
scope: &self.scope,
})?
parser(&header, None, &parse_context)?
})
}
@ -633,12 +629,8 @@ impl<'s> Iterator for PeekableTokens<'s> {
/// The context for parsing a function.
#[derive(Debug)]
pub struct FuncContext<'s> {
/// The header of the function to be parsed.
pub header: &'s FuncHeader,
/// The body source if the function has a body, otherwise nothing.
pub body: Option<&'s str>,
/// The current scope containing function definitions.
pub struct ParseContext<'s> {
/// The scope containing function definitions.
pub scope: &'s Scope,
}
@ -668,7 +660,8 @@ pub struct ParseError(String);
pub type ParseResult<T> = Result<T, ParseError>;
impl ParseError {
fn new<S: Into<String>>(message: S) -> ParseError {
/// Create a new parse error with a message.
pub fn new<S: Into<String>>(message: S) -> ParseError {
ParseError(message.into())
}
}
@ -807,9 +800,10 @@ mod token_tests {
#[cfg(test)]
mod parse_tests {
use super::*;
use funcs::*;
use crate::func::{Function, Scope};
use crate::engine::{TypesetContext, TypesetResult};
use Node::{Space as S, Newline as N, Func as F};
use funcs::*;
/// Two test functions, one which parses it's body as another syntax tree
/// and another one which does not expect a body.
@ -821,14 +815,16 @@ mod parse_tests {
pub struct TreeFn(pub SyntaxTree);
impl Function for TreeFn {
fn parse(context: FuncContext) -> ParseResult<Self> where Self: Sized {
if let Some(src) = context.body {
parse(src, context.scope).map(|tree| TreeFn(tree))
fn parse(_: &FuncHeader, body: Option<&str>, ctx: &ParseContext)
-> ParseResult<Self> where Self: Sized {
if let Some(src) = body {
parse(src, ctx.scope).map(|tree| TreeFn(tree))
} else {
Err(ParseError::new("expected body for tree fn"))
}
}
fn typeset(&self, _header: &FuncHeader) -> Option<Expression> { None }
fn typeset(&self, _: &TypesetContext) -> TypesetResult<()> { Ok(()) }
}
/// A testing function without a body.
@ -836,14 +832,16 @@ mod parse_tests {
pub struct BodylessFn;
impl Function for BodylessFn {
fn parse(context: FuncContext) -> ParseResult<Self> where Self: Sized {
if context.body.is_none() {
fn parse(_: &FuncHeader, body: Option<&str>, _: &ParseContext)
-> ParseResult<Self> where Self: Sized {
if body.is_none() {
Ok(BodylessFn)
} else {
Err(ParseError::new("unexpected body for bodyless fn"))
}
}
fn typeset(&self, _header: &FuncHeader) -> Option<Expression> { None }
fn typeset(&self, _: &TypesetContext) -> TypesetResult<()> { Ok(()) }
}
}

View File

@ -97,6 +97,6 @@ pub struct FuncHeader {
pub kwargs: HashMap<String, Expression>
}
/// A potentially unevaluated expression.
/// A value expression.
#[derive(Debug, Clone, PartialEq)]
pub enum Expression {}