-
Notifications
You must be signed in to change notification settings - Fork 21
/
JmesPathSliceProjection.cs
127 lines (91 loc) · 4.38 KB
/
JmesPathSliceProjection.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
using System;
using System.Collections.Generic;
using System.Linq;
using DevLab.JmesPath.Utils;
using Newtonsoft.Json.Linq;
namespace DevLab.JmesPath.Expressions
{
/// <summary>
/// Represents a JmesPath slice expression.
/// </summary>
public class JmesPathSliceProjection : JmesPathProjection
{
private readonly int? start_;
private readonly int? stop_;
private readonly int? step_;
public JmesPathSliceProjection(int? start, int? stop, int? step)
{
start_ = start;
stop_ = stop;
step_ = step;
}
public int? Start
=> start_;
public int? Stop
=> stop_;
public int? Step
=> step_;
protected override JmesPathArgument Project(JmesPathArgument argument)
{
if (argument.IsProjection)
argument = argument.AsJToken();
var json = argument.Token;
if (json.Type == JTokenType.String)
return Slice((Text)(json as JValue).Value<string>());
if (json.Type != JTokenType.Array)
return null;
// slice expression adhere to the following rule:
// if the element being sliced is not an array, the result is null.
var array = json as JArray;
if (array == null)
return null;
var length = array.Count;
var (start, stop, step) = GetSliceParameters(length);
// if the element being sliced is an array and yields no results, the result MUST be an empty array.
var items = new List<JToken>();
for (var index = start; (step > 0 ? index < stop : index > stop); index += step)
if (index >= 0 && index < length)
items.Add(array[index]);
var arguments = items.Select(i => (JmesPathArgument)i);
return new JmesPathArgument(arguments);
}
private delegate bool Comparator(int l, int r);
private JToken Slice(Text text)
{
var length = text.Length;
var (start, stop, step) = GetSliceParameters(length);
var codePoints = text.CodePoints;
var characters = new List<int>(codePoints.Length);
var compare = (step > 0)
? (Comparator)((int r, int l) => r < l)
: (Comparator)((int r, int l) => r > l)
;
for (var index = start; compare(index, stop); index += step)
if (index >= 0 && index < length)
characters.Add(text.CodePoints[index]);
return new JValue(new Text(characters.ToArray()));
}
private (int start, int stop, int step) GetSliceParameters(int length)
{
// slice expressions adhere to the following rules:
// if the given step is omitted, it it assumed to be 1.
var step = step_ ?? 1;
// if the given step is 0, an error MUST be raised.
// no runtime check here - the parser will ensure that 0 is not a valid value
System.Diagnostics.Debug.Assert(step != 0);
// if no start position is given, it is assumed to be 0 if the given step is greater than 0 or the end of the array if the given step is less than 0.
var start = start_ ?? (step > 0 ? 0 : length - 1);
// if a negative start position is given, it is calculated as the total length of the array plus the given start position.
if (start_.HasValue && start_.Value < 0)
start = length + start_.Value;
// if no stop position is given, it is assumed to be the length of the array if the given step is greater than 0 or 0 if the given step is less than 0.
var stop = stop_ ?? (step > 0 ? length : -1);
// if a negative stop position is given, it is calculated as the total length of the array plus the given stop position.
if (stop_.HasValue && stop_.Value < 0)
stop = length + stop_.Value;
return (start, stop, step);
}
protected override string Format()
=> $"[{(start_?.ToString() ?? "")}:{(stop_?.ToString() ?? "")}{(step_ == null ? "" : $":{step_?.ToString()}")}]";
}
}