Skip to content

Commit 1c6549c

Browse files
authored
Merge pull request #51 from thatportugueseguy/add-value-iterator
Add value iterator
2 parents f9c430b + bf2deb9 commit 1c6549c

File tree

6 files changed

+81
-31
lines changed

6 files changed

+81
-31
lines changed

source/Ast.ml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ type expression =
3232
(* Objects *)
3333
| Walk of expression (* walk() *)
3434
| Transpose of expression (* transpose() *)
35-
| Key of string * bool (* .foo *)
35+
| Key of string (* .foo *)
36+
| Optional of expression (* ? *)
3637
| Has of expression (* has(x) *)
3738
| Keys (* keys *)
3839
| Floor (* floor *)
@@ -58,6 +59,7 @@ type expression =
5859
| IsNan
5960
(* Array *)
6061
| Index of int (* .[1] *)
62+
| Iterator (* .[] *)
6163
| Range of int * int option * int option (* range(1, 10) *)
6264
| Flatten (* flatten *)
6365
| Head (* head *)

source/Compiler.ml

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -229,15 +229,13 @@ let make_error_missing_member op key (value : Json.t) =
229229
^ ":" ^ Formatting.enter 1
230230
^ Chalk.gray (Json.to_string value ~colorize:false ~summarize:true)
231231

232-
let member (key : string) (opt : bool) (json : Json.t) =
232+
let member (key : string) (json : Json.t) =
233233
match json with
234234
| `Assoc _assoc -> (
235235
let access_member = Json.member key json in
236-
match (access_member, opt) with
237-
| `Null, true -> Output.return access_member
238-
| `Null, false -> Error (make_error_missing_member ("." ^ key) key json)
239-
| _, false -> Output.return access_member
240-
| _, true -> Output.return access_member)
236+
match access_member with
237+
| `Null -> Error (make_error_missing_member ("." ^ key) key json)
238+
| _ -> Output.return access_member)
241239
| _ -> Error (make_error ("." ^ key) json)
242240

243241
let index (value : int) (json : Json.t) =
@@ -288,13 +286,25 @@ let slice (start : int option) (finish : int option) (json : Json.t) =
288286
("[" ^ string_of_int start ^ ":" ^ string_of_int finish ^ "]")
289287
json)
290288

