Skip to content

Commit

Permalink
Refactor Len function using generalization
Browse files Browse the repository at this point in the history
Fix grammar to use arrow notation for name binding after functions
Implement head function
Implement tail function
Adapt test cases
  • Loading branch information
mitghi committed Mar 18, 2023
1 parent 2808bb5 commit 712ec15
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 56 deletions.
32 changes: 19 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,16 @@ Jetro combines access path with functions which operate on those values matched

By convention, functions are denoted using # operator. Functions can be composed.

- #pick('string' | expression, ...) [ as | as* 'binding_value' ]
- #all
- #sum
- #formats('format with placeholder {} {}', 'key_a', 'key_b') [ as | as* 'binding_value' ]
- #filter('target_key' (>, <, >=, <=, ==, ~=, !=) (string, boolean, number))

| Function | Action |
| -------- | ------ |
| #pick('string' \| expression, ...) [ as \| as* 'binding_value' ] | Select a key from an object, bind it to a name, select multiple sub queries to create new object |
| #head | Head of the list|
| #tail | Tail of the list |
| #reverse | Reverse the list |
| #all | Whether all boolean values are true |
| #sum | Sum of numbers |
| #formats('format with placeholder {} {}', 'key_a', 'key_b') [ -> \| ->* 'binding_value' ] | Insert formatted key:value into object or return it as single key:value |
| #filter('target_key' (>, <, >=, <=, ==, ~=, !=) (string, boolean, number)) | Perform Filter on list |

