mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Remove stack from parser ♻
This commit is contained in:
parent
f279c52b50
commit
14c9ff571d
87
src/func.rs
87
src/func.rs
@ -3,68 +3,37 @@
|
|||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use crate::syntax::{Token, FuncHeader, Expression};
|
use crate::syntax::{Token, FuncHeader, Expression};
|
||||||
use crate::parsing::ParseError;
|
use crate::parsing::ParseResult;
|
||||||
|
|
||||||
|
|
||||||
/// An optional iterator over the tokens of a function body.
|
/// An optional iterator over the tokens of a function body.
|
||||||
pub type BodyTokens<'a> = Option<Box<dyn Iterator<Item=Token<'a>> + 'a>>;
|
pub type BodyTokens<'a> = Option<Box<dyn Iterator<Item=Token<'a>> + 'a>>;
|
||||||
|
|
||||||
|
/// Parser functions.
|
||||||
|
pub type ParseFunc = dyn Fn(&FuncHeader, BodyTokens<'_>, &Scope)
|
||||||
|
-> ParseResult<Box<dyn Function>>;
|
||||||
|
|
||||||
/// Types that act as functions.
|
/// Types that act as functions.
|
||||||
///
|
///
|
||||||
/// These types have to be able to parse tokens into themselves and store the
|
/// 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.
|
/// relevant information from the parsing to do their role in typesetting later.
|
||||||
///
|
///
|
||||||
/// `FunctionRequirements` is automatically implemented for types which
|
/// The trait `FunctionBounds` is automatically implemented for types which can be
|
||||||
/// can be used as functions, that is they fulfill the bounds
|
/// used as functions, that is they fulfill the bounds `Debug + PartialEq + 'static`.
|
||||||
/// `Debug + PartialEq + 'static`.
|
pub trait Function: FunctionBounds {
|
||||||
pub trait Function: FunctionRequirements + 'static {
|
|
||||||
/// Parse the function.
|
/// Parse the function.
|
||||||
fn parse(header: &FuncHeader, tokens: BodyTokens<'_>, scope: &Scope)
|
fn parse(header: &FuncHeader, tokens: BodyTokens<'_>, scope: &Scope)
|
||||||
-> Result<Self, ParseError> where Self: Sized;
|
-> ParseResult<Self> where Self: Sized;
|
||||||
|
|
||||||
/// Execute the function and optionally yield a return value.
|
/// Execute the function and optionally yield a return value.
|
||||||
fn typeset(&self, header: &FuncHeader) -> Option<Expression>;
|
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.
|
/// A map from identifiers to functions.
|
||||||
pub struct Scope {
|
pub struct Scope {
|
||||||
parsers: HashMap<String, Box<dyn Fn(&FuncHeader, BodyTokens<'_>, &Scope)
|
parsers: HashMap<String, Box<ParseFunc>>,
|
||||||
-> Result<Box<dyn Function>, ParseError>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scope {
|
impl Scope {
|
||||||
@ -85,9 +54,39 @@ impl Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return the parser with the given name if there is one.
|
/// Return the parser with the given name if there is one.
|
||||||
pub fn get_parser(&self, name: &str)
|
pub fn get_parser(&self, name: &str) -> Option<&ParseFunc> {
|
||||||
-> Option<&dyn Fn(&FuncHeader, BodyTokens<'_>, &Scope)
|
|
||||||
-> Result<Box<dyn Function>, ParseError>> {
|
|
||||||
self.parsers.get(name).map(|x| &**x)
|
self.parsers.get(name).map(|x| &**x)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A helper trait that describes requirements for types that can 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 FunctionBounds: 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> FunctionBounds 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
122
src/parsing.rs
122
src/parsing.rs
@ -5,11 +5,13 @@ use std::fmt;
|
|||||||
use std::iter::Peekable;
|
use std::iter::Peekable;
|
||||||
use std::mem::swap;
|
use std::mem::swap;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use unicode_segmentation::{UnicodeSegmentation, UWordBounds};
|
|
||||||
use crate::syntax::*;
|
use crate::syntax::*;
|
||||||
use crate::func::{Scope, BodyTokens};
|
use crate::func::{Scope, BodyTokens};
|
||||||
use crate::utility::{Splinor, Spline, Splined, StrExt};
|
use crate::utility::{Splinor, Spline, Splined, StrExt};
|
||||||
|
|
||||||
|
use unicode_segmentation::{UnicodeSegmentation, UWordBounds};
|
||||||
|
|
||||||
|
|
||||||
/// An iterator over the tokens of source code.
|
/// An iterator over the tokens of source code.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -211,7 +213,6 @@ pub struct Parser<'s, T> where T: Iterator<Item=Token<'s>> {
|
|||||||
tokens: Peekable<T>,
|
tokens: Peekable<T>,
|
||||||
scope: ParserScope<'s>,
|
scope: ParserScope<'s>,
|
||||||
state: ParserState,
|
state: ParserState,
|
||||||
stack: Vec<FuncInvocation>,
|
|
||||||
tree: SyntaxTree,
|
tree: SyntaxTree,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,23 +229,6 @@ enum ParserState {
|
|||||||
Function,
|
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>> {
|
impl<'s, T> Parser<'s, T> where T: Iterator<Item=Token<'s>> {
|
||||||
/// Create a new parser from a type that emits results of tokens.
|
/// Create a new parser from a type that emits results of tokens.
|
||||||
pub fn new(tokens: T) -> Parser<'s, T> {
|
pub fn new(tokens: T) -> Parser<'s, T> {
|
||||||
@ -262,7 +246,6 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item=Token<'s>> {
|
|||||||
tokens: tokens.peekable(),
|
tokens: tokens.peekable(),
|
||||||
scope,
|
scope,
|
||||||
state: ParserState::Body,
|
state: ParserState::Body,
|
||||||
stack: vec![],
|
|
||||||
tree: SyntaxTree::new(),
|
tree: SyntaxTree::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -310,11 +293,7 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item=Token<'s>> {
|
|||||||
// Functions
|
// Functions
|
||||||
Token::LeftBracket => self.switch_consumed(PS::Function),
|
Token::LeftBracket => self.switch_consumed(PS::Function),
|
||||||
Token::RightBracket => {
|
Token::RightBracket => {
|
||||||
self.advance();
|
return Err(ParseError::new("unexpected closing bracket"));
|
||||||
match self.stack.pop() {
|
|
||||||
Some(func) => self.append(Node::Func(func)),
|
|
||||||
None => return Err(ParseError::new("unexpected closing bracket")),
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Modifiers
|
// Modifiers
|
||||||
@ -366,20 +345,18 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item=Token<'s>> {
|
|||||||
let body = if let Some(parser) = self.scope.get_parser(&name) {
|
let body = if let Some(parser) = self.scope.get_parser(&name) {
|
||||||
parser(&header, borrow_tokens, &self.scope)?
|
parser(&header, borrow_tokens, &self.scope)?
|
||||||
} else {
|
} else {
|
||||||
let message = format!("unknown function: '{}'", &name);
|
return Err(ParseError::new(format!("unknown function: '{}'", &name)));
|
||||||
return Err(ParseError::new(message));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Expect the closing bracket if it had a body.
|
// Expect the closing bracket if it had a body.
|
||||||
if let Some(tokens) = tokens {
|
if let Some(tokens) = tokens {
|
||||||
println!("lulz");
|
|
||||||
if tokens.unexpected_end {
|
if tokens.unexpected_end {
|
||||||
return Err(ParseError::new("expected closing bracket"));
|
return Err(ParseError::new("expected closing bracket"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally this function is parsed to the end.
|
// Finally this function is parsed to the end.
|
||||||
self.append(Node::Func(FuncInvocation {
|
self.append(Node::Func(FuncCall {
|
||||||
header,
|
header,
|
||||||
body,
|
body,
|
||||||
}));
|
}));
|
||||||
@ -390,10 +367,6 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item=Token<'s>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if !self.stack.is_empty() {
|
|
||||||
// return Err(ParseError::new("expected closing bracket"));
|
|
||||||
// }
|
|
||||||
|
|
||||||
Ok(self.tree)
|
Ok(self.tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -402,13 +375,9 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item=Token<'s>> {
|
|||||||
self.tokens.next();
|
self.tokens.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Append a node to the top-of-stack function or the main tree itself.
|
/// Append a node to the tree.
|
||||||
fn append(&mut self, node: Node) {
|
fn append(&mut self, node: Node) {
|
||||||
self.tree.nodes.push(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.
|
/// Advance and return the given node.
|
||||||
@ -422,7 +391,10 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item=Token<'s>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Advance and append a space if there is not one already.
|
/// Advance and append a space if there is not one already.
|
||||||
fn append_space_consumed(&mut self) { self.advance(); self.append_space(); }
|
fn append_space_consumed(&mut self) {
|
||||||
|
self.advance();
|
||||||
|
self.append_space();
|
||||||
|
}
|
||||||
|
|
||||||
/// Switch the state.
|
/// Switch the state.
|
||||||
fn switch(&mut self, state: ParserState) {
|
fn switch(&mut self, state: ParserState) {
|
||||||
@ -430,15 +402,14 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item=Token<'s>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Advance and switch the state.
|
/// Advance and switch the state.
|
||||||
fn switch_consumed(&mut self, state: ParserState) { self.advance(); self.state = state; }
|
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.
|
/// The last appended node of the tree.
|
||||||
fn last(&self) -> Option<&Node> {
|
fn last(&self) -> Option<&Node> {
|
||||||
self.tree.nodes.last()
|
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.
|
/// Skip tokens until the condition is met.
|
||||||
@ -452,6 +423,23 @@ impl<'s, T> Parser<'s, T> where T: Iterator<Item=Token<'s>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A token iterator that that stops after the first unbalanced right paren.
|
/// A token iterator that that stops after the first unbalanced right paren.
|
||||||
pub struct FuncTokens<'s, T> where T: Iterator<Item=Token<'s>> {
|
pub struct FuncTokens<'s, T> where T: Iterator<Item=Token<'s>> {
|
||||||
tokens: T,
|
tokens: T,
|
||||||
@ -505,9 +493,11 @@ impl ParseError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The result type for parsing.
|
||||||
|
pub type ParseResult<T> = Result<T, ParseError>;
|
||||||
|
|
||||||
error_type! {
|
error_type! {
|
||||||
err: ParseError,
|
err: ParseError,
|
||||||
res: ParseResult,
|
|
||||||
show: f => f.write_str(&err.message),
|
show: f => f.write_str(&err.message),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -623,6 +613,7 @@ mod token_tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod parse_tests {
|
mod parse_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -653,7 +644,8 @@ mod parse_tests {
|
|||||||
struct BodylessFn;
|
struct BodylessFn;
|
||||||
|
|
||||||
impl Function for BodylessFn {
|
impl Function for BodylessFn {
|
||||||
fn parse(_: &FuncHeader, tokens: BodyTokens<'_>, _: &Scope) -> ParseResult<Self> where Self: Sized {
|
fn parse(_: &FuncHeader, tokens: BodyTokens<'_>, _: &Scope)
|
||||||
|
-> ParseResult<Self> where Self: Sized {
|
||||||
if tokens.is_none() {
|
if tokens.is_none() {
|
||||||
Ok(BodylessFn)
|
Ok(BodylessFn)
|
||||||
} else {
|
} else {
|
||||||
@ -672,7 +664,7 @@ mod parse_tests {
|
|||||||
func!(@$name, Box::new(TreeFn($tree)))
|
func!(@$name, Box::new(TreeFn($tree)))
|
||||||
};
|
};
|
||||||
(@$name:expr, $body:expr) => {
|
(@$name:expr, $body:expr) => {
|
||||||
FuncInvocation {
|
FuncCall {
|
||||||
header: FuncHeader {
|
header: FuncHeader {
|
||||||
name: Ident::new($name).unwrap(),
|
name: Ident::new($name).unwrap(),
|
||||||
args: vec![],
|
args: vec![],
|
||||||
@ -707,7 +699,7 @@ mod parse_tests {
|
|||||||
assert_eq!(Parser::new(Tokens::new(src)).parse().unwrap_err().message, err);
|
assert_eq!(Parser::new(Tokens::new(src)).parse().unwrap_err().message, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test if the source parses into the error.
|
/// Test with a scope if the source parses into the error.
|
||||||
fn test_err_scoped(scope: &Scope, src: &str, err: &str) {
|
fn test_err_scoped(scope: &Scope, src: &str, err: &str) {
|
||||||
assert_eq!(Parser::with_scope(scope, Tokens::new(src)).parse().unwrap_err().message, err);
|
assert_eq!(Parser::with_scope(scope, Tokens::new(src)).parse().unwrap_err().message, err);
|
||||||
}
|
}
|
||||||
@ -735,10 +727,10 @@ mod parse_tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn parse_functions() {
|
fn parse_functions() {
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
let tree_fns = ["modifier", "func", "links", "bodyempty", "nested"];
|
scope.add::<BodylessFn>("test");
|
||||||
let bodyless_fns = ["test", "end"];
|
scope.add::<BodylessFn>("end");
|
||||||
for func in &bodyless_fns { scope.add::<BodylessFn>(func); }
|
scope.add::<TreeFn>("modifier");
|
||||||
for func in &tree_fns { 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", body => None }) ]);
|
||||||
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! [
|
||||||
@ -746,13 +738,13 @@ mod parse_tests {
|
|||||||
F(func! { name => "modifier", body => 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(".")
|
W("of"), S, W("a"), S, W("function"), S, W("invocation"), W(".")
|
||||||
]);
|
]);
|
||||||
test_scoped(&scope, "[func][Hello][links][Here][end]", tree! [
|
test_scoped(&scope, "[func][Hello][modifier][Here][end]", tree! [
|
||||||
F(func! {
|
F(func! {
|
||||||
name => "func",
|
name => "func",
|
||||||
body => tree! [ W("Hello") ],
|
body => tree! [ W("Hello") ],
|
||||||
}),
|
}),
|
||||||
F(func! {
|
F(func! {
|
||||||
name => "links",
|
name => "modifier",
|
||||||
body => tree! [ W("Here") ],
|
body => tree! [ W("Here") ],
|
||||||
}),
|
}),
|
||||||
F(func! {
|
F(func! {
|
||||||
@ -760,15 +752,15 @@ mod parse_tests {
|
|||||||
body => None,
|
body => None,
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
test_scoped(&scope, "[bodyempty][]", tree! [
|
test_scoped(&scope, "[func][]", tree! [
|
||||||
F(func! {
|
F(func! {
|
||||||
name => "bodyempty",
|
name => "func",
|
||||||
body => tree! [],
|
body => tree! [],
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
test_scoped(&scope, "[nested][[func][call]] outside", tree! [
|
test_scoped(&scope, "[modifier][[func][call]] outside", tree! [
|
||||||
F(func! {
|
F(func! {
|
||||||
name => "nested",
|
name => "modifier",
|
||||||
body => tree! [
|
body => tree! [
|
||||||
F(func! {
|
F(func! {
|
||||||
name => "func",
|
name => "func",
|
||||||
@ -784,19 +776,19 @@ mod parse_tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn parse_unicode() {
|
fn parse_unicode() {
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
scope.add::<BodylessFn>("lib_parse");
|
scope.add::<BodylessFn>("func");
|
||||||
scope.add::<TreeFn>("func123");
|
scope.add::<TreeFn>("bold");
|
||||||
|
|
||||||
test_scoped(&scope, "[lib_parse] ⺐.", tree! [
|
test_scoped(&scope, "[func] ⺐.", tree! [
|
||||||
F(func! {
|
F(func! {
|
||||||
name => "lib_parse",
|
name => "func",
|
||||||
body => None,
|
body => None,
|
||||||
}),
|
}),
|
||||||
S, W("⺐"), W(".")
|
S, W("⺐"), W(".")
|
||||||
]);
|
]);
|
||||||
test_scoped(&scope, "[func123][Hello 🌍!]", tree! [
|
test_scoped(&scope, "[bold][Hello 🌍!]", tree! [
|
||||||
F(func! {
|
F(func! {
|
||||||
name => "func123",
|
name => "bold",
|
||||||
body => tree! [ W("Hello"), S, W("🌍"), W("!") ],
|
body => tree! [ W("Hello"), S, W("🌍"), W("!") ],
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
use crate::func::Function;
|
use crate::func::Function;
|
||||||
use crate::utility::StrExt;
|
use crate::utility::StrExt;
|
||||||
|
|
||||||
@ -68,18 +69,18 @@ pub enum Node {
|
|||||||
/// A literal word.
|
/// A literal word.
|
||||||
Word(String),
|
Word(String),
|
||||||
/// A function invocation.
|
/// A function invocation.
|
||||||
Func(FuncInvocation),
|
Func(FuncCall),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A complete function invocation consisting of header and body.
|
/// A complete function invocation consisting of header and body.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FuncInvocation {
|
pub struct FuncCall {
|
||||||
pub header: FuncHeader,
|
pub header: FuncHeader,
|
||||||
pub body: Box<dyn Function>,
|
pub body: Box<dyn Function>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for FuncInvocation {
|
impl PartialEq for FuncCall {
|
||||||
fn eq(&self, other: &FuncInvocation) -> bool {
|
fn eq(&self, other: &FuncCall) -> bool {
|
||||||
(self.header == other.header) && (&self.body == &other.body)
|
(self.header == other.header) && (&self.body == &other.body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user