use super::vector_filter_expression::{ExprProgram, ExprToken, ExprTokenType};
const MAX_ARRAY_ELEMENTS: usize = 64;
pub const BINARY_MARKER: u8 = 0xFF;
const BIN_TYPE_STRING: u8 = 0;
const BIN_TYPE_NUMBER: u8 = 1;
const BIN_TYPE_BOOL_TRUE: u8 = 2;
const BIN_TYPE_BOOL_FALSE: u8 = 3;
const BIN_TYPE_NULL: u8 = 4;
pub type SelectorRange = (i32, i32);
pub fn extract_fields(
json: &[u8],
filter_bytes: &[u8],
selector_ranges: &[SelectorRange],
results: &mut [ExprToken],
program: &mut ExprProgram,
) -> usize {
for r in results.iter_mut().take(selector_ranges.len()) {
*r = ExprToken::default();
}
let mut pos = 0;
trim_white_space(json, &mut pos);
if pos >= json.len() || json[pos] != b'{' {
return 0;
}
pos += 1;
let mut found = 0;
let needed = selector_ranges.len();
loop {
trim_white_space(json, &mut pos);
if pos >= json.len() || json[pos] == b'}' {
return found;
}
if json[pos] != b'"' {
return found;
}
let key_start = pos + 1;
if !skip_string(json, &mut pos) {
return found;
}
let key_content = &json[key_start..pos - 1];
let mut match_index = None;
for (i, range) in selector_ranges.iter().enumerate() {
if results[i].is_none() && key_content == slice_at(filter_bytes, range.0, range.1) {
match_index = Some(i);
break;
}
}
trim_white_space(json, &mut pos);
if pos >= json.len() || json[pos] != b':' {
return found;
}
pos += 1;
trim_white_space(json, &mut pos);
if pos >= json.len() {
return found;
}
if let Some(i) = match_index {
results[i] = parse_value_token(json, &mut pos, program).unwrap_or_default();
found += 1;
if found == needed {
return found;
}
} else if !skip_value(json, &mut pos) {
return found;
}
trim_white_space(json, &mut pos);
if pos >= json.len() {
return found;
}
match json[pos] {
b',' => pos += 1,
_ => return found,
}
}
}
pub fn extract_field(json: &[u8], field_name_utf8: &[u8]) -> ExprToken {
let mut pos = 0;
trim_white_space(json, &mut pos);
if pos >= json.len() || json[pos] != b'{' {
return ExprToken::default();
}
pos += 1;
loop {
trim_white_space(json, &mut pos);
if pos >= json.len() || json[pos] == b'}' || json[pos] != b'"' {
return ExprToken::default();
}
let key_start = pos + 1;
if !skip_string(json, &mut pos) {
return ExprToken::default();
}
let matched = &json[key_start..pos - 1] == field_name_utf8;
trim_white_space(json, &mut pos);
if pos >= json.len() || json[pos] != b':' {
return ExprToken::default();
}
pos += 1;
trim_white_space(json, &mut pos);
if pos >= json.len() {
return ExprToken::default();
}
if matched {
return parse_value_token_no_pool(json, &mut pos).unwrap_or_default();
}
if !skip_value(json, &mut pos) {
return ExprToken::default();
}
trim_white_space(json, &mut pos);
if pos >= json.len() {
return ExprToken::default();
}
match json[pos] {
b',' => pos += 1,
_ => return ExprToken::default(),
}
}
}
fn parse_value_token(json: &[u8], pos: &mut usize, program: &mut ExprProgram) -> Option<ExprToken> {
trim_white_space(json, pos);
let c = *json.get(*pos)?;
match c {
b'"' => parse_string_token(json, pos),
b'[' => parse_array_token(json, pos, program),
b'{' => None,
b't' => parse_literal_token(json, pos, b"true", ExprTokenType::Num, 1.0),
b'f' => parse_literal_token(json, pos, b"false", ExprTokenType::Num, 0.0),
b'n' => parse_literal_token(json, pos, b"null", ExprTokenType::Null, 0.0),
_ if is_digit(c) || c == b'-' || c == b'+' => parse_number_token(json, pos),
_ => None,
}
}
fn parse_value_token_no_pool(json: &[u8], pos: &mut usize) -> Option<ExprToken> {
trim_white_space(json, pos);
let c = *json.get(*pos)?;
match c {
b'"' => parse_string_token(json, pos),
b'[' => parse_array_token_no_pool(json, pos),
b'{' => None, b't' => parse_literal_token(json, pos, b"true", ExprTokenType::Num, 1.0),
b'f' => parse_literal_token(json, pos, b"false", ExprTokenType::Num, 0.0),
b'n' => parse_literal_token(json, pos, b"null", ExprTokenType::Null, 0.0),
_ if is_digit(c) || c == b'-' || c == b'+' => parse_number_token(json, pos),
_ => None,
}
}
fn parse_string_token(json: &[u8], pos: &mut usize) -> Option<ExprToken> {
if *pos >= json.len() || json[*pos] != b'"' {
return None;
}
*pos += 1;
let content_start = *pos;
let mut has_escape = false;
while *pos < json.len() {
match json[*pos] {
b'\\' => {
has_escape = true;
*pos += 2;
}
b'"' => {
let token = ExprToken::new_str(
content_start as i32,
(*pos - content_start) as i32,
has_escape,
);
*pos += 1;
return Some(token);
}
_ => *pos += 1,
}
}
None
}
fn parse_number_token(json: &[u8], pos: &mut usize) -> Option<ExprToken> {
let start = *pos;
while *pos < json.len() && is_number_char(json[*pos]) {
*pos += 1;
}
let span = &json[start..*pos];
if span.is_empty() {
return None;
}
let value = parse_f64_exact(span)?;
Some(ExprToken::new_num(value))
}
fn parse_literal_token(
json: &[u8],
pos: &mut usize,
literal: &[u8],
token_type: ExprTokenType,
num: f64,
) -> Option<ExprToken> {
if json.len() < *pos + literal.len() {
return None;
}
if &json[*pos..*pos + literal.len()] != literal {
return None;
}
if let Some(&next) = json.get(*pos + literal.len())
&& !is_white_space(next)
&& next != b','
&& next != b']'
&& next != b'}'
{
return None;
}
*pos += literal.len();
Some(if token_type == ExprTokenType::Null {
ExprToken::new_null()
} else {
ExprToken::new_num(num)
})
}
fn parse_array_token(json: &[u8], pos: &mut usize, program: &mut ExprProgram) -> Option<ExprToken> {
if *pos >= json.len() || json[*pos] != b'[' {
return None;
}
*pos += 1;
trim_white_space(json, pos);
if json.get(*pos) == Some(&b']') {
*pos += 1;
return Some(ExprToken::new_tuple(0, 0));
}
let mut local_buf: Vec<ExprToken> = Vec::with_capacity(MAX_ARRAY_ELEMENTS);
loop {
trim_white_space(json, pos);
if *pos >= json.len() {
return None;
}
if local_buf.len() >= MAX_ARRAY_ELEMENTS {
let _ = skip_bracketed(json, pos, b'[', b']');
return Some(ExprToken::new_null());
}
let elem = parse_value_token_no_pool(json, pos)?;
local_buf.push(elem);
trim_white_space(json, pos);
match json.get(*pos) {
Some(b']') => {
*pos += 1;
break;
}
Some(b',') => *pos += 1,
_ => return None,
}
}
let start = program.runtime_pool_len;
if start + local_buf.len() > super::expr_compiler::MAX_RUNTIME_POOL {
return Some(ExprToken::new_null());
}
program.runtime_pool.truncate(start);
program.runtime_pool.extend_from_slice(&local_buf);
program.runtime_pool_len += local_buf.len();
Some(ExprToken::new_runtime_tuple(
start as i32,
local_buf.len() as i32,
))
}
fn parse_array_token_no_pool(json: &[u8], pos: &mut usize) -> Option<ExprToken> {
if skip_value(json, pos) {
Some(ExprToken::new_null())
} else {
None
}
}
fn skip_value(json: &[u8], pos: &mut usize) -> bool {
trim_white_space(json, pos);
let Some(&c) = json.get(*pos) else {
return false;
};
match c {
b'"' => skip_string(json, pos),
b'{' => skip_bracketed(json, pos, b'{', b'}'),
b'[' => skip_bracketed(json, pos, b'[', b']'),
b't' => skip_literal(json, pos, b"true"),
b'f' => skip_literal(json, pos, b"false"),
b'n' => skip_literal(json, pos, b"null"),
_ => skip_number(json, pos),
}
}
fn skip_string(json: &[u8], pos: &mut usize) -> bool {
if *pos >= json.len() || json[*pos] != b'"' {
return false;
}
*pos += 1;
while *pos < json.len() {
match json[*pos] {
b'\\' => *pos += 2,
b'"' => {
*pos += 1;
return true;
}
_ => *pos += 1,
}
}
false
}
fn skip_bracketed(json: &[u8], pos: &mut usize, opener: u8, closer: u8) -> bool {
let mut depth = 1;
*pos += 1;
while *pos < json.len() && depth > 0 {
match json[*pos] {
b'"' => {
if !skip_string(json, pos) {
return false;
}
continue;
}
c if c == opener => depth += 1,
c if c == closer => depth -= 1,
_ => {}
}
*pos += 1;
}
depth == 0
}
fn skip_literal(json: &[u8], pos: &mut usize, literal: &[u8]) -> bool {
if json.len() < *pos + literal.len() {
return false;
}
if &json[*pos..*pos + literal.len()] != literal {
return false;
}
*pos += literal.len();
true
}
fn skip_number(json: &[u8], pos: &mut usize) -> bool {
let start = *pos;
while *pos < json.len() && is_number_char(json[*pos]) {
*pos += 1;
}
*pos > start
}
#[inline]
pub fn is_digit(b: u8) -> bool {
b.is_ascii_digit()
}
#[inline]
pub fn is_letter(b: u8) -> bool {
b.is_ascii_alphabetic()
}
#[inline]
pub fn is_letter_or_digit(b: u8) -> bool {
b.is_ascii_alphanumeric()
}
#[inline]
pub fn is_white_space(b: u8) -> bool {
matches!(b, b' ' | b'\t' | b'\n' | b'\r')
}
#[inline]
pub fn trim_white_space(json: &[u8], pos: &mut usize) {
while *pos < json.len() && is_white_space(json[*pos]) {
*pos += 1;
}
}
#[inline]
fn is_number_char(b: u8) -> bool {
is_digit(b) || matches!(b, b'-' | b'+' | b'.' | b'e' | b'E')
}
fn parse_f64_exact(span: &[u8]) -> Option<f64> {
let text = str::from_utf8(span).ok()?;
let value = text.parse::<f64>().ok()?;
value.is_finite().then_some(value)
}
#[inline]
fn slice_at(buf: &[u8], start: i32, len: i32) -> &[u8] {
let start = start.max(0) as usize;
let len = len.max(0) as usize;
buf
.get(start..)
.map_or(&[] as &[u8], |tail| &tail[..len.min(tail.len())])
}
pub fn convert_json_to_binary(json: &[u8], output: &mut [u8]) -> i32 {
let mut pos = 0;
trim_white_space(json, &mut pos);
if pos >= json.len() || json[pos] != b'{' {
return -1;
}
pos += 1;
if output.len() < 2 {
return -1;
}
output[0] = BINARY_MARKER;
let mut out_pos = 2;
let mut field_count: u8 = 0;
loop {
trim_white_space(json, &mut pos);
if pos >= json.len() {
return -1;
}
if json[pos] == b'}' {
break;
}
if json[pos] != b'"' {
return -1;
}
let key_start = pos + 1;
if !skip_string(json, &mut pos) {
return -1;
}
let key_content = &json[key_start..pos - 1];
if key_content.len() > 255 || key_content.contains(&b'\\') {
return -1;
}
if out_pos + 1 + key_content.len() + 1 + 2 > output.len() {
return -1;
}
output[out_pos] = key_content.len() as u8;
out_pos += 1;
output[out_pos..out_pos + key_content.len()].copy_from_slice(key_content);
out_pos += key_content.len();
trim_white_space(json, &mut pos);
if pos >= json.len() || json[pos] != b':' {
return -1;
}
pos += 1;
trim_white_space(json, &mut pos);
if pos >= json.len() {
return -1;
}
match json[pos] {
b'"' => {
pos += 1;
let content_start = pos;
let mut has_escape = false;
while pos < json.len() {
match json[pos] {
b'\\' => {
has_escape = true;
pos += 2;
continue;
}
b'"' => break,
_ => pos += 1,
}
}
if pos >= json.len() {
return -1;
}
let str_content = &json[content_start..pos];
pos += 1;
output[out_pos] = BIN_TYPE_STRING;
out_pos += 1;
if !has_escape {
if out_pos + 2 + str_content.len() > output.len() {
return -1;
}
write_u16_le(&mut output[out_pos..], str_content.len() as u16);
out_pos += 2;
output[out_pos..out_pos + str_content.len()].copy_from_slice(str_content);
out_pos += str_content.len();
} else {
let value_len_pos = out_pos;
out_pos += 2;
let value_start = out_pos;
let mut si = 0;
while si < str_content.len() {
if out_pos >= output.len() {
return -1;
}
if str_content[si] == b'\\' && si + 1 < str_content.len() {
si += 1;
output[out_pos] = match str_content[si] {
b'n' => b'\n',
b'r' => b'\r',
b't' => b'\t',
other => other, };
} else {
output[out_pos] = str_content[si];
}
out_pos += 1;
si += 1;
}
let value_len = (out_pos - value_start) as u16;
write_u16_le(&mut output[value_len_pos..], value_len);
}
}
c if is_digit(c) || c == b'-' || c == b'+' => {
let num_start = pos;
while pos < json.len() && is_number_char(json[pos]) {
pos += 1;
}
let Some(num_val) = parse_f64_exact(&json[num_start..pos]) else {
return -1;
};
output[out_pos] = BIN_TYPE_NUMBER;
out_pos += 1;
if out_pos + 2 + 8 > output.len() {
return -1;
}
write_u16_le(&mut output[out_pos..], 8);
out_pos += 2;
output[out_pos..out_pos + 8].copy_from_slice(&num_val.to_le_bytes());
out_pos += 8;
}
b't' => {
if !skip_literal(json, &mut pos, b"true") {
return -1;
}
output[out_pos] = BIN_TYPE_BOOL_TRUE;
out_pos += 1;
if out_pos + 2 > output.len() {
return -1;
}
write_u16_le(&mut output[out_pos..], 0);
out_pos += 2;
}
b'f' => {
if !skip_literal(json, &mut pos, b"false") {
return -1;
}
output[out_pos] = BIN_TYPE_BOOL_FALSE;
out_pos += 1;
if out_pos + 2 > output.len() {
return -1;
}
write_u16_le(&mut output[out_pos..], 0);
out_pos += 2;
}
b'n' => {
if !skip_literal(json, &mut pos, b"null") {
return -1;
}
output[out_pos] = BIN_TYPE_NULL;
out_pos += 1;
if out_pos + 2 > output.len() {
return -1;
}
write_u16_le(&mut output[out_pos..], 0);
out_pos += 2;
}
_ => return -1,
}
field_count += 1;
trim_white_space(json, &mut pos);
if pos >= json.len() {
return -1;
}
match json[pos] {
b',' => pos += 1,
b'}' => break,
_ => return -1,
}
}
output[1] = field_count;
out_pos as i32
}
pub fn extract_fields_binary(
binary: &[u8],
filter_bytes: &[u8],
selector_ranges: &[SelectorRange],
results: &mut [ExprToken],
) -> usize {
for r in results.iter_mut().take(selector_ranges.len()) {
*r = ExprToken::default();
}
if binary.len() < 2 || binary[0] != BINARY_MARKER {
return 0;
}
let num_fields = binary[1];
let mut pos = 2;
let mut found = 0;
let needed = selector_ranges.len();
for _ in 0..num_fields {
if pos >= binary.len() {
break;
}
let name_len = binary[pos] as usize;
pos += 1;
if pos + name_len > binary.len() {
break;
}
let field_name = &binary[pos..pos + name_len];
pos += name_len;
if pos >= binary.len() {
break;
}
let value_type = binary[pos];
pos += 1;
if pos + 2 > binary.len() {
break;
}
let value_len = u16::from_le_bytes([binary[pos], binary[pos + 1]]) as usize;
pos += 2;
if pos + value_len > binary.len() {
break;
}
let mut match_index = None;
for (i, range) in selector_ranges.iter().enumerate() {
if results[i].is_none() && field_name == slice_at(filter_bytes, range.0, range.1) {
match_index = Some(i);
break;
}
}
if let Some(i) = match_index {
match value_type {
BIN_TYPE_STRING => {
results[i] = ExprToken::new_str(pos as i32, value_len as i32, false);
}
BIN_TYPE_NUMBER if value_len == 8 => {
results[i] =
ExprToken::new_num(f64::from_le_bytes(binary[pos..pos + 8].try_into().unwrap()));
}
BIN_TYPE_BOOL_TRUE => results[i] = ExprToken::new_num(1.0),
BIN_TYPE_BOOL_FALSE => results[i] = ExprToken::new_num(0.0),
BIN_TYPE_NULL => results[i] = ExprToken::new_null(),
_ => {}
}
found += 1;
if found == needed {
return found;
}
}
pos += value_len;
}
found
}
fn write_u16_le(buf: &mut [u8], value: u16) {
buf[..2].copy_from_slice(&value.to_le_bytes());
}
#[cfg(test)]
mod tests {
use ExprTokenType::Str;
use super::*;
#[test]
fn extract_single_field() {
let json = br#"{"year": 2000, "rating": 7.5, "name": "k\"x", "ok": true, "no": null}"#;
assert_eq!(extract_field(json, b"year"), ExprToken::new_num(2000.0));
assert_eq!(extract_field(json, b"rating"), ExprToken::new_num(7.5));
assert_eq!(extract_field(json, b"ok"), ExprToken::new_num(1.0));
assert_eq!(extract_field(json, b"no"), ExprToken::new_null());
assert_eq!(extract_field(json, b"missing"), ExprToken::default());
let t = extract_field(json, b"name");
assert_eq!(t.token_type, Str);
assert!(t.has_escape());
let s = &json[t.utf8_start as usize..(t.utf8_start + t.utf8_length) as usize];
assert_eq!(s, b"k\\\"x");
}
#[test]
fn extract_field_array_no_pool_degrades_to_null() {
let json = br#"{"xs": [1,2,3]}"#;
let t = extract_field(json, b"xs");
assert_eq!(t, ExprToken::new_null());
}
#[test]
fn extract_multi_fields_single_pass() {
let json = br#" { "a": 1, "b": "two", "c": [3, 4] } "#;
let filter = b"aXXXXbXXXXcXXXX"; let selectors = [(0i32, 1i32), (5, 1), (10, 1)];
let mut program = ExprProgram::default();
let mut results = [ExprToken::default(); 3];
let found = extract_fields(json, filter, &selectors, &mut results, &mut program);
assert_eq!(found, 3);
assert_eq!(results[0], ExprToken::new_num(1.0));
assert_eq!(results[1].token_type, Str);
assert!(results[2].is_runtime_tuple());
assert_eq!(results[2].utf8_length, 2);
assert_eq!(program.runtime_pool_len, 2);
assert_eq!(program.runtime_pool[0], ExprToken::new_num(3.0));
}
#[test]
fn skip_helpers() {
let raw: &[u8] = b" \"esc \\\" x\" tail";
let mut pos = 2; assert!(skip_string(raw, &mut pos));
assert_eq!(&raw[pos..], b" tail");
let raw: &[u8] = b"[1,[2,{\"x\":\"}\"}],3] end";
let mut pos = 0;
assert!(skip_bracketed(raw, &mut pos, b'[', b']'));
assert_eq!(&raw[pos..], b" end");
let mut pos = 0;
assert!(skip_literal(b"truex", &mut pos, b"true"));
assert_eq!(pos, 4);
let mut pos = 0;
assert!(skip_literal(b"true", &mut pos, b"true"));
let mut pos = 0;
assert!(skip_value(b"{\"a\":1}", &mut pos));
assert_eq!(pos, 7);
let mut pos = 0;
assert!(skip_number(b"-12.5e3|", &mut pos));
assert_eq!(pos, 7);
}
#[test]
fn char_classes() {
assert!(is_digit(b'0') && is_digit(b'9'));
assert!(!is_digit(b'/'));
assert!(is_letter(b'a') && is_letter(b'Z') && !is_letter(b'0'));
assert!(is_letter_or_digit(b'5'));
assert!(is_white_space(b'\t') && is_white_space(b'\r'));
assert!(is_number_char(b'e') && is_number_char(b'.') && !is_number_char(b'x'));
}
#[test]
fn json_to_binary_roundtrip() {
let json = br#"{"s":"hello","n":-3.5e2,"t":true,"f":false,"z":null}"#;
let mut buf = vec![0u8; 256];
let written = convert_json_to_binary(json, &mut buf);
assert!(written > 0);
let bin = &buf[..written as usize];
assert_eq!(bin[0], BINARY_MARKER);
assert_eq!(bin[1], 5);
for (name, expect) in [
("s", ExprToken::new_str(0, 5, false)), ("n", ExprToken::new_num(-350.0)),
("t", ExprToken::new_num(1.0)),
("f", ExprToken::new_num(0.0)),
("z", ExprToken::new_null()),
] {
let ranges = vec![(0i32, name.len() as i32)];
let mut results = [ExprToken::default()];
let found = extract_fields_binary(bin, name.as_bytes(), &ranges, &mut results);
assert_eq!(found, 1, "{name}");
if name == "s" {
assert_eq!(results[0].token_type, Str);
assert_eq!(results[0].utf8_length, 5);
let s = &bin[results[0].utf8_start as usize..];
assert_eq!(&s[..5], b"hello");
} else {
assert_eq!(results[0], expect, "{name}");
}
}
}
#[test]
fn binary_string_unescape() {
let json = br#"{"k":"a\nb"}"#;
let mut buf = vec![0u8; 64];
let written = convert_json_to_binary(json, &mut buf);
assert!(written > 0);
let bin = &buf[..written as usize];
let ranges = vec![(0i32, 1i32)];
let mut results = [ExprToken::default()];
assert_eq!(extract_fields_binary(bin, b"k", &ranges, &mut results), 1);
assert_eq!(results[0].token_type, Str);
assert_eq!(bin[results[0].utf8_start as usize], b'a');
assert_eq!(bin[results[0].utf8_start as usize + 1], b'\n');
assert_eq!(bin[results[0].utf8_start as usize + 2], b'b');
}
#[test]
fn binary_rejects_bad_input() {
let mut buf = vec![0u8; 64];
assert_eq!(convert_json_to_binary(b"[1,2]", &mut buf), -1);
assert_eq!(convert_json_to_binary(b"{\"a\":{\"b\":1}}", &mut buf), -1);
assert_eq!(convert_json_to_binary(b"{\"a\":1}", &mut buf[..3]), -1);
assert_eq!(convert_json_to_binary(b"{\"a\":1.2.3}", &mut buf), -1);
}
}