Compare commits
6 commits
b2a97c56db
...
705c6a8818
| Author | SHA1 | Date | |
|---|---|---|---|
| 705c6a8818 | |||
| f87f4a0262 | |||
| 10bcd77040 | |||
| 52a63a7066 | |||
| cb55c00739 | |||
| 70f616d60c |
25 changed files with 2260 additions and 1735 deletions
|
|
@ -19,8 +19,8 @@ fn asting_benchmark(c: &mut Criterion) {
|
||||||
parsing.throughput(criterion::Throughput::Bytes(input.len() as u64));
|
parsing.throughput(criterion::Throughput::Bytes(input.len() as u64));
|
||||||
parsing.bench_with_input(BenchmarkId::from_parameter(size), &input, |b, input| {
|
parsing.bench_with_input(BenchmarkId::from_parameter(size), &input, |b, input| {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let tokens = nomo::parser::parse(input.clone()).unwrap();
|
let tokens = nomo::lexer::parse(input.clone()).unwrap();
|
||||||
let _ast = nomo::ast::parse(tokens.tokens()).unwrap();
|
let _ast = nomo::parser::parse(tokens.tokens()).unwrap();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -43,8 +43,8 @@ fn asting_nested(c: &mut Criterion) {
|
||||||
parsing.throughput(criterion::Throughput::Bytes(input.len() as u64));
|
parsing.throughput(criterion::Throughput::Bytes(input.len() as u64));
|
||||||
parsing.bench_with_input(BenchmarkId::from_parameter(size), &input, |b, input| {
|
parsing.bench_with_input(BenchmarkId::from_parameter(size), &input, |b, input| {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let tokens = nomo::parser::parse(input.clone()).unwrap();
|
let tokens = nomo::lexer::parse(input.clone()).unwrap();
|
||||||
let _ast = nomo::ast::parse(tokens.tokens()).unwrap();
|
let _ast = nomo::parser::parse(tokens.tokens()).unwrap();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ fn parsing_benchmark(c: &mut Criterion) {
|
||||||
|
|
||||||
parsing.throughput(criterion::Throughput::Bytes(input.len() as u64));
|
parsing.throughput(criterion::Throughput::Bytes(input.len() as u64));
|
||||||
parsing.bench_with_input(BenchmarkId::from_parameter(size), &input, |b, input| {
|
parsing.bench_with_input(BenchmarkId::from_parameter(size), &input, |b, input| {
|
||||||
b.iter(|| nomo::parser::parse(input.clone()).unwrap());
|
b.iter(|| nomo::lexer::parse(input.clone()).unwrap());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -39,7 +39,7 @@ fn parsing_nested(c: &mut Criterion) {
|
||||||
|
|
||||||
parsing.throughput(criterion::Throughput::Bytes(input.len() as u64));
|
parsing.throughput(criterion::Throughput::Bytes(input.len() as u64));
|
||||||
parsing.bench_with_input(BenchmarkId::from_parameter(size), &input, |b, input| {
|
parsing.bench_with_input(BenchmarkId::from_parameter(size), &input, |b, input| {
|
||||||
b.iter(|| nomo::parser::parse(input.clone()).unwrap());
|
b.iter(|| nomo::lexer::parse(input.clone()).unwrap());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@ use libfuzzer_sys::Corpus;
|
||||||
use libfuzzer_sys::fuzz_target;
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
fuzz_target!(|data: String| -> Corpus {
|
fuzz_target!(|data: String| -> Corpus {
|
||||||
let Ok(parsed) = nomo::parser::parse(data.into()) else {
|
let Ok(parsed) = nomo::lexer::parse(data.into()) else {
|
||||||
return Corpus::Reject;
|
return Corpus::Reject;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(ast) = nomo::ast::parse(parsed.tokens()) else {
|
let Ok(ast) = nomo::parser::parse(parsed.tokens()) else {
|
||||||
return Corpus::Keep;
|
return Corpus::Keep;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
1011
src/ast/mod.rs
1011
src/ast/mod.rs
File diff suppressed because it is too large
Load diff
|
|
@ -1,9 +1,9 @@
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use crate::ast::TemplateAstExpr;
|
use crate::parser::TemplateAstExpr;
|
||||||
use crate::input::NomoInput;
|
use crate::input::NomoInput;
|
||||||
use crate::parser::TemplateToken;
|
use crate::lexer::TemplateToken;
|
||||||
use crate::parser::TokenOperator;
|
use crate::lexer::TokenOperator;
|
||||||
use crate::value::NomoValue;
|
use crate::value::NomoValue;
|
||||||
|
|
||||||
pub struct EmitMachine {
|
pub struct EmitMachine {
|
||||||
|
|
@ -102,6 +102,11 @@ pub enum Instruction {
|
||||||
right_slot: VariableSlot,
|
right_slot: VariableSlot,
|
||||||
result_slot: VariableSlot,
|
result_slot: VariableSlot,
|
||||||
},
|
},
|
||||||
|
FunctionCall {
|
||||||
|
name: NomoInput,
|
||||||
|
args: Vec<VariableSlot>,
|
||||||
|
slot: VariableSlot,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -110,7 +115,7 @@ pub struct VMInstructions {
|
||||||
pub instructions: Vec<Instruction>,
|
pub instructions: Vec<Instruction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn emit_machine(input: crate::ast::TemplateAst<'_>) -> VMInstructions {
|
pub fn emit_machine(input: crate::parser::TemplateAst<'_>) -> VMInstructions {
|
||||||
let mut eval = vec![];
|
let mut eval = vec![];
|
||||||
|
|
||||||
let mut machine = EmitMachine {
|
let mut machine = EmitMachine {
|
||||||
|
|
@ -166,7 +171,7 @@ fn emit_ast_expr(
|
||||||
let end_label = machine.reserve_label();
|
let end_label = machine.reserve_label();
|
||||||
let mut end_indices = vec![];
|
let mut end_indices = vec![];
|
||||||
|
|
||||||
let mut previous_post_whitespace_content: &Option<crate::parser::TemplateToken> = &None;
|
let mut previous_post_whitespace_content: &Option<TemplateToken> = &None;
|
||||||
let mut previous_jump: Option<LabelSlot> = None;
|
let mut previous_jump: Option<LabelSlot> = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
|
@ -417,6 +422,7 @@ fn emit_ast_expr(
|
||||||
| TemplateAstExpr::ForElse
|
| TemplateAstExpr::ForElse
|
||||||
| TemplateAstExpr::Invalid { .. }
|
| TemplateAstExpr::Invalid { .. }
|
||||||
| TemplateAstExpr::Literal { .. }
|
| TemplateAstExpr::Literal { .. }
|
||||||
|
| TemplateAstExpr::FunctionCall { .. }
|
||||||
| TemplateAstExpr::Operation { .. }
|
| TemplateAstExpr::Operation { .. }
|
||||||
| TemplateAstExpr::VariableAccess { .. } => eval.push(Instruction::Abort),
|
| TemplateAstExpr::VariableAccess { .. } => eval.push(Instruction::Abort),
|
||||||
}
|
}
|
||||||
|
|
@ -455,6 +461,20 @@ fn emit_expr_load(
|
||||||
result_slot: emit_slot,
|
result_slot: emit_slot,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
TemplateAstExpr::FunctionCall { name, args } => {
|
||||||
|
let mut arg_slots = vec![];
|
||||||
|
for arg in args {
|
||||||
|
let slot = machine.reserve_slot();
|
||||||
|
emit_expr_load(machine, eval, slot, arg);
|
||||||
|
arg_slots.push(slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
eval.push(Instruction::FunctionCall {
|
||||||
|
name: name.source(),
|
||||||
|
args: arg_slots,
|
||||||
|
slot: emit_slot,
|
||||||
|
});
|
||||||
|
}
|
||||||
TemplateAstExpr::Invalid { .. } => eval.push(Instruction::Abort),
|
TemplateAstExpr::Invalid { .. } => eval.push(Instruction::Abort),
|
||||||
TemplateAstExpr::StaticContent { .. } | TemplateAstExpr::Interpolation { .. } => {
|
TemplateAstExpr::StaticContent { .. } | TemplateAstExpr::Interpolation { .. } => {
|
||||||
unreachable!("Invalid AST here")
|
unreachable!("Invalid AST here")
|
||||||
|
|
@ -480,9 +500,9 @@ mod tests {
|
||||||
fn check_simple_variable_interpolation() {
|
fn check_simple_variable_interpolation() {
|
||||||
let input = "Hello {{= world }}";
|
let input = "Hello {{= world }}";
|
||||||
|
|
||||||
let parsed = crate::parser::parse(input.into()).unwrap();
|
let parsed = crate::lexer::parse(input.into()).unwrap();
|
||||||
|
|
||||||
let ast = crate::ast::parse(parsed.tokens()).unwrap();
|
let ast = crate::parser::parse(parsed.tokens()).unwrap();
|
||||||
|
|
||||||
let emit = emit_machine(ast);
|
let emit = emit_machine(ast);
|
||||||
|
|
||||||
|
|
@ -516,9 +536,22 @@ mod tests {
|
||||||
fn check_if_else_if() {
|
fn check_if_else_if() {
|
||||||
let input = "{{ if foo }} foo {{ else if bar }} bar {{ else }} foobar {{ end }}";
|
let input = "{{ if foo }} foo {{ else if bar }} bar {{ else }} foobar {{ end }}";
|
||||||
|
|
||||||
let parsed = crate::parser::parse(input.into()).unwrap();
|
let parsed = crate::lexer::parse(input.into()).unwrap();
|
||||||
|
|
||||||
let ast = crate::ast::parse(parsed.tokens()).unwrap();
|
let ast = crate::parser::parse(parsed.tokens()).unwrap();
|
||||||
|
|
||||||
|
let emit = emit_machine(ast);
|
||||||
|
|
||||||
|
insta::assert_debug_snapshot!(emit);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_function_call() {
|
||||||
|
let input = "{{ if foo(23) }} bar {{ else }} foobar {{ end }}";
|
||||||
|
|
||||||
|
let parsed = crate::lexer::parse(input.into()).unwrap();
|
||||||
|
|
||||||
|
let ast = crate::parser::parse(parsed.tokens()).unwrap();
|
||||||
|
|
||||||
let emit = emit_machine(ast);
|
let emit = emit_machine(ast);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
---
|
||||||
|
source: src/emit/mod.rs
|
||||||
|
expression: emit
|
||||||
|
---
|
||||||
|
VMInstructions {
|
||||||
|
labels: {
|
||||||
|
LabelSlot {
|
||||||
|
index: 0,
|
||||||
|
}: 13,
|
||||||
|
LabelSlot {
|
||||||
|
index: 3,
|
||||||
|
}: 8,
|
||||||
|
},
|
||||||
|
instructions: [
|
||||||
|
LoadLiteralToSlot {
|
||||||
|
source: [Literal(Integer(23))]"23" (10..12),
|
||||||
|
value: Integer {
|
||||||
|
value: 23,
|
||||||
|
},
|
||||||
|
slot: VariableSlot {
|
||||||
|
index: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
FunctionCall {
|
||||||
|
name: "foo" (6..9),
|
||||||
|
args: [
|
||||||
|
VariableSlot {
|
||||||
|
index: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
slot: VariableSlot {
|
||||||
|
index: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
JumpIfNotTrue {
|
||||||
|
emit_slot: VariableSlot {
|
||||||
|
index: 1,
|
||||||
|
},
|
||||||
|
jump: LabelSlot {
|
||||||
|
index: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AppendContent {
|
||||||
|
content: " " (16..17),
|
||||||
|
},
|
||||||
|
AppendContent {
|
||||||
|
content: "bar" (17..20),
|
||||||
|
},
|
||||||
|
AppendContent {
|
||||||
|
content: " " (20..21),
|
||||||
|
},
|
||||||
|
Jump {
|
||||||
|
jump: LabelSlot {
|
||||||
|
index: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AppendContent {
|
||||||
|
content: " " (16..17),
|
||||||
|
},
|
||||||
|
AppendContent {
|
||||||
|
content: " " (31..32),
|
||||||
|
},
|
||||||
|
AppendContent {
|
||||||
|
content: "foobar" (32..38),
|
||||||
|
},
|
||||||
|
AppendContent {
|
||||||
|
content: " " (38..39),
|
||||||
|
},
|
||||||
|
Jump {
|
||||||
|
jump: LabelSlot {
|
||||||
|
index: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AppendContent {
|
||||||
|
content: " " (31..32),
|
||||||
|
},
|
||||||
|
NoOp,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ use crate::Context;
|
||||||
use crate::emit::Instruction;
|
use crate::emit::Instruction;
|
||||||
use crate::emit::VMInstructions;
|
use crate::emit::VMInstructions;
|
||||||
use crate::emit::VariableSlot;
|
use crate::emit::VariableSlot;
|
||||||
|
use crate::functions::FunctionMap;
|
||||||
use crate::input::NomoInput;
|
use crate::input::NomoInput;
|
||||||
use crate::value::NomoValue;
|
use crate::value::NomoValue;
|
||||||
|
|
||||||
|
|
@ -69,7 +70,11 @@ impl Scope {
|
||||||
clippy::unnecessary_to_owned,
|
clippy::unnecessary_to_owned,
|
||||||
reason = "We cannot do the suggested way as the lifetimes would not match up"
|
reason = "We cannot do the suggested way as the lifetimes would not match up"
|
||||||
)]
|
)]
|
||||||
pub fn execute(vm: &VMInstructions, global_context: &Context) -> Result<String, EvaluationError> {
|
pub fn execute(
|
||||||
|
available_functions: &FunctionMap,
|
||||||
|
vm: &VMInstructions,
|
||||||
|
global_context: &Context,
|
||||||
|
) -> Result<String, EvaluationError> {
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
|
|
||||||
let mut scopes = Scope {
|
let mut scopes = Scope {
|
||||||
|
|
@ -197,17 +202,28 @@ pub fn execute(vm: &VMInstructions, global_context: &Context) -> Result<String,
|
||||||
let right_value = scopes.get(right_slot);
|
let right_value = scopes.get(right_slot);
|
||||||
|
|
||||||
let result = match op {
|
let result = match op {
|
||||||
crate::parser::TokenOperator::Plus => left_value.try_add(right_value),
|
crate::lexer::TokenOperator::Plus => left_value.try_add(right_value),
|
||||||
crate::parser::TokenOperator::Minus => left_value.try_sub(right_value),
|
crate::lexer::TokenOperator::Minus => left_value.try_sub(right_value),
|
||||||
crate::parser::TokenOperator::Times => left_value.try_mul(right_value),
|
crate::lexer::TokenOperator::Times => left_value.try_mul(right_value),
|
||||||
crate::parser::TokenOperator::Divide => left_value.try_div(right_value),
|
crate::lexer::TokenOperator::Divide => left_value.try_div(right_value),
|
||||||
crate::parser::TokenOperator::And => left_value.try_and(right_value),
|
crate::lexer::TokenOperator::And => left_value.try_and(right_value),
|
||||||
crate::parser::TokenOperator::Or => left_value.try_or(right_value),
|
crate::lexer::TokenOperator::Or => left_value.try_or(right_value),
|
||||||
_ => todo!(),
|
_ => todo!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
scopes.insert_into_slot(*result_slot, result.unwrap());
|
scopes.insert_into_slot(*result_slot, result.unwrap());
|
||||||
}
|
}
|
||||||
|
Instruction::FunctionCall { name, args, slot } => {
|
||||||
|
let args = args.iter().map(|slot| scopes.get(slot)).cloned().collect();
|
||||||
|
|
||||||
|
let value = available_functions
|
||||||
|
.get(name.as_str())
|
||||||
|
.unwrap()
|
||||||
|
.call(args)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
scopes.insert_into_slot(*slot, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ip += 1;
|
ip += 1;
|
||||||
|
|
@ -220,20 +236,22 @@ pub fn execute(vm: &VMInstructions, global_context: &Context) -> Result<String,
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
use crate::eval::execute;
|
use crate::eval::execute;
|
||||||
|
use crate::functions::FunctionMap;
|
||||||
|
use crate::functions::NomoFunctionError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_simple_variable_interpolation() {
|
fn check_simple_variable_interpolation() {
|
||||||
let input = "Hello {{= world }}";
|
let input = "Hello {{= world }}";
|
||||||
|
|
||||||
let parsed = crate::parser::parse(input.into()).unwrap();
|
let parsed = crate::lexer::parse(input.into()).unwrap();
|
||||||
|
|
||||||
let ast = crate::ast::parse(parsed.tokens()).unwrap();
|
let ast = crate::parser::parse(parsed.tokens()).unwrap();
|
||||||
|
|
||||||
let emit = crate::emit::emit_machine(ast);
|
let emit = crate::emit::emit_machine(ast);
|
||||||
|
|
||||||
let mut context = Context::new();
|
let mut context = Context::new();
|
||||||
context.insert("world", "World");
|
context.insert("world", "World");
|
||||||
let output = execute(&emit, &context);
|
let output = execute(&FunctionMap::default(), &emit, &context);
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(output, @r#"
|
insta::assert_debug_snapshot!(output, @r#"
|
||||||
Ok(
|
Ok(
|
||||||
|
|
@ -241,4 +259,31 @@ mod tests {
|
||||||
)
|
)
|
||||||
"#);
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_method_call() {
|
||||||
|
let input = "Hello {{= foo(world) }}";
|
||||||
|
|
||||||
|
let parsed = crate::lexer::parse(input.into()).unwrap();
|
||||||
|
|
||||||
|
let ast = crate::parser::parse(parsed.tokens()).unwrap();
|
||||||
|
|
||||||
|
let emit = crate::emit::emit_machine(ast);
|
||||||
|
|
||||||
|
let mut context = Context::new();
|
||||||
|
context.insert("world", "World");
|
||||||
|
let mut function_map = FunctionMap::default();
|
||||||
|
|
||||||
|
function_map.register("foo", |arg: String| -> Result<String, NomoFunctionError> {
|
||||||
|
Ok(arg.to_uppercase())
|
||||||
|
});
|
||||||
|
|
||||||
|
let output = execute(&function_map, &emit, &context);
|
||||||
|
|
||||||
|
insta::assert_debug_snapshot!(output, @r#"
|
||||||
|
Ok(
|
||||||
|
"Hello WORLD",
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
175
src/functions.rs
Normal file
175
src/functions.rs
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
use std::any::Any;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use displaydoc::Display;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::NomoValueError;
|
||||||
|
use crate::value::NomoValue;
|
||||||
|
|
||||||
|
#[derive(Debug, Error, Display)]
|
||||||
|
pub enum NomoFunctionError {
|
||||||
|
/// Received {received} arguments, but this function only takes {expected}
|
||||||
|
WrongArgumentCount { received: usize, expected: usize },
|
||||||
|
|
||||||
|
/// The argument at this position is of the wrong type
|
||||||
|
InvalidArgumentType { index: usize },
|
||||||
|
|
||||||
|
/// A user-provided error
|
||||||
|
#[error(transparent)]
|
||||||
|
CustomError {
|
||||||
|
custom: Box<dyn std::error::Error + Send + Sync>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait NomoFunction<T>: 'static + Send + Sync {
|
||||||
|
fn call(&self, args: Vec<NomoValue>) -> Result<NomoValue, NomoFunctionError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct FunctionMap {
|
||||||
|
funcs: HashMap<String, ErasedNomoFunction>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FunctionMap {
|
||||||
|
pub fn register<NF: NomoFunction<T>, T>(&mut self, name: impl Into<String>, func: NF) {
|
||||||
|
self.funcs
|
||||||
|
.insert(name.into(), ErasedNomoFunction::erase(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, name: impl AsRef<str>) -> Option<&ErasedNomoFunction> {
|
||||||
|
self.funcs.get(name.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ErasedNomoFunction {
|
||||||
|
func: Box<dyn Any + Send + Sync>,
|
||||||
|
call_fn: fn(&dyn Any, Vec<NomoValue>) -> Result<NomoValue, NomoFunctionError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErasedNomoFunction {
|
||||||
|
pub fn call(&self, args: Vec<NomoValue>) -> Result<NomoValue, NomoFunctionError> {
|
||||||
|
(self.call_fn)(&*self.func, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn erase<NF, T>(f: NF) -> ErasedNomoFunction
|
||||||
|
where
|
||||||
|
NF: NomoFunction<T>,
|
||||||
|
{
|
||||||
|
ErasedNomoFunction {
|
||||||
|
func: Box::new(f),
|
||||||
|
call_fn: |nf, args| {
|
||||||
|
let nf: &NF = nf.downcast_ref().unwrap();
|
||||||
|
|
||||||
|
nf.call(args)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
macro_rules! for_all_tuples {
|
||||||
|
($name:ident) => {
|
||||||
|
$name!([], T1);
|
||||||
|
$name!([T1], T2);
|
||||||
|
$name!([T1, T2], T3);
|
||||||
|
$name!([T1, T2, T3], T4);
|
||||||
|
$name!([T1, T2, T3, T4], T5);
|
||||||
|
$name!([T1, T2, T3, T4, T5], T6);
|
||||||
|
$name!([T1, T2, T3, T4, T5, T6], T7);
|
||||||
|
$name!([T1, T2, T3, T4, T5, T6, T7], T8);
|
||||||
|
$name!([T1, T2, T3, T4, T5, T6, T7, T8], T9);
|
||||||
|
$name!([T1, T2, T3, T4, T5, T6, T7, T8, T9], T10);
|
||||||
|
$name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T11);
|
||||||
|
$name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], T12);
|
||||||
|
$name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], T13);
|
||||||
|
$name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], T14);
|
||||||
|
$name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], T15);
|
||||||
|
$name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], T16);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_nomo_function {
|
||||||
|
([$($ty:ident),*], $last:ident) => {
|
||||||
|
impl<F, Res, Error, $($ty,)* $last> NomoFunction<($($ty,)* $last,)> for F
|
||||||
|
where
|
||||||
|
F: Fn($($ty,)* $last,) -> Result<Res, Error>,
|
||||||
|
F: Send + Sync + 'static,
|
||||||
|
Res: Into<NomoValue>,
|
||||||
|
Error: Into<NomoFunctionError>,
|
||||||
|
$( $ty: TryFrom<NomoValue, Error = NomoValueError>, )*
|
||||||
|
$last: TryFrom<NomoValue, Error = NomoValueError>,
|
||||||
|
{
|
||||||
|
fn call(&self, args: Vec<NomoValue>) -> Result<NomoValue, NomoFunctionError> {
|
||||||
|
let arg_count = args.len();
|
||||||
|
|
||||||
|
let total_count = 1 $(+ impl_nomo_function!(count $ty))*;
|
||||||
|
|
||||||
|
if arg_count != total_count {
|
||||||
|
return Err(NomoFunctionError::WrongArgumentCount {
|
||||||
|
expected: total_count,
|
||||||
|
received: arg_count,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut args = args.into_iter();
|
||||||
|
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut idx = 0;
|
||||||
|
|
||||||
|
let val = (self)(
|
||||||
|
$({
|
||||||
|
let val = $ty::try_from(args.next().unwrap()).map_err(|_| NomoFunctionError::InvalidArgumentType { index: idx })?;
|
||||||
|
idx += 1;
|
||||||
|
val
|
||||||
|
},)*
|
||||||
|
$last::try_from(args.next().unwrap()).map_err(|_| NomoFunctionError::InvalidArgumentType { index: idx })?,
|
||||||
|
);
|
||||||
|
|
||||||
|
val.map(Into::into).map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(count $ty:ident) => {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for_all_tuples!(impl_nomo_function);
|
||||||
|
|
||||||
|
impl<F, Res, Error> NomoFunction<()> for F
|
||||||
|
where
|
||||||
|
F: Fn() -> Result<Res, Error> + Send + Sync + 'static,
|
||||||
|
Res: Into<NomoValue>,
|
||||||
|
Error: Into<NomoFunctionError>,
|
||||||
|
{
|
||||||
|
fn call(&self, args: Vec<NomoValue>) -> Result<NomoValue, NomoFunctionError> {
|
||||||
|
let arg_count = args.len();
|
||||||
|
|
||||||
|
if arg_count != 0 {
|
||||||
|
return Err(NomoFunctionError::WrongArgumentCount {
|
||||||
|
received: arg_count,
|
||||||
|
expected: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let val = (self)();
|
||||||
|
|
||||||
|
val.map(Into::into).map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::functions::ErasedNomoFunction;
|
||||||
|
use crate::functions::NomoFunctionError;
|
||||||
|
|
||||||
|
#[expect(dead_code, reason = "This is a compile test")]
|
||||||
|
fn check_various_methods() {
|
||||||
|
ErasedNomoFunction::erase(|name: String| -> Result<String, NomoFunctionError> { Ok(name) });
|
||||||
|
ErasedNomoFunction::erase(|left: u64, right: u64| -> Result<u64, NomoFunctionError> {
|
||||||
|
Ok(left + right)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
908
src/lexer/mod.rs
Normal file
908
src/lexer/mod.rs
Normal file
|
|
@ -0,0 +1,908 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use annotate_snippets::AnnotationKind;
|
||||||
|
use annotate_snippets::Level;
|
||||||
|
use annotate_snippets::Renderer;
|
||||||
|
use annotate_snippets::Snippet;
|
||||||
|
use thiserror::Error;
|
||||||
|
use winnow::LocatingSlice;
|
||||||
|
use winnow::Parser;
|
||||||
|
use winnow::RecoverableParser;
|
||||||
|
use winnow::ascii::alpha1;
|
||||||
|
use winnow::ascii::digit1;
|
||||||
|
use winnow::ascii::multispace0;
|
||||||
|
use winnow::ascii::multispace1;
|
||||||
|
use winnow::combinator::alt;
|
||||||
|
use winnow::combinator::cut_err;
|
||||||
|
use winnow::combinator::dispatch;
|
||||||
|
use winnow::combinator::empty;
|
||||||
|
use winnow::combinator::eof;
|
||||||
|
use winnow::combinator::fail;
|
||||||
|
use winnow::combinator::not;
|
||||||
|
use winnow::combinator::opt;
|
||||||
|
use winnow::combinator::peek;
|
||||||
|
use winnow::combinator::preceded;
|
||||||
|
use winnow::combinator::repeat_till;
|
||||||
|
use winnow::combinator::terminated;
|
||||||
|
use winnow::combinator::trace;
|
||||||
|
use winnow::error::AddContext;
|
||||||
|
use winnow::error::FromRecoverableError;
|
||||||
|
use winnow::error::ModalError;
|
||||||
|
use winnow::error::ParserError;
|
||||||
|
use winnow::stream::Location;
|
||||||
|
use winnow::stream::Recoverable;
|
||||||
|
use winnow::stream::Stream;
|
||||||
|
use winnow::token::any;
|
||||||
|
use winnow::token::one_of;
|
||||||
|
use winnow::token::rest;
|
||||||
|
use winnow::token::take_until;
|
||||||
|
use winnow::token::take_while;
|
||||||
|
|
||||||
|
use crate::SourceSpan;
|
||||||
|
use crate::input::NomoInput;
|
||||||
|
use crate::resume_after_cut;
|
||||||
|
|
||||||
|
type Input<'input> = Recoverable<LocatingSlice<NomoInput>, ParseError>;
|
||||||
|
type PResult<'input, T> = Result<T, ParseError>;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub struct ParseFailure {
|
||||||
|
input: Arc<str>,
|
||||||
|
errors: Vec<ParseError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ParseFailure {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(&self.to_report())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParseFailure {
|
||||||
|
fn from_errors(errors: Vec<ParseError>, input: NomoInput) -> ParseFailure {
|
||||||
|
ParseFailure {
|
||||||
|
input: Arc::from(input.to_string()),
|
||||||
|
errors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_report(&self) -> String {
|
||||||
|
let reports = self
|
||||||
|
.errors
|
||||||
|
.iter()
|
||||||
|
.map(|error| {
|
||||||
|
Level::ERROR
|
||||||
|
.primary_title(
|
||||||
|
error
|
||||||
|
.message
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or("An error occurred while parsing"),
|
||||||
|
)
|
||||||
|
.element(
|
||||||
|
Snippet::source(self.input.as_ref()).annotation(
|
||||||
|
AnnotationKind::Primary
|
||||||
|
.span(error.span.clone().map(|s| s.range).unwrap_or_else(|| 0..0)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.elements(error.help.as_ref().map(|help| Level::HELP.message(help)))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let renderer =
|
||||||
|
Renderer::styled().decor_style(annotate_snippets::renderer::DecorStyle::Unicode);
|
||||||
|
renderer.render(&reports)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ParseError {
|
||||||
|
pub(crate) message: Option<String>,
|
||||||
|
pub(crate) help: Option<String>,
|
||||||
|
pub(crate) span: Option<crate::SourceSpan>,
|
||||||
|
|
||||||
|
is_fatal: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParseError {
|
||||||
|
fn ctx() -> Self {
|
||||||
|
ParseError {
|
||||||
|
message: None,
|
||||||
|
help: None,
|
||||||
|
span: None,
|
||||||
|
|
||||||
|
is_fatal: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn msg(mut self, message: &str) -> Self {
|
||||||
|
self.message = Some(message.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn help(mut self, help: &str) -> Self {
|
||||||
|
self.help = Some(help.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModalError for ParseError {
|
||||||
|
fn cut(mut self) -> Self {
|
||||||
|
self.is_fatal = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn backtrack(mut self) -> Self {
|
||||||
|
self.is_fatal = false;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'input> FromRecoverableError<Input<'input>, ParseError> for ParseError {
|
||||||
|
fn from_recoverable_error(
|
||||||
|
token_start: &<Input<'input> as winnow::stream::Stream>::Checkpoint,
|
||||||
|
_err_start: &<Input<'input> as winnow::stream::Stream>::Checkpoint,
|
||||||
|
input: &Input<'input>,
|
||||||
|
mut e: ParseError,
|
||||||
|
) -> Self {
|
||||||
|
e.span = e
|
||||||
|
.span
|
||||||
|
.or_else(|| Some(span_from_checkpoint(input, token_start)));
|
||||||
|
|
||||||
|
e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'input> AddContext<Input<'input>, ParseError> for ParseError {
|
||||||
|
fn add_context(
|
||||||
|
mut self,
|
||||||
|
_input: &Input<'input>,
|
||||||
|
_token_start: &<Input<'input> as Stream>::Checkpoint,
|
||||||
|
context: ParseError,
|
||||||
|
) -> Self {
|
||||||
|
self.message = context.message.or(self.message);
|
||||||
|
self.help = context.help.or(self.help);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn span_from_checkpoint<I: Stream + Location>(
|
||||||
|
input: &I,
|
||||||
|
token_start: &<I as Stream>::Checkpoint,
|
||||||
|
) -> SourceSpan {
|
||||||
|
let offset = input.offset_from(token_start);
|
||||||
|
|
||||||
|
SourceSpan {
|
||||||
|
range: (input.current_token_start() - offset)..input.current_token_start(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'input> ParserError<Input<'input>> for ParseError {
|
||||||
|
type Inner = ParseError;
|
||||||
|
|
||||||
|
fn from_input(_input: &Input<'input>) -> Self {
|
||||||
|
ParseError::ctx()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_inner(self) -> winnow::Result<Self::Inner, Self> {
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_backtrack(&self) -> bool {
|
||||||
|
!self.is_fatal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ParsedTemplate {
|
||||||
|
tokens: Vec<TemplateToken>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParsedTemplate {
|
||||||
|
pub fn tokens(&self) -> &[TemplateToken] {
|
||||||
|
&self.tokens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum TokenKind {
|
||||||
|
Content,
|
||||||
|
LeftDelim,
|
||||||
|
RightDelim,
|
||||||
|
TrimWhitespace,
|
||||||
|
WantsOutput,
|
||||||
|
Ident,
|
||||||
|
LeftArgList,
|
||||||
|
RightArgList,
|
||||||
|
ArgSeperator,
|
||||||
|
Whitespace,
|
||||||
|
Invalid,
|
||||||
|
ConditionalIf,
|
||||||
|
ConditionalElse,
|
||||||
|
For,
|
||||||
|
In,
|
||||||
|
End,
|
||||||
|
Literal(TokenLiteral),
|
||||||
|
Operator(TokenOperator),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum TokenOperator {
|
||||||
|
Plus,
|
||||||
|
Minus,
|
||||||
|
Times,
|
||||||
|
Divide,
|
||||||
|
And,
|
||||||
|
Or,
|
||||||
|
Equal,
|
||||||
|
NotEqual,
|
||||||
|
Greater,
|
||||||
|
GreaterOrEqual,
|
||||||
|
Lesser,
|
||||||
|
LesserOrEqual,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum TokenLiteral {
|
||||||
|
Bool(bool),
|
||||||
|
Integer(u64),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<TokenKind> for TemplateToken {
|
||||||
|
fn eq(&self, other: &TokenKind) -> bool {
|
||||||
|
self.kind == *other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl winnow::stream::ContainsToken<&'_ TemplateToken> for TokenKind {
|
||||||
|
fn contains_token(&self, token: &'_ TemplateToken) -> bool {
|
||||||
|
*self == token.kind
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl winnow::stream::ContainsToken<&'_ TemplateToken> for &'_ [TokenKind] {
|
||||||
|
fn contains_token(&self, token: &'_ TemplateToken) -> bool {
|
||||||
|
self.contains(&token.kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const LEN: usize> winnow::stream::ContainsToken<&'_ TemplateToken> for &'_ [TokenKind; LEN] {
|
||||||
|
fn contains_token(&self, token: &'_ TemplateToken) -> bool {
|
||||||
|
self.contains(&token.kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const LEN: usize> winnow::stream::ContainsToken<&'_ TemplateToken> for [TokenKind; LEN] {
|
||||||
|
fn contains_token(&self, token: &'_ TemplateToken) -> bool {
|
||||||
|
self.contains(&token.kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
|
pub struct TemplateToken {
|
||||||
|
kind: TokenKind,
|
||||||
|
source: NomoInput,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for TemplateToken {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "[{:?}]{:?}", self.kind(), self.source())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Location for TemplateToken {
|
||||||
|
fn previous_token_end(&self) -> usize {
|
||||||
|
NomoInput::get_range(&self.source).start
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_token_start(&self) -> usize {
|
||||||
|
NomoInput::get_range(&self.source).start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_token_kind_builders {
|
||||||
|
($($name:ident => $kind:expr),+ $(,)?) => {
|
||||||
|
$(
|
||||||
|
fn $name(source: NomoInput) -> Self {
|
||||||
|
TemplateToken {
|
||||||
|
kind: $kind,
|
||||||
|
source,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TemplateToken {
|
||||||
|
impl_token_kind_builders! {
|
||||||
|
content => TokenKind::Content,
|
||||||
|
left_delim => TokenKind::LeftDelim,
|
||||||
|
right_delim => TokenKind::RightDelim,
|
||||||
|
trim_whitespace => TokenKind::TrimWhitespace,
|
||||||
|
wants_output => TokenKind::WantsOutput,
|
||||||
|
ident => TokenKind::Ident,
|
||||||
|
left_arg_list => TokenKind::LeftArgList,
|
||||||
|
right_arg_list => TokenKind::RightArgList,
|
||||||
|
arg_seperator => TokenKind::ArgSeperator,
|
||||||
|
whitespace => TokenKind::Whitespace,
|
||||||
|
invalid => TokenKind::Invalid,
|
||||||
|
conditional_if => TokenKind::ConditionalIf,
|
||||||
|
conditional_else => TokenKind::ConditionalElse,
|
||||||
|
keyword_for => TokenKind::For,
|
||||||
|
keyword_in => TokenKind::In,
|
||||||
|
end => TokenKind::End,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn literal(literal: TokenLiteral, source: NomoInput) -> Self {
|
||||||
|
TemplateToken {
|
||||||
|
kind: TokenKind::Literal(literal),
|
||||||
|
source,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn operator(operator: TokenOperator, source: NomoInput) -> Self {
|
||||||
|
TemplateToken {
|
||||||
|
kind: TokenKind::Operator(operator),
|
||||||
|
source,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kind(&self) -> TokenKind {
|
||||||
|
self.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn source(&self) -> NomoInput {
|
||||||
|
self.source.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(input: NomoInput) -> Result<ParsedTemplate, ParseFailure> {
|
||||||
|
let (_remaining, val, errors) =
|
||||||
|
parse_tokens.recoverable_parse(LocatingSlice::new(input.clone()));
|
||||||
|
|
||||||
|
if errors.is_empty()
|
||||||
|
&& let Some(val) = val
|
||||||
|
{
|
||||||
|
Ok(ParsedTemplate { tokens: val })
|
||||||
|
} else {
|
||||||
|
Err(ParseFailure::from_errors(errors, input))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_tokens<'input>(input: &mut Input<'input>) -> PResult<'input, Vec<TemplateToken>> {
|
||||||
|
repeat_till(0.., alt((parse_interpolate, parse_content)), eof)
|
||||||
|
.map(|(v, _): (Vec<_>, _)| v.into_iter().flatten().collect())
|
||||||
|
.parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_content<'input>(input: &mut Input<'input>) -> PResult<'input, Vec<TemplateToken>> {
|
||||||
|
alt((
|
||||||
|
repeat_till(1.., any, peek((multispace0, "{{"))).map(|((), _)| ()),
|
||||||
|
rest.void(),
|
||||||
|
))
|
||||||
|
.take()
|
||||||
|
.map(TemplateToken::content)
|
||||||
|
.map(|v| vec![v])
|
||||||
|
.parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_interpolate<'input>(input: &mut Input<'input>) -> PResult<'input, Vec<TemplateToken>> {
|
||||||
|
let prev_whitespace = opt(parse_whitespace).parse_next(input)?;
|
||||||
|
let left_delim = "{{".map(TemplateToken::left_delim).parse_next(input)?;
|
||||||
|
let left_trim = opt("-".map(TemplateToken::trim_whitespace)).parse_next(input)?;
|
||||||
|
let wants_output = opt("=".map(TemplateToken::wants_output)).parse_next(input)?;
|
||||||
|
|
||||||
|
let get_tokens = repeat_till(1.., parse_block_token, peek(preceded(opt("-"), "}}")));
|
||||||
|
let recover = take_until(0.., "}}").void();
|
||||||
|
|
||||||
|
let (inside_tokens, _): (Vec<_>, _) = get_tokens
|
||||||
|
.resume_after(recover)
|
||||||
|
.with_taken()
|
||||||
|
.map(|(val, taken)| {
|
||||||
|
val.unwrap_or_else(|| (vec![TemplateToken::invalid(taken)], NomoInput::from("")))
|
||||||
|
})
|
||||||
|
.parse_next(input)?;
|
||||||
|
|
||||||
|
let right_trim = opt("-".map(TemplateToken::trim_whitespace)).parse_next(input)?;
|
||||||
|
let right_delim = "}}".map(TemplateToken::right_delim).parse_next(input)?;
|
||||||
|
let post_whitespace = opt(parse_whitespace).parse_next(input)?;
|
||||||
|
|
||||||
|
let mut tokens = vec![];
|
||||||
|
tokens.extend(prev_whitespace);
|
||||||
|
tokens.push(left_delim);
|
||||||
|
tokens.extend(left_trim);
|
||||||
|
tokens.extend(wants_output);
|
||||||
|
tokens.extend(inside_tokens);
|
||||||
|
tokens.extend(right_trim);
|
||||||
|
tokens.push(right_delim);
|
||||||
|
tokens.extend(post_whitespace);
|
||||||
|
|
||||||
|
Ok(tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_block_token<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
|
||||||
|
trace(
|
||||||
|
"parse_block_token",
|
||||||
|
alt((
|
||||||
|
parse_ident,
|
||||||
|
parse_function,
|
||||||
|
parse_keyword,
|
||||||
|
parse_whitespace,
|
||||||
|
parse_operator,
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_literal<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
|
||||||
|
trace(
|
||||||
|
"parse_literal",
|
||||||
|
alt((parse_boolean, parse_number))
|
||||||
|
.with_taken()
|
||||||
|
.map(|(lit, span)| TemplateToken::literal(lit, span)),
|
||||||
|
)
|
||||||
|
.parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_number<'input>(input: &mut Input<'input>) -> PResult<'input, TokenLiteral> {
|
||||||
|
digit1
|
||||||
|
.verify_map(|digits: NomoInput| digits.parse::<u64>().ok())
|
||||||
|
.map(TokenLiteral::Integer)
|
||||||
|
.parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_boolean<'input>(input: &mut Input<'input>) -> PResult<'input, TokenLiteral> {
|
||||||
|
alt((
|
||||||
|
"true".value(TokenLiteral::Bool(true)),
|
||||||
|
"false".value(TokenLiteral::Bool(false)),
|
||||||
|
))
|
||||||
|
.parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_condition_if<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
|
||||||
|
trace(
|
||||||
|
"parse_condition_if",
|
||||||
|
"if".map(TemplateToken::conditional_if),
|
||||||
|
)
|
||||||
|
.parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_condition_else<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
|
||||||
|
trace(
|
||||||
|
"parse_condition_else",
|
||||||
|
"else".map(TemplateToken::conditional_else),
|
||||||
|
)
|
||||||
|
.parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_end<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
|
||||||
|
trace("parse_end", "end".map(TemplateToken::end)).parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_for<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
|
||||||
|
trace("parse_for", "for".map(TemplateToken::keyword_for)).parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_in<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
|
||||||
|
trace("parse_in", "in".map(TemplateToken::keyword_in)).parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_keyword<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
|
||||||
|
alt((
|
||||||
|
terminated(parse_literal, ident_terminator_check),
|
||||||
|
terminated(parse_condition_if, ident_terminator_check),
|
||||||
|
terminated(parse_condition_else, ident_terminator_check),
|
||||||
|
terminated(parse_for, ident_terminator_check),
|
||||||
|
terminated(parse_in, ident_terminator_check),
|
||||||
|
terminated(parse_end, ident_terminator_check),
|
||||||
|
))
|
||||||
|
.parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_whitespace<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
|
||||||
|
trace(
|
||||||
|
"parse_whitespace",
|
||||||
|
multispace1.map(TemplateToken::whitespace),
|
||||||
|
)
|
||||||
|
.parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_function<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
|
||||||
|
trace(
|
||||||
|
"parse_function",
|
||||||
|
alt((
|
||||||
|
"(".map(TemplateToken::left_arg_list),
|
||||||
|
")".map(TemplateToken::right_arg_list),
|
||||||
|
",".map(TemplateToken::arg_seperator),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_ident<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
|
||||||
|
resume_after_cut(
|
||||||
|
terminated(
|
||||||
|
ident.map(TemplateToken::ident),
|
||||||
|
cut_err(ident_terminator_check),
|
||||||
|
)
|
||||||
|
.context(
|
||||||
|
ParseError::ctx()
|
||||||
|
.msg("Invalid variable identifier")
|
||||||
|
.help("valid variable identifiers are alphanumeric"),
|
||||||
|
),
|
||||||
|
bad_ident,
|
||||||
|
)
|
||||||
|
.with_taken()
|
||||||
|
.map(|(val, taken)| val.unwrap_or(TemplateToken::invalid(taken)))
|
||||||
|
.parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_operator<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
|
||||||
|
let (operator, source) = trace(
|
||||||
|
"operator",
|
||||||
|
dispatch! {any;
|
||||||
|
'+' => empty.value(TokenOperator::Plus),
|
||||||
|
'-' => empty.value(TokenOperator::Minus),
|
||||||
|
'*' => empty.value(TokenOperator::Times),
|
||||||
|
'/' => empty.value(TokenOperator::Divide),
|
||||||
|
'&' => alt((
|
||||||
|
"&".value(TokenOperator::And),
|
||||||
|
cut_err(fail),
|
||||||
|
)),
|
||||||
|
'|' => alt((
|
||||||
|
"|".value(TokenOperator::Or),
|
||||||
|
cut_err(fail),
|
||||||
|
)),
|
||||||
|
'<' => alt((
|
||||||
|
"=".value(TokenOperator::LesserOrEqual),
|
||||||
|
empty.value(TokenOperator::Lesser),
|
||||||
|
)),
|
||||||
|
'>' => alt((
|
||||||
|
"=".value(TokenOperator::GreaterOrEqual),
|
||||||
|
empty.value(TokenOperator::Greater),
|
||||||
|
)),
|
||||||
|
'!' => alt((
|
||||||
|
"=".value(TokenOperator::NotEqual),
|
||||||
|
cut_err(fail),
|
||||||
|
)),
|
||||||
|
'=' => alt((
|
||||||
|
"=".value(TokenOperator::Equal),
|
||||||
|
cut_err(fail),
|
||||||
|
)),
|
||||||
|
_ => fail,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_taken()
|
||||||
|
.parse_next(input)?;
|
||||||
|
|
||||||
|
Ok(TemplateToken::operator(operator, source))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ident<'input>(input: &mut Input<'input>) -> PResult<'input, NomoInput> {
|
||||||
|
peek(not(parse_keyword))
|
||||||
|
.context(ParseError::ctx().msg("Expected an ident, but found a literal instead"))
|
||||||
|
.parse_next(input)?;
|
||||||
|
|
||||||
|
let literal_start = alt((alpha1, "_"));
|
||||||
|
|
||||||
|
(
|
||||||
|
literal_start,
|
||||||
|
take_while(0.., |c: char| c.is_alphanumeric() || "_".contains(c)),
|
||||||
|
)
|
||||||
|
.take()
|
||||||
|
.parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bad_ident<'input>(input: &mut Input<'input>) -> PResult<'input, ()> {
|
||||||
|
repeat_till(1.., any, ident_terminator_check)
|
||||||
|
.map(|((), _)| ())
|
||||||
|
.parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ident_terminator_check<'input>(input: &mut Input<'input>) -> PResult<'input, ()> {
|
||||||
|
peek(ident_terminator).parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ident_terminator<'input>(input: &mut Input<'input>) -> PResult<'input, ()> {
|
||||||
|
alt((
|
||||||
|
eof.void(),
|
||||||
|
one_of(('{', '}')).void(),
|
||||||
|
one_of(('(', ',', ')')).void(),
|
||||||
|
one_of((' ', '\t', '\r', '\n')).void(),
|
||||||
|
))
|
||||||
|
.parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::lexer::parse;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_simple() {
|
||||||
|
let input = "Hello There";
|
||||||
|
let output = parse(input.into());
|
||||||
|
|
||||||
|
insta::assert_debug_snapshot!(output, @r#"
|
||||||
|
Ok(
|
||||||
|
ParsedTemplate {
|
||||||
|
tokens: [
|
||||||
|
[Content]"Hello There" (0..11),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_interpolate() {
|
||||||
|
let input = "Hello {{ there }}";
|
||||||
|
let output = parse(input.into());
|
||||||
|
|
||||||
|
insta::assert_debug_snapshot!(output, @r#"
|
||||||
|
Ok(
|
||||||
|
ParsedTemplate {
|
||||||
|
tokens: [
|
||||||
|
[Content]"Hello" (0..5),
|
||||||
|
[Whitespace]" " (5..6),
|
||||||
|
[LeftDelim]"{{" (6..8),
|
||||||
|
[Whitespace]" " (8..9),
|
||||||
|
[Ident]"there" (9..14),
|
||||||
|
[Whitespace]" " (14..15),
|
||||||
|
[RightDelim]"}}" (15..17),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_interpolate_bad() {
|
||||||
|
let input = "Hello {{ the2re }} {{ the@re }}";
|
||||||
|
let output = parse(input.into());
|
||||||
|
|
||||||
|
insta::assert_debug_snapshot!(output, @r#"
|
||||||
|
Err(
|
||||||
|
ParseFailure {
|
||||||
|
input: "Hello {{ the2re }} {{ the@re }}",
|
||||||
|
errors: [
|
||||||
|
ParseError {
|
||||||
|
message: Some(
|
||||||
|
"Invalid variable identifier",
|
||||||
|
),
|
||||||
|
help: Some(
|
||||||
|
"valid variable identifiers are alphanumeric",
|
||||||
|
),
|
||||||
|
span: Some(
|
||||||
|
SourceSpan {
|
||||||
|
range: 22..28,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
is_fatal: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
|
||||||
|
let error = output.unwrap_err();
|
||||||
|
|
||||||
|
insta::assert_snapshot!(error.to_report());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_simple_condition() {
|
||||||
|
let input = "{{ if true }} Hello! {{ else }} Bye {{ end }}";
|
||||||
|
let output = parse(input.into());
|
||||||
|
|
||||||
|
insta::assert_debug_snapshot!(output, @r#"
|
||||||
|
Ok(
|
||||||
|
ParsedTemplate {
|
||||||
|
tokens: [
|
||||||
|
[LeftDelim]"{{" (0..2),
|
||||||
|
[Whitespace]" " (2..3),
|
||||||
|
[ConditionalIf]"if" (3..5),
|
||||||
|
[Whitespace]" " (5..6),
|
||||||
|
[Literal(Bool(true))]"true" (6..10),
|
||||||
|
[Whitespace]" " (10..11),
|
||||||
|
[RightDelim]"}}" (11..13),
|
||||||
|
[Whitespace]" " (13..14),
|
||||||
|
[Content]"Hello!" (14..20),
|
||||||
|
[Whitespace]" " (20..21),
|
||||||
|
[LeftDelim]"{{" (21..23),
|
||||||
|
[Whitespace]" " (23..24),
|
||||||
|
[ConditionalElse]"else" (24..28),
|
||||||
|
[Whitespace]" " (28..29),
|
||||||
|
[RightDelim]"}}" (29..31),
|
||||||
|
[Whitespace]" " (31..32),
|
||||||
|
[Content]"Bye" (32..35),
|
||||||
|
[Whitespace]" " (35..36),
|
||||||
|
[LeftDelim]"{{" (36..38),
|
||||||
|
[Whitespace]" " (38..39),
|
||||||
|
[End]"end" (39..42),
|
||||||
|
[Whitespace]" " (42..43),
|
||||||
|
[RightDelim]"}}" (43..45),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_trim_whitespace() {
|
||||||
|
let input = "\n\n{{-= hello -}} \n\n";
|
||||||
|
let output = parse(input.into());
|
||||||
|
|
||||||
|
insta::assert_debug_snapshot!(output, @r#"
|
||||||
|
Ok(
|
||||||
|
ParsedTemplate {
|
||||||
|
tokens: [
|
||||||
|
[Whitespace]"\n\n" (0..2),
|
||||||
|
[LeftDelim]"{{" (2..4),
|
||||||
|
[TrimWhitespace]"-" (4..5),
|
||||||
|
[WantsOutput]"=" (5..6),
|
||||||
|
[Whitespace]" " (6..7),
|
||||||
|
[Ident]"hello" (7..12),
|
||||||
|
[Whitespace]" " (12..13),
|
||||||
|
[TrimWhitespace]"-" (13..14),
|
||||||
|
[RightDelim]"}}" (14..16),
|
||||||
|
[Whitespace]" \n\n" (16..19),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_for_loop() {
|
||||||
|
let input = "{{ for value in array }} Hi: {{= value }} {{ end }}";
|
||||||
|
let output = parse(input.into());
|
||||||
|
|
||||||
|
insta::assert_debug_snapshot!(output, @r#"
|
||||||
|
Ok(
|
||||||
|
ParsedTemplate {
|
||||||
|
tokens: [
|
||||||
|
[LeftDelim]"{{" (0..2),
|
||||||
|
[Whitespace]" " (2..3),
|
||||||
|
[For]"for" (3..6),
|
||||||
|
[Whitespace]" " (6..7),
|
||||||
|
[Ident]"value" (7..12),
|
||||||
|
[Whitespace]" " (12..13),
|
||||||
|
[In]"in" (13..15),
|
||||||
|
[Whitespace]" " (15..16),
|
||||||
|
[Ident]"array" (16..21),
|
||||||
|
[Whitespace]" " (21..22),
|
||||||
|
[RightDelim]"}}" (22..24),
|
||||||
|
[Whitespace]" " (24..25),
|
||||||
|
[Content]"Hi:" (25..28),
|
||||||
|
[Whitespace]" " (28..29),
|
||||||
|
[LeftDelim]"{{" (29..31),
|
||||||
|
[WantsOutput]"=" (31..32),
|
||||||
|
[Whitespace]" " (32..33),
|
||||||
|
[Ident]"value" (33..38),
|
||||||
|
[Whitespace]" " (38..39),
|
||||||
|
[RightDelim]"}}" (39..41),
|
||||||
|
[Whitespace]" " (41..42),
|
||||||
|
[LeftDelim]"{{" (42..44),
|
||||||
|
[Whitespace]" " (44..45),
|
||||||
|
[End]"end" (45..48),
|
||||||
|
[Whitespace]" " (48..49),
|
||||||
|
[RightDelim]"}}" (49..51),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_operations() {
|
||||||
|
let input = "{{= 5 * 14 + 3 / 2 - 1 }}{{ if foo && bar || baz && 2 != 3 || 4 > 2 || 43 <= 5 }}{{ end }}";
|
||||||
|
let output = parse(input.into());
|
||||||
|
|
||||||
|
insta::assert_debug_snapshot!(output, @r#"
|
||||||
|
Ok(
|
||||||
|
ParsedTemplate {
|
||||||
|
tokens: [
|
||||||
|
[LeftDelim]"{{" (0..2),
|
||||||
|
[WantsOutput]"=" (2..3),
|
||||||
|
[Whitespace]" " (3..4),
|
||||||
|
[Literal(Integer(5))]"5" (4..5),
|
||||||
|
[Whitespace]" " (5..6),
|
||||||
|
[Operator(Times)]"*" (6..7),
|
||||||
|
[Whitespace]" " (7..8),
|
||||||
|
[Literal(Integer(14))]"14" (8..10),
|
||||||
|
[Whitespace]" " (10..11),
|
||||||
|
[Operator(Plus)]"+" (11..12),
|
||||||
|
[Whitespace]" " (12..13),
|
||||||
|
[Literal(Integer(3))]"3" (13..14),
|
||||||
|
[Whitespace]" " (14..15),
|
||||||
|
[Operator(Divide)]"/" (15..16),
|
||||||
|
[Whitespace]" " (16..17),
|
||||||
|
[Literal(Integer(2))]"2" (17..18),
|
||||||
|
[Whitespace]" " (18..19),
|
||||||
|
[Operator(Minus)]"-" (19..20),
|
||||||
|
[Whitespace]" " (20..21),
|
||||||
|
[Literal(Integer(1))]"1" (21..22),
|
||||||
|
[Whitespace]" " (22..23),
|
||||||
|
[RightDelim]"}}" (23..25),
|
||||||
|
[LeftDelim]"{{" (25..27),
|
||||||
|
[Whitespace]" " (27..28),
|
||||||
|
[ConditionalIf]"if" (28..30),
|
||||||
|
[Whitespace]" " (30..31),
|
||||||
|
[Ident]"foo" (31..34),
|
||||||
|
[Whitespace]" " (34..35),
|
||||||
|
[Operator(And)]"&&" (35..37),
|
||||||
|
[Whitespace]" " (37..38),
|
||||||
|
[Ident]"bar" (38..41),
|
||||||
|
[Whitespace]" " (41..42),
|
||||||
|
[Operator(Or)]"||" (42..44),
|
||||||
|
[Whitespace]" " (44..45),
|
||||||
|
[Ident]"baz" (45..48),
|
||||||
|
[Whitespace]" " (48..49),
|
||||||
|
[Operator(And)]"&&" (49..51),
|
||||||
|
[Whitespace]" " (51..52),
|
||||||
|
[Literal(Integer(2))]"2" (52..53),
|
||||||
|
[Whitespace]" " (53..54),
|
||||||
|
[Operator(NotEqual)]"!=" (54..56),
|
||||||
|
[Whitespace]" " (56..57),
|
||||||
|
[Literal(Integer(3))]"3" (57..58),
|
||||||
|
[Whitespace]" " (58..59),
|
||||||
|
[Operator(Or)]"||" (59..61),
|
||||||
|
[Whitespace]" " (61..62),
|
||||||
|
[Literal(Integer(4))]"4" (62..63),
|
||||||
|
[Whitespace]" " (63..64),
|
||||||
|
[Operator(Greater)]">" (64..65),
|
||||||
|
[Whitespace]" " (65..66),
|
||||||
|
[Literal(Integer(2))]"2" (66..67),
|
||||||
|
[Whitespace]" " (67..68),
|
||||||
|
[Operator(Or)]"||" (68..70),
|
||||||
|
[Whitespace]" " (70..71),
|
||||||
|
[Literal(Integer(43))]"43" (71..73),
|
||||||
|
[Whitespace]" " (73..74),
|
||||||
|
[Operator(LesserOrEqual)]"<=" (74..76),
|
||||||
|
[Whitespace]" " (76..77),
|
||||||
|
[Literal(Integer(5))]"5" (77..78),
|
||||||
|
[Whitespace]" " (78..79),
|
||||||
|
[RightDelim]"}}" (79..81),
|
||||||
|
[LeftDelim]"{{" (81..83),
|
||||||
|
[Whitespace]" " (83..84),
|
||||||
|
[End]"end" (84..87),
|
||||||
|
[Whitespace]" " (87..88),
|
||||||
|
[RightDelim]"}}" (88..90),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_function() {
|
||||||
|
let input = "{{= foo(23, 4, 3 * 5) }}";
|
||||||
|
let output = parse(input.into());
|
||||||
|
|
||||||
|
insta::assert_debug_snapshot!(output, @r#"
|
||||||
|
Ok(
|
||||||
|
ParsedTemplate {
|
||||||
|
tokens: [
|
||||||
|
[LeftDelim]"{{" (0..2),
|
||||||
|
[WantsOutput]"=" (2..3),
|
||||||
|
[Whitespace]" " (3..4),
|
||||||
|
[Ident]"foo" (4..7),
|
||||||
|
[LeftArgList]"(" (7..8),
|
||||||
|
[Literal(Integer(23))]"23" (8..10),
|
||||||
|
[ArgSeperator]"," (10..11),
|
||||||
|
[Whitespace]" " (11..12),
|
||||||
|
[Literal(Integer(4))]"4" (12..13),
|
||||||
|
[ArgSeperator]"," (13..14),
|
||||||
|
[Whitespace]" " (14..15),
|
||||||
|
[Literal(Integer(3))]"3" (15..16),
|
||||||
|
[Whitespace]" " (16..17),
|
||||||
|
[Operator(Times)]"*" (17..18),
|
||||||
|
[Whitespace]" " (18..19),
|
||||||
|
[Literal(Integer(5))]"5" (19..20),
|
||||||
|
[RightArgList]")" (20..21),
|
||||||
|
[Whitespace]" " (21..22),
|
||||||
|
[RightDelim]"}}" (22..24),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/parser/mod.rs
|
source: src/lexer/mod.rs
|
||||||
expression: error.to_report()
|
expression: error.to_report()
|
||||||
---
|
---
|
||||||
[1m[91merror[0m[1m: Invalid variable identifier[0m
|
[1m[91merror[0m[1m: Invalid variable identifier[0m
|
||||||
18
src/lib.rs
18
src/lib.rs
|
|
@ -4,15 +4,17 @@ use displaydoc::Display;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::emit::VMInstructions;
|
use crate::emit::VMInstructions;
|
||||||
|
use crate::functions::FunctionMap;
|
||||||
use crate::input::NomoInput;
|
use crate::input::NomoInput;
|
||||||
use crate::value::NomoValue;
|
use crate::value::NomoValue;
|
||||||
use crate::value::NomoValueError;
|
use crate::value::NomoValueError;
|
||||||
|
|
||||||
pub mod ast;
|
pub mod parser;
|
||||||
pub mod emit;
|
pub mod emit;
|
||||||
pub mod eval;
|
pub mod eval;
|
||||||
|
pub mod functions;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod parser;
|
pub mod lexer;
|
||||||
pub mod value;
|
pub mod value;
|
||||||
|
|
||||||
#[derive(Debug, Error, Display)]
|
#[derive(Debug, Error, Display)]
|
||||||
|
|
@ -20,12 +22,12 @@ pub enum NomoError {
|
||||||
/// Could not parse the given template
|
/// Could not parse the given template
|
||||||
ParseError {
|
ParseError {
|
||||||
#[from]
|
#[from]
|
||||||
source: parser::ParseFailure,
|
source: lexer::ParseFailure,
|
||||||
},
|
},
|
||||||
/// Invalid Template
|
/// Invalid Template
|
||||||
AstError {
|
AstError {
|
||||||
#[from]
|
#[from]
|
||||||
source: ast::AstFailure,
|
source: parser::AstFailure,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// An error occurred while evaluating
|
/// An error occurred while evaluating
|
||||||
|
|
@ -40,6 +42,7 @@ pub enum NomoError {
|
||||||
|
|
||||||
pub struct Nomo {
|
pub struct Nomo {
|
||||||
templates: HashMap<String, Template>,
|
templates: HashMap<String, Template>,
|
||||||
|
function_map: FunctionMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Nomo {
|
impl Default for Nomo {
|
||||||
|
|
@ -52,6 +55,7 @@ impl Nomo {
|
||||||
pub fn new() -> Nomo {
|
pub fn new() -> Nomo {
|
||||||
Nomo {
|
Nomo {
|
||||||
templates: HashMap::new(),
|
templates: HashMap::new(),
|
||||||
|
function_map: FunctionMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,8 +65,8 @@ impl Nomo {
|
||||||
value: impl Into<NomoInput>,
|
value: impl Into<NomoInput>,
|
||||||
) -> Result<(), NomoError> {
|
) -> Result<(), NomoError> {
|
||||||
let source = value.into();
|
let source = value.into();
|
||||||
let parse = parser::parse(source.clone())?;
|
let parse = lexer::parse(source.clone())?;
|
||||||
let ast = ast::parse(parse.tokens())?;
|
let ast = parser::parse(parse.tokens())?;
|
||||||
|
|
||||||
let instructions = emit::emit_machine(ast);
|
let instructions = emit::emit_machine(ast);
|
||||||
|
|
||||||
|
|
@ -78,7 +82,7 @@ impl Nomo {
|
||||||
.get(name)
|
.get(name)
|
||||||
.ok_or_else(|| NomoError::UnknownTemplate(name.to_string()))?;
|
.ok_or_else(|| NomoError::UnknownTemplate(name.to_string()))?;
|
||||||
|
|
||||||
let res = eval::execute(&template.instructions, ctx)?;
|
let res = eval::execute(&self.function_map, &template.instructions, ctx)?;
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1542
src/parser/mod.rs
1542
src/parser/mod.rs
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/ast/mod.rs
|
source: src/parser/mod.rs
|
||||||
expression: ast
|
expression: ast
|
||||||
---
|
---
|
||||||
TemplateAst {
|
TemplateAst {
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
---
|
||||||
|
source: src/parser/mod.rs
|
||||||
|
expression: ast
|
||||||
|
---
|
||||||
|
TemplateAst {
|
||||||
|
root: [
|
||||||
|
Interpolation {
|
||||||
|
prev_whitespace_content: None,
|
||||||
|
expression: FunctionCall {
|
||||||
|
name: [Ident]"foo" (4..7),
|
||||||
|
args: [
|
||||||
|
Operation {
|
||||||
|
op: Times,
|
||||||
|
lhs: Literal {
|
||||||
|
source: [Literal(Integer(2))]"2" (8..9),
|
||||||
|
value: Integer {
|
||||||
|
value: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rhs: Literal {
|
||||||
|
source: [Literal(Integer(3))]"3" (12..13),
|
||||||
|
value: Integer {
|
||||||
|
value: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
FunctionCall {
|
||||||
|
name: [Ident]"bar" (15..18),
|
||||||
|
args: [
|
||||||
|
Operation {
|
||||||
|
op: Plus,
|
||||||
|
lhs: Literal {
|
||||||
|
source: [Literal(Integer(2))]"2" (19..20),
|
||||||
|
value: Integer {
|
||||||
|
value: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rhs: VariableAccess(
|
||||||
|
[Ident]"baz" (23..26),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
post_whitespace_content: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/ast/mod.rs
|
source: src/parser/mod.rs
|
||||||
expression: ast
|
expression: ast
|
||||||
---
|
---
|
||||||
TemplateAst {
|
TemplateAst {
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/ast/mod.rs
|
source: src/parser/mod.rs
|
||||||
expression: ast
|
expression: ast
|
||||||
---
|
---
|
||||||
TemplateAst {
|
TemplateAst {
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/ast/mod.rs
|
source: src/parser/mod.rs
|
||||||
expression: ast.to_report(input)
|
expression: ast.to_report(input)
|
||||||
---
|
---
|
||||||
[1m[91merror[0m[1m: Standlone action block[0m
|
[1m[91merror[0m[1m: Standlone action block[0m
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/ast/mod.rs
|
source: src/parser/mod.rs
|
||||||
expression: ast
|
expression: ast
|
||||||
---
|
---
|
||||||
TemplateAst {
|
TemplateAst {
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/ast/mod.rs
|
source: src/parser/mod.rs
|
||||||
expression: ast
|
expression: ast
|
||||||
---
|
---
|
||||||
TemplateAst {
|
TemplateAst {
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/ast/mod.rs
|
source: src/parser/mod.rs
|
||||||
expression: ast
|
expression: ast
|
||||||
---
|
---
|
||||||
TemplateAst {
|
TemplateAst {
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/ast/mod.rs
|
source: src/parser/mod.rs
|
||||||
expression: ast
|
expression: ast
|
||||||
---
|
---
|
||||||
TemplateAst {
|
TemplateAst {
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
source: src/ast/mod.rs
|
source: src/parser/mod.rs
|
||||||
expression: parsed
|
expression: parsed
|
||||||
---
|
---
|
||||||
ParsedTemplate {
|
ParsedTemplate {
|
||||||
50
src/value.rs
50
src/value.rs
|
|
@ -5,8 +5,6 @@ use std::collections::BTreeMap;
|
||||||
use displaydoc::Display;
|
use displaydoc::Display;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::Nomo;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum NomoValue {
|
pub enum NomoValue {
|
||||||
String {
|
String {
|
||||||
|
|
@ -312,6 +310,18 @@ impl From<&'static str> for NomoValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<u64> for NomoValue {
|
||||||
|
fn from(val: u64) -> Self {
|
||||||
|
NomoValue::Integer { value: val }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<i64> for NomoValue {
|
||||||
|
fn from(val: i64) -> Self {
|
||||||
|
NomoValue::SignedInteger { value: val }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<V> From<Vec<V>> for NomoValue
|
impl<V> From<Vec<V>> for NomoValue
|
||||||
where
|
where
|
||||||
V: Into<NomoValue>,
|
V: Into<NomoValue>,
|
||||||
|
|
@ -357,11 +367,43 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde_json")]
|
|
||||||
#[derive(Debug, Error, Display)]
|
#[derive(Debug, Error, Display)]
|
||||||
/// Could not transform value to [`NomoValue`]
|
/// Could not transform value to/from [`NomoValue`]
|
||||||
pub struct NomoValueError;
|
pub struct NomoValueError;
|
||||||
|
|
||||||
|
impl TryFrom<NomoValue> for String {
|
||||||
|
type Error = NomoValueError;
|
||||||
|
|
||||||
|
fn try_from(value: NomoValue) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
NomoValue::String { value } => Ok(value.to_string()),
|
||||||
|
_ => Err(NomoValueError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<NomoValue> for i64 {
|
||||||
|
type Error = NomoValueError;
|
||||||
|
|
||||||
|
fn try_from(value: NomoValue) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
NomoValue::SignedInteger { value } => Ok(value),
|
||||||
|
_ => Err(NomoValueError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<NomoValue> for u64 {
|
||||||
|
type Error = NomoValueError;
|
||||||
|
|
||||||
|
fn try_from(value: NomoValue) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
NomoValue::Integer { value } => Ok(value),
|
||||||
|
_ => Err(NomoValueError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde_json")]
|
#[cfg(feature = "serde_json")]
|
||||||
impl TryFrom<serde_json::Value> for NomoValue {
|
impl TryFrom<serde_json::Value> for NomoValue {
|
||||||
type Error = NomoValueError;
|
type Error = NomoValueError;
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,11 @@ fn check_files() {
|
||||||
for file in files {
|
for file in files {
|
||||||
let input = std::fs::read_to_string(file.unwrap().path()).unwrap();
|
let input = std::fs::read_to_string(file.unwrap().path()).unwrap();
|
||||||
|
|
||||||
let Ok(parsed) = nomo::parser::parse(input.into()) else {
|
let Ok(parsed) = nomo::lexer::parse(input.into()) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(ast) = nomo::ast::parse(parsed.tokens()) else {
|
let Ok(ast) = nomo::parser::parse(parsed.tokens()) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use std::collections::HashMap;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use nomo::Context;
|
use nomo::Context;
|
||||||
|
use nomo::functions::FunctionMap;
|
||||||
|
|
||||||
test_each_file::test_each_path! { for ["nomo"] in "./tests/cases/" as cases => check_for_input }
|
test_each_file::test_each_path! { for ["nomo"] in "./tests/cases/" as cases => check_for_input }
|
||||||
|
|
||||||
|
|
@ -38,13 +39,13 @@ fn check_for_input([path]: [&Path; 1]) {
|
||||||
context.try_insert(k, v).unwrap();
|
context.try_insert(k, v).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let parsed = nomo::parser::parse(input.into()).unwrap();
|
let parsed = nomo::lexer::parse(input.into()).unwrap();
|
||||||
|
|
||||||
let _guard = settings.bind_to_scope();
|
let _guard = settings.bind_to_scope();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(format!("{basename}.1-parsed"), parsed);
|
insta::assert_debug_snapshot!(format!("{basename}.1-parsed"), parsed);
|
||||||
|
|
||||||
let ast = match nomo::ast::parse(parsed.tokens()) {
|
let ast = match nomo::parser::parse(parsed.tokens()) {
|
||||||
Ok(ast) => ast,
|
Ok(ast) => ast,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("{}", err.to_report(input));
|
eprintln!("{}", err.to_report(input));
|
||||||
|
|
@ -58,7 +59,7 @@ fn check_for_input([path]: [&Path; 1]) {
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(format!("{basename}.3-instructions"), emit);
|
insta::assert_debug_snapshot!(format!("{basename}.3-instructions"), emit);
|
||||||
|
|
||||||
let output = nomo::eval::execute(&emit, &context).unwrap();
|
let output = nomo::eval::execute(&FunctionMap::default(), &emit, &context).unwrap();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(format!("{basename}.4-output"), output);
|
insta::assert_debug_snapshot!(format!("{basename}.4-output"), output);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue