mod contents;
mod function;
mod markdown;
mod parser;
use crate::functions::Function;
use crate::plugins::Manager;
pub use self::contents::ParsedContents;
pub use self::function::{RawArgument, RawFunction};
pub use self::markdown::{parse_markdown, ParsedMarkdown};
pub use self::parser::Parser;
pub use crate::error::{ParseError, TracebackError};
use std::fmt::Debug;
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::rc::Rc;
#[derive(Clone, Debug)]
pub struct LocatableToken {
pub inner: Token,
pub path: PathBuf,
pub line: u32,
pub column: u32,
}
#[derive(Clone, Debug)]
pub enum Token {
Raw(String),
Function(Rc<Box<dyn Function>>),
Variable(String),
}
impl LocatableToken {
pub fn traceback<T: Clone + Debug>(&self, e: T) -> TracebackError<T> {
TracebackError {
path: self.path.clone(),
line: self.line,
column: self.column,
kind: e,
}
}
}
impl Deref for LocatableToken {
type Target = Token;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl Token {
pub fn as_raw(&self) -> Option<&str> {
match self {
Token::Raw(s) => Some(s.as_str()),
_ => None,
}
}
pub fn as_function(&self) -> Option<Rc<Box<dyn Function>>> {
match self {
Token::Function(f) => Some(f.clone()),
_ => None,
}
}
pub fn as_variable(&self) -> Option<&str> {
match self {
Token::Variable(s) => Some(s.as_str()),
_ => None,
}
}
}
pub fn parse_html(
input: &str,
path: &Path,
plugins: Option<&dyn Manager>,
) -> Result<Vec<LocatableToken>, TracebackError<ParseError>> {
let chars = input.chars();
let mut parser = Parser::new(chars, path);
let mut tokens = Vec::new();
let (mut line, mut column) = parser.location();
while let Some(raw) = parser.extract_until("{{", true) {
if !raw.is_empty() {
tokens.push(LocatableToken {
inner: Token::Raw(raw),
path: path.to_path_buf(),
line,
column,
});
}
parser.ignore_while(|c| c.is_whitespace());
(line, column) = parser.location();
let token = match parser.peek() {
Some('$') => parse_variable(&mut parser)?,
Some(_) => parse_function(&mut parser, plugins)?,
None => return Err(parser.traceback(ParseError::UnexpectedEOF)),
};
tokens.push(LocatableToken {
inner: token,
path: path.to_path_buf(),
line,
column,
});
parser.ignore_while(|c| c.is_whitespace());
parser.expect("}}")?;
(line, column) = parser.location();
}
let remaining = parser.extract_remaining(true);
if !remaining.is_empty() {
tokens.push(LocatableToken {
inner: Token::Raw(remaining),
path: path.to_path_buf(),
line,
column,
});
}
Ok(tokens)
}
fn parse_variable(parser: &mut Parser) -> Result<Token, TracebackError<ParseError>> {
parser.expect("$")?;
let variable_name = parser.extract_while(|c| c.is_alphanumeric() || c == '_' || c == '.');
if variable_name.is_empty() {
return Err(parser.traceback(ParseError::InvalidVariableName("<empty>".to_string())));
}
Ok(Token::Variable(variable_name))
}
fn parse_function(
parser: &mut Parser,
plugins: Option<&dyn Manager>,
) -> Result<Token, TracebackError<ParseError>> {
let (line, column) = parser.location();
let function_name = parser.extract_while(|c| c.is_alphanumeric() || c == '_' || c == ':');
if function_name.is_empty() {
return Err(parser.traceback(ParseError::InvalidFunctionName("<empty>".to_string())));
}
parser.ignore_while(|c| c.is_whitespace());
parser.expect("(")?;
let mut positional_args: Vec<RawArgument> = Vec::new();
let mut named_args: Vec<(String, RawArgument)> = Vec::new();
loop {
parser.ignore_while(|c| c.is_whitespace());
let mut open_quote = false;
let arg = parser
.extract_while(|c| {
if c == '"' {
open_quote = !open_quote;
}
open_quote || (c != ')' && c != ',')
})
.trim()
.to_string();
if arg.chars().next().map(|c| c != '"').unwrap_or(false) && arg.contains('=') {
let mut parts = arg.splitn(2, '=');
let name = parts
.next()
.ok_or_else(|| parser.traceback(ParseError::GenericSyntaxError))?;
let value = parts
.next()
.ok_or_else(|| parser.traceback(ParseError::GenericSyntaxError))?;
if name.is_empty()
|| value.is_empty()
|| !name.chars().all(|c| c.is_alphanumeric() || c == '_')
{
return Err(parser.traceback(ParseError::GenericSyntaxError));
}
let argument = RawArgument::parse(value).map_err(|e| parser.traceback(e))?;
named_args.push((name.to_string(), argument));
} else if !arg.is_empty() {
if !named_args.is_empty() {
return Err(parser.traceback(ParseError::PositionalArgAfterNamedArg));
}
let argument = RawArgument::parse(&arg).map_err(|e| parser.traceback(e))?;
positional_args.push(argument);
}
match parser.next()? {
')' => break,
',' => continue,
_ => unreachable!(),
}
}
parser.ignore_while(|c| c.is_whitespace());
let raw_function = RawFunction {
name: function_name.clone(),
positional_args,
named_args,
};
for function_parser in &*crate::FUNCTION_PARSERS {
if function_parser.can_parse(&raw_function) {
return Ok(Token::Function(Rc::new(
function_parser
.parse(raw_function)
.map_err(|e| parser.traceback(e))?,
)));
}
}
if let Some(plugins) = plugins {
for plugin in plugins.plugins() {
for function in &plugin.functions {
if function_name == function.name()
|| function_name == format!("{}::{}", plugin.name, function.name())
{
return Ok(Token::Function(Rc::new(
function
.parse(raw_function)
.map_err(|e| parser.traceback(e))?,
)));
}
}
}
}
Err(TracebackError {
path: parser.path().to_path_buf(),
line,
column,
kind: ParseError::NonexistentFunction(function_name),
})
}