#[cfg(not(test))]
use alloc::boxed::Box;
#[cfg(not(test))]
use alloc::collections::BTreeMap;
#[cfg(not(test))]
use alloc::string::String;
#[cfg(not(test))]
use alloc::vec::Vec;
#[cfg(test)]
use std::collections::BTreeMap;
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
Identity,
Field(String),
Index(i64),
Slice {
start: Option<i64>,
end: Option<i64>,
},
Iterate,
Optional(Box<Expr>),
Pipe(Vec<Expr>),
Comma(Vec<Expr>),
Array(Box<Expr>),
Object(Vec<ObjectEntry>),
Literal(Literal),
RecursiveDescent,
Paren(Box<Expr>),
Arithmetic {
op: ArithOp,
left: Box<Expr>,
right: Box<Expr>,
},
Compare {
op: CompareOp,
left: Box<Expr>,
right: Box<Expr>,
},
And(Box<Expr>, Box<Expr>),
Or(Box<Expr>, Box<Expr>),
Not,
Alternative(Box<Expr>, Box<Expr>),
If {
cond: Box<Expr>,
then_branch: Box<Expr>,
else_branch: Box<Expr>,
},
Try {
expr: Box<Expr>,
catch: Option<Box<Expr>>,
},
Error(Option<Box<Expr>>),
Builtin(Builtin),
StringInterpolation(Vec<StringPart>),
Format(FormatType),
As {
expr: Box<Expr>,
var: String,
body: Box<Expr>,
},
Var(String),
Loc {
line: usize,
},
Env,
Reduce {
input: Box<Expr>,
var: String,
init: Box<Expr>,
update: Box<Expr>,
},
Foreach {
input: Box<Expr>,
var: String,
init: Box<Expr>,
update: Box<Expr>,
extract: Option<Box<Expr>>,
},
Limit {
n: Box<Expr>,
expr: Box<Expr>,
},
FirstExpr(Box<Expr>),
LastExpr(Box<Expr>),
NthExpr { n: Box<Expr>, expr: Box<Expr> },
Until { cond: Box<Expr>, update: Box<Expr> },
While { cond: Box<Expr>, update: Box<Expr> },
Repeat(Box<Expr>),
Range {
from: Box<Expr>,
to: Option<Box<Expr>>,
step: Option<Box<Expr>>,
},
Label {
name: String,
body: Box<Expr>,
},
Break(String),
AsPattern {
expr: Box<Expr>,
pattern: Pattern,
body: Box<Expr>,
},
FuncDef {
name: String,
params: Vec<String>,
body: Box<Expr>,
then: Box<Expr>,
},
FuncCall {
name: String,
args: Vec<Expr>,
},
NamespacedCall {
namespace: String,
name: String,
args: Vec<Expr>,
},
Assign {
path: Box<Expr>,
value: Box<Expr>,
},
Update {
path: Box<Expr>,
filter: Box<Expr>,
},
CompoundAssign {
op: AssignOp,
path: Box<Expr>,
value: Box<Expr>,
},
AlternativeAssign {
path: Box<Expr>,
value: Box<Expr>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct Program {
pub module: Option<ModuleMeta>,
pub imports: Vec<Import>,
pub includes: Vec<Include>,
pub expr: Expr,
}
impl Default for Program {
fn default() -> Self {
Program {
module: None,
imports: Vec::new(),
includes: Vec::new(),
expr: Expr::Identity,
}
}
}
impl Program {
pub fn from_expr(expr: Expr) -> Self {
Program {
module: None,
imports: Vec::new(),
includes: Vec::new(),
expr,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ModuleMeta {
pub metadata: BTreeMap<String, MetaValue>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum MetaValue {
String(String),
Number(f64),
Bool(bool),
Array(Vec<MetaValue>),
Object(BTreeMap<String, MetaValue>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct Import {
pub path: String,
pub alias: String,
pub metadata: Option<BTreeMap<String, MetaValue>>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Include {
pub path: String,
pub metadata: Option<BTreeMap<String, MetaValue>>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Pattern {
Var(String),
Object(Vec<PatternEntry>),
Array(Vec<Pattern>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct PatternEntry {
pub key: String,
pub pattern: Pattern,
}
#[derive(Debug, Clone, PartialEq)]
pub enum StringPart {
Literal(String),
Expr(Box<Expr>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FormatType {
Text,
Json,
Uri,
Csv,
Tsv,
Dsv(String),
Base64,
Base64d,
Html,
Sh,
Urid,
Yaml,
Props,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Builtin {
Type,
IsNull,
IsBoolean,
IsNumber,
IsString,
IsArray,
IsObject,
Values,
Nulls,
Booleans,
Numbers,
Strings,
Arrays,
Objects,
Iterables,
Scalars,
Length,
Utf8ByteLength,
Keys,
KeysUnsorted,
Has(Box<Expr>),
In(Box<Expr>),
Select(Box<Expr>),
Empty,
Map(Box<Expr>),
MapValues(Box<Expr>),
Add,
Any,
All,
Min,
Max,
MinBy(Box<Expr>),
MaxBy(Box<Expr>),
AsciiDowncase,
AsciiUpcase,
Ltrimstr(Box<Expr>),
Rtrimstr(Box<Expr>),
Startswith(Box<Expr>),
Endswith(Box<Expr>),
Split(Box<Expr>),
Join(Box<Expr>),
Contains(Box<Expr>),
Inside(Box<Expr>),
First,
Last,
Nth(Box<Expr>),
Reverse,
Flatten,
FlattenDepth(Box<Expr>),
GroupBy(Box<Expr>),
Unique,
UniqueBy(Box<Expr>),
Sort,
SortBy(Box<Expr>),
ToEntries,
FromEntries,
WithEntries(Box<Expr>),
ToString,
ToNumber,
ToJson,
FromJson,
Explode,
Implode,
Test(Box<Expr>),
Indices(Box<Expr>),
Index(Box<Expr>),
Rindex(Box<Expr>),
ToJsonStream,
FromJsonStream,
GetPath(Box<Expr>),
Recurse,
RecurseF(Box<Expr>),
RecurseCond(Box<Expr>, Box<Expr>),
Walk(Box<Expr>),
IsValid(Box<Expr>),
Path(Box<Expr>),
PathNoArg,
Parent,
ParentN(Box<Expr>),
Paths,
PathsFilter(Box<Expr>),
LeafPaths,
SetPath(Box<Expr>, Box<Expr>),
DelPaths(Box<Expr>),
Floor,
Ceil,
Round,
Sqrt,
Fabs,
Log,
Log10,
Log2,
Exp,
Exp10,
Exp2,
Pow(Box<Expr>, Box<Expr>),
Sin,
Cos,
Tan,
Asin,
Acos,
Atan,
Atan2(Box<Expr>, Box<Expr>),
Sinh,
Cosh,
Tanh,
Asinh,
Acosh,
Atanh,
Infinite,
Nan,
IsInfinite,
IsNan,
IsNormal,
IsFinite,
Debug,
DebugMsg(Box<Expr>),
Env,
EnvVar(Box<Expr>),
EnvObject(String),
StrEnv(String),
NullLit,
Trim,
Ltrim,
Rtrim,
Transpose,
BSearch(Box<Expr>),
ModuleMeta(Box<Expr>),
Pick(Box<Expr>),
Omit(Box<Expr>),
Tag,
Anchor,
Style,
Kind,
Key,
Line,
Column,
DocumentIndex,
Shuffle,
Pivot,
SplitDoc,
Del(Box<Expr>),
Now,
Abs,
Builtins,
Normals,
Finites,
Limit(Box<Expr>, Box<Expr>),
FirstStream(Box<Expr>),
LastStream(Box<Expr>),
NthStream(Box<Expr>, Box<Expr>),
Range(Box<Expr>),
RangeFromTo(Box<Expr>, Box<Expr>),
RangeFromToBy(Box<Expr>, Box<Expr>, Box<Expr>),
IsEmpty(Box<Expr>),
RecurseDown,
Gmtime,
Localtime,
Mktime,
Strftime(Box<Expr>),
Strptime(Box<Expr>),
Todate,
Fromdate,
Todateiso8601,
Fromdateiso8601,
TestFlags(Box<Expr>, Box<Expr>),
Match(Box<Expr>),
MatchFlags(Box<Expr>, Box<Expr>),
Capture(Box<Expr>),
CaptureFlags(Box<Expr>, Box<Expr>),
Sub(Box<Expr>, Box<Expr>),
SubFlags(Box<Expr>, Box<Expr>, Box<Expr>),
Gsub(Box<Expr>, Box<Expr>),
GsubFlags(Box<Expr>, Box<Expr>, Box<Expr>),
Scan(Box<Expr>),
ScanFlags(Box<Expr>, Box<Expr>),
SplitRegex(Box<Expr>, Box<Expr>),
Splits(Box<Expr>),
SplitsFlags(Box<Expr>, Box<Expr>),
Combinations,
CombinationsN(Box<Expr>),
Trunc,
ToBoolean,
Skip(Box<Expr>, Box<Expr>),
FromUnix,
ToUnix,
Tz(Box<Expr>),
Load(Box<Expr>),
AtOffset(Box<Expr>),
AtPosition(Box<Expr>, Box<Expr>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArithOp {
Add,
Sub,
Mul,
Div,
Mod,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompareOp {
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AssignOp {
Add,
Sub,
Mul,
Div,
Mod,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ObjectEntry {
pub key: ObjectKey,
pub value: Expr,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ObjectKey {
Literal(String),
Expr(Box<Expr>),
}
#[derive(Debug, Clone, PartialEq)]
pub enum Literal {
Null,
Bool(bool),
Int(i64),
Float(f64),
String(String),
}
impl Expr {
pub fn identity() -> Self {
Expr::Identity
}
pub fn field(name: impl Into<String>) -> Self {
Expr::Field(name.into())
}
pub fn index(i: i64) -> Self {
Expr::Index(i)
}
pub fn iterate() -> Self {
Expr::Iterate
}
pub fn slice(start: Option<i64>, end: Option<i64>) -> Self {
Expr::Slice { start, end }
}
pub fn optional(self) -> Self {
Expr::Optional(Box::new(self))
}
pub fn pipe(exprs: Vec<Expr>) -> Self {
if exprs.len() == 1 {
exprs.into_iter().next().unwrap()
} else {
Expr::Pipe(exprs)
}
}
pub fn comma(exprs: Vec<Expr>) -> Self {
if exprs.len() == 1 {
exprs.into_iter().next().unwrap()
} else {
Expr::Comma(exprs)
}
}
pub fn array(inner: Expr) -> Self {
Expr::Array(Box::new(inner))
}
pub fn object(entries: Vec<ObjectEntry>) -> Self {
Expr::Object(entries)
}
pub fn literal(lit: Literal) -> Self {
Expr::Literal(lit)
}
pub fn recursive_descent() -> Self {
Expr::RecursiveDescent
}
pub fn paren(inner: Expr) -> Self {
Expr::Paren(Box::new(inner))
}
pub fn arithmetic(op: ArithOp, left: Expr, right: Expr) -> Self {
Expr::Arithmetic {
op,
left: Box::new(left),
right: Box::new(right),
}
}
pub fn compare(op: CompareOp, left: Expr, right: Expr) -> Self {
Expr::Compare {
op,
left: Box::new(left),
right: Box::new(right),
}
}
pub fn and(left: Expr, right: Expr) -> Self {
Expr::And(Box::new(left), Box::new(right))
}
pub fn or(left: Expr, right: Expr) -> Self {
Expr::Or(Box::new(left), Box::new(right))
}
pub fn not() -> Self {
Expr::Not
}
pub fn alternative(left: Expr, right: Expr) -> Self {
Expr::Alternative(Box::new(left), Box::new(right))
}
pub fn if_then_else(cond: Expr, then_branch: Expr, else_branch: Expr) -> Self {
Expr::If {
cond: Box::new(cond),
then_branch: Box::new(then_branch),
else_branch: Box::new(else_branch),
}
}
pub fn try_expr(expr: Expr, catch: Option<Expr>) -> Self {
Expr::Try {
expr: Box::new(expr),
catch: catch.map(Box::new),
}
}
pub fn error(msg: Option<Expr>) -> Self {
Expr::Error(msg.map(Box::new))
}
pub fn builtin(b: Builtin) -> Self {
Expr::Builtin(b)
}
pub fn is_identity(&self) -> bool {
matches!(self, Expr::Identity)
}
}
impl ObjectEntry {
pub fn new(key: impl Into<String>, value: Expr) -> Self {
ObjectEntry {
key: ObjectKey::Literal(key.into()),
value,
}
}
pub fn dynamic(key_expr: Expr, value: Expr) -> Self {
ObjectEntry {
key: ObjectKey::Expr(Box::new(key_expr)),
value,
}
}
}
impl Literal {
pub fn null() -> Self {
Literal::Null
}
pub fn bool(b: bool) -> Self {
Literal::Bool(b)
}
pub fn int(n: i64) -> Self {
Literal::Int(n)
}
pub fn float(f: f64) -> Self {
Literal::Float(f)
}
pub fn string(s: impl Into<String>) -> Self {
Literal::String(s.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_expr_constructors() {
assert_eq!(Expr::identity(), Expr::Identity);
assert_eq!(Expr::field("foo"), Expr::Field("foo".into()));
assert_eq!(Expr::index(0), Expr::Index(0));
assert_eq!(Expr::iterate(), Expr::Iterate);
assert_eq!(
Expr::slice(Some(1), Some(3)),
Expr::Slice {
start: Some(1),
end: Some(3)
}
);
}
#[test]
fn test_pipe_simplification() {
let single = Expr::pipe(vec![Expr::field("foo")]);
assert_eq!(single, Expr::Field("foo".into()));
let multi = Expr::pipe(vec![Expr::field("foo"), Expr::field("bar")]);
assert!(matches!(multi, Expr::Pipe(_)));
}
#[test]
fn test_comma_simplification() {
let single = Expr::comma(vec![Expr::field("foo")]);
assert_eq!(single, Expr::Field("foo".into()));
let multi = Expr::comma(vec![Expr::field("foo"), Expr::field("bar")]);
assert!(matches!(multi, Expr::Comma(_)));
}
#[test]
fn test_array_construction() {
let arr = Expr::array(Expr::iterate());
assert!(matches!(arr, Expr::Array(_)));
}
#[test]
fn test_object_construction() {
let obj = Expr::object(vec![
ObjectEntry::new("name", Expr::field("name")),
ObjectEntry::dynamic(Expr::field("key"), Expr::field("value")),
]);
assert!(matches!(obj, Expr::Object(_)));
}
#[test]
fn test_literals() {
assert_eq!(Expr::literal(Literal::null()), Expr::Literal(Literal::Null));
assert_eq!(
Expr::literal(Literal::bool(true)),
Expr::Literal(Literal::Bool(true))
);
assert_eq!(
Expr::literal(Literal::int(42)),
Expr::Literal(Literal::Int(42))
);
assert_eq!(
Expr::literal(Literal::string("hello")),
Expr::Literal(Literal::String("hello".into()))
);
}
}