summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-04-29 00:12:36 +0200
committerLaurenz <laurmaedje@gmail.com>2019-04-29 00:12:36 +0200
commitf279c52b503e9dacb558d8a46a75b42bc5222ee4 (patch)
treeb70dd229877d18b5db45e88966d6114993281dad /src
parenta51e7a0758758d92ce3655896db6c56d5c1013cf (diff)
Simple dynamic, scoped function parsing πŸ“₯
Diffstat (limited to 'src')
-rw-r--r--src/engine/mod.rs32
-rw-r--r--src/func.rs93
-rw-r--r--src/lib.rs3
-rw-r--r--src/parsing.rs364
-rw-r--r--src/syntax.rs73
5 files changed, 404 insertions, 161 deletions
diff --git a/src/engine/mod.rs b/src/engine/mod.rs
index ea81053b..37fce8dc 100644
--- a/src/engine/mod.rs
+++ b/src/engine/mod.rs
@@ -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) {
diff --git a/src/func.rs b/src/func.rs
new file mode 100644
index 00000000..cbc2f58b
--- /dev/null
+++ b/src/func.rs
@@ -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)
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index f9883a87..423aeb8d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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()
}
diff --git a/src/parsing.rs b/src/parsing.rs
index 9d921fe1..7c4d6041 100644
--- a/src/parsing.rs
+++ b/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.
- if let Some(Token::LeftBracket) = self.tokens.peek() {
+ let mut tokens = if let Some(Token::LeftBracket) = self.tokens.peek() {
self.advance();
- func.body = Some(SyntaxTree::new());
- self.stack.push(func);
+ Some(FuncTokens::new(&mut self.tokens))
+ } else {
+ None
+ };
+
+ // 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();
}
}
+}
+
+/// 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,
+}
- /// Gives a parsing error with a message.
- fn err<R, S: Into<String>>(&self, message: S) -> ParseResult<R> {
- Err(ParseError { message: message.into() })
+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,110 +626,190 @@ 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};
+
+ #[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 }
+ }
+
+ /// 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 }
+ }
+
+ /// 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),*) => (
+ SyntaxTree { nodes: vec![$($x),*] }
+ );
+ ($($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);
}
- /// Short cut macro to create a syntax tree.
- /// Is `vec`-like and the elements are the nodes.
- macro_rules! tree {
- ($($x:expr),*) => (
- SyntaxTree { nodes: vec![$($x),*] }
- );
- ($($x:expr,)*) => (tree![$($x),*])
+ /// 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");
}
diff --git a/src/syntax.rs b/src/syntax.rs
index fdb71d14..dd83b4a3 100644
--- a/src/syntax.rs
+++ b/src/syntax.rs
@@ -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;