#![warn(missing_docs)]
#![deny(missing_debug_implementations)]
#![doc(html_no_source)]
mod parser;
use crate::parser::{Rule, SigmaParser};
use pest::{
error::{Error as PestError, ErrorVariant},
iterators::{Pair, Pairs},
Parser, Span,
};
use regex::{NoExpand, Regex};
use std::collections::HashMap;
type SigmaResult<'a, T> = Result<T, PestError<Rule>>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DataType {
U8,
I8,
U16,
I16,
U32,
I32,
U64,
I64,
F32,
F64,
Bool,
Str,
}
#[doc(hidden)]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Variable<'a> {
pub name: &'a str,
pub nullable: bool,
pub typed: bool,
pub data_type: Option<(DataType, Span<'a>)>,
pub location: (usize, usize),
pub functions: Vec<(&'a str, Span<'a>)>,
pub name_span: Option<Span<'a>>,
pub pair_str: &'a str,
}
#[doc(hidden)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Function {
pub name: String,
pub call: fn(String) -> String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Sigma<'s> {
vars: HashMap<&'s str, Variable<'s>>,
registry: HashMap<&'s str, &'s str>,
input: &'s str,
is_parsed: bool,
ignore_unbinded: bool,
functions: HashMap<&'s str, Function>,
}
impl<'s> Sigma<'s> {
pub fn new(input: &'s str) -> Self {
let sigma = Self {
input,
vars: HashMap::new(),
functions: HashMap::new(),
is_parsed: false,
ignore_unbinded: false,
registry: HashMap::new(),
};
let sigma = sigma.register_fn("UPPERCASE", |input| input.to_uppercase());
let sigma =
sigma.register_fn("TRIM_END", |input| input.trim_end().to_owned());
let sigma =
sigma.register_fn("TRIM_START", |input| input.trim_start().to_owned());
let sigma =
sigma.register_fn("TRIM_START", |input| input.trim_start().to_owned());
let sigma = sigma.register_fn("TRIM", |input| input.trim().to_owned());
sigma.register_fn("LOWERRCASE", |input| input.to_lowercase())
}
pub fn bind(mut self, key: &'s str, value: &'s str) -> Self {
self.registry.insert(key, value);
self
}
pub fn bind_map(mut self, map: HashMap<&'s str, &'s str>) -> Self {
self.registry.extend(map);
self
}
pub fn override_bind(mut self, map: HashMap<&'s str, &'s str>) -> Self {
self.registry = map;
self
}
pub fn ignore_unbinded(mut self) -> Self {
self.ignore_unbinded = true;
self
}
pub fn register_fn(
mut self,
func_name: &'static str,
func: fn(String) -> String,
) -> Self {
self.functions.insert(
func_name,
Function {
name: func_name.to_uppercase(),
call: func,
},
);
self
}
pub fn parse(mut self) -> SigmaResult<'s, Self> {
for sigma in SigmaParser::parse(Rule::sigma, &self.input)? {
if sigma.as_rule() == Rule::var_pair {
self.parse_var_pair(sigma)?;
}
}
self.is_parsed = true;
Ok(self)
}
pub fn compile(self) -> SigmaResult<'s, String> {
assert!(self.is_parsed, "The template must be parsed first");
let mut output = self.input.to_owned(); for var in self.vars.values() {
let var_regex = Regex::new(®ex::escape(var.pair_str)).unwrap();
if let Some(value) = self.registry.get(var.name) {
let mut current_data = (*value).to_owned();
for function in &var.functions {
let f = &self.functions[&function.0]; current_data = (f.call)(current_data);
}
self.validate_data_type(&var, ¤t_data)?;
output = var_regex
.replace_all(&output, NoExpand(¤t_data))
.to_string();
} else if var.nullable {
output = var_regex.replace_all(&output, NoExpand("")).to_string();
}
}
Ok(output)
}
fn parse_var_pair(&mut self, pair: Pair<'s, Rule>) -> SigmaResult<()> {
let mut variable = Variable::default();
variable.pair_str = pair.as_str();
let mut inner_rules = pair.into_inner();
let open_pairs = inner_rules.next().unwrap();
let var = inner_rules.next().unwrap();
let var_inner = var.into_inner();
for var_rules in var_inner {
match var_rules.as_rule() {
Rule::nullable => {
variable.nullable = true;
},
Rule::var_name => {
variable.name = var_rules.as_str();
variable.name_span = Some(var_rules.as_span());
},
Rule::data_type_sep => {
variable.typed = true;
},
Rule::data_type => {
let data_type = var_rules;
variable.data_type =
Some((self.parse_data_type(&data_type)?, data_type.as_span()));
},
_ => {},
};
}
if variable.typed && variable.data_type.is_none() {
return Err(PestError::new_from_span(
ErrorVariant::ParsingError {
positives: vec![Rule::data_type],
negatives: vec![],
},
variable.name_span.clone().unwrap(),
));
}
let mut variable = self.parse_function(inner_rules, variable)?;
variable.location = (open_pairs.as_span().start(), variable.location.1);
if !self.registry.contains_key(variable.name)
&& !variable.nullable
&& !self.ignore_unbinded
{
let extra_help;
if let Some(matches) =
parser::did_you_mean(variable.name, self.registry.keys())
{
extra_help = format!("did you mean: `{}` ?", matches);
} else {
extra_help = "consider adding a bind for it".to_owned();
}
return Err(PestError::new_from_span(
ErrorVariant::CustomError {
message: format!(
"unbinded variable: `{}` {}",
variable.name, extra_help
),
},
variable.name_span.clone().unwrap(),
));
}
self.vars.insert(variable.name, variable);
Ok(())
}
#[inline(always)]
fn parse_data_type<'b>(
&self,
pair: &Pair<Rule>,
) -> SigmaResult<'b, DataType> {
use self::DataType::*;
let val = pair.as_str();
let result = match val {
"u8" => U8,
"i8" => I8,
"u16" => U16,
"i16" => I16,
"u32" => U32,
"i32" => I32,
"u64" => U64,
"i64" => I64,
"f32" => F32,
"f64" => F64,
"bool" => Bool,
"str" => Str,
_ => {
let p_vals = [
"u8", "u16", "u32", "u64", "i8", "i16", "i32", "i64", "f32", "f64",
"str", "bool",
];
let mut extra_help = String::new();
if let Some(matches) = parser::did_you_mean(val, p_vals.iter()) {
extra_help = format!("did you mean: `{}` ?", matches);
}
return Err(PestError::new_from_span(
ErrorVariant::CustomError {
message: format!("unknown data type: `{}` {}", val, extra_help),
},
pair.as_span(),
));
},
};
Ok(result)
}
fn parse_function<'f>(
&self,
pairs: Pairs<'f, Rule>,
mut var: Variable<'f>,
) -> SigmaResult<'f, Variable<'f>> {
for pair in pairs {
let rule = pair.as_rule();
match rule {
Rule::function => {
if var.data_type.is_none() || !var.typed {
return Err(PestError::new_from_span(
ErrorVariant::ParsingError {
positives: vec![Rule::data_type],
negatives: vec![],
},
var.name_span.unwrap(),
));
}
let mut function = pair.into_inner();
let _sep = function.next().unwrap();
let function_name = function.next().unwrap();
if !self.functions.contains_key(function_name.as_str()) {
let mut extra_help = String::new();
if let Some(matches) = parser::did_you_mean(
function_name.as_str(),
self.functions.keys(),
) {
extra_help = format!("did you mean: `{}` ?", matches);
}
return Err(PestError::new_from_span(
ErrorVariant::CustomError {
message: format!(
"undefined function: {} {}",
function_name.as_str(),
extra_help
),
},
function_name.as_span(),
));
}
var
.functions
.push((function_name.as_str(), function_name.as_span()));
},
Rule::pair_close => {
var.location = (0, pair.as_span().end());
break;
},
_ => {},
};
}
Ok(var)
}
#[inline]
fn validate_data_type(
&self,
var: &Variable,
data: &str,
) -> SigmaResult<'s, ()> {
if let Some(data_type) = &var.data_type {
use self::DataType::*;
let data_type_error = {
let extra = if data.len() > 15 { "..." } else { "" };
PestError::<Rule>::new_from_span(
ErrorVariant::CustomError {
message: format!(
"cannot parse input `{}{}` into `{:?}` for var `{}` !",
data.chars().take(15).collect::<String>(),
extra,
data_type.0,
var.name
),
},
data_type.1.clone(),
)
};
match data_type.0 {
U8 => {
data.parse::<u8>().map_err(|_| data_type_error)?;
},
I8 => {
data.parse::<i8>().map_err(|_| data_type_error)?;
},
U16 => {
data.parse::<u16>().map_err(|_| data_type_error)?;
},
I16 => {
data.parse::<i16>().map_err(|_| data_type_error)?;
},
U32 => {
data.parse::<u32>().map_err(|_| data_type_error)?;
},
I32 => {
data.parse::<i32>().map_err(|_| data_type_error)?;
},
U64 => {
data.parse::<u64>().map_err(|_| data_type_error)?;
},
I64 => {
data.parse::<i64>().map_err(|_| data_type_error)?;
},
F32 => {
data.parse::<f32>().map_err(|_| data_type_error)?;
},
F64 => {
data.parse::<f64>().map_err(|_| data_type_error)?;
},
Bool => {
data.parse::<bool>().map_err(|_| data_type_error)?;
},
_ => {
},
};
return Ok(());
}
Ok(())
}
}
impl<'s> From<&'s str> for Sigma<'s> {
fn from(template: &'s str) -> Sigma<'s> {
Sigma::new(template)
.parse()
.map_err(|e| eprintln!("Parse Error:\n{}", e))
.unwrap()
}
}
#[macro_export]
macro_rules! sigma {
($template:expr, $($k:expr), *) => {
{
let mut map = std::collections::HashMap::new();
let s = $crate::Sigma::new($template);
$(
map.insert(stringify!($k), $k);
)*
let s = s.bind_map(map);
let s = s.parse().map_err(|e| eprintln!("Parse Error:\n{}", e)).unwrap();
s.compile()
}
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn missing_data_type() {
let input = "{{ username: }}";
let _ = Sigma::new(input)
.bind("username", "test")
.parse()
.unwrap()
.compile();
}
#[test]
#[should_panic]
fn unknown_data_type() {
let input = "{{ username: unknown }}";
let output = Sigma::new(input)
.bind("username", "test")
.parse()
.unwrap()
.compile();
println!("{:?}", output);
}
#[test]
fn test_sigma_macro() {
let username = "someone";
let s = sigma!("{{ username }}", username);
assert_eq!("someone", s.unwrap());
}
}