mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Simple dynamic, scoped function parsing 📥
This commit is contained in:
parent
a51e7a0758
commit
f279c52b50
@ -14,13 +14,13 @@ pub use size::Size;
|
||||
|
||||
|
||||
/// The core typesetting engine, transforming an abstract syntax tree into a document.
|
||||
pub struct Engine<'t> {
|
||||
pub struct Engine<'a> {
|
||||
// Input
|
||||
tree: &'t SyntaxTree<'t>,
|
||||
ctx: &'t Context<'t>,
|
||||
tree: &'a SyntaxTree,
|
||||
ctx: &'a Context<'a>,
|
||||
|
||||
// Internal
|
||||
font_loader: FontLoader<'t>,
|
||||
font_loader: FontLoader<'a>,
|
||||
|
||||
// Output
|
||||
text_commands: Vec<TextCommand>,
|
||||
@ -34,9 +34,9 @@ pub struct Engine<'t> {
|
||||
italic: bool,
|
||||
}
|
||||
|
||||
impl<'t> Engine<'t> {
|
||||
impl<'a> Engine<'a> {
|
||||
/// Create a new generator from a syntax tree.
|
||||
pub(crate) fn new(tree: &'t SyntaxTree<'t>, context: &'t Context<'t>) -> Engine<'t> {
|
||||
pub(crate) fn new(tree: &'a SyntaxTree, context: &'a Context<'a>) -> Engine<'a> {
|
||||
Engine {
|
||||
tree,
|
||||
ctx: context,
|
||||
@ -211,30 +211,30 @@ impl<'t> Engine<'t> {
|
||||
}
|
||||
|
||||
/// Serves matching fonts given a query.
|
||||
struct FontLoader<'t> {
|
||||
struct FontLoader<'a> {
|
||||
/// The context containing the used font providers.
|
||||
context: &'t Context<'t>,
|
||||
context: &'a Context<'a>,
|
||||
/// All available fonts indexed by provider.
|
||||
provider_fonts: Vec<&'t [FontInfo]>,
|
||||
provider_fonts: Vec<&'a [FontInfo]>,
|
||||
/// The internal state.
|
||||
state: RefCell<FontLoaderState<'t>>,
|
||||
state: RefCell<FontLoaderState<'a>>,
|
||||
}
|
||||
|
||||
/// Internal state of the font loader (wrapped in a RefCell).
|
||||
struct FontLoaderState<'t> {
|
||||
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<'t>, usize>,
|
||||
query_cache: HashMap<FontQuery<'a>, usize>,
|
||||
/// Allows to lookup fonts by their infos.
|
||||
info_cache: HashMap<&'t FontInfo, usize>,
|
||||
info_cache: HashMap<&'a FontInfo, usize>,
|
||||
/// Indexed by outside and indices maps to internal indices.
|
||||
inner_index: Vec<usize>,
|
||||
}
|
||||
|
||||
impl<'t> FontLoader<'t> {
|
||||
impl<'a> FontLoader<'a> {
|
||||
/// Create a new font loader.
|
||||
pub fn new(context: &'t Context<'t>) -> FontLoader {
|
||||
pub fn new(context: &'a Context<'a>) -> FontLoader {
|
||||
let provider_fonts = context.font_providers.iter()
|
||||
.map(|prov| prov.available()).collect();
|
||||
|
||||
@ -251,7 +251,7 @@ impl<'t> FontLoader<'t> {
|
||||
}
|
||||
|
||||
/// Return the best matching font and it's index (if there is any) given the query.
|
||||
pub fn get(&self, query: FontQuery<'t>) -> Option<(usize, Ref<Font>)> {
|
||||
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) {
|
||||
|
93
src/func.rs
Normal file
93
src/func.rs
Normal file
@ -0,0 +1,93 @@
|
||||
//! Dynamic typesetting functions.
|
||||
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use crate::syntax::{Token, FuncHeader, Expression};
|
||||
use crate::parsing::ParseError;
|
||||
|
||||
|
||||
/// An optional iterator over the tokens of a function body.
|
||||
pub type BodyTokens<'a> = Option<Box<dyn Iterator<Item=Token<'a>> + 'a>>;
|
||||
|
||||
|
||||
/// Types that act as functions.
|
||||
///
|
||||
/// These types have to be able to parse tokens into themselves and store the
|
||||
/// relevant information from the parsing to do their role in typesetting later.
|
||||
///
|
||||
/// `FunctionRequirements` is automatically implemented for types which
|
||||
/// can be used as functions, that is they fulfill the bounds
|
||||
/// `Debug + PartialEq + 'static`.
|
||||
pub trait Function: FunctionRequirements + 'static {
|
||||
/// Parse the function.
|
||||
fn parse(header: &FuncHeader, tokens: BodyTokens<'_>, scope: &Scope)
|
||||
-> Result<Self, ParseError> where Self: Sized;
|
||||
|
||||
/// Execute the function and optionally yield a return value.
|
||||
fn typeset(&self, header: &FuncHeader) -> Option<Expression>;
|
||||
}
|
||||
|
||||
/// A helper trait that describes requirements for types implement [`Function`].
|
||||
///
|
||||
/// Automatically implemented for all types which fulfill to the bounds
|
||||
/// `Debug + PartialEq + 'static`. There should be no need to implement this
|
||||
/// manually.
|
||||
pub trait FunctionRequirements: Debug {
|
||||
/// Cast self into `Any`.
|
||||
fn help_cast_as_any(&self) -> &dyn Any;
|
||||
|
||||
/// Compare self with another function.
|
||||
fn help_eq(&self, other: &dyn Function) -> bool;
|
||||
}
|
||||
|
||||
impl<T> FunctionRequirements for T where T: Debug + PartialEq + 'static {
|
||||
fn help_cast_as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn help_eq(&self, other: &dyn Function) -> bool {
|
||||
if let Some(other) = other.help_cast_as_any().downcast_ref::<Self>() {
|
||||
self == other
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for dyn Function {
|
||||
fn eq(&self, other: &dyn Function) -> bool {
|
||||
self.help_eq(other)
|
||||
}
|
||||
}
|
||||
|
||||
/// A map from identifiers to functions.
|
||||
pub struct Scope {
|
||||
parsers: HashMap<String, Box<dyn Fn(&FuncHeader, BodyTokens<'_>, &Scope)
|
||||
-> Result<Box<dyn Function>, ParseError>>>,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
/// Create a new empty scope.
|
||||
pub fn new() -> Scope {
|
||||
Scope { parsers: HashMap::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(|header, tokens, scope| match F::parse(header, tokens, scope) {
|
||||
Ok(func) => Ok(Box::new(func)),
|
||||
Err(err) => Err(err),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/// Return the parser with the given name if there is one.
|
||||
pub fn get_parser(&self, name: &str)
|
||||
-> Option<&dyn Fn(&FuncHeader, BodyTokens<'_>, &Scope)
|
||||
-> Result<Box<dyn Function>, ParseError>> {
|
||||
self.parsers.get(name).map(|x| &**x)
|
||||
}
|
||||
}
|
@ -57,6 +57,7 @@ pub mod engine;
|
||||
pub mod export;
|
||||
#[macro_use]
|
||||
pub mod font;
|
||||
pub mod func;
|
||||
pub mod parsing;
|
||||
pub mod syntax;
|
||||
|
||||
@ -105,7 +106,7 @@ impl<'p> Compiler<'p> {
|
||||
impl<'p> Compiler<'p> {
|
||||
/// Parse source code into a syntax tree.
|
||||
#[inline]
|
||||
pub fn parse<'s>(&self, src: &'s str) -> Result<SyntaxTree<'s>, ParseError> {
|
||||
pub fn parse(&self, src: &str) -> Result<SyntaxTree, ParseError> {
|
||||
Parser::new(Tokens::new(src)).parse()
|
||||
}
|
||||
|
||||
|
364
src/parsing.rs
364
src/parsing.rs
@ -4,8 +4,10 @@ use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::iter::Peekable;
|
||||
use std::mem::swap;
|
||||
use std::ops::Deref;
|
||||
use unicode_segmentation::{UnicodeSegmentation, UWordBounds};
|
||||
use crate::syntax::*;
|
||||
use crate::func::{Scope, BodyTokens};
|
||||
use crate::utility::{Splinor, Spline, Splined, StrExt};
|
||||
|
||||
|
||||
@ -205,12 +207,12 @@ impl<'s> Tokens<'s> {
|
||||
}
|
||||
|
||||
/// Transforms token streams to syntax trees.
|
||||
#[derive(Debug)]
|
||||
pub struct Parser<'s, T> where T: Iterator<Item=Token<'s>> {
|
||||
tokens: Peekable<T>,
|
||||
scope: ParserScope<'s>,
|
||||
state: ParserState,
|
||||
stack: Vec<FuncInvocation>,
|
||||
tree: SyntaxTree<'s>,
|
||||
tree: SyntaxTree,
|
||||
}
|
||||
|
||||
/// The state the parser is in.
|
||||
@ -226,11 +228,39 @@ enum ParserState {
|
||||
Function,
|
||||
}
|
||||
|
||||
/// An owned or shared scope.
|
||||
enum ParserScope<'s> {
|
||||
Owned(Scope),
|
||||
Shared(&'s Scope)
|
||||
}
|
||||
|
||||
impl Deref for ParserScope<'_> {
|
||||
type Target = Scope;
|
||||
|
||||
fn deref(&self) -> &Scope {
|
||||
match self {
|
||||
ParserScope::Owned(scope) => &scope,
|
||||
ParserScope::Shared(scope) => scope,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s, T> Parser<'s, T> where T: Iterator<Item=Token<'s>> {
|
||||
/// Create a new parser from a type that emits results of tokens.
|
||||
pub(crate) fn new(tokens: T) -> Parser<'s, T> {
|
||||
pub fn new(tokens: T) -> Parser<'s, T> {
|
||||
Parser::new_internal(ParserScope::Owned(Scope::new()), tokens)
|
||||
}
|
||||
|
||||
/// Create a new parser with a scope containing function definitions.
|
||||
pub fn with_scope(scope: &'s Scope, tokens: T) -> Parser<'s, T> {
|
||||
Parser::new_internal(ParserScope::Shared(scope), tokens)
|
||||
}
|
||||
|
||||
/// Internal helper for construction.
|
||||
fn new_internal(scope: ParserScope<'s>, tokens: T) -> Parser<'s, T> {
|
||||
Parser {
|
||||
tokens: tokens.peekable(),
|
||||
scope,
|
||||
state: ParserState::Body,
|
||||
stack: vec![],
|
||||
tree: SyntaxTree::new(),
|
||||
@ -238,7 +268,7 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item=Token<'s>> {
|
||||
}
|
||||
|
||||
/// Parse into an abstract syntax tree.
|
||||
pub(crate) fn parse(mut self) -> ParseResult<SyntaxTree<'s>> {
|
||||
pub(crate) fn parse(mut self) -> ParseResult<SyntaxTree> {
|
||||
use ParserState as PS;
|
||||
|
||||
while let Some(token) = self.tokens.peek() {
|
||||
@ -275,7 +305,7 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item=Token<'s>> {
|
||||
Token::Newline => self.switch_consumed(PS::FirstNewline),
|
||||
|
||||
// Words
|
||||
Token::Word(word) => self.append_consumed(Node::Word(word)),
|
||||
Token::Word(word) => self.append_consumed(Node::Word(word.to_owned())),
|
||||
|
||||
// Functions
|
||||
Token::LeftBracket => self.switch_consumed(PS::Function),
|
||||
@ -283,7 +313,7 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item=Token<'s>> {
|
||||
self.advance();
|
||||
match self.stack.pop() {
|
||||
Some(func) => self.append(Node::Func(func)),
|
||||
None => return self.err("unexpected closing bracket"),
|
||||
None => return Err(ParseError::new("unexpected closing bracket")),
|
||||
}
|
||||
},
|
||||
|
||||
@ -300,46 +330,69 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item=Token<'s>> {
|
||||
let name = if let Token::Word(word) = token {
|
||||
match Ident::new(word) {
|
||||
Some(ident) => ident,
|
||||
None => return self.err("invalid identifier"),
|
||||
None => return Err(ParseError::new("invalid identifier")),
|
||||
}
|
||||
} else {
|
||||
return self.err("expected identifier");
|
||||
return Err(ParseError::new("expected identifier"));
|
||||
};
|
||||
|
||||
self.advance();
|
||||
|
||||
// Expect the header closing bracket.
|
||||
if self.tokens.next() != Some(Token::RightBracket) {
|
||||
return self.err("expected closing bracket");
|
||||
return Err(ParseError::new("expected closing bracket"));
|
||||
}
|
||||
|
||||
// Store the header information of the function invocation.
|
||||
let header = FuncHeader {
|
||||
name,
|
||||
name: name.clone(),
|
||||
args: vec![],
|
||||
kwargs: HashMap::new(),
|
||||
};
|
||||
|
||||
let func = FuncInvocation {
|
||||
header,
|
||||
body: unimplemented!(),
|
||||
// This function has a body.
|
||||
let mut tokens = if let Some(Token::LeftBracket) = self.tokens.peek() {
|
||||
self.advance();
|
||||
Some(FuncTokens::new(&mut self.tokens))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// This function has a body.
|
||||
if let Some(Token::LeftBracket) = self.tokens.peek() {
|
||||
self.advance();
|
||||
func.body = Some(SyntaxTree::new());
|
||||
self.stack.push(func);
|
||||
// A mutably borrowed view over the tokens.
|
||||
let borrow_tokens: BodyTokens<'_> = tokens.as_mut().map(|toks| {
|
||||
Box::new(toks) as Box<dyn Iterator<Item=Token<'_>>>
|
||||
});
|
||||
|
||||
// Run the parser over the tokens.
|
||||
let body = if let Some(parser) = self.scope.get_parser(&name) {
|
||||
parser(&header, borrow_tokens, &self.scope)?
|
||||
} else {
|
||||
self.append(Node::Func(func));
|
||||
let message = format!("unknown function: '{}'", &name);
|
||||
return Err(ParseError::new(message));
|
||||
};
|
||||
|
||||
// Expect the closing bracket if it had a body.
|
||||
if let Some(tokens) = tokens {
|
||||
println!("lulz");
|
||||
if tokens.unexpected_end {
|
||||
return Err(ParseError::new("expected closing bracket"));
|
||||
}
|
||||
}
|
||||
|
||||
// Finally this function is parsed to the end.
|
||||
self.append(Node::Func(FuncInvocation {
|
||||
header,
|
||||
body,
|
||||
}));
|
||||
|
||||
self.switch(PS::Body);
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if !self.stack.is_empty() {
|
||||
return self.err("expected closing bracket");
|
||||
}
|
||||
// if !self.stack.is_empty() {
|
||||
// return Err(ParseError::new("expected closing bracket"));
|
||||
// }
|
||||
|
||||
Ok(self.tree)
|
||||
}
|
||||
@ -350,15 +403,16 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item=Token<'s>> {
|
||||
}
|
||||
|
||||
/// Append a node to the top-of-stack function or the main tree itself.
|
||||
fn append(&mut self, node: Node<'s>) {
|
||||
match self.stack.last_mut() {
|
||||
Some(func) => func.body.as_mut().unwrap(),
|
||||
None => &mut self.tree,
|
||||
}.nodes.push(node);
|
||||
fn append(&mut self, node: Node) {
|
||||
self.tree.nodes.push(node);
|
||||
// match self.stack.last_mut() {
|
||||
// Some(func) => func.body.as_mut().unwrap(),
|
||||
// None => &mut self.tree,
|
||||
// }.nodes.push(node);
|
||||
}
|
||||
|
||||
/// Advance and return the given node.
|
||||
fn append_consumed(&mut self, node: Node<'s>) { self.advance(); self.append(node); }
|
||||
fn append_consumed(&mut self, node: Node) { self.advance(); self.append(node); }
|
||||
|
||||
/// Append a space if there is not one already.
|
||||
fn append_space(&mut self) {
|
||||
@ -379,11 +433,12 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item=Token<'s>> {
|
||||
fn switch_consumed(&mut self, state: ParserState) { self.advance(); self.state = state; }
|
||||
|
||||
/// The last appended node of the top-of-stack function or of the main tree.
|
||||
fn last(&self) -> Option<&Node<'s>> {
|
||||
match self.stack.last() {
|
||||
Some(func) => func.body.as_ref().unwrap(),
|
||||
None => &self.tree,
|
||||
}.nodes.last()
|
||||
fn last(&self) -> Option<&Node> {
|
||||
self.tree.nodes.last()
|
||||
// match self.stack.last() {
|
||||
// Some(func) => func.body.as_ref().unwrap(),
|
||||
// None => &self.tree,
|
||||
// }.nodes.last()
|
||||
}
|
||||
|
||||
/// Skip tokens until the condition is met.
|
||||
@ -395,10 +450,47 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item=Token<'s>> {
|
||||
self.advance();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gives a parsing error with a message.
|
||||
fn err<R, S: Into<String>>(&self, message: S) -> ParseResult<R> {
|
||||
Err(ParseError { message: message.into() })
|
||||
/// A token iterator that that stops after the first unbalanced right paren.
|
||||
pub struct FuncTokens<'s, T> where T: Iterator<Item=Token<'s>> {
|
||||
tokens: T,
|
||||
parens: u32,
|
||||
unexpected_end: bool,
|
||||
}
|
||||
|
||||
impl<'s, T> FuncTokens<'s, T> where T: Iterator<Item=Token<'s>> {
|
||||
/// Create a new iterator operating over an existing one.
|
||||
pub fn new(tokens: T) -> FuncTokens<'s, T> {
|
||||
FuncTokens {
|
||||
tokens,
|
||||
parens: 0,
|
||||
unexpected_end: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s, T> Iterator for FuncTokens<'s, T> where T: Iterator<Item=Token<'s>> {
|
||||
type Item = Token<'s>;
|
||||
|
||||
fn next(&mut self) -> Option<Token<'s>> {
|
||||
let token = self.tokens.next();
|
||||
match token {
|
||||
Some(Token::RightBracket) if self.parens == 0 => None,
|
||||
Some(Token::RightBracket) => {
|
||||
self.parens -= 1;
|
||||
token
|
||||
},
|
||||
Some(Token::LeftBracket) => {
|
||||
self.parens += 1;
|
||||
token
|
||||
}
|
||||
None => {
|
||||
self.unexpected_end = true;
|
||||
None
|
||||
}
|
||||
token => token,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -407,6 +499,12 @@ pub struct ParseError {
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl ParseError {
|
||||
fn new<S: Into<String>>(message: S) -> ParseError {
|
||||
ParseError { message: message.into() }
|
||||
}
|
||||
}
|
||||
|
||||
error_type! {
|
||||
err: ParseError,
|
||||
res: ParseResult,
|
||||
@ -528,19 +626,64 @@ mod token_tests {
|
||||
#[cfg(test)]
|
||||
mod parse_tests {
|
||||
use super::*;
|
||||
use Node::{Space as S, Word as W, Newline as N, Func as F};
|
||||
use crate::func::{Function, Scope, BodyTokens};
|
||||
use Node::{Space as S, Newline as N, Func as F};
|
||||
|
||||
/// Test if the source code parses into the syntax tree.
|
||||
fn test(src: &str, tree: SyntaxTree) {
|
||||
assert_eq!(Parser::new(Tokens::new(src)).parse().unwrap(), tree);
|
||||
#[allow(non_snake_case)]
|
||||
fn W(s: &str) -> Node { Node::Word(s.to_owned()) }
|
||||
|
||||
/// A testing function which just parses it's body into a syntax tree.
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct TreeFn(SyntaxTree);
|
||||
|
||||
impl Function for TreeFn {
|
||||
fn parse(_: &FuncHeader, tokens: BodyTokens<'_>, scope: &Scope)
|
||||
-> ParseResult<Self> where Self: Sized {
|
||||
if let Some(tokens) = tokens {
|
||||
Parser::with_scope(scope, tokens).parse().map(|tree| TreeFn(tree))
|
||||
} else {
|
||||
Err(ParseError::new("expected body for tree fn"))
|
||||
}
|
||||
}
|
||||
fn typeset(&self, _header: &FuncHeader) -> Option<Expression> { None }
|
||||
}
|
||||
|
||||
/// Test if the source parses into the error.
|
||||
fn test_err(src: &str, err: &str) {
|
||||
assert_eq!(Parser::new(Tokens::new(src)).parse().unwrap_err().message, err);
|
||||
/// A testing function without a body.
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct BodylessFn;
|
||||
|
||||
impl Function for BodylessFn {
|
||||
fn parse(_: &FuncHeader, tokens: BodyTokens<'_>, _: &Scope) -> ParseResult<Self> where Self: Sized {
|
||||
if tokens.is_none() {
|
||||
Ok(BodylessFn)
|
||||
} else {
|
||||
Err(ParseError::new("unexpected body for bodyless fn"))
|
||||
}
|
||||
}
|
||||
fn typeset(&self, _header: &FuncHeader) -> Option<Expression> { None }
|
||||
}
|
||||
|
||||
/// Short cut macro to create a syntax tree.
|
||||
/// Shortcut macro to create a function.
|
||||
macro_rules! func {
|
||||
(name => $name:expr, body => None $(,)*) => {
|
||||
func!(@$name, Box::new(BodylessFn))
|
||||
};
|
||||
(name => $name:expr, body => $tree:expr $(,)*) => {
|
||||
func!(@$name, Box::new(TreeFn($tree)))
|
||||
};
|
||||
(@$name:expr, $body:expr) => {
|
||||
FuncInvocation {
|
||||
header: FuncHeader {
|
||||
name: Ident::new($name).unwrap(),
|
||||
args: vec![],
|
||||
kwargs: HashMap::new(),
|
||||
},
|
||||
body: $body,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Shortcut macro to create a syntax tree.
|
||||
/// Is `vec`-like and the elements are the nodes.
|
||||
macro_rules! tree {
|
||||
($($x:expr),*) => (
|
||||
@ -549,89 +692,124 @@ mod parse_tests {
|
||||
($($x:expr,)*) => (tree![$($x),*])
|
||||
}
|
||||
|
||||
/// Test if the source code parses into the syntax tree.
|
||||
fn test(src: &str, tree: SyntaxTree) {
|
||||
assert_eq!(Parser::new(Tokens::new(src)).parse().unwrap(), tree);
|
||||
}
|
||||
|
||||
/// Test with a scope containing function definitions.
|
||||
fn test_scoped(scope: &Scope, src: &str, tree: SyntaxTree) {
|
||||
assert_eq!(Parser::with_scope(scope, Tokens::new(src)).parse().unwrap(), tree);
|
||||
}
|
||||
|
||||
/// Test if the source parses into the error.
|
||||
fn test_err(src: &str, err: &str) {
|
||||
assert_eq!(Parser::new(Tokens::new(src)).parse().unwrap_err().message, err);
|
||||
}
|
||||
|
||||
/// Test if the source parses into the error.
|
||||
fn test_err_scoped(scope: &Scope, src: &str, err: &str) {
|
||||
assert_eq!(Parser::with_scope(scope, Tokens::new(src)).parse().unwrap_err().message, err);
|
||||
}
|
||||
|
||||
/// Parse the basic cases.
|
||||
#[test]
|
||||
fn parse_base() {
|
||||
test("", tree! {});
|
||||
test("Hello World!", tree! { W("Hello"), S, W("World"), W("!")});
|
||||
test("", tree! []);
|
||||
test("Hello World!", tree! [ W("Hello"), S, W("World"), W("!") ]);
|
||||
}
|
||||
|
||||
/// Test whether newlines generate the correct whitespace.
|
||||
#[test]
|
||||
fn parse_newlines_whitespace() {
|
||||
test("Hello\nWorld", tree! { W("Hello"), S, W("World") });
|
||||
test("Hello \n World", tree! { W("Hello"), S, W("World") });
|
||||
test("Hello\n\nWorld", tree! { W("Hello"), N, W("World") });
|
||||
test("Hello \n\nWorld", tree! { W("Hello"), S, N, W("World") });
|
||||
test("Hello\n\n World", tree! { W("Hello"), N, S, W("World") });
|
||||
test("Hello \n \n \n World", tree! { W("Hello"), S, N, S, W("World") });
|
||||
test("Hello\n \n\n World", tree! { W("Hello"), S, N, S, W("World") });
|
||||
test("Hello\nWorld", tree! [ W("Hello"), S, W("World") ]);
|
||||
test("Hello \n World", tree! [ W("Hello"), S, W("World") ]);
|
||||
test("Hello\n\nWorld", tree! [ W("Hello"), N, W("World") ]);
|
||||
test("Hello \n\nWorld", tree! [ W("Hello"), S, N, W("World") ]);
|
||||
test("Hello\n\n World", tree! [ W("Hello"), N, S, W("World") ]);
|
||||
test("Hello \n \n \n World", tree! [ W("Hello"), S, N, S, W("World") ]);
|
||||
test("Hello\n \n\n World", tree! [ W("Hello"), S, N, S, W("World") ]);
|
||||
}
|
||||
|
||||
/// Parse things dealing with functions.
|
||||
#[test]
|
||||
fn parse_functions() {
|
||||
test("[test]", tree! { F(Function { name: "test", body: None }) });
|
||||
test("This is an [modifier][example] of a function invocation.", tree! {
|
||||
let mut scope = Scope::new();
|
||||
let tree_fns = ["modifier", "func", "links", "bodyempty", "nested"];
|
||||
let bodyless_fns = ["test", "end"];
|
||||
for func in &bodyless_fns { scope.add::<BodylessFn>(func); }
|
||||
for func in &tree_fns { scope.add::<TreeFn>(func); }
|
||||
|
||||
test_scoped(&scope,"[test]", tree! [ F(func! { name => "test", body => None }) ]);
|
||||
test_scoped(&scope, "This is an [modifier][example] of a function invocation.", tree! [
|
||||
W("This"), S, W("is"), S, W("an"), S,
|
||||
F(Function { name: "modifier", body: Some(tree! { W("example") }) }), S,
|
||||
F(func! { name => "modifier", body => tree! [ W("example") ] }), S,
|
||||
W("of"), S, W("a"), S, W("function"), S, W("invocation"), W(".")
|
||||
});
|
||||
test("[func][Hello][links][Here][end]", tree! {
|
||||
F(Function {
|
||||
name: "func",
|
||||
body: Some(tree! { W("Hello") }),
|
||||
]);
|
||||
test_scoped(&scope, "[func][Hello][links][Here][end]", tree! [
|
||||
F(func! {
|
||||
name => "func",
|
||||
body => tree! [ W("Hello") ],
|
||||
}),
|
||||
F(Function {
|
||||
name: "links",
|
||||
body: Some(tree! { W("Here") }),
|
||||
F(func! {
|
||||
name => "links",
|
||||
body => tree! [ W("Here") ],
|
||||
}),
|
||||
F(Function {
|
||||
name: "end",
|
||||
body: None,
|
||||
F(func! {
|
||||
name => "end",
|
||||
body => None,
|
||||
}),
|
||||
});
|
||||
test("[bodyempty][]", tree! {
|
||||
F(Function {
|
||||
name: "bodyempty",
|
||||
body: Some(tree! {})
|
||||
]);
|
||||
test_scoped(&scope, "[bodyempty][]", tree! [
|
||||
F(func! {
|
||||
name => "bodyempty",
|
||||
body => tree! [],
|
||||
})
|
||||
});
|
||||
test("[nested][[func][call]] outside", tree! {
|
||||
F(Function {
|
||||
name: "nested",
|
||||
body: Some(tree! { F(Function {
|
||||
name: "func",
|
||||
body: Some(tree! { W("call") }),
|
||||
}), }),
|
||||
]);
|
||||
test_scoped(&scope, "[nested][[func][call]] outside", tree! [
|
||||
F(func! {
|
||||
name => "nested",
|
||||
body => tree! [
|
||||
F(func! {
|
||||
name => "func",
|
||||
body => tree! [ W("call") ],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
S, W("outside")
|
||||
});
|
||||
]);
|
||||
}
|
||||
|
||||
/// Tests if the parser handles non-ASCII stuff correctly.
|
||||
#[test]
|
||||
fn parse_unicode() {
|
||||
test("[lib_parse] ⺐.", tree! {
|
||||
F(Function {
|
||||
name: "lib_parse",
|
||||
body: None
|
||||
let mut scope = Scope::new();
|
||||
scope.add::<BodylessFn>("lib_parse");
|
||||
scope.add::<TreeFn>("func123");
|
||||
|
||||
test_scoped(&scope, "[lib_parse] ⺐.", tree! [
|
||||
F(func! {
|
||||
name => "lib_parse",
|
||||
body => None,
|
||||
}),
|
||||
S, W("⺐"), W(".")
|
||||
});
|
||||
test("[func123][Hello 🌍!]", tree! {
|
||||
F(Function {
|
||||
name: "func123",
|
||||
body: Some(tree! { W("Hello"), S, W("🌍"), W("!") }),
|
||||
]);
|
||||
test_scoped(&scope, "[func123][Hello 🌍!]", tree! [
|
||||
F(func! {
|
||||
name => "func123",
|
||||
body => tree! [ W("Hello"), S, W("🌍"), W("!") ],
|
||||
})
|
||||
});
|
||||
]);
|
||||
}
|
||||
|
||||
/// Tests whether errors get reported correctly.
|
||||
#[test]
|
||||
fn parse_errors() {
|
||||
let mut scope = Scope::new();
|
||||
scope.add::<TreeFn>("hello");
|
||||
|
||||
test_err("No functions here]", "unexpected closing bracket");
|
||||
test_err("[hello][world", "expected closing bracket");
|
||||
test_err_scoped(&scope, "[hello][world", "expected closing bracket");
|
||||
test_err("[hello world", "expected closing bracket");
|
||||
test_err("[ no-name][Why?]", "expected identifier");
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
//! Tokenized and syntax tree representations of source code.
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::ops::Deref;
|
||||
use crate::func::Function;
|
||||
use crate::utility::StrExt;
|
||||
|
||||
|
||||
@ -38,22 +39,22 @@ pub enum Token<'s> {
|
||||
|
||||
/// A tree representation of the source.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct SyntaxTree<'s> {
|
||||
pub struct SyntaxTree {
|
||||
/// The children.
|
||||
pub nodes: Vec<Node<'s>>,
|
||||
pub nodes: Vec<Node>,
|
||||
}
|
||||
|
||||
impl<'s> SyntaxTree<'s> {
|
||||
impl SyntaxTree {
|
||||
/// Create an empty syntax tree.
|
||||
#[inline]
|
||||
pub fn new() -> SyntaxTree<'s> {
|
||||
pub fn new() -> SyntaxTree {
|
||||
SyntaxTree { nodes: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
/// A node in the abstract syntax tree.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Node<'s> {
|
||||
pub enum Node {
|
||||
/// Whitespace between other nodes.
|
||||
Space,
|
||||
/// A line feed.
|
||||
@ -65,18 +66,24 @@ pub enum Node<'s> {
|
||||
/// Indicates that math mode was enabled/disabled.
|
||||
ToggleMath,
|
||||
/// A literal word.
|
||||
Word(&'s str),
|
||||
Word(String),
|
||||
/// A function invocation.
|
||||
Func(FuncInvocation),
|
||||
}
|
||||
|
||||
/// A complete function invocation consisting of header and body.
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
pub struct FuncInvocation {
|
||||
pub header: FuncHeader,
|
||||
pub body: Box<dyn Function>,
|
||||
}
|
||||
|
||||
impl PartialEq for FuncInvocation {
|
||||
fn eq(&self, other: &FuncInvocation) -> bool {
|
||||
(self.header == other.header) && (&self.body == &other.body)
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains header information of a function invocation.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FuncHeader {
|
||||
@ -85,53 +92,11 @@ pub struct FuncHeader {
|
||||
pub kwargs: HashMap<Ident, Expression>
|
||||
}
|
||||
|
||||
use std::any::Any;
|
||||
|
||||
/// Types that act as functions.
|
||||
pub trait Function: Debug + FunctionHelper {
|
||||
/// Parse the function.
|
||||
fn parse() -> Self where Self: Sized;
|
||||
|
||||
/// Execute the function and optionally yield a return value.
|
||||
fn typeset(&self, header: &FuncHeader) -> Option<Expression>;
|
||||
}
|
||||
|
||||
trait FunctionHelper {
|
||||
fn help_as_any(&self) -> &dyn Any;
|
||||
fn help_eq(&self, other: &dyn Function) -> bool;
|
||||
}
|
||||
|
||||
impl<T> FunctionHelper for T where T: Clone + PartialEq + 'static {
|
||||
fn help_as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn help_eq(&self, other: &dyn Function) -> bool {
|
||||
if let Some(other) = other.help_as_any().downcast_ref::<Self>() {
|
||||
self == other
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<dyn Function> for &dyn Function {
|
||||
fn eq(&self, other: &dyn Function) -> bool {
|
||||
self.help_eq(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Box<dyn Function>> for Box<dyn Function> {
|
||||
fn eq(&self, other: &Box<dyn Function>) -> bool {
|
||||
&*self == &*other
|
||||
}
|
||||
}
|
||||
|
||||
/// A potentially unevaluated expression.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Expression {}
|
||||
|
||||
/// A valid identifier.
|
||||
/// An owned valid identifier.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Ident(String);
|
||||
|
||||
@ -154,6 +119,12 @@ impl Ident {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Ident {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Ident {
|
||||
type Target = str;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user