Skip to content

Commit 5f6bca9

Browse files
authored
Merge pull request #48 from thatportugueseguy/add-array-string-slice
Add array and string slice. update index with tests
2 parents c0c58e7 + ed6bff0 commit 5f6bca9

File tree

6 files changed

+101
-7
lines changed

6 files changed

+101
-7
lines changed

source/Ast.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ type expression =
5050
| Map of expression
5151
(* .[] *)
5252
(* map(x) *)
53+
| Slice of int option * int option
54+
(* slice(Some 1, Some 10), slice(None, Some 10), slice(Some 1, None) *)
5355
| FlatMap of expression (* flat_map(x) *)
5456
| Reduce of expression (* reduce(x) *)
5557
| Select of expression (* select(x) *)

source/Compiler.ml

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ module Output = struct
4646

4747
let lift2 (f : 'a -> 'b -> 'c) (mx : ('a, string) result)
4848
(my : ('b, string) result) : ('c, string) result =
49-
match mx with
50-
| Ok x -> ( match my with Ok y -> Ok (f x y) | Error err -> Error err)
51-
| Error err -> Error err
49+
match (mx, my) with
50+
| Ok x, Ok y -> Ok (f x y)
51+
| Error err, _ | _, Error err -> Error err
5252

5353
let collect (xs : ('a list, string) result list) : ('a list, string) result =
5454
List.fold_right (lift2 ( @ )) xs empty
@@ -173,16 +173,58 @@ let index (value : int) (json : Json.t) =
173173
match json with
174174
| `List list when List.length list > value ->
175175
Output.return (Json.index value json)
176-
| `List list -> Error (make_acessing_to_missing_item value (List.length list))
176+
| `List _ -> Output.return `Null
177177
| _ -> Error (make_error ("[" ^ string_of_int value ^ "]") json)
178178

179+
let slice (start : int option) (end_ : int option) (json : Json.t) =
180+
let start =
181+
match (json, start) with
182+
| `String s, Some start when start > String.length s -> String.length s
183+
| `String s, Some start when start < 0 -> start + String.length s
184+
| `List l, Some start when start > List.length l -> List.length l
185+
| `List l, Some start when start < 0 -> start + List.length l
186+
| (`String _ | `List _), Some start -> start
187+
| (`String _ | `List _), None -> 0
188+
| _ -> assert false
189+
in
190+
let end_ =
191+
match (json, end_) with
192+
| `String s, None -> String.length s
193+
| `String s, Some end_ when end_ > String.length s -> String.length s
194+
| `String s, Some end_ when end_ < 0 -> end_ + String.length s
195+
| `List l, None -> List.length l
196+
| `List l, Some end_ when end_ > List.length l -> List.length l
197+
| `List l, Some end_ when end_ < 0 -> end_ + List.length l
198+
| (`String _ | `List _), Some end_ -> end_
199+
| _ -> assert false
200+
in
201+
match json with
202+
| `String _s when end_ < start -> Output.return (`String "")
203+
| `String s -> Output.return (`String (String.sub s start (end_ - start)))
204+
| `List _l when end_ < start -> Output.return (`List [])
205+
| `List l ->
206+
let sliced =
207+
List.fold_left
208+
(fun (acc, i) x ->
209+
if i >= start && i < end_ then (x :: acc, i + 1) else (acc, i + 1))
210+
([], 0) l
211+
|> fst |> List.rev
212+
in
213+
Output.return (`List sliced)
214+
| _ ->
215+
Error
216+
(make_error
217+
("[" ^ string_of_int start ^ ":" ^ string_of_int end_ ^ "]")
218+
json)
219+
179220
let rec compile expression json : (Json.t list, string) result =
180221
match expression with
181222
| Identity -> Output.return json
182223
| Empty -> empty
183224
| Keys -> keys json
184225
| Key (key, opt) -> member key opt json
185226
| Index idx -> index idx json
227+
| Slice (start, end_) -> slice start end_ json
186228
| Head -> head json
187229
| Tail -> tail json
188230
| Length -> length json

source/Parser.mly

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
%token CLOSE_BRACKET
2929

3030
%token COMMA
31+
%token COLON
3132
/* %token OPEN_BRACE
3233
%token CLOSE_BRACE */
3334
%token EOF
@@ -92,20 +93,26 @@ item_expr:
9293
| e = term
9394
{ e }
9495

