use crate::data::context::EvalError;
use crate::data::value::Val;
use indexmap::IndexMap;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum BuiltinMethod {
Len = 0,
Keys,
Values,
Entries,
ToPairs,
FromPairs,
Invert,
Reverse,
Type,
ToString,
ToJson,
FromJson,
Rows,
Sum,
Avg,
Min,
Max,
Count,
Any,
All,
FindIndex,
IndicesWhere,
MaxBy,
MinBy,
GroupBy,
CountBy,
IndexBy,
GroupShape,
Explode,
Implode,
Filter,
Map,
FlatMap,
Find,
FindAll,
Sort,
Unique,
UniqueBy,
Collect,
DeepFind,
DeepShape,
DeepLike,
Walk,
WalkPre,
Rec,
TracePath,
Flatten,
Compact,
Join,
First,
Last,
Nth,
Take,
Skip,
Append,
Prepend,
Remove,
Diff,
Intersect,
Union,
Enumerate,
Pairwise,
Window,
Chunk,
TakeWhile,
DropWhile,
FindFirst,
FindOne,
ApproxCountDistinct,
Accumulate,
Fold,
Partition,
Zip,
ZipLongest,
Fanout,
ZipShape,
Pick,
Omit,
Merge,
DeepMerge,
Defaults,
Rename,
TransformKeys,
TransformValues,
FilterKeys,
FilterValues,
Pivot,
GetPath,
SetPath,
DelPath,
DelPaths,
HasPath,
FlattenKeys,
UnflattenKeys,
ToCsv,
ToTsv,
Or,
Has,
HasAll,
HasKey,
Missing,
Includes,
Index,
IndicesOf,
Set,
Update,
Ceil,
Floor,
Round,
Abs,
RollingSum,
RollingAvg,
RollingMin,
RollingMax,
Lag,
Lead,
DiffWindow,
PctChange,
CumMax,
CumMin,
Zscore,
Upper,
Lower,
Capitalize,
TitleCase,
Trim,
TrimLeft,
TrimRight,
SnakeCase,
KebabCase,
CamelCase,
PascalCase,
ReverseStr,
Lines,
Words,
Chars,
CharsOf,
Bytes,
ByteLen,
IsBlank,
IsNumeric,
IsAlpha,
IsAscii,
ToNumber,
ToBool,
ParseInt,
ParseFloat,
ParseBool,
ToBase64,
FromBase64,
UrlEncode,
UrlDecode,
HtmlEscape,
HtmlUnescape,
Repeat,
PadLeft,
PadRight,
Center,
StartsWith,
EndsWith,
IndexOf,
LastIndexOf,
Replace,
ReplaceAll,
StripPrefix,
StripSuffix,
Slice,
Split,
Indent,
Dedent,
Matches,
Scan,
ReMatch,
ReMatchFirst,
ReMatchAll,
ReCaptures,
ReCapturesAll,
ReSplit,
ReReplace,
ReReplaceAll,
ContainsAny,
ContainsAll,
Schema,
EquiJoin,
Unknown,
}
#[macro_export]
macro_rules! for_each_builtin {
($macro:ident) => {
$macro! (
Abs, Accumulate, All, Any, Append, ApproxCountDistinct, Avg, ByteLen, Bytes,
CamelCase, Capitalize, Ceil, Center, Chars, CharsOf, Chunk, Collect, Compact,
ContainsAll, ContainsAny, Count, CountBy, CumMax, CumMin, Dedent, DeepFind,
DeepLike, DeepMerge, DeepShape, Defaults, DelPath, DelPaths, Diff, DiffWindow,
DropWhile, EndsWith, Entries, Enumerate, EquiJoin, Explode, Fanout, Filter,
FilterKeys, FilterValues, Find, FindAll, FindFirst, FindIndex, FindOne, First,
FlatMap, Flatten, FlattenKeys, Floor, Fold, FromBase64, FromJson, FromPairs, GetPath,
GroupBy, GroupShape, Has, HasAll, HasKey, HasPath, HtmlEscape, HtmlUnescape, Implode,
Includes, Indent, Index, IndexBy, IndexOf, IndicesOf, IndicesWhere, Intersect, Invert,
IsAlpha, IsAscii, IsBlank, IsNumeric, Join, KebabCase, Keys, Lag, Last,
LastIndexOf, Lead, Len, Lines, Lower, Map, Matches, Max, MaxBy, Merge, Min,
MinBy, Missing, Nth, Omit, Or, PadLeft, PadRight, Pairwise, ParseBool,
ParseFloat, ParseInt, Partition, PascalCase, PctChange, Pick, Pivot, Prepend,
Rec, ReCaptures, ReCapturesAll, ReMatch, ReMatchAll, ReMatchFirst, Remove,
Rename, Repeat, Replace, ReplaceAll, ReReplace, ReReplaceAll, ReSplit, Reverse,
ReverseStr, RollingAvg, RollingMax, RollingMin, RollingSum, Round, Rows, Scan, Schema,
Set, SetPath, Skip, Slice, SnakeCase, Sort, Split, StartsWith, StripPrefix,
StripSuffix, Sum, Take, TakeWhile, TitleCase, ToBase64, ToBool, ToCsv, ToJson,
ToNumber, ToPairs, ToString, ToTsv, TracePath, TransformKeys, TransformValues,
Trim, TrimLeft, TrimRight, Type, UnflattenKeys, Union, Unique, UniqueBy, Unknown,
Update, Upper, UrlDecode, UrlEncode, Values, Walk, WalkPre, Window, Words, Zip,
ZipLongest, ZipShape, Zscore
)
};
}
impl BuiltinMethod {
pub fn from_name(name: &str) -> Self {
crate::builtins::registry::by_name(name)
.and_then(|id| id.method())
.unwrap_or(Self::Unknown)
}
pub(crate) fn is_lambda_method(self) -> bool {
matches!(
self,
Self::Filter
| Self::Map
| Self::FlatMap
| Self::Sort
| Self::Any
| Self::All
| Self::Count
| Self::GroupBy
| Self::CountBy
| Self::IndexBy
| Self::TakeWhile
| Self::DropWhile
| Self::Accumulate
| Self::Fold
| Self::Partition
| Self::TransformKeys
| Self::TransformValues
| Self::FilterKeys
| Self::FilterValues
| Self::Pivot
| Self::Update
)
}
}
#[derive(Debug, Clone)]
pub enum BuiltinArgs {
None,
Str(Arc<str>),
Path(Arc<[PathSeg]>),
StrPair { first: Arc<str>, second: Arc<str> },
StrVec(Vec<Arc<str>>),
I64(i64),
I64Opt { first: i64, second: Option<i64> },
Usize(usize),
Val(Val),
ValVec(Vec<Val>),
Pad { width: usize, fill: char },
}
#[derive(Debug, Clone)]
pub struct BuiltinCall {
pub method: BuiltinMethod,
pub args: BuiltinArgs,
}
struct StaticArgDecoder<'a, E, I> {
name: &'a str,
eval_arg: E,
ident_arg: I,
}
impl<E, I> StaticArgDecoder<'_, E, I>
where
E: FnMut(usize) -> Result<Option<Val>, EvalError>,
I: FnMut(usize) -> Option<Arc<str>>,
{
fn val(&mut self, idx: usize) -> Result<Val, EvalError> {
(self.eval_arg)(idx)?.ok_or_else(|| EvalError(format!("{}: missing argument", self.name)))
}
fn str(&mut self, idx: usize) -> Result<Arc<str>, EvalError> {
if let Some(value) = (self.ident_arg)(idx) {
return Ok(value);
}
match self.val(idx)? {
Val::Str(s) => Ok(s),
other => Ok(Arc::from(crate::util::val_to_string(&other).as_str())),
}
}
fn str_lit(&mut self, idx: usize) -> Option<Arc<str>> {
match (self.eval_arg)(idx).ok().flatten()? {
Val::Str(s) => Some(s),
Val::StrSlice(r) => Some(r.to_arc()),
_ => None,
}
}
fn i64(&mut self, idx: usize) -> Result<i64, EvalError> {
match self.val(idx)? {
Val::Int(n) => Ok(n),
Val::Float(f) => Ok(f as i64),
_ => Err(EvalError(format!(
"{}: expected number argument",
self.name
))),
}
}
fn usize(&mut self, idx: usize) -> Result<usize, EvalError> {
Ok(self.i64(idx)?.max(0) as usize)
}
fn vec(&mut self, idx: usize) -> Result<Vec<Val>, EvalError> {
self.val(idx).and_then(|value| {
value
.into_vec()
.ok_or_else(|| EvalError(format!("{}: expected array arg", self.name)))
})
}
fn str_vec(&mut self, idx: usize) -> Result<Vec<Arc<str>>, EvalError> {
Ok(self
.vec(idx)?
.iter()
.map(|v| match v {
Val::Str(s) => s.clone(),
other => Arc::from(crate::util::val_to_string(other).as_str()),
})
.collect())
}
fn char(&mut self, idx: usize, arg_len: usize) -> Result<char, EvalError> {
if idx >= arg_len {
return Ok(' ');
}
match self.str(idx)? {
s if s.chars().count() == 1 => Ok(s.chars().next().unwrap()),
_ => Err(EvalError(format!(
"{}: filler must be a single-char string",
self.name
))),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct BuiltinSpec {
pub pure: bool,
pub category: BuiltinCategory,
pub cardinality: BuiltinCardinality,
pub can_indexed: bool,
pub view_native: bool,
pub view_scalar: bool,
pub view_stage: Option<BuiltinViewStage>,
pub sink: Option<BuiltinSinkSpec>,
pub keyed_reducer: Option<BuiltinKeyedReducer>,
pub numeric_reducer: Option<BuiltinNumericReducer>,
pub stage_merge: Option<BuiltinStageMerge>,
pub cancellation: Option<BuiltinCancellation>,
pub columnar_stage: Option<BuiltinColumnarStage>,
pub structural: Option<BuiltinStructural>,
pub cost: f64,
pub demand_law: BuiltinDemandLaw,
pub materialization: BuiltinPipelineMaterialization,
pub pipeline_shape: Option<BuiltinPipelineShape>,
pub order_effect: Option<BuiltinPipelineOrderEffect>,
pub lowering: Option<BuiltinPipelineLowering>,
pub is_element: bool,
pub never_unwrap: bool,
pub stream_source: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinDemandLaw {
Identity,
FilterLike,
TakeWhile,
DropWhile,
UniqueLike,
MapLike,
Slice,
FlatMapLike,
Take,
Skip,
Chunk,
Window,
First,
Last,
Nth,
Count,
NumericReducer,
KeyOnlyReducer,
RowKeyedReducer,
OrderBarrier,
Reverse,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinStructural {
DeepFind,
DeepShape,
DeepLike,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinViewStage {
Filter,
Compact,
RemoveValue,
Map,
FlatMap,
TakeWhile,
DropWhile,
Distinct,
KeyedReduce,
Take,
Skip,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinViewInputMode {
ReadsView,
SkipsViewRead,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinViewOutputMode {
PreservesInputView,
BorrowedSubview,
BorrowedSubviews,
EmitsOwnedValue,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BuiltinSinkSpec {
pub accumulator: BuiltinSinkAccumulator,
pub demand: BuiltinSinkDemand,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinSinkAccumulator {
Count,
Numeric,
ApproxDistinct,
SelectOne(BuiltinSelectionPosition),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinKeyedReducer {
Count,
Index,
Group,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinSelectionPosition {
First,
Last,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinSinkDemand {
All {
value: BuiltinSinkValueNeed,
order: bool,
},
First {
value: BuiltinSinkValueNeed,
},
Last {
value: BuiltinSinkValueNeed,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinSinkValueNeed {
None,
Whole,
Numeric,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinNumericReducer {
Sum,
Avg,
Min,
Max,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinStageMerge {
UsizeMin,
UsizeSaturatingAdd,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinCancellation {
SelfInverse(BuiltinCancelGroup),
Inverse {
group: BuiltinCancelGroup,
side: BuiltinCancelSide,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinCancelGroup {
Reverse,
Base64,
Url,
Html,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinCancelSide {
Forward,
Backward,
}
impl BuiltinCancellation {
#[inline]
pub fn cancels_with(self, other: Self) -> bool {
match (self, other) {
(Self::SelfInverse(a), Self::SelfInverse(b)) => a == b,
(Self::Inverse { group: a, side: sa }, Self::Inverse { group: b, side: sb }) => {
a == b && sa != sb
}
_ => false,
}
}
}
impl BuiltinStageMerge {
#[inline]
pub fn combine_usize(self, a: usize, b: usize) -> usize {
match self {
Self::UsizeMin => a.min(b),
Self::UsizeSaturatingAdd => a.saturating_add(b),
}
}
}
impl BuiltinViewStage {
#[inline]
pub fn input_mode(self) -> BuiltinViewInputMode {
match self {
Self::Filter
| Self::Compact
| Self::RemoveValue
| Self::Map
| Self::FlatMap
| Self::TakeWhile
| Self::DropWhile
| Self::Distinct
| Self::KeyedReduce => BuiltinViewInputMode::ReadsView,
Self::Take | Self::Skip => BuiltinViewInputMode::SkipsViewRead,
}
}
#[inline]
pub fn output_mode(self) -> BuiltinViewOutputMode {
match self {
Self::Map => BuiltinViewOutputMode::BorrowedSubview,
Self::FlatMap => BuiltinViewOutputMode::BorrowedSubviews,
Self::KeyedReduce => BuiltinViewOutputMode::EmitsOwnedValue,
Self::Filter
| Self::Compact
| Self::RemoveValue
| Self::TakeWhile
| Self::DropWhile
| Self::Distinct
| Self::Take
| Self::Skip => BuiltinViewOutputMode::PreservesInputView,
}
}
#[inline]
pub fn cardinality(self) -> BuiltinCardinality {
match self {
Self::Filter | Self::Compact | Self::RemoveValue => BuiltinCardinality::Filtering,
Self::Map => BuiltinCardinality::OneToOne,
Self::FlatMap => BuiltinCardinality::Expanding,
Self::TakeWhile | Self::DropWhile => BuiltinCardinality::Filtering,
Self::Distinct => BuiltinCardinality::Filtering,
Self::KeyedReduce => BuiltinCardinality::Reducing,
Self::Take | Self::Skip => BuiltinCardinality::Bounded,
}
}
#[inline]
pub fn can_indexed(self) -> bool {
matches!(self, Self::Map | Self::KeyedReduce)
}
#[inline]
pub fn cost(self) -> f64 {
match self {
Self::Filter
| Self::Compact
| Self::RemoveValue
| Self::Map
| Self::FlatMap
| Self::TakeWhile
| Self::DropWhile
| Self::Distinct
| Self::KeyedReduce => 10.0,
Self::Take | Self::Skip => 0.5,
}
}
#[inline]
pub fn selectivity(self) -> f64 {
match self {
Self::Filter | Self::Compact | Self::RemoveValue | Self::TakeWhile | Self::DropWhile => 0.5,
Self::Distinct => 1.0,
Self::Map | Self::FlatMap | Self::KeyedReduce => 1.0,
Self::Take | Self::Skip => 0.5,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BuiltinPipelineShape {
pub cardinality: BuiltinCardinality,
pub can_indexed: bool,
pub cost: f64,
pub selectivity: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinPipelineMaterialization {
Streaming,
ComposedBarrier,
LegacyMaterialized,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinPipelineOrderEffect {
Preserves,
PredicatePrefix,
Blocks,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinColumnarStage {
Filter,
Map,
FlatMap,
GroupBy,
}
impl BuiltinPipelineShape {
#[inline]
pub fn new(
cardinality: BuiltinCardinality,
can_indexed: bool,
cost: f64,
selectivity: f64,
) -> Self {
Self {
cardinality,
can_indexed,
cost,
selectivity,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinPipelineLowering {
ExprArg,
TerminalExprArg {
terminal: BuiltinMethod,
},
Nullary,
UsizeArg {
min: usize,
},
StringArg,
StringPairArg,
IntRangeArg,
Sort,
TerminalSink,
TerminalUsizeSink {
min: usize,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinCategory {
Scalar,
StreamingOneToOne,
StreamingFilter,
StreamingExpand,
Reducer,
Positional,
Barrier,
Object,
Path,
Deep,
Serialization,
Relational,
Mutation,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinCardinality {
OneToOne,
Filtering,
Expanding,
Bounded,
Reducing,
Barrier,
}
impl BuiltinSpec {
fn new(category: BuiltinCategory, cardinality: BuiltinCardinality) -> Self {
Self {
pure: true,
category,
cardinality,
can_indexed: false,
view_native: false,
view_scalar: false,
view_stage: None,
sink: None,
keyed_reducer: None,
numeric_reducer: None,
stage_merge: None,
cancellation: None,
columnar_stage: None,
structural: None,
cost: 1.0,
demand_law: BuiltinDemandLaw::Identity,
materialization: BuiltinPipelineMaterialization::Streaming,
pipeline_shape: None,
order_effect: None,
lowering: None,
is_element: false,
never_unwrap: false,
stream_source: false,
}
}
pub fn dispatches_scalar_direct(&self) -> bool {
matches!(
self.category,
BuiltinCategory::Scalar | BuiltinCategory::Object
) && matches!(self.cardinality, BuiltinCardinality::OneToOne)
&& !self.never_unwrap
}
fn indexed(mut self) -> Self {
self.can_indexed = true;
self
}
fn view_native(mut self) -> Self {
self.view_native = true;
self
}
fn view_stage(mut self, stage: BuiltinViewStage) -> Self {
self.view_stage = Some(stage);
self
}
fn view_scalar(mut self) -> Self {
self.view_scalar = true;
self.view_native = true;
self
}
fn columnar_stage(mut self, stage: BuiltinColumnarStage) -> Self {
self.columnar_stage = Some(stage);
self
}
fn count_sink(mut self) -> Self {
self.sink = Some(BuiltinSinkSpec {
accumulator: BuiltinSinkAccumulator::Count,
demand: BuiltinSinkDemand::All {
value: BuiltinSinkValueNeed::None,
order: false,
},
});
self
}
fn select_one_sink(mut self, position: BuiltinSelectionPosition) -> Self {
self.sink = Some(BuiltinSinkSpec {
accumulator: BuiltinSinkAccumulator::SelectOne(position),
demand: match position {
BuiltinSelectionPosition::First => BuiltinSinkDemand::First {
value: BuiltinSinkValueNeed::Whole,
},
BuiltinSelectionPosition::Last => BuiltinSinkDemand::Last {
value: BuiltinSinkValueNeed::Whole,
},
},
});
self
}
fn numeric_sink(mut self, reducer: BuiltinNumericReducer) -> Self {
self.sink = Some(BuiltinSinkSpec {
accumulator: BuiltinSinkAccumulator::Numeric,
demand: BuiltinSinkDemand::All {
value: BuiltinSinkValueNeed::Numeric,
order: false,
},
});
self.numeric_reducer = Some(reducer);
self
}
fn approx_distinct_sink(mut self) -> Self {
self.sink = Some(BuiltinSinkSpec {
accumulator: BuiltinSinkAccumulator::ApproxDistinct,
demand: BuiltinSinkDemand::All {
value: BuiltinSinkValueNeed::Whole,
order: false,
},
});
self
}
fn keyed_reducer(mut self, reducer: BuiltinKeyedReducer) -> Self {
self.keyed_reducer = Some(reducer);
self
}
fn stage_merge(mut self, merge: BuiltinStageMerge) -> Self {
self.stage_merge = Some(merge);
self
}
fn cancellation(mut self, cancellation: BuiltinCancellation) -> Self {
self.cancellation = Some(cancellation);
self
}
fn structural(mut self, structural: BuiltinStructural) -> Self {
self.structural = Some(structural);
self
}
fn cost(mut self, cost: f64) -> Self {
self.cost = cost;
self
}
fn demand_law(mut self, law: BuiltinDemandLaw) -> Self {
self.demand_law = law;
self
}
fn materialization(mut self, m: BuiltinPipelineMaterialization) -> Self {
self.materialization = m;
self
}
fn pipeline_shape(mut self, s: BuiltinPipelineShape) -> Self {
self.pipeline_shape = Some(s);
self
}
fn order_effect(mut self, o: BuiltinPipelineOrderEffect) -> Self {
self.order_effect = Some(o);
self
}
fn lowering(mut self, l: BuiltinPipelineLowering) -> Self {
self.lowering = Some(l);
self
}
fn element(mut self) -> Self {
self.is_element = true;
self
}
#[allow(dead_code)]
fn never_unwrap(mut self) -> Self {
self.never_unwrap = true;
self
}
fn stream_source(mut self) -> Self {
self.stream_source = true;
self
}
}
impl BuiltinMethod {
#[inline]
pub(crate) fn is_string_arg_view_scalar(self) -> bool {
matches!(
self,
Self::StartsWith | Self::EndsWith | Self::Matches | Self::IndexOf | Self::LastIndexOf
)
}
#[inline]
pub(crate) fn is_string_no_arg_view_scalar(self) -> bool {
matches!(
self,
Self::Upper
| Self::Lower
| Self::Trim
| Self::TrimLeft
| Self::TrimRight
| Self::ByteLen
| Self::IsBlank
| Self::IsNumeric
| Self::IsAlpha
| Self::IsAscii
| Self::ToNumber
| Self::ToBool
)
}
#[inline]
pub(crate) fn is_numeric_no_arg_view_scalar(self) -> bool {
matches!(self, Self::Ceil | Self::Floor | Self::Round | Self::Abs)
}
#[inline]
pub(crate) fn is_view_scalar_method(self) -> bool {
self == Self::Len
|| self.is_string_arg_view_scalar()
|| self.is_string_no_arg_view_scalar()
|| self.is_numeric_no_arg_view_scalar()
}
#[inline]
pub(crate) fn is_view_object_key_method(self) -> bool {
matches!(
self,
Self::Has | Self::HasKey | Self::Missing | Self::GetPath | Self::HasPath
)
}
#[inline]
pub(crate) fn is_view_projection_method(self) -> bool {
self.spec().view_scalar || self.is_view_object_key_method()
}
#[inline]
pub fn spec(self) -> BuiltinSpec {
macro_rules! spec_arm {
( $( $variant:ident ),* $(,)? ) => {
match self {
$( Self::$variant => <defs::$variant as builtin::Builtin>::spec(), )*
}
};
}
let spec = crate::for_each_builtin!(spec_arm);
macro_rules! cancel_arm {
( $( $variant:ident ),* $(,)? ) => {
match self {
$( Self::$variant => <defs::$variant as builtin::Builtin>::cancellation(), )*
}
};
}
match crate::for_each_builtin!(cancel_arm) {
Some(c) => spec.cancellation(c),
None => spec,
}
}
}
impl BuiltinCall {
#[inline]
pub fn new(method: BuiltinMethod, args: BuiltinArgs) -> Self {
Self { method, args }
}
#[inline]
pub fn spec(&self) -> BuiltinSpec {
let mut spec = self.method.spec();
let (cost, can_indexed) = match self.method {
BuiltinMethod::Keys | BuiltinMethod::Values | BuiltinMethod::Entries => (1.0, false),
BuiltinMethod::Repeat
| BuiltinMethod::Indent
| BuiltinMethod::PadLeft
| BuiltinMethod::PadRight
| BuiltinMethod::Center => (2.0, true),
BuiltinMethod::IndexOf
| BuiltinMethod::LastIndexOf
| BuiltinMethod::Scan
| BuiltinMethod::StartsWith
| BuiltinMethod::EndsWith
| BuiltinMethod::StripPrefix
| BuiltinMethod::StripSuffix
| BuiltinMethod::Matches
| BuiltinMethod::ReMatch
| BuiltinMethod::ReMatchFirst
| BuiltinMethod::ReMatchAll
| BuiltinMethod::ReCaptures
| BuiltinMethod::ReCapturesAll
| BuiltinMethod::ReSplit
| BuiltinMethod::ReReplace
| BuiltinMethod::ReReplaceAll
| BuiltinMethod::ContainsAny
| BuiltinMethod::ContainsAll => (2.0, true),
_ => (spec.cost, spec.can_indexed),
};
spec.cost = cost;
spec.can_indexed = can_indexed;
spec
}
#[inline]
pub fn is_idempotent(&self) -> bool {
matches!(
self.method,
BuiltinMethod::Upper
| BuiltinMethod::Lower
| BuiltinMethod::Trim
| BuiltinMethod::TrimLeft
| BuiltinMethod::TrimRight
| BuiltinMethod::Capitalize
| BuiltinMethod::TitleCase
| BuiltinMethod::SnakeCase
| BuiltinMethod::KebabCase
| BuiltinMethod::CamelCase
| BuiltinMethod::PascalCase
| BuiltinMethod::Dedent
)
}
pub fn apply(&self, recv: &Val) -> Option<Val> {
macro_rules! apply_or_recv {
($expr:expr) => {
return Some($expr.unwrap_or_else(|| recv.clone()))
};
}
macro_rules! trait_arm {
( $( $variant:ident ),* $(,)? ) => {
match self.method {
$( BuiltinMethod::$variant => {
if matches!(self.args, BuiltinArgs::None) {
if let Some(v) = <defs::$variant as builtin::Builtin>::apply_one(recv) {
return Some(v);
}
}
if let Some(v) = <defs::$variant as builtin::Builtin>::apply_args(recv, &self.args) {
return Some(v);
}
} )*
}
};
}
crate::for_each_builtin!(trait_arm);
match (self.method, &self.args) {
(BuiltinMethod::ByteLen, BuiltinArgs::None)
| (BuiltinMethod::IsBlank, BuiltinArgs::None)
| (BuiltinMethod::IsNumeric, BuiltinArgs::None)
| (BuiltinMethod::IsAlpha, BuiltinArgs::None)
| (BuiltinMethod::IsAscii, BuiltinArgs::None)
| (BuiltinMethod::ToNumber, BuiltinArgs::None)
| (BuiltinMethod::ToBool, BuiltinArgs::None) => {
apply_or_recv!(str_no_arg_scalar_val_apply(self.method, recv))
}
(BuiltinMethod::Sum, BuiltinArgs::None)
| (BuiltinMethod::Avg, BuiltinArgs::None)
| (BuiltinMethod::Min, BuiltinArgs::None)
| (BuiltinMethod::Max, BuiltinArgs::None) => {
return Some(numeric_aggregate_apply(recv, self.method));
}
(BuiltinMethod::Len, BuiltinArgs::None) | (BuiltinMethod::Count, BuiltinArgs::None) => {
apply_or_recv!(len_apply(recv))
}
(BuiltinMethod::Keys, BuiltinArgs::None) => return Some(keys_apply(recv)),
(BuiltinMethod::Values, BuiltinArgs::None) => return Some(values_apply(recv)),
(BuiltinMethod::Entries, BuiltinArgs::None) => return Some(entries_apply(recv)),
(BuiltinMethod::Collect, BuiltinArgs::None) => return Some(collect_apply(recv)),
(BuiltinMethod::FromJson, BuiltinArgs::None) => return from_json_apply(recv),
(BuiltinMethod::Ceil, BuiltinArgs::None)
| (BuiltinMethod::Floor, BuiltinArgs::None)
| (BuiltinMethod::Round, BuiltinArgs::None)
| (BuiltinMethod::Abs, BuiltinArgs::None) => {
return numeric_no_arg_scalar_val_apply(self.method, recv)
}
(BuiltinMethod::Or, BuiltinArgs::Val(default)) => return Some(or_apply(recv, default)),
(BuiltinMethod::Missing, BuiltinArgs::Str(k)) => return Some(missing_apply(recv, k)),
(BuiltinMethod::Includes, BuiltinArgs::Val(item)) => {
return Some(includes_apply(recv, item))
}
(BuiltinMethod::Index, BuiltinArgs::Val(item)) => return index_value_apply(recv, item),
(BuiltinMethod::IndicesOf, BuiltinArgs::Val(item)) => {
return indices_of_apply(recv, item)
}
(BuiltinMethod::Set, BuiltinArgs::Val(item)) => return Some(item.clone()),
(BuiltinMethod::Join, BuiltinArgs::Str(sep)) => return join_apply(recv, sep),
(BuiltinMethod::Enumerate, BuiltinArgs::None) => return enumerate_apply(recv),
(BuiltinMethod::Flatten, BuiltinArgs::Usize(depth)) => {
apply_or_recv!(flatten_depth_apply(recv, *depth))
}
(BuiltinMethod::First, BuiltinArgs::I64(n)) => apply_or_recv!(first_apply(recv, *n)),
(BuiltinMethod::Last, BuiltinArgs::I64(n)) => apply_or_recv!(last_apply(recv, *n)),
(BuiltinMethod::Nth, BuiltinArgs::I64(n)) => apply_or_recv!(nth_any_apply(recv, *n)),
(BuiltinMethod::Append, BuiltinArgs::Val(item)) => {
apply_or_recv!(append_apply(recv, item))
}
(BuiltinMethod::Prepend, BuiltinArgs::Val(item)) => {
apply_or_recv!(prepend_apply(recv, item))
}
(BuiltinMethod::Remove, BuiltinArgs::Val(item)) => {
apply_or_recv!(remove_value_apply(recv, item))
}
(BuiltinMethod::Diff, BuiltinArgs::ValVec(other)) => {
let arr_recv = recv.clone().into_vec().map(Val::arr)?;
apply_or_recv!(diff_apply(&arr_recv, other))
}
(BuiltinMethod::Intersect, BuiltinArgs::ValVec(other)) => {
let arr_recv = recv.clone().into_vec().map(Val::arr)?;
apply_or_recv!(intersect_apply(&arr_recv, other))
}
(BuiltinMethod::Union, BuiltinArgs::ValVec(other)) => {
let arr_recv = recv.clone().into_vec().map(Val::arr)?;
apply_or_recv!(union_apply(&arr_recv, other))
}
(BuiltinMethod::Window, BuiltinArgs::Usize(n)) => {
let arr_recv = recv.clone().into_vec().map(Val::arr)?;
apply_or_recv!(window_arr_apply(&arr_recv, *n))
}
(BuiltinMethod::Chunk, BuiltinArgs::Usize(n)) => {
let arr_recv = recv.clone().into_vec().map(Val::arr)?;
apply_or_recv!(chunk_arr_apply(&arr_recv, *n))
}
(BuiltinMethod::RollingSum, BuiltinArgs::Usize(n)) => {
apply_or_recv!(rolling_sum_apply(recv, *n))
}
(BuiltinMethod::RollingAvg, BuiltinArgs::Usize(n)) => {
apply_or_recv!(rolling_avg_apply(recv, *n))
}
(BuiltinMethod::RollingMin, BuiltinArgs::Usize(n)) => {
apply_or_recv!(rolling_min_apply(recv, *n))
}
(BuiltinMethod::RollingMax, BuiltinArgs::Usize(n)) => {
apply_or_recv!(rolling_max_apply(recv, *n))
}
(BuiltinMethod::Lag, BuiltinArgs::Usize(n)) => apply_or_recv!(lag_apply(recv, *n)),
(BuiltinMethod::Lead, BuiltinArgs::Usize(n)) => apply_or_recv!(lead_apply(recv, *n)),
(BuiltinMethod::Merge, BuiltinArgs::Val(other)) => {
apply_or_recv!(merge_apply(recv, other))
}
(BuiltinMethod::DeepMerge, BuiltinArgs::Val(other)) => {
apply_or_recv!(deep_merge_apply(recv, other))
}
(BuiltinMethod::Defaults, BuiltinArgs::Val(other)) => {
apply_or_recv!(defaults_apply(recv, other))
}
(BuiltinMethod::Rename, BuiltinArgs::Val(other)) => {
apply_or_recv!(rename_apply(recv, other))
}
(BuiltinMethod::Explode, BuiltinArgs::Str(field)) => {
apply_or_recv!(explode_apply(recv, field))
}
(BuiltinMethod::Implode, BuiltinArgs::Str(field)) => {
apply_or_recv!(implode_apply(recv, field))
}
(BuiltinMethod::Has, BuiltinArgs::Str(k)) => {
apply_or_recv!(has_apply(recv, k))
}
(BuiltinMethod::HasAll, BuiltinArgs::Val(v)) => {
apply_or_recv!(has_all_apply(recv, v))
}
(BuiltinMethod::HasAll, BuiltinArgs::StrVec(keys)) => {
apply_or_recv!(has_all_keys_apply(recv, keys))
}
(BuiltinMethod::HasKey, BuiltinArgs::Str(k)) => return Some(has_key_apply(recv, k)),
(BuiltinMethod::GetPath, BuiltinArgs::Str(p)) => {
apply_or_recv!(get_path_apply(recv, p))
}
(BuiltinMethod::GetPath, BuiltinArgs::Path(path)) => {
return Some(get_path_impl(recv, path))
}
(BuiltinMethod::HasPath, BuiltinArgs::Str(p)) => {
apply_or_recv!(has_path_apply(recv, p))
}
(BuiltinMethod::HasPath, BuiltinArgs::Path(path)) => {
return Some(Val::Bool(!get_path_impl(recv, path).is_null()))
}
(BuiltinMethod::DelPath, BuiltinArgs::Str(p)) => {
apply_or_recv!(del_path_apply(recv, p))
}
(BuiltinMethod::FlattenKeys, BuiltinArgs::Str(p)) => {
apply_or_recv!(flatten_keys_apply(recv, p))
}
(BuiltinMethod::UnflattenKeys, BuiltinArgs::Str(p)) => {
apply_or_recv!(unflatten_keys_apply(recv, p))
}
(BuiltinMethod::StartsWith, BuiltinArgs::Str(p))
| (BuiltinMethod::EndsWith, BuiltinArgs::Str(p))
| (BuiltinMethod::Matches, BuiltinArgs::Str(p))
| (BuiltinMethod::IndexOf, BuiltinArgs::Str(p))
| (BuiltinMethod::LastIndexOf, BuiltinArgs::Str(p)) => {
apply_or_recv!(str_arg_scalar_val_apply(self.method, recv, p))
}
(BuiltinMethod::StripPrefix, BuiltinArgs::Str(p)) => {
apply_or_recv!(strip_prefix_apply(recv, p))
}
(BuiltinMethod::StripSuffix, BuiltinArgs::Str(p)) => {
apply_or_recv!(strip_suffix_apply(recv, p))
}
(BuiltinMethod::Scan, BuiltinArgs::Str(p)) => apply_or_recv!(scan_apply(recv, p)),
(BuiltinMethod::Split, BuiltinArgs::Str(p)) => apply_or_recv!(split_apply(recv, p)),
(BuiltinMethod::Slice, BuiltinArgs::I64Opt { first, second }) => {
return Some(slice_apply(recv.clone(), *first, *second));
}
(BuiltinMethod::Replace, BuiltinArgs::StrPair { first, second }) => {
apply_or_recv!(replace_apply(recv.clone(), first, second, false))
}
(BuiltinMethod::ReplaceAll, BuiltinArgs::StrPair { first, second }) => {
apply_or_recv!(replace_apply(recv.clone(), first, second, true))
}
(BuiltinMethod::ReMatch, BuiltinArgs::Str(p)) => {
apply_or_recv!(re_match_apply(recv, p))
}
(BuiltinMethod::ReMatchFirst, BuiltinArgs::Str(p)) => {
apply_or_recv!(re_match_first_apply(recv, p))
}
(BuiltinMethod::ReMatchAll, BuiltinArgs::Str(p)) => {
apply_or_recv!(re_match_all_apply(recv, p))
}
(BuiltinMethod::ReCaptures, BuiltinArgs::Str(p)) => {
apply_or_recv!(re_captures_apply(recv, p))
}
(BuiltinMethod::ReCapturesAll, BuiltinArgs::Str(p)) => {
apply_or_recv!(re_captures_all_apply(recv, p))
}
(BuiltinMethod::ReSplit, BuiltinArgs::Str(p)) => {
apply_or_recv!(re_split_apply(recv, p))
}
(BuiltinMethod::ReReplace, BuiltinArgs::StrPair { first, second }) => {
apply_or_recv!(re_replace_apply(recv, first, second))
}
(BuiltinMethod::ReReplaceAll, BuiltinArgs::StrPair { first, second }) => {
apply_or_recv!(re_replace_all_apply(recv, first, second))
}
(BuiltinMethod::ContainsAny, BuiltinArgs::StrVec(ns)) => {
apply_or_recv!(contains_any_apply(recv, ns))
}
(BuiltinMethod::ContainsAll, BuiltinArgs::StrVec(ns)) => {
apply_or_recv!(contains_all_apply(recv, ns))
}
(BuiltinMethod::Pick, BuiltinArgs::StrVec(keys)) => {
apply_or_recv!(pick_apply(recv, keys))
}
(BuiltinMethod::Omit, BuiltinArgs::StrVec(keys)) => {
apply_or_recv!(omit_apply(recv, keys))
}
(BuiltinMethod::Repeat, BuiltinArgs::Usize(n)) => {
apply_or_recv!(repeat_apply(recv, *n))
}
(BuiltinMethod::Indent, BuiltinArgs::Usize(n)) => {
apply_or_recv!(indent_apply(recv, *n))
}
(BuiltinMethod::Indent, BuiltinArgs::Str(prefix)) => {
apply_or_recv!(indent_with_prefix_apply(recv, prefix.as_ref()))
}
(BuiltinMethod::PadLeft, BuiltinArgs::Pad { width, fill }) => {
apply_or_recv!(pad_left_apply(recv, *width, *fill))
}
(BuiltinMethod::PadRight, BuiltinArgs::Pad { width, fill }) => {
apply_or_recv!(pad_right_apply(recv, *width, *fill))
}
(BuiltinMethod::Center, BuiltinArgs::Pad { width, fill }) => {
apply_or_recv!(center_apply(recv, *width, *fill))
}
_ => None,
}
}
pub fn try_apply(&self, recv: &Val) -> Result<Option<Val>, EvalError> {
match (self.method, &self.args) {
(BuiltinMethod::ReMatch, BuiltinArgs::Str(p)) => try_re_match_apply(recv, p),
(BuiltinMethod::ReMatchFirst, BuiltinArgs::Str(p)) => try_re_match_first_apply(recv, p),
(BuiltinMethod::ReMatchAll, BuiltinArgs::Str(p)) => try_re_match_all_apply(recv, p),
(BuiltinMethod::ReCaptures, BuiltinArgs::Str(p)) => try_re_captures_apply(recv, p),
(BuiltinMethod::ReCapturesAll, BuiltinArgs::Str(p)) => {
try_re_captures_all_apply(recv, p)
}
(BuiltinMethod::ReSplit, BuiltinArgs::Str(p)) => try_re_split_apply(recv, p),
(BuiltinMethod::ReReplace, BuiltinArgs::StrPair { first, second }) => {
try_re_replace_apply(recv, first, second)
}
(BuiltinMethod::ReReplaceAll, BuiltinArgs::StrPair { first, second }) => {
try_re_replace_all_apply(recv, first, second)
}
(BuiltinMethod::FromJson, BuiltinArgs::None) => try_from_json_apply(recv),
(BuiltinMethod::Join, BuiltinArgs::Str(sep)) => join_apply(recv, sep)
.map(Some)
.ok_or_else(|| EvalError("join: expected array".into())),
(BuiltinMethod::Enumerate, BuiltinArgs::None) => enumerate_apply(recv)
.map(Some)
.ok_or_else(|| EvalError("enumerate: expected array".into())),
(BuiltinMethod::Sort, BuiltinArgs::None) => sort_apply(recv.clone()).map(Some),
(BuiltinMethod::Index, BuiltinArgs::Val(item)) => index_value_apply(recv, item)
.map(Some)
.ok_or_else(|| EvalError("index: expected array".into())),
(BuiltinMethod::IndicesOf, BuiltinArgs::Val(item)) => indices_of_apply(recv, item)
.map(Some)
.ok_or_else(|| EvalError("indices_of: expected array".into())),
(BuiltinMethod::Ceil, BuiltinArgs::None) => try_ceil_apply(recv),
(BuiltinMethod::Floor, BuiltinArgs::None) => try_floor_apply(recv),
(BuiltinMethod::Round, BuiltinArgs::None) => try_round_apply(recv),
(BuiltinMethod::Abs, BuiltinArgs::None) => try_abs_apply(recv),
(BuiltinMethod::RollingSum, BuiltinArgs::Usize(0)) => {
Err(EvalError("rolling_sum: window must be > 0".into()))
}
(BuiltinMethod::RollingAvg, BuiltinArgs::Usize(0)) => {
Err(EvalError("rolling_avg: window must be > 0".into()))
}
(BuiltinMethod::RollingMin, BuiltinArgs::Usize(0)) => {
Err(EvalError("rolling_min: window must be > 0".into()))
}
(BuiltinMethod::RollingMax, BuiltinArgs::Usize(0)) => {
Err(EvalError("rolling_max: window must be > 0".into()))
}
(BuiltinMethod::RollingSum, BuiltinArgs::Usize(_))
| (BuiltinMethod::RollingAvg, BuiltinArgs::Usize(_))
| (BuiltinMethod::RollingMin, BuiltinArgs::Usize(_))
| (BuiltinMethod::RollingMax, BuiltinArgs::Usize(_))
| (BuiltinMethod::Lag, BuiltinArgs::Usize(_))
| (BuiltinMethod::Lead, BuiltinArgs::Usize(_))
| (BuiltinMethod::DiffWindow, BuiltinArgs::None)
| (BuiltinMethod::PctChange, BuiltinArgs::None)
| (BuiltinMethod::CumMax, BuiltinArgs::None)
| (BuiltinMethod::CumMin, BuiltinArgs::None)
| (BuiltinMethod::Zscore, BuiltinArgs::None) => self
.apply(recv)
.map(Some)
.ok_or_else(|| EvalError("expected numeric array".into())),
_ => Ok(self.apply(recv)),
}
}
pub fn from_static_args<E, I>(
method: BuiltinMethod,
name: &str,
arg_len: usize,
eval_arg: E,
ident_arg: I,
) -> Result<Option<Self>, EvalError>
where
E: FnMut(usize) -> Result<Option<Val>, EvalError>,
I: FnMut(usize) -> Option<Arc<str>>,
{
if method == BuiltinMethod::Unknown {
return Ok(None);
}
let mut args = StaticArgDecoder {
name,
eval_arg,
ident_arg,
};
let call = match method {
BuiltinMethod::Flatten => {
let depth = if arg_len > 0 { args.usize(0)? } else { 1 };
Self::new(method, BuiltinArgs::Usize(depth))
}
BuiltinMethod::First | BuiltinMethod::Last => {
let n = if arg_len > 0 { args.i64(0)? } else { 1 };
Self::new(method, BuiltinArgs::I64(n))
}
BuiltinMethod::Nth => Self::new(method, BuiltinArgs::I64(args.i64(0)?)),
BuiltinMethod::Take | BuiltinMethod::Skip => {
Self::new(method, BuiltinArgs::Usize(args.usize(0)?))
}
BuiltinMethod::Append | BuiltinMethod::Prepend | BuiltinMethod::Set => {
let item = if arg_len > 0 { args.val(0)? } else { Val::Null };
Self::new(method, BuiltinArgs::Val(item))
}
BuiltinMethod::Or => {
let default = if arg_len > 0 { args.val(0)? } else { Val::Null };
Self::new(method, BuiltinArgs::Val(default))
}
BuiltinMethod::Includes | BuiltinMethod::Index | BuiltinMethod::IndicesOf => {
Self::new(method, BuiltinArgs::Val(args.val(0)?))
}
BuiltinMethod::Diff | BuiltinMethod::Intersect | BuiltinMethod::Union => {
Self::new(method, BuiltinArgs::ValVec(args.vec(0)?))
}
BuiltinMethod::Window
| BuiltinMethod::Chunk
| BuiltinMethod::RollingSum
| BuiltinMethod::RollingAvg
| BuiltinMethod::RollingMin
| BuiltinMethod::RollingMax => Self::new(method, BuiltinArgs::Usize(args.usize(0)?)),
BuiltinMethod::Lag | BuiltinMethod::Lead => {
let n = if arg_len > 0 { args.usize(0)? } else { 1 };
Self::new(method, BuiltinArgs::Usize(n))
}
BuiltinMethod::Merge
| BuiltinMethod::DeepMerge
| BuiltinMethod::Defaults
| BuiltinMethod::Rename => Self::new(method, BuiltinArgs::Val(args.val(0)?)),
BuiltinMethod::Slice => {
let start = args.i64(0)?;
let end = if arg_len > 1 {
Some(args.i64(1)?)
} else {
None
};
Self::new(
method,
BuiltinArgs::I64Opt {
first: start,
second: end,
},
)
}
BuiltinMethod::Missing if arg_len >= 2 => {
let mut keys = Vec::with_capacity(arg_len);
for i in 0..arg_len {
keys.push(args.str(i)?);
}
Self::new(method, BuiltinArgs::StrVec(keys))
}
BuiltinMethod::GetPath | BuiltinMethod::HasPath => {
let path = args.str(0)?;
Self::new(method, BuiltinArgs::Path(parse_path_segs(path.as_ref()).into()))
}
BuiltinMethod::HasAll => Self::new(method, BuiltinArgs::Val(args.val(0)?)),
BuiltinMethod::Has
| BuiltinMethod::HasKey
| BuiltinMethod::Join
| BuiltinMethod::Explode
| BuiltinMethod::Implode
| BuiltinMethod::DelPath
| BuiltinMethod::FlattenKeys
| BuiltinMethod::UnflattenKeys
| BuiltinMethod::Missing
| BuiltinMethod::StartsWith
| BuiltinMethod::EndsWith
| BuiltinMethod::IndexOf
| BuiltinMethod::LastIndexOf
| BuiltinMethod::StripPrefix
| BuiltinMethod::StripSuffix
| BuiltinMethod::Matches
| BuiltinMethod::Scan
| BuiltinMethod::Split
| BuiltinMethod::ReMatch
| BuiltinMethod::ReMatchFirst
| BuiltinMethod::ReMatchAll
| BuiltinMethod::ReCaptures
| BuiltinMethod::ReCapturesAll
| BuiltinMethod::ReSplit => {
let s = if arg_len > 0 {
args.str(0)?
} else if matches!(method, BuiltinMethod::Join) {
Arc::from("")
} else if matches!(
method,
BuiltinMethod::FlattenKeys | BuiltinMethod::UnflattenKeys
) {
Arc::from(".")
} else {
return Ok(None);
};
Self::new(method, BuiltinArgs::Str(s))
}
BuiltinMethod::Replace
| BuiltinMethod::ReplaceAll
| BuiltinMethod::ReReplace
| BuiltinMethod::ReReplaceAll => Self::new(
method,
BuiltinArgs::StrPair {
first: args.str(0)?,
second: args.str(1)?,
},
),
BuiltinMethod::ContainsAny | BuiltinMethod::ContainsAll => {
Self::new(method, BuiltinArgs::StrVec(args.str_vec(0)?))
}
BuiltinMethod::Omit => {
let mut keys = Vec::with_capacity(arg_len);
for idx in 0..arg_len {
keys.push(args.str(idx)?);
}
Self::new(method, BuiltinArgs::StrVec(keys))
}
BuiltinMethod::Repeat => Self::new(method, BuiltinArgs::Usize(args.usize(0)?)),
BuiltinMethod::Indent => {
if arg_len > 0 {
if let Some(prefix) = args.str_lit(0) {
Self::new(method, BuiltinArgs::Str(prefix))
} else {
Self::new(method, BuiltinArgs::Usize(args.usize(0)?))
}
} else {
Self::new(method, BuiltinArgs::Usize(2))
}
}
BuiltinMethod::PadLeft | BuiltinMethod::PadRight | BuiltinMethod::Center => Self::new(
method,
BuiltinArgs::Pad {
width: args.usize(0)?,
fill: args.char(1, arg_len)?,
},
),
_ if arg_len == 0 => Self::new(method, BuiltinArgs::None),
_ => return Ok(None),
};
Ok(Some(call))
}
pub fn from_literal_ast_args(name: &str, args: &[crate::parse::ast::Arg]) -> Option<Self> {
use crate::parse::ast::{Arg, ArrayElem, Expr, ObjField};
let method = BuiltinMethod::from_name(name);
if method == BuiltinMethod::Unknown {
return None;
}
fn literal_val(expr: &Expr) -> Option<Val> {
match expr {
Expr::Null => Some(Val::Null),
Expr::Bool(b) => Some(Val::Bool(*b)),
Expr::Int(n) => Some(Val::Int(*n)),
Expr::Float(f) => Some(Val::Float(*f)),
Expr::Str(s) => Some(Val::Str(Arc::from(s.as_str()))),
Expr::Array(elems) => {
let mut out = Vec::with_capacity(elems.len());
for elem in elems {
match elem {
ArrayElem::Expr(expr) => out.push(literal_val(expr)?),
ArrayElem::Spread(_) => return None,
}
}
Some(Val::Arr(Arc::new(out)))
}
Expr::Object(fields) => {
let mut out = IndexMap::with_capacity(fields.len());
for field in fields {
match field {
ObjField::Kv {
key,
val,
optional: false,
cond: None,
} => {
out.insert(Arc::from(key.as_str()), literal_val(val)?);
}
_ => return None,
}
}
Some(Val::Obj(Arc::new(out)))
}
_ => None,
}
}
if method == BuiltinMethod::Remove {
return match args {
[Arg::Pos(expr)] => {
Some(Self::new(method, BuiltinArgs::Val(literal_val(expr)?)))
}
_ => None,
};
}
if method == BuiltinMethod::HasAll {
return match args {
[Arg::Pos(Expr::Array(elems))] => {
let mut keys = Vec::with_capacity(elems.len());
for elem in elems {
let ArrayElem::Expr(expr) = elem else {
return None;
};
keys.push(Arc::from(crate::util::val_to_key(&literal_val(expr)?)));
}
Some(Self::new(method, BuiltinArgs::StrVec(keys)))
}
[Arg::Pos(expr)] => Some(Self::new(method, BuiltinArgs::Val(literal_val(expr)?))),
_ => None,
};
}
Self::from_static_args(
method,
name,
args.len(),
|idx| {
Ok(match args.get(idx) {
Some(Arg::Pos(expr)) => literal_val(expr),
_ => None,
})
},
|idx| match args.get(idx) {
Some(Arg::Pos(Expr::Ident(value))) => Some(Arc::from(value.as_str())),
_ => None,
},
)
.ok()
.flatten()
}
pub fn from_pipeline_literal_args(name: &str, args: &[crate::parse::ast::Arg]) -> Option<Self> {
let call = Self::from_literal_ast_args(name, args)?;
call.method.is_pipeline_element_method().then_some(call)
}
pub fn try_apply_json_view(&self, recv: crate::util::JsonView<'_>) -> Option<Val> {
if !self.spec().view_scalar {
return None;
}
match (self.method, &self.args) {
(BuiltinMethod::Len, BuiltinArgs::None) => json_view_len(recv).map(Val::Int),
(method, BuiltinArgs::None) if method.is_string_no_arg_view_scalar() => {
let value = json_view_str(recv)?;
str_no_arg_scalar_apply(method, value)
}
(method, BuiltinArgs::None) if method.is_numeric_no_arg_view_scalar() => {
numeric_no_arg_scalar_apply(method, recv)
}
(method, BuiltinArgs::Str(arg)) if method.is_string_arg_view_scalar() => {
let value = json_view_str(recv)?;
str_arg_scalar_apply(method, value, arg.as_ref())
}
(BuiltinMethod::Includes, BuiltinArgs::Val(Val::Str(arg))) => {
let value = json_view_str(recv)?;
Some(Val::Bool(value.contains(arg.as_ref())))
}
(BuiltinMethod::Includes, BuiltinArgs::Val(Val::StrSlice(arg))) => {
let value = json_view_str(recv)?;
Some(Val::Bool(value.contains(arg.as_str())))
}
_ => None,
}
}
}
#[inline]
fn numeric_no_arg_scalar_apply(
method: BuiltinMethod,
recv: crate::util::JsonView<'_>,
) -> Option<Val> {
match (method, recv) {
(
BuiltinMethod::Ceil | BuiltinMethod::Floor | BuiltinMethod::Round,
crate::util::JsonView::Int(n),
) => Some(Val::Int(n)),
(
BuiltinMethod::Ceil | BuiltinMethod::Floor | BuiltinMethod::Round,
crate::util::JsonView::UInt(n),
) => Some(uint_to_val(n)),
(BuiltinMethod::Ceil, crate::util::JsonView::Float(f)) => Some(Val::Int(f.ceil() as i64)),
(BuiltinMethod::Floor, crate::util::JsonView::Float(f)) => Some(Val::Int(f.floor() as i64)),
(BuiltinMethod::Round, crate::util::JsonView::Float(f)) => Some(Val::Int(f.round() as i64)),
(BuiltinMethod::Abs, crate::util::JsonView::Int(n)) => Some(Val::Int(n.wrapping_abs())),
(BuiltinMethod::Abs, crate::util::JsonView::UInt(n)) => Some(uint_to_val(n)),
(BuiltinMethod::Abs, crate::util::JsonView::Float(f)) => Some(Val::Float(f.abs())),
_ => None,
}
}
#[inline]
fn numeric_no_arg_scalar_val_apply(method: BuiltinMethod, recv: &Val) -> Option<Val> {
numeric_no_arg_scalar_apply(method, crate::util::JsonView::from_val(recv))
}
#[inline]
fn uint_to_val(n: u64) -> Val {
if n <= i64::MAX as u64 {
Val::Int(n as i64)
} else {
Val::Float(n as f64)
}
}
#[inline]
fn str_no_arg_scalar_apply(method: BuiltinMethod, value: &str) -> Option<Val> {
match method {
BuiltinMethod::Upper => {
if value.is_ascii() {
let mut buf = value.to_owned();
buf.make_ascii_uppercase();
Some(Val::Str(Arc::from(buf)))
} else {
Some(Val::Str(Arc::from(value.to_uppercase())))
}
}
BuiltinMethod::Lower => {
if value.is_ascii() {
let mut buf = value.to_owned();
buf.make_ascii_lowercase();
Some(Val::Str(Arc::from(buf)))
} else {
Some(Val::Str(Arc::from(value.to_lowercase())))
}
}
BuiltinMethod::Trim => Some(Val::Str(Arc::from(value.trim()))),
BuiltinMethod::TrimLeft => Some(Val::Str(Arc::from(value.trim_start()))),
BuiltinMethod::TrimRight => Some(Val::Str(Arc::from(value.trim_end()))),
BuiltinMethod::ByteLen => Some(Val::Int(value.len() as i64)),
BuiltinMethod::IsBlank => Some(Val::Bool(value.chars().all(|c| c.is_whitespace()))),
BuiltinMethod::IsNumeric => Some(Val::Bool(
!value.is_empty() && value.chars().all(|c| c.is_ascii_digit()),
)),
BuiltinMethod::IsAlpha => Some(Val::Bool(
!value.is_empty() && value.chars().all(|c| c.is_alphabetic()),
)),
BuiltinMethod::IsAscii => Some(Val::Bool(value.is_ascii())),
BuiltinMethod::ToNumber => {
if let Ok(i) = value.parse::<i64>() {
return Some(Val::Int(i));
}
if let Ok(f) = value.parse::<f64>() {
return Some(Val::Float(f));
}
Some(Val::Null)
}
BuiltinMethod::ToBool => Some(match value {
"true" => Val::Bool(true),
"false" => Val::Bool(false),
_ => Val::Null,
}),
_ => None,
}
}
#[inline]
fn str_no_arg_scalar_val_apply(method: BuiltinMethod, recv: &Val) -> Option<Val> {
str_no_arg_scalar_apply(method, recv.as_str_ref()?)
}
#[inline]
fn str_arg_scalar_apply(method: BuiltinMethod, value: &str, arg: &str) -> Option<Val> {
match method {
BuiltinMethod::StartsWith => Some(Val::Bool(value.starts_with(arg))),
BuiltinMethod::EndsWith => Some(Val::Bool(value.ends_with(arg))),
BuiltinMethod::Matches => Some(Val::Bool(value.contains(arg))),
BuiltinMethod::IndexOf => Some(str_index_of(value, arg, false)),
BuiltinMethod::LastIndexOf => Some(str_index_of(value, arg, true)),
_ => None,
}
}
#[inline]
fn str_arg_scalar_val_apply(method: BuiltinMethod, recv: &Val, arg: &str) -> Option<Val> {
str_arg_scalar_apply(method, recv.as_str_ref()?, arg)
}
#[inline]
fn str_index_of(value: &str, needle: &str, last: bool) -> Val {
let offset = if last {
value.rfind(needle)
} else {
value.find(needle)
};
match offset {
Some(i) => Val::Int(value[..i].chars().count() as i64),
None => Val::Int(-1),
}
}
#[inline]
fn json_view_len(recv: crate::util::JsonView<'_>) -> Option<i64> {
match recv {
crate::util::JsonView::Str(s) => Some(s.chars().count() as i64),
crate::util::JsonView::ArrayLen(n) | crate::util::JsonView::ObjectLen(n) => Some(n as i64),
_ => None,
}
}
#[inline]
fn json_view_str(recv: crate::util::JsonView<'_>) -> Option<&str> {
match recv {
crate::util::JsonView::Str(s) => Some(s),
_ => None,
}
}
pub(crate) fn eval_builtin_method<F, G, H>(
recv: Val,
name: &str,
args: &[crate::parse::ast::Arg],
mut eval_arg: F,
mut eval_item: G,
mut eval_pair: H,
) -> Result<Val, EvalError>
where
F: FnMut(&crate::parse::ast::Arg) -> Result<Val, EvalError>,
G: FnMut(&Val, &crate::parse::ast::Arg) -> Result<Val, EvalError>,
H: FnMut(&Val, &Val, &crate::parse::ast::Arg) -> Result<Val, EvalError>,
{
use crate::parse::ast::{Arg, Expr, ObjField};
let method = BuiltinMethod::from_name(name);
if method == BuiltinMethod::Unknown {
return Err(EvalError(format!("unknown method '{}'", name)));
}
macro_rules! arg_val {
($idx:expr) => {{
let arg = args
.get($idx)
.ok_or_else(|| EvalError(format!("{}: missing argument", name)))?;
eval_arg(arg)
}};
}
macro_rules! str_arg {
($idx:expr) => {{
match args.get($idx) {
Some(Arg::Pos(Expr::Ident(s))) => Ok(Arc::from(s.as_str())),
Some(_) => match arg_val!($idx)? {
Val::Str(s) => Ok(s),
other => Ok(Arc::from(crate::util::val_to_string(&other).as_str())),
},
None => Err(EvalError(format!("{}: missing argument", name))),
}
}};
}
macro_rules! i64_arg {
($idx:expr) => {{
match arg_val!($idx)? {
Val::Int(n) => Ok(n),
Val::Float(f) => Ok(f as i64),
_ => Err(EvalError(format!("{}: expected number argument", name))),
}
}};
}
macro_rules! vec_arg {
($idx:expr) => {{
arg_val!($idx)?
.into_vec()
.ok_or_else(|| EvalError(format!("{}: expected array arg", name)))
}};
}
macro_rules! str_vec_arg {
($idx:expr) => {{
Ok(vec_arg!($idx)?
.iter()
.map(|v| match v {
Val::Str(s) => s.clone(),
other => Arc::from(crate::util::val_to_string(other).as_str()),
})
.collect())
}};
}
macro_rules! fill_arg {
($idx:expr) => {{
match args.get($idx) {
None => Ok(' '),
Some(_) => {
let s = str_arg!($idx)?;
if s.chars().count() == 1 {
Ok(s.chars().next().unwrap())
} else {
Err(EvalError(format!(
"{}: filler must be a single-char string",
name
)))
}
}
}
}};
}
let call = match method {
BuiltinMethod::Len
| BuiltinMethod::Count
| BuiltinMethod::Sum
| BuiltinMethod::Avg
| BuiltinMethod::Min
| BuiltinMethod::Max
| BuiltinMethod::Keys
| BuiltinMethod::Values
| BuiltinMethod::Entries
| BuiltinMethod::Reverse
| BuiltinMethod::Unique
| BuiltinMethod::Collect
| BuiltinMethod::Compact
| BuiltinMethod::FromJson
| BuiltinMethod::FromPairs
| BuiltinMethod::ToPairs
| BuiltinMethod::Invert
| BuiltinMethod::Enumerate
| BuiltinMethod::Pairwise
| BuiltinMethod::Ceil
| BuiltinMethod::Floor
| BuiltinMethod::Round
| BuiltinMethod::Abs
| BuiltinMethod::DiffWindow
| BuiltinMethod::PctChange
| BuiltinMethod::CumMax
| BuiltinMethod::CumMin
| BuiltinMethod::Zscore
| BuiltinMethod::Upper
| BuiltinMethod::Lower
| BuiltinMethod::Trim
| BuiltinMethod::TrimLeft
| BuiltinMethod::TrimRight
| BuiltinMethod::Capitalize
| BuiltinMethod::TitleCase
| BuiltinMethod::SnakeCase
| BuiltinMethod::KebabCase
| BuiltinMethod::CamelCase
| BuiltinMethod::PascalCase
| BuiltinMethod::ReverseStr
| BuiltinMethod::HtmlEscape
| BuiltinMethod::HtmlUnescape
| BuiltinMethod::UrlEncode
| BuiltinMethod::UrlDecode
| BuiltinMethod::ToBase64
| BuiltinMethod::FromBase64
| BuiltinMethod::Dedent
| BuiltinMethod::Lines
| BuiltinMethod::Words
| BuiltinMethod::Chars
| BuiltinMethod::CharsOf
| BuiltinMethod::Bytes
| BuiltinMethod::ByteLen
| BuiltinMethod::IsBlank
| BuiltinMethod::IsNumeric
| BuiltinMethod::IsAlpha
| BuiltinMethod::IsAscii
| BuiltinMethod::ToNumber
| BuiltinMethod::ToBool
| BuiltinMethod::ParseInt
| BuiltinMethod::ParseFloat
| BuiltinMethod::ParseBool
| BuiltinMethod::Type
| BuiltinMethod::ToString
| BuiltinMethod::ToJson
| BuiltinMethod::ToCsv
| BuiltinMethod::ToTsv
| BuiltinMethod::Schema
| BuiltinMethod::ApproxCountDistinct
| BuiltinMethod::ZipShape
| BuiltinMethod::GroupShape
if args.is_empty() =>
{
BuiltinCall::new(method, BuiltinArgs::None)
}
BuiltinMethod::Sum | BuiltinMethod::Avg | BuiltinMethod::Min | BuiltinMethod::Max => {
return numeric_aggregate_projected_apply(&recv, method, |item| {
eval_item(item, &args[0])
});
}
BuiltinMethod::Count => {
let items = recv
.as_vals()
.ok_or_else(|| EvalError("count: expected array".into()))?;
let mut n: i64 = 0;
for item in items.iter() {
if crate::util::is_truthy(&eval_item(item, &args[0])?) {
n += 1;
}
}
return Ok(Val::Int(n));
}
BuiltinMethod::Find | BuiltinMethod::FindFirst => {
return find_first_apply(recv, args.len(), |item, idx| eval_item(item, &args[idx]));
}
BuiltinMethod::FindAll => {
return find_apply(recv, args.len(), |item, idx| eval_item(item, &args[idx]));
}
BuiltinMethod::FindIndex => {
return find_index_apply(recv, args.len(), |item, idx| eval_item(item, &args[idx]));
}
BuiltinMethod::IndicesWhere => {
return indices_where_apply(recv, args.len(), |item, idx| eval_item(item, &args[idx]));
}
BuiltinMethod::UniqueBy => {
let key_arg = args
.first()
.ok_or_else(|| EvalError("unique_by: requires key fn".into()))?;
return unique_by_apply(recv, |item| eval_item(item, key_arg));
}
BuiltinMethod::MaxBy | BuiltinMethod::MinBy => {
let key_arg = args
.first()
.ok_or_else(|| EvalError(format!("{}: requires a key expression", name)))?;
return extreme_by_apply(recv, method == BuiltinMethod::MaxBy, |item| {
eval_item(item, key_arg)
});
}
BuiltinMethod::DeepFind => {
return deep_find_apply(recv, args.len(), |item, idx| eval_item(item, &args[idx]));
}
BuiltinMethod::DeepShape => {
let arg = args
.first()
.ok_or_else(|| EvalError("shape: requires pattern".into()))?;
let expr = match arg {
Arg::Pos(e) | Arg::Named(_, e) => e,
};
let Expr::Object(fields) = expr else {
return Err(EvalError(
"shape: expected `{k1, k2, ...}` object pattern".into(),
));
};
let mut keys = Vec::with_capacity(fields.len());
for field in fields {
match field {
ObjField::Short(k) => keys.push(Arc::from(k.as_str())),
ObjField::Kv { key, val, .. } if matches!(val, Expr::Ident(n) if n == key) => {
keys.push(Arc::from(key.as_str()));
}
_ => return Err(EvalError("shape: unsupported pattern field".into())),
}
}
return deep_shape_apply(recv, &keys);
}
BuiltinMethod::DeepLike => {
let arg = args
.first()
.ok_or_else(|| EvalError("like: requires pattern".into()))?;
let expr = match arg {
Arg::Pos(e) | Arg::Named(_, e) => e,
};
let Expr::Object(fields) = expr else {
return Err(EvalError(
"like: expected `{k: lit, ...}` object pattern".into(),
));
};
let mut pats = Vec::with_capacity(fields.len());
for field in fields {
match field {
ObjField::Kv { key, val, .. } => {
pats.push((Arc::from(key.as_str()), eval_arg(&Arg::Pos(val.clone()))?));
}
ObjField::Short(k) => {
pats.push((
Arc::from(k.as_str()),
eval_arg(&Arg::Pos(Expr::Ident(k.clone())))?,
));
}
_ => return Err(EvalError("like: unsupported pattern field".into())),
}
}
return deep_like_apply(recv, &pats);
}
BuiltinMethod::Walk | BuiltinMethod::WalkPre => {
let arg = args
.first()
.ok_or_else(|| EvalError("walk: requires fn".into()))?;
let pre = method == BuiltinMethod::WalkPre;
let mut eval = |value: Val| eval_item(&value, arg);
return walk_apply(recv, pre, &mut eval);
}
BuiltinMethod::Rec => {
let arg = args
.first()
.ok_or_else(|| EvalError("rec: requires step expression".into()))?;
if let Some(cond_arg) = args.get(1) {
let eval_cell = std::cell::RefCell::new(eval_item);
return rec_cond_apply(
recv,
|value| eval_cell.borrow_mut()(&value, arg),
|value| eval_cell.borrow_mut()(value, cond_arg),
);
}
return rec_apply(recv, |value| eval_item(&value, arg));
}
BuiltinMethod::TracePath => {
let arg = args
.first()
.ok_or_else(|| EvalError("trace_path: requires predicate".into()))?;
return trace_path_apply(recv, |value| eval_item(value, arg));
}
BuiltinMethod::Fanout => {
return fanout_apply(&recv, args.len(), |value, idx| eval_item(value, &args[idx]));
}
BuiltinMethod::ZipShape => {
if args.is_empty() {
return zip_shape_obj_apply(&recv)
.ok_or_else(|| EvalError("zip_shape: expected object receiver".into()));
}
if args.len() == 1 {
if let Arg::Pos(Expr::Object(fields)) = &args[0] {
let all_short = fields.iter().all(|f| {
matches!(f, crate::parse::ast::ObjField::Short(_))
});
if all_short {
let obj = eval_arg(&args[0])?;
return zip_shape_obj_apply(&obj).ok_or_else(|| {
EvalError("zip_shape: expected object receiver".into())
});
}
}
}
let mut names = Vec::with_capacity(args.len());
for arg in args {
let name: Arc<str> = match arg {
Arg::Named(n, _) => Arc::from(n.as_str()),
Arg::Pos(Expr::Ident(n)) => Arc::from(n.as_str()),
_ => {
return Err(EvalError(
"zip_shape: args must be `name: expr` or bare identifier".into(),
))
}
};
names.push(name);
}
return zip_shape_apply(&recv, &names, |value, idx| {
eval_item(value, &args[idx])
});
}
BuiltinMethod::GroupShape => {
if args.is_empty() {
return group_shape_by_keys_apply(recv)
.ok_or_else(|| EvalError("group_shape: expected array".into()));
}
if args.len() == 1 {
let key_arg = &args[0];
return group_shape_apply(recv, |value, idx| {
if idx == 0 {
eval_item(&value, key_arg)
} else {
Ok(value)
}
});
}
let key_arg = &args[0];
let shape_arg = &args[1];
return group_shape_apply(recv, |value, idx| {
if idx == 0 {
eval_item(&value, key_arg)
} else {
eval_item(&value, shape_arg)
}
});
}
BuiltinMethod::Sort => {
if args.is_empty() {
return sort_apply(recv);
}
let mut key_args = Vec::with_capacity(args.len());
let mut desc = Vec::with_capacity(args.len());
for arg in args {
match arg {
Arg::Pos(Expr::Lambda { params, .. })
| Arg::Named(_, Expr::Lambda { params, .. })
if params.len() == 2 =>
{
return sort_comparator_apply(recv, |left, right| {
eval_pair(left, right, arg)
});
}
Arg::Pos(Expr::UnaryNeg(inner)) => {
desc.push(true);
key_args.push(Arg::Pos((**inner).clone()));
}
Arg::Pos(e) => {
desc.push(false);
key_args.push(Arg::Pos(e.clone()));
}
Arg::Named(name, Expr::UnaryNeg(inner)) => {
desc.push(true);
key_args.push(Arg::Named(name.clone(), (**inner).clone()));
}
Arg::Named(name, e) => {
desc.push(false);
key_args.push(Arg::Named(name.clone(), e.clone()));
}
}
}
return sort_by_apply(recv, &desc, |item, idx| eval_item(item, &key_args[idx]));
}
BuiltinMethod::Flatten => {
let depth = if args.is_empty() {
1
} else {
i64_arg!(0)?.max(0) as usize
};
BuiltinCall::new(method, BuiltinArgs::Usize(depth))
}
BuiltinMethod::First | BuiltinMethod::Last => {
let n = if args.is_empty() { 1 } else { i64_arg!(0)? };
BuiltinCall::new(method, BuiltinArgs::I64(n))
}
BuiltinMethod::Nth => BuiltinCall::new(method, BuiltinArgs::I64(i64_arg!(0)?)),
BuiltinMethod::Take | BuiltinMethod::Skip => {
BuiltinCall::new(method, BuiltinArgs::Usize(i64_arg!(0)?.max(0) as usize))
}
BuiltinMethod::Append | BuiltinMethod::Prepend | BuiltinMethod::Set => {
let item = if args.is_empty() {
Val::Null
} else {
arg_val!(0)?
};
BuiltinCall::new(method, BuiltinArgs::Val(item))
}
BuiltinMethod::Or => {
let default = if args.is_empty() {
Val::Null
} else {
arg_val!(0)?
};
BuiltinCall::new(method, BuiltinArgs::Val(default))
}
BuiltinMethod::Includes | BuiltinMethod::Index | BuiltinMethod::IndicesOf => {
BuiltinCall::new(method, BuiltinArgs::Val(arg_val!(0)?))
}
BuiltinMethod::Diff | BuiltinMethod::Intersect | BuiltinMethod::Union => {
BuiltinCall::new(method, BuiltinArgs::ValVec(vec_arg!(0)?))
}
BuiltinMethod::Window
| BuiltinMethod::Chunk
| BuiltinMethod::RollingSum
| BuiltinMethod::RollingAvg
| BuiltinMethod::RollingMin
| BuiltinMethod::RollingMax => {
BuiltinCall::new(method, BuiltinArgs::Usize(i64_arg!(0)?.max(0) as usize))
}
BuiltinMethod::Lag | BuiltinMethod::Lead => {
let n = if args.is_empty() {
1
} else {
i64_arg!(0)?.max(0) as usize
};
BuiltinCall::new(method, BuiltinArgs::Usize(n))
}
BuiltinMethod::Merge
| BuiltinMethod::DeepMerge
| BuiltinMethod::Defaults
| BuiltinMethod::Rename => BuiltinCall::new(method, BuiltinArgs::Val(arg_val!(0)?)),
BuiltinMethod::ParseInt if !args.is_empty() => {
let radix = i64_arg!(0)?;
BuiltinCall::new(method, BuiltinArgs::Usize(radix.max(0) as usize))
}
BuiltinMethod::ToCsv | BuiltinMethod::ToTsv if !args.is_empty() => {
let headers = str_vec_arg!(0)?;
BuiltinCall::new(method, BuiltinArgs::StrVec(headers))
}
BuiltinMethod::Remove => match args.first() {
Some(Arg::Pos(Expr::Lambda { .. })) | Some(Arg::Named(_, Expr::Lambda { .. })) => {
return remove_predicate_apply(recv, |item| eval_item(item, &args[0]));
}
Some(arg) if arg_uses_current(arg) => {
return remove_predicate_apply(recv, |item| eval_item(item, &args[0]));
}
Some(_) => BuiltinCall::new(method, BuiltinArgs::Val(arg_val!(0)?)),
None => return Err(EvalError("remove: requires arg".into())),
},
BuiltinMethod::Zip => {
let other = args
.first()
.map(|arg| eval_arg(arg))
.transpose()?
.unwrap_or_else(|| Val::arr(Vec::new()));
return zip_apply(recv, other);
}
BuiltinMethod::ZipLongest => {
let mut other = Val::arr(Vec::new());
let mut fill = Val::Null;
for arg in args {
match arg {
Arg::Pos(_) => other = eval_arg(arg)?,
Arg::Named(n, _) if n == "fill" => fill = eval_arg(arg)?,
Arg::Named(_, _) => {}
}
}
return zip_longest_apply(recv, other, fill);
}
BuiltinMethod::EquiJoin => {
let other = arg_val!(0)?;
let lhs_key = str_arg!(1)?;
let rhs_key = str_arg!(2)?;
return equi_join_apply(recv, other, &lhs_key, &rhs_key);
}
BuiltinMethod::Pivot => {
return pivot_apply(recv, args.len(), |item, idx| match &args[idx] {
Arg::Pos(Expr::Str(s)) | Arg::Named(_, Expr::Str(s)) => {
Ok(item.get_field(s.as_str()))
}
arg => eval_item(item, arg),
});
}
BuiltinMethod::Slice => {
let start = i64_arg!(0)?;
let end = if args.len() > 1 {
Some(i64_arg!(1)?)
} else {
None
};
BuiltinCall::new(
method,
BuiltinArgs::I64Opt {
first: start,
second: end,
},
)
}
BuiltinMethod::Join => {
let sep = if args.is_empty() {
Arc::from("")
} else {
str_arg!(0)?
};
BuiltinCall::new(method, BuiltinArgs::Str(sep))
}
BuiltinMethod::FlattenKeys | BuiltinMethod::UnflattenKeys if args.is_empty() => {
BuiltinCall::new(method, BuiltinArgs::Str(Arc::from(".")))
}
BuiltinMethod::Missing if args.len() >= 2 => {
let keys = (0..args.len())
.map(|i| str_arg!(i))
.collect::<Result<Vec<_>, _>>()?;
BuiltinCall::new(method, BuiltinArgs::StrVec(keys))
}
BuiltinMethod::GetPath | BuiltinMethod::HasPath => {
BuiltinCall::new(
method,
BuiltinArgs::Path(parse_path_segs(str_arg!(0)?.as_ref()).into()),
)
}
BuiltinMethod::HasAll => BuiltinCall::new(method, BuiltinArgs::Val(arg_val!(0)?)),
BuiltinMethod::Has
| BuiltinMethod::HasKey
| BuiltinMethod::Missing
| BuiltinMethod::Explode
| BuiltinMethod::Implode
| BuiltinMethod::DelPath
| BuiltinMethod::FlattenKeys
| BuiltinMethod::UnflattenKeys
| BuiltinMethod::StartsWith
| BuiltinMethod::EndsWith
| BuiltinMethod::IndexOf
| BuiltinMethod::LastIndexOf
| BuiltinMethod::StripPrefix
| BuiltinMethod::StripSuffix
| BuiltinMethod::Matches
| BuiltinMethod::Scan
| BuiltinMethod::Split
| BuiltinMethod::ReMatch
| BuiltinMethod::ReMatchFirst
| BuiltinMethod::ReMatchAll
| BuiltinMethod::ReCaptures
| BuiltinMethod::ReCapturesAll
| BuiltinMethod::ReSplit => BuiltinCall::new(method, BuiltinArgs::Str(str_arg!(0)?)),
BuiltinMethod::Replace
| BuiltinMethod::ReplaceAll
| BuiltinMethod::ReReplace
| BuiltinMethod::ReReplaceAll => BuiltinCall::new(
method,
BuiltinArgs::StrPair {
first: str_arg!(0)?,
second: str_arg!(1)?,
},
),
BuiltinMethod::ContainsAny | BuiltinMethod::ContainsAll => {
BuiltinCall::new(method, BuiltinArgs::StrVec(str_vec_arg!(0)?))
}
BuiltinMethod::Pick => {
let mut specs = Vec::with_capacity(args.len());
for arg in args {
let resolved: Option<(Arc<str>, Arc<str>)> = match arg {
Arg::Pos(Expr::Ident(s)) => {
let key: Arc<str> = Arc::from(s.as_str());
Some((key.clone(), key))
}
Arg::Pos(_) => match eval_arg(arg)? {
Val::Str(s) => {
let out_key: Arc<str> = if s.contains('.') || s.contains('[') {
match parse_path_segs(&s).first() {
Some(PathSeg::Field(f)) => Arc::from(f.as_str()),
Some(PathSeg::Index(i)) => Arc::from(i.to_string().as_str()),
None => s.clone(),
}
} else {
s.clone()
};
Some((out_key, s))
}
_ => None,
},
Arg::Named(alias, Expr::Ident(src)) => {
Some((Arc::from(alias.as_str()), Arc::from(src.as_str())))
}
Arg::Named(alias, _) => match eval_arg(arg)? {
Val::Str(s) => Some((Arc::from(alias.as_str()), s)),
_ => None,
},
};
let Some((out_key, src)) = resolved else {
continue;
};
let source = if src.contains('.') || src.contains('[') {
PickSource::Path(parse_path_segs(&src))
} else {
PickSource::Field(src)
};
specs.push(PickSpec { out_key, source });
}
return pick_specs_apply(&recv, &specs)
.ok_or_else(|| EvalError("pick: expected object or array of objects".into()));
}
BuiltinMethod::Omit => {
let mut keys = Vec::with_capacity(args.len());
for idx in 0..args.len() {
keys.push(str_arg!(idx)?);
}
BuiltinCall::new(method, BuiltinArgs::StrVec(keys))
}
BuiltinMethod::Repeat | BuiltinMethod::Indent => {
let prefix_arg = if matches!(method, BuiltinMethod::Indent) && !args.is_empty() {
match &args[0] {
Arg::Pos(Expr::Str(s)) => Some(Arc::<str>::from(s.as_str())),
Arg::Named(_, Expr::Str(s)) => Some(Arc::<str>::from(s.as_str())),
_ => None,
}
} else {
None
};
if let Some(prefix) = prefix_arg {
BuiltinCall::new(method, BuiltinArgs::Str(prefix))
} else {
let n = if args.is_empty() {
if matches!(method, BuiltinMethod::Indent) {
2
} else {
1
}
} else {
i64_arg!(0)?.max(0) as usize
};
BuiltinCall::new(method, BuiltinArgs::Usize(n))
}
}
BuiltinMethod::PadLeft | BuiltinMethod::PadRight | BuiltinMethod::Center => {
BuiltinCall::new(
method,
BuiltinArgs::Pad {
width: i64_arg!(0)?.max(0) as usize,
fill: fill_arg!(1)?,
},
)
}
BuiltinMethod::SetPath => {
return set_path_apply(&recv, &str_arg!(0)?, &arg_val!(1)?)
.ok_or_else(|| EvalError("set_path: builtin unsupported".into()));
}
BuiltinMethod::DelPaths => {
let mut paths = Vec::with_capacity(args.len());
for idx in 0..args.len() {
paths.push(str_arg!(idx)?);
}
return del_paths_apply(&recv, &paths)
.ok_or_else(|| EvalError("del_paths: builtin unsupported".into()));
}
_ => {
return Err(EvalError(format!(
"{}: builtin not migrated to builtins.rs AST adapter",
name
)));
}
};
call.try_apply(&recv)?
.ok_or_else(|| EvalError(format!("{}: builtin unsupported", name)))
}
fn arg_uses_current(arg: &crate::parse::ast::Arg) -> bool {
use crate::parse::ast::{Arg, Expr};
fn walk(e: &Expr) -> bool {
match e {
Expr::Current => true,
Expr::Lambda { .. } => false, Expr::Chain(base, _) => walk(base),
Expr::UnaryNeg(x) | Expr::Not(x) => walk(x),
Expr::BinOp(l, _, r) => walk(l) || walk(r),
Expr::Coalesce(l, r) => walk(l) || walk(r),
Expr::IfElse { cond, then_, else_ } => walk(cond) || walk(then_) || walk(else_),
Expr::Try { body, default } => walk(body) || walk(default),
Expr::Cast { expr, .. } => walk(expr),
Expr::FString(parts) => parts.iter().any(|p| match p {
crate::parse::ast::FStringPart::Interp { expr, .. } => walk(expr),
_ => false,
}),
Expr::Let { init, body, .. } => walk(init) || walk(body),
_ => false,
}
}
match arg {
Arg::Pos(e) | Arg::Named(_, e) => walk(e),
}
}
pub(crate) fn eval_builtin_no_args(recv: Val, name: &str) -> Result<Val, EvalError> {
eval_builtin_method(
recv,
name,
&[],
|_| {
Err(EvalError(format!(
"{}: unexpected argument evaluation",
name
)))
},
|_, _| Err(EvalError(format!("{}: unexpected item evaluation", name))),
|_, _, _| Err(EvalError(format!("{}: unexpected pair evaluation", name))),
)
}
impl BuiltinMethod {
#[inline]
pub fn is_pipeline_element_method(self) -> bool {
crate::builtins::registry::pipeline_element(crate::builtins::registry::BuiltinId::from_method(
self,
))
}
}
pub mod ops;
pub(crate) mod builtin;
pub(crate) mod defs;
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) mod helpers;
pub(crate) mod registry;
pub use ops::array::*;
pub use ops::collection::*;
pub use ops::misc::*;
pub use ops::path::*;
pub use ops::regex::*;
pub use ops::schema::*;
pub use ops::string::*;