///|
priv struct EvalState {
limits : Limits
mut steps : Int
mut results : Int
}
///|
fn EvalState::tick(self : EvalState) -> Unit raise JmesPathError {
self.steps += 1
if self.steps > self.limits.max_steps {
fail("limit_exceeded", 0, "evaluation exceeds max_steps")
}
}
///|
fn EvalState::add_results(
self : EvalState,
count : Int,
) -> Unit raise JmesPathError {
self.results += count
if self.results > self.limits.max_results {
fail("limit_exceeded", 0, "evaluation exceeds max_results")
}
}
///|
fn truthy(value : Json) -> Bool {
match value {
Null | False | String("") => false
Array(items) => !items.is_empty()
Object(entries) => !entries.is_empty()
_ => true
}
}
///|
fn json_bool(value : Bool) -> Json {
Json::boolean(value)
}
///|
fn normalize_positive(value : Int, length : Int, default : Int) -> Int {
let mut result = value
if value == 2147483647 {
result = default
}
if result < 0 {
result += length
}
if result < 0 {
0
} else if result > length {
length
} else {
result
}
}
///|
fn slice_array(
items : Array[Json],
start : Int?,
stop : Int?,
step : Int?,
) -> Array[Json] raise JmesPathError {
let stride = step.unwrap_or(1)
if stride == 0 {
fail("invalid_value", 0, "slice step cannot be zero")
}
let length = items.length()
let result : Array[Json] = []
if stride > 0 {
let first = normalize_positive(start.unwrap_or(2147483647), length, 0)
let last = normalize_positive(stop.unwrap_or(2147483647), length, length)
let mut i = first
while i < last {
result.push(items[i])
i += stride
}
} else {
let mut first = match start {
None => length - 1
Some(value) => if value < 0 { value + length } else { value }
}
let last = match stop {
None => -1
Some(value) => if value < 0 { value + length } else { value }
}
if first >= length {
first = length - 1
}
if first < -1 {
first = -1
}
let mut end = last
if end >= length {
end = length - 1
}
if end < -1 {
end = -1
}
let mut i = first
while i > end {
result.push(items[i])
i += stride
}
}
result
}
///|
fn relation(op : CompareOp, left : Json, right : Json) -> Json {
match op {
Eq => json_bool(left == right)
Ne => json_bool(left != right)
Lt | Le | Gt | Ge =>
match (left, right) {
(Number(a, ..), Number(b, ..)) =>
json_bool(
match op {
Lt => a < b
Le => a <= b
Gt => a > b
Ge => a >= b
_ => false
},
)
_ => Json::null()
}
}
}
///|
fn evaluate(
expr : Expr,
data : Json,
state : EvalState,
) -> Json raise JmesPathError {
state.tick()
match expr {
Current => data
Field(name) =>
match data {
Object(entries) => entries.get(name).unwrap_or(Json::null())
_ => Json::null()
}
Literal(value) => value
Subexpression(left, right) => {
let value = evaluate(left, data, state)
if value is Null {
Json::null()
} else {
evaluate(right, value, state)
}
}
Index(base, index) =>
match evaluate(base, data, state) {
Array(items) => {
let resolved = if index < 0 { items.length() + index } else { index }
if resolved < 0 || resolved >= items.length() {
Json::null()
} else {
items[resolved]
}
}
_ => Json::null()
}
Slice(base, start, stop, step) =>
match evaluate(base, data, state) {
Array(items) => Json::array(slice_array(items, start, stop, step))
_ => Json::null()
}
ListWildcard(base) =>
match evaluate(base, data, state) {
Array(items) => Json::array(items)
_ => Json::null()
}
ObjectWildcard(base) =>
match evaluate(base, data, state) {
Object(entries) => {
let values : Array[Json] = []
for entry in entries {
let (_, value) = entry
values.push(value)
}
Json::array(values)
}
_ => Json::null()
}
Flatten(base) =>
match evaluate(base, data, state) {
Array(items) => {
let flattened : Array[Json] = []
for item in items {
match item {
Array(children) =>
for child in children {
flattened.push(child)
}
_ => flattened.push(item)
}
}
state.add_results(flattened.length())
Json::array(flattened)
}
_ => Json::null()
}
Projection(source, rhs) =>
match evaluate(source, data, state) {
Array(items) => {
let projected : Array[Json] = []
for item in items {
let value = evaluate(rhs, item, state)
if !(value is Null) {
projected.push(value)
}
}
state.add_results(projected.length())
Json::array(projected)
}
_ => Json::null()
}
Filter(source, condition) =>
match evaluate(source, data, state) {
Array(items) => {
let selected : Array[Json] = []
for item in items {
if truthy(evaluate(condition, item, state)) {
selected.push(item)
}
}
state.add_results(selected.length())
Json::array(selected)
}
_ => Json::null()
}
Pipe(left, right) => evaluate(right, evaluate(left, data, state), state)
Or(left, right) => {
let value = evaluate(left, data, state)
if truthy(value) {
value
} else {
evaluate(right, data, state)
}
}
And(left, right) => {
let value = evaluate(left, data, state)
if truthy(value) {
evaluate(right, data, state)
} else {
value
}
}
Not(value) => json_bool(!truthy(evaluate(value, data, state)))
Compare(op, left, right) =>
relation(op, evaluate(left, data, state), evaluate(right, data, state))
MultiList(items) => {
let values = items.map(item => evaluate(item, data, state))
state.add_results(values.length())
Json::array(values)
}
MultiHash(entries) => {
let values : Map[String, Json] = Map([])
for entry in entries {
let (key, item) = entry
values[key] = evaluate(item, data, state)
}
state.add_results(values.length())
Json::object(values)
}
Function(name, args) => evaluate_function(name, args, data, state)
ExpressionRef(_) =>
fail(
"invalid_type", 0, "expression references are only valid as function arguments",
)
}
}