```json
{
Expand Down Expand Up @@ -210,7 +214,7 @@ By convention, functions are denoted using # operator. Functions can be composed
---

```
>/..values/#filter('priority' < 11 and 'name' ~= 'STEam')
>/..rolls/#filter('priority' < 11 and 'roll' ~= 'ON Side')
```

<details>
Expand All @@ -221,8 +225,8 @@ By convention, functions are denoted using # operator. Functions can be composed
```json
[
{
"name": "STEAM",
"priority": 2
"priority": 1,
"roll": "on side"
}
]
```
Expand All @@ -231,7 +235,7 @@ By convention, functions are denoted using # operator. Functions can be composed
---

```
>/entry/values/#filter('name' ~= 'gearBOX')/[0]/#formats('Enter {}', 'name') as* 'name'
>/..rolls/#head/#formats('Rolling {}', 'roll') -> 'msg'
```

<details>
Expand All @@ -241,7 +245,9 @@ By convention, functions are denoted using # operator. Functions can be composed

```json
{
"name": "Enter GEARBOX"
"msg": "Rolling on side",
"priority": 1,
"roll": "on side"
}
```
</details>
Expand Down Expand Up @@ -309,7 +315,7 @@ By convention, functions are denoted using # operator. Functions can be composed
---

```
>/#pick('foo', >/..person/#formats('Herrn {} {}', 'firstname', 'lastname') as* 'fullname')
>/#pick('foo', >/..person/#formats('Herrn {} {}', 'firstname', 'lastname') ->* 'fullname')
```

<details>
Expand Down Expand Up @@ -339,7 +345,7 @@ By convention, functions are denoted using # operator. Functions can be composed
---

```
>/..foo/..contract/#pick('kind' as 'contract', </..person/#formats('Welcome {}', 'firstname') as* 'welcome_message')
>/..foo/..contract/#pick('kind' as 'contract', </..person/#formats('Welcome {}', 'firstname') ->* 'welcome_message')
```

<details>
Expand Down
27 changes: 0 additions & 27 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -850,33 +850,6 @@ impl<'a> Context<'a> {
todo!("implement handling of unmatched arm for filter");
}
},
(Filter::Len, Some(tail)) => match *current.value {
Value::Object(ref obj) => {
push_to_stack_or_produce!(
self.results,
self.stack,
tail,
Value::Number(serde_json::Number::from(obj.len()))
);
}
Value::Array(ref array) => {
push_to_stack_or_produce!(
self.results,
self.stack,
tail,
Value::Number(serde_json::Number::from(array.len()))
);
}
_ => {
let count: i64 = self.reduce_stack_to_num_count() + 1;
push_to_stack_or_produce!(
self.results,
self.stack,
tail,
Value::Number(serde_json::Number::from(count))
);
}
},

(Filter::All, Some(tail)) => match *current.value {
Value::Object(ref _obj) => {}
Expand Down
113 changes: 113 additions & 0 deletions src/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ impl Callable for Formats {
return Err(Error::FuncEval("invalid type, expected string".to_owned()));
}
};
if func.alias.is_none() {
return Err(Error::FuncEval(
"expected alias to have some value".to_owned(),
));
}
let mut args: Vec<String> = vec![];
for v in func.args[1..].iter() {
match &v {
Expand Down Expand Up @@ -157,12 +162,87 @@ impl Callable for SumFn {
}
}

struct LenFn;
impl Callable for LenFn {
fn call(
&mut self,
_func: &Func,
value: &Value,
ctx: Option<&mut Context<'_>>,
) -> Result<Value, Error> {
match &value {
Value::Object(ref obj) => {
return Ok(Value::Number(serde_json::Number::from(obj.len())));
}
Value::Array(ref array) => {
return Ok(Value::Number(serde_json::Number::from(array.len())));
}
_ => {
let count: i64 = ctx.unwrap().reduce_stack_to_num_count() + 1;
return Ok(Value::Number(serde_json::Number::from(count)));
}
}
}
}

struct Head;
impl Callable for Head {
fn call(
&mut self,
_func: &Func,
value: &Value,
_ctx: Option<&mut Context<'_>>,
) -> Result<Value, Error> {
match &value {
Value::Array(ref array) => {
if array.len() == 0 {
return Ok(value.clone());
} else {
let head = array[0].clone();
return Ok(head);
}
}
_ => {
return Err(Error::FuncEval("expected array".to_owned()));
}
}
}
}

struct Tail;
impl Callable for Tail {
fn call(
&mut self,
_func: &Func,
value: &Value,
_ctx: Option<&mut Context<'_>>,
) -> Result<Value, Error> {
match &value {
Value::Array(ref array) => match array.len() {
0 | 1 => {
return Ok(Value::Array(vec![]));
}
_ => {
let tail = array[1..].to_vec();
return Ok(Value::Array(tail));
}
},
_ => {
return Err(Error::FuncEval("expected array".to_owned()));
}
}
}
}

impl Default for FuncRegistry {
fn default() -> Self {
let mut output = FuncRegistry::new();
output.register("reverse", Box::new(Reverse));
output.register("formats", Box::new(Formats));
output.register("sum", Box::new(SumFn));
output.register("len", Box::new(LenFn));
output.register("head", Box::new(Head));
output.register("tail", Box::new(Tail));
output
}
}
Expand Down Expand Up @@ -207,4 +287,37 @@ mod test {
Err(err) => panic!("{}", err),
};
}

#[test]
fn get_head() {
const NAME: &'static str = "head";
let mut m = FuncRegistry::new();
m.register("head", Box::new(Head));

assert_eq!(m.map.len(), 1);

let v = m.get(NAME);
assert_eq!(v.is_none(), false);

let d = v.unwrap().as_mut();
let serde_value = Value::Array(vec![
Value::String("foo".to_string()),
Value::String("bar".to_string()),
]);

let func = Func::new();
match d.call(&func, &serde_value, None) {
Ok(result) => match result {
Value::String(ref output) => {
assert_eq!(*output, Value::String("foo".to_string()));
}
_ => {
panic!("invalid types {:?}", result);
}
},
Err(err) => {
panic!("{}", err);
}
}
}
}
17 changes: 8 additions & 9 deletions src/grammar.pest
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@ slash = { "/" }
double_dot = _{ ".." }
ident = { (alpha | digit | special_charaters)+ }
_as = _{ "as" }
_arrow = _{ "->" }
as = { (" ")* ~ _as ~ (" ")* ~ literal }
_asDeref = _{ "as*"}
_arrowDeref = _{"->*"}
asDeref = { (" ")* ~ _asDeref ~ (" ")* ~ literal }
arrow = { (" ")* ~ _arrow ~ (" ")* ~ literal }
arrowDeref = { (" ")* ~ _arrowDeref ~ (" ")* ~ literal }
sharp = _{ "#" }
dot = _{ "." }
greater = { ">" }
Expand Down Expand Up @@ -51,22 +55,17 @@ pick = {
((",") ~ (" ")* ~ (literal_keyed | (sub_expression_keyed | sub_expression_keyed_reversed) ))* ~
(" ")* ~ ")"
}
sub_expression = { path ~ (lenFn | allFn | pickFn | filterFn | child | any_child | grouped_any | descendant_child | array_index | slice | array_to | array_from | fn)* }
sub_expression_reversed = { reverse_path ~ (lenFn | allFn | pickFn | filterFn | grouped_any | child | any_child | descendant_child | array_index | slice | array_to | array_from | fn)* }
sub_expression = { path ~ (allFn | pickFn | filterFn | child | any_child | grouped_any | descendant_child | array_index | slice | array_to | array_from | fn)* }
sub_expression_reversed = { reverse_path ~ (allFn | pickFn | filterFn | grouped_any | child | any_child | descendant_child | array_index | slice | array_to | array_from | fn )* }
sub_expression_keyed = { sub_expression ~ as? }
sub_expression_keyed_reversed = { sub_expression_reversed ~ as? }

squash = { sharp ~ "squash" ~ "(" ~ ")" }
sum = { sharp ~ "sum" }
len = { sharp ~ "len" }
all = { sharp ~ "all" }
allFn = { slash ~ all }
lenFn = { slash ~ len }
squashFn = { slash ~ squash }
pickFn = { slash ~ pick }
fnLit = { literal }
fnExpr = { sub_expression }
fnCall = { sharp ~ ident ~ (whitespace ~ lparen ~ ((filterStmtCollection|fnLit|fnExpr ) ~ whitespace ~ ((",")* ~ whitespace ~ (filterStmtCollection|fnLit|fnExpr))*)? ~ whitespace ~ rparen ~ whitespace)? ~ (as | asDeref)? }
fnCall = { sharp ~ ident ~ (whitespace ~ lparen ~ ((filterStmtCollection|fnLit|fnExpr ) ~ whitespace ~ ((",")* ~ whitespace ~ (filterStmtCollection|fnLit|fnExpr))*)? ~ whitespace ~ rparen ~ whitespace)? ~ (arrow | arrowDeref)? }
fn = {slash ~ fnCall}
filterStmt = { ( filter_elem ~ whitespace ~ cmp ~ whitespace ~ (float | truthy | literal | number ) ) }
filterStmtCollection = { filterStmt ~ whitespace ~ (logical_cmp ~ whitespace ~ filterStmt~ whitespace)* }
Expand All @@ -76,6 +75,6 @@ filterFn = { slash ~ filter }

expression = {
(path|reverse_path) ~
(lenFn | allFn | pickFn | filterFn | grouped_any | child | any_child | descendant_child | array_index | slice | array_to | array_from | fn)* ~
(allFn | pickFn | filterFn | grouped_any | child | any_child | descendant_child | array_index | slice | array_to | array_from | fn)* ~
EOI
}
38 changes: 31 additions & 7 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ pub(crate) fn parse<'a>(input: &'a str) -> Result<Vec<Filter>, pest::error::Erro
Rule::filterStmtCollection => {
todo!("handle filter statements");
}
Rule::r#as => {
Rule::arrow => {
func.alias = Some(
value
.clone()
Expand All @@ -76,7 +76,7 @@ pub(crate) fn parse<'a>(input: &'a str) -> Result<Vec<Filter>, pest::error::Erro
.to_string(),
);
}
Rule::asDeref => {
Rule::arrowDeref => {
func.alias = Some(
value
.clone()
Expand Down Expand Up @@ -114,7 +114,6 @@ pub(crate) fn parse<'a>(input: &'a str) -> Result<Vec<Filter>, pest::error::Erro
}
Rule::path | Rule::reverse_path => actions.push(Filter::Root),
Rule::allFn => actions.push(Filter::All),
Rule::lenFn => actions.push(Filter::Len),
Rule::grouped_any => {
let elem = token.into_inner().nth(1).unwrap().into_inner();
let mut values: Vec<String> = vec![];
Expand Down Expand Up @@ -503,7 +502,7 @@ mod test {

#[test]
fn test_format() {
let actions = parse(">/#formats('{}{}', 'name', 'alias') as 'some_key'").unwrap();
let actions = parse(">/#formats('{}{}', 'name', 'alias') -> 'some_key'").unwrap();
assert_eq!(
actions,
vec![
Expand Down Expand Up @@ -701,7 +700,7 @@ mod test {

#[test]
fn formats_without_deref() {
let actions = parse(">/foo/#formats('Msg {}', 'msg') as 'message'").unwrap();
let actions = parse(">/foo/#formats('Msg {}', 'msg') -> 'message'").unwrap();
assert_eq!(
actions,
vec![
Expand All @@ -723,7 +722,7 @@ mod test {
#[test]
fn pick_formats_deref() {
let actions =
parse(">/#pick('foo', >/bar/#formats('Msg {}', 'msg') as* 'message')").unwrap();
parse(">/#pick('foo', >/bar/#formats('Msg {}', 'msg') ->* 'message')").unwrap();
assert_eq!(
actions,
vec![
Expand Down Expand Up @@ -824,7 +823,7 @@ mod test {

#[test]
fn parse_call_ending_with_deref() {
let actions = parse(">/#call('format {}', 'a', 'a') as* 'test'").unwrap();
let actions = parse(">/#call('format {}', 'a', 'a') ->* 'test'").unwrap();
assert_eq!(
actions,
vec![
Expand All @@ -846,4 +845,29 @@ mod test {
]
);
}

#[test]
fn parse_len_inside_pick() {
let actions = parse(">/#pick(>/..priority/#len as 'test')").unwrap();
assert_eq!(
actions,
vec![
Filter::Root,
Filter::Pick(vec![PickFilterInner::KeyedSubpath {
subpath: vec![
Filter::Root,
Filter::Descendant("priority".to_string()),
Filter::Function(Func {
name: "len".to_string(),
args: vec![],
alias: None,
should_deref: false,
}),
],
alias: "test".to_string(),
reverse: false,
}]),
]
);
}
}

0 comments on commit 712ec15

Please sign in to comment.