96+
number:
97+
| n = NUMBER;
98+
{ n }
99+
| SUB; n = NUMBER;
100+
{ -.n }
101+
95102
term:
96103
| DOT;
97104
{ Identity }
98105
| RECURSE;
99106
{ Recurse }
100107
| s = STRING;
101108
{ Literal (String s) }
102-
| n = NUMBER;
109+
| n = number;
103110
{ Literal (Number n) }
104111
| b = BOOL;
105112
{ Literal (Bool b) }
106113
| NULL
107114
{ Literal(Null) }
108-
| f = FUNCTION; from = NUMBER; SEMICOLON; upto = NUMBER; CLOSE_PARENT;
115+
| f = FUNCTION; from = number; SEMICOLON; upto = number; CLOSE_PARENT;
109116
{ match f with
110117
| "range" -> Range (int_of_float from, int_of_float upto)
111118
| _ -> failwith (f ^ " is not a valid function")
@@ -197,9 +204,21 @@ term:
197204
// Parentheses allow a full sequence_expr inside, reducing to an item_expr
198205
| OPEN_PARENT; e = sequence_expr; CLOSE_PARENT;
199206
{ e }
200-
| e = term; OPEN_BRACKET; i = NUMBER; CLOSE_BRACKET
207+
| e = term; OPEN_BRACKET; i = number; CLOSE_BRACKET
201208
{ Pipe (e, Index (int_of_float i)) }
202209

210+
/* Full slice with both indices: .[1:5] */
211+
| e = term; OPEN_BRACKET; start = number; COLON; end_ = number; CLOSE_BRACKET
212+
{ Pipe (e, Slice (Some (int_of_float start), Some (int_of_float end_))) }
213+
214+
/* Start-only slice: .[3:] */
215+
| e = term; OPEN_BRACKET; start = number; COLON; CLOSE_BRACKET
216+
{ Pipe (e, Slice (Some (int_of_float start), None)) }
217+
218+
/* End-only slice: .[:3] */
219+
| e = term; OPEN_BRACKET; COLON; end_ = number; CLOSE_BRACKET
220+
{ Pipe (e, Slice (None, Some (int_of_float end_))) }
221+
203222
| DOT; k = STRING; opt = boption(QUESTION_MARK)
204223
{ Key (k, opt) }
205224

source/Tokenizer.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ type token =
1818
| OPEN_BRACKET
1919
| CLOSE_BRACKET
2020
| SEMICOLON
21+
| COLON
2122
| DOT
2223
| RECURSE
2324
| PIPE
@@ -80,6 +81,7 @@ let rec tokenize buf =
8081
| "]" -> Ok CLOSE_BRACKET
8182
| "|" -> Ok PIPE
8283
| ";" -> Ok SEMICOLON
84+
| ":" -> Ok COLON
8385
| "," -> Ok COMMA
8486
| "?" -> Ok QUESTION_MARK
8587
| "null" -> Ok NULL

test/Test_parse.ml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ let case input expected =
1919

2020
let tests =
2121
[
22+
case ".[-1]" (Pipe (Identity, Index (-1)));
2223
case ".[1]" (Pipe (Identity, Index 1));
2324
case "[1]" (List [ Literal (Number 1.) ]);
2425
case ".store.books" (Pipe (Key ("store", false), Key ("books", false)));
@@ -44,4 +45,8 @@ let tests =
4445
case "[1][0]" (Pipe (List [ Literal (Number 1.) ], Index 0));
4546
case "[1].foo" (Pipe (List [ Literal (Number 1.) ], Key ("foo", false)));
4647
case "(empty).foo?" (Pipe (Empty, Key ("foo", true)));
48+
case ".[1:3]" (Pipe (Identity, Slice (Some 1, Some 3)));
49+
case ".[1:]" (Pipe (Identity, Slice (Some 1, None)));
50+
case ".[:3]" (Pipe (Identity, Slice (None, Some 3)));
51+
case ".[-2:]" (Pipe (Identity, Slice (Some (-2), None)));
4752
]

test/Test_runtime.ml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,30 @@ let tests =
3030
test "[[2]]" "[3]" "[ [ 2 ] ]";
3131
(* test "[{}]" "[2]" "[{}]"; *)
3232
(* test "[.[]]" "[\"a\"]" "[\"a\"]"; *)
33+
34+
(* Array index tests *)
35+
test ".[0]" {|["a","b","c","d","e"]|} {|"a"|};
36+
test ".[3]" {|["a","b"]|} "null";
37+
test ".[-1]" {|["a","b","c","d","e"]|} {|"e"|};
38+
39+
(* Array / String slice tests *)
40+
test ".[2:4]" {|["a","b","c","d","e"]|} {|[ "c", "d" ]|};
41+
test ".[2:4]" {|"abcdefghi"|} {|"cd"|};
42+
test ".[5:7]" {|["a","b","c"]|} {|[]|};
43+
test ".[5:7]" {|"abc"|} {|""|};
44+
test ".[:3]" {|["a","b","c","d","e"]|} {|[ "a", "b", "c" ]|};
45+
test ".[:-2]" {|["a","b","c","d","e"]|} {|[ "a", "b", "c" ]|};
46+
test ".[:3]" {|"abcdefghi"|} {|"abc"|};
47+
test ".[:-2]" {|"abcdefghi"|} {|"abcdefg"|};
48+
test ".[-2:]" {|["a","b","c","d","e"]|} {|[ "d", "e" ]|};
49+
test ".[2:]" {|["a","b","c","d","e"]|} {|[ "c", "d", "e" ]|};
50+
test ".[-2:]" {|"abcdefghi"|} {|"hi"|};
51+
test ".[2:]" {|"abcdefghi"|} {|"cdefghi"|};
52+
test ".[-4:-2]" {|["a","b","c","d","e"]|} {|[ "b", "c" ]|};
53+
test ".[-2:-4]" {|["a","b","c","d","e"]|} {|[]|};
54+
test ".[-4:-2]" {|"abcdefghi"|} {|"fg"|};
55+
test ".[-2:-4]" {|"abcde"|} {|""|};
56+
3357
test "1,1" "[]" "1\n1";
3458
test "1,." "[]" "1\n[]";
3559
test {|.foo | .bar|} {|{"foo": {"bar": 42}, "bar": "badvalue"}|} {|42|};

0 commit comments

Comments
 (0)