Skip to content

Commit

Permalink
make most function args evaluate lazily and support first-class funct…
Browse files Browse the repository at this point in the history
…ions (29)
  • Loading branch information
adamtrilling committed May 1, 2022
1 parent edd3c07 commit 04ac575
Show file tree
Hide file tree
Showing 31 changed files with 413 additions and 258 deletions.
2 changes: 1 addition & 1 deletion rust/src/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ mod tests {
input: "[",
rule: Rule::query,
positives: vec![
Rule::piped_expr, Rule::function, Rule::indexed_value, Rule::not_op,
Rule::piped_expr, Rule::fn_ident, Rule::indexed_value, Rule::not_op,
Rule::infix_expr, Rule::compound_reference, Rule::object, Rule::array,
Rule::ident, Rule::string, Rule::number, Rule::bool, Rule::null, Rule::at,
Rule::dollar
Expand Down
3 changes: 2 additions & 1 deletion rust/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ pub fn eval(pair: Pair<Rule>, data: &Value, context: Option<Value>) -> Result<Va
Ok(data.clone())
}
}
Rule::ident | Rule::function => function::eval(pair, &data, context),
Rule::ident => function::ident_eval(pair, &data, context),
Rule::function => function::eval(pair, &data, context),
Rule::bool | Rule::number | Rule::string | Rule::null => terminal::eval(pair),
Rule::array => array::eval(pair, &data),
Rule::object => object::eval(pair, &data),
Expand Down
6 changes: 3 additions & 3 deletions rust/src/function/apply.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::{expr, Result, Value};
use super::args::ArgParser;
use crate::{expr, Result, Value};

pub fn apply(arg_parser: ArgParser) -> Result<Value> {
let (func, target) = arg_parser.one_func_one_arg()?;
expr::eval(func.clone(), &target, None)
let (func, target) = arg_parser.two_args()?;
expr::eval(func.to_pair()?, &target.to_value(arg_parser.data)?, None)
}

#[cfg(test)]
Expand Down
181 changes: 103 additions & 78 deletions rust/src/function/args.rs
Original file line number Diff line number Diff line change
@@ -1,111 +1,136 @@
use crate::{expr, Error, Result, Rule, Value};
use pest::iterators::Pair;

#[derive(Clone)]
#[derive(Clone, Debug)]
pub enum Arg<'a> {
Eager(Value),
Lazy(Pair<'a, Rule>),
}

#[derive(Clone, Debug)]
pub struct ArgParser<'a> {
pair: Pair<'a, Rule>,
pub data: &'a Value,
context: Option<Value>,
pub function: String,
args: Vec<Arg<'a>>,
}