289+
let iterator (json : Json.t) =
290+
match json with
291+
| `List [] -> empty
292+
| `List items -> Ok items
293+
| `Assoc obj -> Ok (List.map snd obj)
294+
| _ -> Error (make_error "[]" json)
295+
291296
let rec compile expression json : (Json.t list, string) result =
292297
match expression with
293298
| Identity -> Output.return json
294299
| Empty -> empty
295300
| Keys -> keys json
296-
| Key (key, opt) -> member key opt json
301+
| Key key -> member key json
302+
| Optional expr -> (
303+
match compile expr json with
304+
| Ok values -> Ok values (* If successful, return the values *)
305+
| Error _ -> Output.return `Null)
297306
| Index idx -> index idx json
307+
| Iterator -> iterator json
298308
| Slice (start, finish) -> slice start finish json
299309
| Head -> head json
300310
| Tail -> tail json
@@ -392,7 +402,7 @@ and handle_objects list json =
392402
r
393403
| Literal (String key), Some right_expr -> (
394404
match right_expr with
395-
| Key (search_val, _) -> (
405+
| Key search_val -> (
396406
match json with
397407
| `Assoc l -> (
398408
match List.assoc_opt search_val l with

source/Parser.mly

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ term:
123123
| NULL
124124
{ Literal(Null) }
125125
| RANGE; OPEN_PARENT; nl = separated_nonempty_list(SEMICOLON, NUMBER); CLOSE_PARENT;
126-
{
126+
{
127127
let nl = List.map int_of_float nl in
128128
match nl with
129129
| [] -> assert false (* nonempty_list *)
@@ -217,7 +217,7 @@ term:
217217
// List elements are item_expr, not sequence_expr, separated by COMMA
218218
| e = delimited(OPEN_BRACKET, separated_nonempty_list(COMMA, item_expr), CLOSE_BRACKET);
219219
{ List e }
220-
220+
221221
| OPEN_BRACE; CLOSE_BRACE;
222222
{ Object [] }
223223

@@ -230,6 +230,14 @@ term:
230230
| e = term; OPEN_BRACKET; i = number; CLOSE_BRACKET
231231
{ Pipe (e, Index (int_of_float i)) }
232232

233+
/* Iterator: .[] */
234+
| e = term; OPEN_BRACKET; CLOSE_BRACKET
235+
{ Pipe (e, Iterator) }
236+
237+
/* Optiona iterator: .[]? */
238+
| e = term; OPEN_BRACKET; CLOSE_BRACKET; QUESTION_MARK
239+
{ Pipe (e, Optional (Iterator)) }
240+
233241
/* Full slice with both indices: .[1:5] */
234242
| e = term; OPEN_BRACKET; start = number; COLON; end_ = number; CLOSE_BRACKET
235243
{ Pipe (e, Slice (Some (int_of_float start), Some (int_of_float end_))) }
@@ -243,16 +251,18 @@ term:
243251
{ Pipe (e, Slice (None, Some (int_of_float end_))) }
244252

245253
| DOT; k = STRING; opt = boption(QUESTION_MARK)
246-
{ Key (k, opt) }
247-
248-
| e = term; DOT; k = STRING; opt = boption(QUESTION_MARK)
249-
{ Pipe (e, Key (k, opt)) }
250-
251254
| DOT; k = IDENTIFIER; opt = boption(QUESTION_MARK)
252-
{ Key (k, opt) }
255+
{ match opt with
256+
| true -> Optional (Key k)
257+
| false -> Key k
258+
}
253259

260+
| e = term; DOT; k = STRING; opt = boption(QUESTION_MARK)
254261
| e = term; DOT; k = IDENTIFIER; opt = boption(QUESTION_MARK)
255-
{ Pipe (e, Key (k, opt)) }
262+
{ match opt with
263+
| true -> Pipe (e, Optional (Key k))
264+
| false -> Pipe (e, Key k)
265+
}
256266

257267
| IF; cond = item_expr; THEN e1 = term; elifs = list(elif_term) ELSE; e2 = term; END
258268
{

test/Test_parse.ml

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,17 @@ let tests =
2222
case ".[-1]" (Pipe (Identity, Index (-1)));
2323
case ".[1]" (Pipe (Identity, Index 1));
2424
case "[1]" (List [ Literal (Number 1.) ]);
25-
case ".store.books" (Pipe (Key ("store", false), Key ("books", false)));
26-
case ".books[1]" (Pipe (Key ("books", false), Index 1));
27-
case ".books[1].author"
28-
(Pipe (Pipe (Key ("books", false), Index 1), Key ("author", false)));
29-
case ".store" (Key ("store", false));
25+
case ".store.books" (Pipe (Key "store", Key "books"));
26+
case ".books[1]" (Pipe (Key "books", Index 1));
27+
case ".books[1].author" (Pipe (Pipe (Key "books", Index 1), Key "author"));
28+
case ".store" (Key "store");
3029
case "." Identity;
31-
case ".store | .books" (Pipe (Key ("store", false), Key ("books", false)));
30+
case ".store | .books" (Pipe (Key "store", Key "books"));
3231
case ". | map(.price + 1)"
33-
(Pipe
34-
( Identity,
35-
Map (Operation (Key ("price", false), Add, Literal (Number 1.))) ));
36-
case ".WAT" (Key ("WAT", false));
32+
(Pipe (Identity, Map (Operation (Key "price", Add, Literal (Number 1.)))));
33+
case ".WAT" (Key "WAT");
3734
case "head" Head;
38-
case ".WAT?" (Key ("WAT", true));
35+
case ".WAT?" (Optional (Key "WAT"));
3936
case "1, 2" (Comma (Literal (Number 1.), Literal (Number 2.)));
4037
case "empty" Empty;
4138
case "(1, 2) + 3"
@@ -51,12 +48,17 @@ let tests =
5148
case "[1, 2]" (List [ Literal (Number 1.); Literal (Number 2.) ]);
5249
case "select(true)" (Select (Literal (Bool true)));
5350
case "[1][0]" (Pipe (List [ Literal (Number 1.) ], Index 0));
54-
case "[1].foo" (Pipe (List [ Literal (Number 1.) ], Key ("foo", false)));
55-
case "(empty).foo?" (Pipe (Empty, Key ("foo", true)));
51+
case "[1].foo" (Pipe (List [ Literal (Number 1.) ], Key "foo"));
52+
case "(empty).foo?" (Pipe (Empty, Optional (Key "foo")));
5653
case ".[1:3]" (Pipe (Identity, Slice (Some 1, Some 3)));
5754
case ".[1:]" (Pipe (Identity, Slice (Some 1, None)));
5855
case ".[:3]" (Pipe (Identity, Slice (None, Some 3)));
5956
case ".[-2:]" (Pipe (Identity, Slice (Some (-2), None)));
57+
case ".[]" (Pipe (Identity, Iterator));
58+
case ".foo[]" (Pipe (Key "foo", Iterator));
59+
case ".foo[]?" (Pipe (Key "foo", Optional Iterator));
60+
case ".foo?[]" (Pipe (Optional (Key "foo"), Iterator));
61+
case ".foo?[]?" (Pipe (Optional (Key "foo"), Optional Iterator));
6062
case "{}" (Object []);
6163
case "{\"foo\": 42, bar: [\"hello world\", 42], user}"
6264
(Object

test/Test_runtime.ml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ let tests =
3131
(* test "[{}]" "[2]" "[{}]"; *)
3232
(* test "[.[]]" "[\"a\"]" "[\"a\"]"; *)
3333

34+
test ".foo?" {|{"foo": 42}|} {|42|};
35+
test ".foo?" {|{}|} "null";
36+
3437
(* Array index tests *)
3538
test ".[0]" {|["a","b","c","d","e"]|} {|"a"|};
3639
test ".[3]" {|["a","b"]|} "null";
@@ -54,6 +57,12 @@ let tests =
5457
test ".[-4:-2]" {|"abcdefghi"|} {|"fg"|};
5558
test ".[-2:-4]" {|"abcde"|} {|""|};
5659

60+
(* Iterator tests *)
61+
test ".[]" {|["a","b","c"]|} "\"a\"\n\"b\"\n\"c\"";
62+
test ".[]" {|[{"name":"JSON", "good":true}, {"name":"XML", "good":false}]|} "{ \"name\": \"JSON\", \"good\": true }\n{ \"name\": \"XML\", \"good\": false }";
63+
test ".foo[]" {|{"foo":[1,2,3]}|} "1\n2\n3";
64+
test ".[]" {|{"a": 1, "b": 1}|} "1\n1";
65+
5766
test "1,1" "[]" "1\n1";
5867
test "1,." "[]" "1\n[]";
5968
test {|.foo | .bar|} {|{"foo": {"bar": 42}, "bar": "badvalue"}|} {|42|};

test/access.t

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,25 @@ non defined field gives back null when "?"
2222
$ echo '{ "foo": { "bar": {} }}' | query-json '.foo.bar.baz?'
2323
null
2424

25+
$ echo '{}' | query-json '.foo'
26+
27+
Error: Trying to ".foo" on an object, that don't have the field "foo":
28+
{}
29+
2530
$ echo '{ "foo": { "bar": {} }}' | query-json '.foo.bar.baz'
2631

2732
Error: Trying to ".baz" on an object, that don't have the field "baz":
2833
{}
2934

35+
$ echo '{"foo":[1,2,3]}' | query-json '.foo[]'
36+
1
37+
2
38+
3
39+
40+
$ echo '{"foo":[1,2,3]}' | query-json '.fool[]'
41+
42+
Error: Trying to ".fool" on an object, that don't have the field "fool":
43+
{ "foo": ... }
44+
45+
$ echo '{"foo":[1,2,3]}' | query-json '.fool?[]?'
46+
null

0 commit comments

Comments
 (0)