impl<'a> ArgParser<'a> {
pub fn new(pair: Pair<'a, Rule>, data: &'a Value, context: Option<Value>) -> Result<Self> {
let function = match pair.as_rule() {
Rule::ident => pair.as_str().to_string(),
Rule::function => ident_from_fn(pair.clone().into_inner().next().unwrap(), data)?,
_ => unreachable!(),
};

Ok(Self {
pair: pair,
data: data,
context: context,
function: function,
})
impl<'a> Arg<'a> {
pub fn to_pair(&self) -> Result<Pair<'a, Rule>> {
match self {
Arg::Lazy(pair) => Ok(pair.clone()),
Arg::Eager(_) => Err(Error::eval(
"can't convert evaluated argument to lazy argument".to_string(),
)),
}
}

fn eager_args(&self, skip: usize) -> Result<Vec<Value>> {
let mut args = self
.pair
.clone()
.into_inner()
.skip(skip)
.map(|arg| expr::eval(arg, self.data, None))
.collect::<Result<Vec<Value>>>()?;

if let Some(ctx) = self.context.clone() {
args.push(ctx);
pub fn to_value(&self, data: &Value) -> Result<Value> {
match self {
Arg::Eager(val) => Ok(val.clone()),
Arg::Lazy(pair) => expr::eval(pair.clone(), data, None),
}

Ok(args)
}

fn lazy_arg(&self) -> Result<Pair<Rule>> {
match self.pair.clone().into_inner().skip(1).next() {
None => Err(Error::eval("expected fn as argument, got none".to_string())),
Some(pair) => Ok(pair),
pub fn to_ident(&self) -> Result<Value> {
match self {
Arg::Eager(Value::Ident(val)) => Ok(Value::Ident(val.clone())),
Arg::Eager(val) => Err(Error::eval(format!("expected ident, got {:?}", val))),
Arg::Lazy(pair) => match pair.as_rule() {
Rule::ident => Ok(Value::Ident(pair.as_str().to_string())),
_ => Err(Error::eval(format!("pair isn't an ident: {:?}", pair))),
},
}
}
}

pub fn one_arg(&self) -> Result<Value> {
let args = self.eager_args(1)?;
if args.len() == 1 {
Ok(args[0].clone())
} else {
Err(Error::arity(self.function.clone(), 1, args.len()))
impl<'a> ArgParser<'a> {
pub fn from_pair(
pair: Pair<'a, Rule>,
data: &'a Value,
context: Option<Value>,
) -> Result<Self> {
let mut components_itr = pair.into_inner();
let function_pair = components_itr.next().unwrap().into_inner().next().unwrap();

let function = match function_pair.as_rule() {
Rule::ident => function_pair.as_str().to_string(),
Rule::function => match super::fn_ident_eval(function_pair, data, context.clone())? {
Value::Ident(s) => s,
value => {
return Err(Error::eval(format!(
"higher-order function must return an identifier (got {:?}",
value
)))
}
},
_ => {
return Err(Error::unimplemented(format!(
"fn_ident rule {:?}",
function_pair
)))
}
};

let mut args: Vec<Arg<'a>> = components_itr
.next()
.unwrap()
.into_inner()
.map(|arg| Arg::Lazy(arg))
.collect();

if let Some(ctx) = context.clone() {
args.push(Arg::Eager(ctx));
}

Ok(Self {
data: data,
function: function,
args: args,
})
}

pub fn two_args(&self) -> Result<(Value, Value)> {
let args = self.eager_args(1)?;
if args.len() == 2 {
Ok((args[0].clone(), args[1].clone()))
} else {
Err(Error::arity(self.function.clone(), 2, args.len()))
}
pub fn from_ident(
pair: &Pair<'a, Rule>,
data: &'a Value,
context: Option<Value>,
) -> Result<Self> {
let args = match context {
Some(ctx) => vec![Arg::Eager(ctx)],
None => vec![Arg::Eager(data.clone())],
};

Ok(Self {
data: data,
function: pair.as_str().to_string(),
args: args,
})
}

pub fn three_args(&self) -> Result<(Value, Value, Value)> {
let args = self.eager_args(1)?;
if args.len() == 3 {
Ok((args[0].clone(), args[1].clone(), args[2].clone()))
pub fn one_arg(&self) -> Result<Arg> {
if self.args.len() == 1 {
Ok(self.args[0].clone())
} else {
Err(Error::arity(self.function.clone(), 3, args.len()))
Err(Error::arity(self.function.to_string(), 1, self.args.len()))
}
}

pub fn one_func_one_arg(&self) -> Result<(Pair<Rule>, Value)> {
let func = self.lazy_arg()?;
let args = self.eager_args(2)?;

if args.len() == 1 {
Ok((func, args[0].clone()))
pub fn two_args(&self) -> Result<(Arg, Arg)> {
if self.args.len() == 2 {
Ok((self.args[0].clone(), self.args[1].clone()))
} else {
Err(Error::arity(self.function.clone(), 1, args.len()))
Err(Error::arity(self.function.to_string(), 2, self.args.len()))
}
}

pub fn one_func_two_args(&self) -> Result<(Pair<Rule>, Value, Value)> {
let func = self.lazy_arg()?;
let args = self.eager_args(2)?;

if args.len() == 2 {
Ok((func, args[0].clone(), args[1].clone()))
pub fn three_args(&self) -> Result<(Arg, Arg, Arg)> {
if self.args.len() == 3 {
Ok((
self.args[0].clone(),
self.args[1].clone(),
self.args[2].clone(),
))
} else {
Err(Error::arity(self.function.clone(), 2, args.len()))
Err(Error::arity(self.function.to_string(), 3, self.args.len()))
}
}
}

fn ident_from_fn<'a>(pair: Pair<'a, Rule>, data: &'a Value) -> Result<String> {
dbg!(pair.as_str());
match pair.as_rule() {
Rule::ident => Ok(pair.as_str().to_string()),
Rule::function => match super::eval(pair, data, None)? {
Value::Ident(s) => Ok(s),
_ => unreachable!(),
},
rule => Err(Error::unimplemented(format!("fn rule {:?}", rule))),
}
}
2 changes: 1 addition & 1 deletion rust/src/function/count.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::args::ArgParser;
use crate::{Error, Number, Result, Value};

pub fn count(arg_parser: ArgParser) -> Result<Value> {
let arg = arg_parser.one_arg()?;
let arg = arg_parser.one_arg()?.to_value(arg_parser.data)?;

match arg {
Value::Array(arr) => Ok(Value::Number(Number::Int(arr.len() as i64))),
Expand Down
26 changes: 15 additions & 11 deletions rust/src/function/entries.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::{Error, Result, Value};
use super::args::ArgParser;
use crate::{Error, Result, Value};

pub fn entries(arg_parser: ArgParser) -> Result<Value> {
let arg = arg_parser.one_arg()?;
let arg = arg_parser.one_arg()?.to_value(arg_parser.data)?;
match arg {
Value::Object(val) => Ok(Value::Array(
val.iter()
Expand Down Expand Up @@ -38,15 +38,19 @@ mod tests {
rule: Rule::query,
tokens: [
function(0,20, [
ident(0,7),
object(8,20, [
keyval(9,13, [
ident(9,10),
number(12,13)
]),
keyval(15,19, [
ident(15,16),
number(18,19)
fn_ident(0,7, [
ident(0,7)
]),
fn_args(8,20, [
object(8,20, [
keyval(9,13, [
ident(9,10),
number(12,13)
]),
keyval(15,19, [
ident(15,16),
number(18,19)
])
])
])
])
Expand Down
12 changes: 6 additions & 6 deletions rust/src/function/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use crate::{Error, Result};
use std::collections::BTreeMap;

pub fn filter(arg_parser: ArgParser) -> Result<Value> {
let args = arg_parser.one_func_one_arg()?;
match args {
let (func_arg, target_arg) = arg_parser.two_args()?;
match (func_arg.to_pair()?, target_arg.to_value(arg_parser.data)?) {
(func, Value::Array(val)) => {
let mut filtered = vec![];
for elt in val {
Expand All @@ -25,8 +25,8 @@ pub fn filter(arg_parser: ArgParser) -> Result<Value> {
}

pub fn filterkeys(arg_parser: ArgParser) -> Result<Value> {
let args = arg_parser.one_func_one_arg()?;
match args {
let (func_arg, target_arg) = arg_parser.two_args()?;
match (func_arg.to_pair()?, target_arg.to_value(arg_parser.data)?) {
(func, Value::Object(val)) => {
let mut mapped: BTreeMap<String, Value> = BTreeMap::new();
for (k, v) in val.iter() {
Expand All @@ -45,8 +45,8 @@ pub fn filterkeys(arg_parser: ArgParser) -> Result<Value> {
}

pub fn filtervalues(arg_parser: ArgParser) -> Result<Value> {
let args = arg_parser.one_func_one_arg()?;
match args {
let (func_arg, target_arg) = arg_parser.two_args()?;
match (func_arg.to_pair()?, target_arg.to_value(arg_parser.data)?) {
(func, Value::Object(val)) => {
let mut mapped: BTreeMap<String, Value> = BTreeMap::new();
for (k, v) in val.iter() {
Expand Down
4 changes: 2 additions & 2 deletions rust/src/function/find.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use crate::prefix::truthiness;
use crate::{expr, Error, Result, Value};

pub fn find(arg_parser: ArgParser) -> Result<Value> {
let args = arg_parser.one_func_one_arg()?;
match args {
let (func_arg, target_arg) = arg_parser.two_args()?;
match (func_arg.to_pair()?, target_arg.to_value(arg_parser.data)?) {
(func, Value::Array(val)) => {
for elt in val.into_iter() {
let predicate = expr::eval(func.clone(), &elt, None)?;
Expand Down
2 changes: 1 addition & 1 deletion rust/src/function/flatten.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::args::ArgParser;
use crate::{Error, Result, Value};

pub fn flatten(arg_parser: ArgParser) -> Result<Value> {
let arg = arg_parser.one_arg()?;
let arg = arg_parser.one_arg()?.to_value(arg_parser.data)?;
match arg {
Value::Array(val) => {
let mut flattened: Vec<Value> = vec![];
Expand Down
2 changes: 1 addition & 1 deletion rust/src/function/float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::args::ArgParser;
use crate::{Error, Number, Result, Value};

pub fn float(arg_parser: ArgParser) -> Result<Value> {
let arg = arg_parser.one_arg()?;
let arg = arg_parser.one_arg()?.to_value(arg_parser.data)?;

match arg {
Value::Number(Number::Float(_)) => Ok(arg.clone()),
Expand Down
2 changes: 1 addition & 1 deletion rust/src/function/fromentries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{Error, Result, Value};
use std::collections::BTreeMap;

pub fn fromentries(arg_parser: ArgParser) -> Result<Value> {
let arg = arg_parser.one_arg()?;
let arg = arg_parser.one_arg()?.to_value(arg_parser.data)?;
match arg {
Value::Array(entries) => {
let mut result = BTreeMap::new();
Expand Down
5 changes: 3 additions & 2 deletions rust/src/function/groupby.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ use crate::{expr, Error, Result, Value};
use std::collections::BTreeMap;

pub fn groupby(arg_parser: ArgParser) -> Result<Value> {
let args = arg_parser.one_func_one_arg()?;
match args {
let (func_arg, target_arg) = arg_parser.two_args()?;

match (func_arg.to_pair()?, target_arg.to_value(arg_parser.data)?) {
(func, Value::Array(entries)) => {
let mut result: BTreeMap<String, Value> = BTreeMap::new();
for entry in entries.into_iter() {
Expand Down
Loading

0 comments on commit 04ac575

Please sign in to comment.