use std::collections::{BTreeMap, HashSet};
use std::fmt;
use std::io::{self, BufRead, Read, Write};
use std::str::Chars;
#[derive(Debug, Clone)]
pub enum Value {
Object(BTreeMap<String, Value>),
Array(Vec<Value>),
String(String),
Number(f64),
Bool(bool),
Null,
}
impl Value {
pub fn type_name(&self) -> &'static str {
match self {
Value::Object(_) => "object",
Value::Array(_) => "array",
Value::String(_) => "string",
Value::Number(_) => "number",
Value::Bool(_) => "boolean",
Value::Null => "null",
}
}
pub fn is_truthy(&self) -> bool {
match self {
Value::Bool(false) | Value::Null => false,
_ => true,
}
}
pub fn as_f64(&self) -> Option<f64> {
match self {
Value::Number(n) => Some(*n),
_ => None,
}
}
pub fn as_str(&self) -> Option<&str> {
match self {
Value::String(s) => Some(s),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Value::Bool(b) => Some(*b),
_ => None,
}
}
pub fn jq_len(&self) -> usize {
match self {
Value::Array(a) => a.len(),
Value::Object(o) => o.len(),
Value::String(s) => s.chars().count(),
Value::Number(n) => n.abs() as usize,
Value::Bool(_) => 1,
Value::Null => 0,
}
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
compact_print_value(self, f, 0)
}
}
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Value::Object(a), Value::Object(b)) => a == b,
(Value::Array(a), Value::Array(b)) => a == b,
(Value::String(a), Value::String(b)) => a == b,
(Value::Number(a), Value::Number(b)) => {
if a.is_nan() && b.is_nan() {
true
} else {
a == b
}
}
(Value::Bool(a), Value::Bool(b)) => a == b,
(Value::Null, Value::Null) => true,
_ => false,
}
}
}
impl Eq for Value {}
impl PartialOrd for Value {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Value {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
use std::cmp::Ordering;
match (self, other) {
(Value::Null, Value::Null) => Ordering::Equal,
(Value::Null, _) => Ordering::Less,
(_, Value::Null) => Ordering::Greater,
(Value::Bool(a), Value::Bool(b)) => a.cmp(b),
(Value::Bool(_), _) => Ordering::Less,
(_, Value::Bool(_)) => Ordering::Greater,
(Value::Number(a), Value::Number(b)) => {
if a < b {
Ordering::Less
} else if a > b {
Ordering::Greater
} else {
Ordering::Equal
}
}
(Value::Number(_), _) => Ordering::Less,
(_, Value::Number(_)) => Ordering::Greater,
(Value::String(a), Value::String(b)) => a.cmp(b),
(Value::String(_), _) => Ordering::Less,
(_, Value::String(_)) => Ordering::Greater,
(Value::Array(a), Value::Array(b)) => {
for (x, y) in a.iter().zip(b.iter()) {
match x.cmp(y) {
Ordering::Equal => continue,
o => return o,
}
}
a.len().cmp(&b.len())
}
(Value::Array(_), _) => Ordering::Less,
(_, Value::Array(_)) => Ordering::Greater,
(Value::Object(a), Value::Object(b)) => {
let ak: Vec<&String> = a.keys().collect();
let bk: Vec<&String> = b.keys().collect();
for (x, y) in ak.iter().zip(bk.iter()) {
match x.cmp(y) {
Ordering::Equal => continue,
o => return o,
}
}
ak.len().cmp(&bk.len())
}
}
}
}
#[derive(Debug, Clone)]
pub struct JqError {
pub message: String,
pub position: Option<usize>,
}
impl JqError {
pub fn new(msg: impl Into<String>) -> Self {
JqError {
message: msg.into(),
position: None,
}
}
pub fn with_pos(msg: impl Into<String>, pos: usize) -> Self {
JqError {
message: msg.into(),
position: Some(pos),
}
}
}
impl fmt::Display for JqError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(pos) = self.position {
write!(f, "jq error at position {}: {}", pos, self.message)
} else {
write!(f, "jq error: {}", self.message)
}
}
}
struct JsonTokenizer<'a> {
chars: Chars<'a>,
offset: usize,
peeked: Option<Result<Token, JqError>>,
}
#[derive(Debug, Clone)]
enum Token {
LBrace,
RBrace,
LBracket,
RBracket,
Colon,
Comma,
String(String),
Number(f64),
True,
False,
Null,
Eof,
}
impl<'a> JsonTokenizer<'a> {
fn new(input: &'a str) -> Self {
JsonTokenizer {
chars: input.chars(),
offset: 0,
peeked: None,
}
}
fn peek(&mut self) -> Result<&Token, JqError> {
if self.peeked.is_none() {
let tok = self.next_inner()?;
self.peeked = Some(Ok(tok));
}
match self.peeked.as_ref().unwrap() {
Ok(tok) => Ok(tok),
Err(e) => Err(e.clone()),
}
}
fn next(&mut self) -> Result<Token, JqError> {
if self.peeked.is_some() {
let val = self.peeked.take().unwrap();
return val;
}
self.next_inner()
}
fn next_inner(&mut self) -> Result<Token, JqError> {
self.skip_whitespace();
let start = self.offset;
match self.chars.next() {
None => Ok(Token::Eof),
Some('{') => {
self.offset += 1;
Ok(Token::LBrace)
}
Some('}') => {
self.offset += 1;
Ok(Token::RBrace)
}
Some('[') => {
self.offset += 1;
Ok(Token::LBracket)
}
Some(']') => {
self.offset += 1;
Ok(Token::RBracket)
}
Some(':') => {
self.offset += 1;
Ok(Token::Colon)
}
Some(',') => {
self.offset += 1;
Ok(Token::Comma)
}
Some('"') => {
self.offset += 1;
let s = self.read_string()?;
Ok(Token::String(s))
}
Some(c) if c.is_ascii_digit() || c == '-' => {
let n = self.read_number(c)?;
Ok(Token::Number(n))
}
Some('t') => {
self.expect_keyword("true")?;
Ok(Token::True)
}
Some('f') => {
self.expect_keyword("false")?;
Ok(Token::False)
}
Some('n') => {
self.expect_keyword("null")?;
Ok(Token::Null)
}
Some(c) => Err(JqError::with_pos(
format!("unexpected character '{}'", c),
start,
)),
}
}
fn skip_whitespace(&mut self) {
}
fn read_string(&mut self) -> Result<String, JqError> {
let mut s = String::new();
loop {
match self.chars.next() {
None => {
return Err(JqError::with_pos(
"unterminated string".to_string(),
self.offset,
));
}
Some('"') => {
self.offset += 1;
return Ok(s);
}
Some('\\') => {
self.offset += 1;
let escaped = self.chars.next();
self.offset += 1;
match escaped {
Some('"') => s.push('"'),
Some('\\') => s.push('\\'),
Some('/') => s.push('/'),
Some('b') => s.push('\u{0008}'),
Some('f') => s.push('\u{000C}'),
Some('n') => s.push('\n'),
Some('r') => s.push('\r'),
Some('t') => s.push('\t'),
Some('u') => {
let hex = self.read_hex4()?;
if (0xD800..=0xDBFF).contains(&hex) {
if self.chars.next() == Some('\\') && self.chars.next() == Some('u')
{
self.offset += 2;
let low = self.read_hex4()?;
if (0xDC00..=0xDFFF).contains(&low) {
let codepoint =
0x10000 + ((hex - 0xD800) as u32 * 0x400)
+ (low - 0xDC00) as u32;
if let Some(c) = char::from_u32(codepoint) {
s.push(c);
} else {
return Err(JqError::new("invalid surrogate pair"));
}
} else {
return Err(JqError::new("invalid low surrogate"));
}
} else {
return Err(JqError::new("expected low surrogate after high surrogate"));
}
} else if let Some(c) = char::from_u32(hex) {
s.push(c);
} else {
return Err(JqError::new("invalid unicode escape"));
}
}
_ => {
return Err(JqError::new("invalid escape sequence"));
}
}
}
Some(c) => {
self.offset += 1;
s.push(c);
}
}
}
}
fn read_hex4(&mut self) -> Result<u32, JqError> {
let mut val: u32 = 0;
for _ in 0..4 {
match self.chars.next() {
Some(c) if c.is_ascii_hexdigit() => {
self.offset += 1;
val = val * 16 + c.to_digit(16).unwrap();
}
_ => return Err(JqError::new("invalid unicode escape: expected 4 hex digits")),
}
}
Ok(val)
}
fn read_number(&mut self, first: char) -> Result<f64, JqError> {
let mut num_str = String::new();
num_str.push(first);
self.offset += 1;
if first == '-' {
match self.chars.next() {
Some(c) if c.is_ascii_digit() => {
num_str.push(c);
self.offset += 1;
}
_ => return Err(JqError::new("expected digit after minus")),
}
}
if num_str == "0" || (num_str == "-0") {
}
loop {
break;
}
Err(JqError::new("internal: tokenizer not fully implemented yet"))
}
fn expect_keyword(&mut self, kw: &str) -> Result<(), JqError> {
for expected in kw.chars().skip(1) {
match self.chars.next() {
Some(c) if c == expected => {
self.offset += 1;
}
_ => {
return Err(JqError::new(format!(
"expected keyword '{}'",
kw
)));
}
}
}
Ok(())
}
}
struct JsonParser<'a> {
input: &'a [u8],
pos: usize,
}
impl<'a> JsonParser<'a> {
fn new(input: &'a str) -> Self {
JsonParser {
input: input.as_bytes(),
pos: 0,
}
}
fn parse_value(&mut self) -> Result<Value, JqError> {
self.skip_ws();
match self.peek() {
Some(b'{') => self.parse_object(),
Some(b'[') => self.parse_array(),
Some(b'"') => self.parse_string_val(),
Some(b't') => self.parse_true(),
Some(b'f') => self.parse_false(),
Some(b'n') => self.parse_null(),
Some(b'-') | Some(b'0'..=b'9') => self.parse_number_val(),
Some(c) => Err(JqError::with_pos(
format!("unexpected character '{}'", *c as char),
self.pos,
)),
None => Err(JqError::new("unexpected end of input")),
}
}
fn peek(&self) -> Option<&u8> {
self.input.get(self.pos)
}
fn advance(&mut self) -> Option<u8> {
let b = self.input.get(self.pos).copied();
if b.is_some() {
self.pos += 1;
}
b
}
fn skip_ws(&mut self) {
while let Some(b) = self.peek() {
if b.is_ascii_whitespace() {
self.pos += 1;
} else {
break;
}
}
}
fn expect(&mut self, expected: u8) -> Result<(), JqError> {
match self.advance() {
Some(b) if b == expected => Ok(()),
Some(b) => Err(JqError::with_pos(
format!(
"expected '{}', found '{}'",
expected as char, b as char
),
self.pos - 1,
)),
None => Err(JqError::with_pos(
format!("expected '{}', found end of input", expected as char),
self.pos,
)),
}
}
fn parse_object(&mut self) -> Result<Value, JqError> {
self.expect(b'{')?;
self.skip_ws();
let mut map = BTreeMap::new();
if self.peek() == Some(&b'}') {
self.pos += 1;
return Ok(Value::Object(map));
}
loop {
self.skip_ws();
if self.peek() != Some(&b'"') {
return Err(JqError::with_pos(
"expected string key in object".to_string(),
self.pos,
));
}
let key = self.parse_raw_string()?;
self.skip_ws();
self.expect(b':')?;
self.skip_ws();
let val = self.parse_value()?;
map.insert(key, val);
self.skip_ws();
match self.peek() {
Some(b',') => {
self.pos += 1;
continue;
}
Some(b'}') => {
self.pos += 1;
break;
}
Some(c) => {
return Err(JqError::with_pos(
format!("expected ',' or '}}', found '{}'", *c as char),
self.pos,
));
}
None => {
return Err(JqError::with_pos(
"unterminated object".to_string(),
self.pos,
));
}
}
}
Ok(Value::Object(map))
}
fn parse_array(&mut self) -> Result<Value, JqError> {
self.expect(b'[')?;
self.skip_ws();
let mut arr = Vec::new();
if self.peek() == Some(&b']') {
self.pos += 1;
return Ok(Value::Array(arr));
}
loop {
self.skip_ws();
let val = self.parse_value()?;
arr.push(val);
self.skip_ws();
match self.peek() {
Some(b',') => {
self.pos += 1;
continue;
}
Some(b']') => {
self.pos += 1;
break;
}
Some(c) => {
return Err(JqError::with_pos(
format!("expected ',' or ']', found '{}'", *c as char),
self.pos,
));
}
None => {
return Err(JqError::with_pos(
"unterminated array".to_string(),
self.pos,
));
}
}
}
Ok(Value::Array(arr))
}
fn parse_string_val(&mut self) -> Result<Value, JqError> {
let s = self.parse_raw_string()?;
Ok(Value::String(s))
}
fn parse_raw_string(&mut self) -> Result<String, JqError> {
self.expect(b'"')?;
let mut s = String::new();
loop {
match self.advance() {
None => {
return Err(JqError::with_pos(
"unterminated string".to_string(),
self.pos,
));
}
Some(b'"') => return Ok(s),
Some(b'\\') => {
match self.advance() {
Some(b'"') => s.push('"'),
Some(b'\\') => s.push('\\'),
Some(b'/') => s.push('/'),
Some(b'b') => s.push('\u{0008}'),
Some(b'f') => s.push('\u{000C}'),
Some(b'n') => s.push('\n'),
Some(b'r') => s.push('\r'),
Some(b't') => s.push('\t'),
Some(b'u') => {
let hex = self.read_hex4_parser()?;
if (0xD800..=0xDBFF).contains(&hex) {
if self.pos + 1 < self.input.len()
&& self.input[self.pos] == b'\\'
&& self.input[self.pos + 1] == b'u'
{
self.pos += 2; let low = self.read_hex4_parser()?;
if (0xDC00..=0xDFFF).contains(&low) {
let codepoint = 0x10000
+ ((hex - 0xD800) as u32) * 0x400
+ (low - 0xDC00) as u32;
if let Some(c) = char::from_u32(codepoint) {
s.push(c);
} else {
return Err(JqError::new(
"invalid surrogate pair codepoint",
));
}
} else {
return Err(JqError::new(
"invalid low surrogate after high surrogate",
));
}
} else {
return Err(JqError::new(
"expected low surrogate after high surrogate",
));
}
} else if let Some(c) = char::from_u32(hex) {
s.push(c);
} else {
return Err(JqError::new("invalid unicode escape codepoint"));
}
}
Some(c) => {
return Err(JqError::with_pos(
format!("invalid escape character '{}'", c as char),
self.pos - 1,
));
}
None => {
return Err(JqError::new("unterminated escape sequence"));
}
}
}
Some(c) => s.push(c as char),
}
}
}
fn read_hex4_parser(&mut self) -> Result<u32, JqError> {
let mut val: u32 = 0;
for _ in 0..4 {
match self.advance() {
Some(c) if c.is_ascii_hexdigit() => {
val = val * 16 + (c as char).to_digit(16).unwrap();
}
Some(c) => {
return Err(JqError::with_pos(
format!("expected hex digit in unicode escape, got '{}'", c as char),
self.pos - 1,
));
}
None => {
return Err(JqError::new(
"unterminated unicode escape sequence",
));
}
}
}
Ok(val)
}
fn parse_true(&mut self) -> Result<Value, JqError> {
self.expect_keyword_parser(b"true")?;
Ok(Value::Bool(true))
}
fn parse_false(&mut self) -> Result<Value, JqError> {
self.expect_keyword_parser(b"false")?;
Ok(Value::Bool(false))
}
fn parse_null(&mut self) -> Result<Value, JqError> {
self.expect_keyword_parser(b"null")?;
Ok(Value::Null)
}
fn expect_keyword_parser(&mut self, kw: &[u8]) -> Result<(), JqError> {
for &expected in kw.iter() {
match self.advance() {
Some(b) if b == expected => {}
_ => {
return Err(JqError::with_pos(
format!("expected keyword '{}'", String::from_utf8_lossy(kw)),
self.pos,
));
}
}
}
Ok(())
}
fn parse_number_val(&mut self) -> Result<Value, JqError> {
let start = self.pos;
let mut num_str = String::new();
if self.peek() == Some(&b'-') {
num_str.push('-');
self.pos += 1;
}
match self.peek() {
Some(b'0') => {
num_str.push('0');
self.pos += 1;
}
Some(b'1'..=b'9') => {
while let Some(b) = self.peek() {
if b.is_ascii_digit() {
num_str.push(b as char);
self.pos += 1;
} else {
break;
}
}
}
_ => {
return Err(JqError::with_pos(
"expected digit in number".to_string(),
self.pos,
));
}
}
if self.peek() == Some(&b'.') {
if self.pos + 1 < self.input.len()
&& self.input[self.pos + 1].is_ascii_digit()
{
num_str.push('.');
self.pos += 1;
while let Some(b) = self.peek() {
if b.is_ascii_digit() {
num_str.push(b as char);
self.pos += 1;
} else {
break;
}
}
}
}
if self.peek() == Some(&b'e') || self.peek() == Some(&b'E') {
let peek_pos = self.pos + 1;
if peek_pos < self.input.len() {
let next = self.input[peek_pos];
if next.is_ascii_digit() || next == b'+' || next == b'-' {
num_str.push(self.advance().unwrap() as char); if self.peek() == Some(&b'+') || self.peek() == Some(&b'-') {
num_str.push(self.advance().unwrap() as char);
}
let mut has_digit = false;
while let Some(b) = self.peek() {
if b.is_ascii_digit() {
num_str.push(b as char);
self.pos += 1;
has_digit = true;
} else {
break;
}
}
if !has_digit {
return Err(JqError::with_pos(
"expected digit in exponent".to_string(),
self.pos,
));
}
}
}
}
let n: f64 = num_str.parse().map_err(|_| {
JqError::with_pos(format!("invalid number '{}'", num_str), start)
})?;
if n.is_infinite() || n.is_nan() {
return Err(JqError::with_pos(
"JSON does not support Infinity or NaN".to_string(),
start,
));
}
Ok(Value::Number(n))
}
}
pub fn parse_json(input: &str) -> Result<Value, JqError> {
let mut parser = JsonParser::new(input);
let val = parser.parse_value()?;
parser.skip_ws();
if parser.peek().is_some() {
return Err(JqError::with_pos(
"trailing content after JSON value".to_string(),
parser.pos,
));
}
Ok(val)
}
pub fn pretty_print(value: &Value, indent: usize) -> String {
let mut buf = String::new();
pretty_print_inner(value, &mut buf, 0, indent);
buf.push('\n');
buf
}
fn pretty_print_inner(value: &Value, buf: &mut String, depth: usize, indent: usize) {
match value {
Value::Object(map) => {
if map.is_empty() {
buf.push_str("{}");
return;
}
buf.push_str("{\n");
let prefix = " ".repeat((depth + 1) * indent);
let entries: Vec<_> = map.iter().collect();
for (i, (key, val)) in entries.iter().enumerate() {
buf.push_str(&prefix);
write_quoted_string(buf, key);
buf.push_str(": ");
pretty_print_inner(val, buf, depth + 1, indent);
if i + 1 < entries.len() {
buf.push(',');
}
buf.push('\n');
}
buf.push_str(&" ".repeat(depth * indent));
buf.push('}');
}
Value::Array(arr) => {
if arr.is_empty() {
buf.push_str("[]");
return;
}
let all_scalar = arr.iter().all(|v| {
matches!(
v,
Value::String(_) | Value::Number(_) | Value::Bool(_) | Value::Null
)
});
if all_scalar && arr.len() <= 8 {
buf.push('[');
for (i, v) in arr.iter().enumerate() {
compact_print_value(v, buf, 0);
if i + 1 < arr.len() {
buf.push_str(", ");
}
}
buf.push(']');
return;
}
buf.push_str("[\n");
let prefix = " ".repeat((depth + 1) * indent);
for (i, val) in arr.iter().enumerate() {
buf.push_str(&prefix);
pretty_print_inner(val, buf, depth + 1, indent);
if i + 1 < arr.len() {
buf.push(',');
}
buf.push('\n');
}
buf.push_str(&" ".repeat(depth * indent));
buf.push(']');
}
other => {
compact_print_value(other, buf, 0);
}
}
}
pub fn compact_print(value: &Value) -> String {
let mut buf = String::new();
compact_print_value(value, &mut buf, 0);
buf
}
fn compact_print_value(value: &Value, buf: &mut String, _depth: usize) -> fmt::Result {
match value {
Value::Null => buf.push_str("null"),
Value::Bool(true) => buf.push_str("true"),
Value::Bool(false) => buf.push_str("false"),
Value::Number(n) => {
if *n == (*n).trunc() && n.is_finite() && n.abs() < 1e15 {
write!(buf, "{}", *n as i64).unwrap();
} else {
write!(buf, "{}", n).unwrap();
}
}
Value::String(s) => write_quoted_string(buf, s),
Value::Array(arr) => {
buf.push('[');
for (i, v) in arr.iter().enumerate() {
if i > 0 {
buf.push(',');
}
compact_print_value(v, buf, 0).unwrap();
}
buf.push(']');
}
Value::Object(map) => {
buf.push('{');
for (i, (k, v)) in map.iter().enumerate() {
if i > 0 {
buf.push(',');
}
write_quoted_string(buf, k);
buf.push(':');
compact_print_value(v, buf, 0).unwrap();
}
buf.push('}');
}
}
Ok(())
}
fn write_quoted_string(buf: &mut String, s: &str) {
buf.push('"');
for c in s.chars() {
match c {
'"' => buf.push_str("\\\""),
'\\' => buf.push_str("\\\\"),
'\u{0008}' => buf.push_str("\\b"),
'\u{000C}' => buf.push_str("\\f"),
'\n' => buf.push_str("\\n"),
'\r' => buf.push_str("\\r"),
'\t' => buf.push_str("\\t"),
c if c.is_control() => {
buf.push_str(&format!("\\u{:04x}", c as u32));
}
c => buf.push(c),
}
}
buf.push('"');
}
#[derive(Debug, Clone)]
pub enum Filter {
Identity,
FieldAccess(Box<Filter>, String),
Index(Box<Filter>, Box<Filter>),
Iterator(Box<Filter>),
Pipe(Box<Filter>, Box<Filter>),
Select(Box<Filter>),
Map(Box<Filter>),
Keys,
Length,
Has(String),
Del(Box<Filter>),
ToEntries,
FromEntries,
Add,
Sort,
SortBy(Box<Filter>),
Unique,
Reverse,
GroupBy(Box<Filter>),
Min,
Max,
First,
Last,
Nth(Box<Filter>),
Limit(Box<Filter>, Box<Filter>),
Range(Box<Filter>, Box<Filter>),
Empty,
IfThenElse(Box<Filter>, Box<Filter>, Box<Filter>),
Equal(Box<Filter>, Box<Filter>),
NotEqual(Box<Filter>, Box<Filter>),
Less(Box<Filter>, Box<Filter>),
Greater(Box<Filter>, Box<Filter>),
LessEq(Box<Filter>, Box<Filter>),
GreaterEq(Box<Filter>, Box<Filter>),
FilterAdd(Box<Filter>, Box<Filter>),
FilterSub(Box<Filter>, Box<Filter>),
FilterMul(Box<Filter>, Box<Filter>),
FilterDiv(Box<Filter>, Box<Filter>),
FilterMod(Box<Filter>, Box<Filter>),
And(Box<Filter>, Box<Filter>),
Or(Box<Filter>, Box<Filter>),
Not(Box<Filter>),
StringLiteral(String),
NumberLiteral(f64),
TrueLiteral,
FalseLiteral,
NullLiteral,
StringInterpolation(Vec<InterpPart>),
ObjectConstruction(Vec<(String, Box<Filter>)>),
ArrayConstruction(Vec<Box<Filter>>),
Comma(Box<Filter>, Box<Filter>),
TryCatch(Box<Filter>, Box<Filter>),
Recurse(Option<Box<Filter>>),
RecursiveDescent,
RecursiveDescentShorthand,
Paren(Box<Filter>),
}
#[derive(Debug, Clone)]
pub enum InterpPart {
Text(String),
Expr(Box<Filter>),
}
struct FilterParser<'a> {
input: &'a [u8],
pos: usize,
}
impl<'a> FilterParser<'a> {
fn new(input: &'a str) -> Self {
FilterParser {
input: input.as_bytes(),
pos: 0,
}
}
fn peek(&self) -> Option<u8> {
self.input.get(self.pos).copied()
}
fn advance(&mut self) -> Option<u8> {
let b = self.input.get(self.pos).copied();
if b.is_some() {
self.pos += 1;
}
b
}
fn skip_ws(&mut self) {
while let Some(b) = self.peek() {
if b.is_ascii_whitespace() {
self.pos += 1;
} else {
break;
}
}
}
pub fn parse_filter(&mut self) -> Result<Filter, JqError> {
self.skip_ws();
let result = self.parse_pipe()?;
self.skip_ws();
if self.peek().is_some() {
return Err(JqError::with_pos(
format!(
"unexpected character '{}' after filter",
self.peek().unwrap() as char
),
self.pos,
));
}
Ok(result)
}
fn parse_pipe(&mut self) -> Result<Filter, JqError> {
let mut left = self.parse_comma()?;
self.skip_ws();
while self.peek() == Some(b'|') {
if self.pos + 1 < self.input.len() && self.input[self.pos + 1] == b'=' {
break;
}
self.pos += 1; self.skip_ws();
let right = self.parse_comma()?;
left = Filter::Pipe(Box::new(left), Box::new(right));
self.skip_ws();
}
Ok(left)
}
fn parse_comma(&mut self) -> Result<Filter, JqError> {
let mut left = self.parse_or()?;
self.skip_ws();
while self.peek() == Some(b',') {
self.pos += 1;
self.skip_ws();
let right = self.parse_or()?;
left = Filter::Comma(Box::new(left), Box::new(right));
self.skip_ws();
}
Ok(left)
}
fn parse_or(&mut self) -> Result<Filter, JqError> {
let mut left = self.parse_and()?;
self.skip_ws();
while self.peek_word("or") {
self.pos += 2;
self.skip_ws();
let right = self.parse_and()?;
left = Filter::Or(Box::new(left), Box::new(right));
self.skip_ws();
}
Ok(left)
}
fn parse_and(&mut self) -> Result<Filter, JqError> {
let mut left = self.parse_comparison()?;
self.skip_ws();
while self.peek_word("and") {
self.pos += 3;
self.skip_ws();
let right = self.parse_comparison()?;
left = Filter::And(Box::new(left), Box::new(right));
self.skip_ws();
}
Ok(left)
}
fn peek_word(&self, word: &str) -> bool {
let bytes = word.as_bytes();
if self.pos + bytes.len() > self.input.len() {
return false;
}
let slice = &self.input[self.pos..self.pos + bytes.len()];
if slice != bytes {
return false;
}
if self.pos + bytes.len() < self.input.len() {
let next = self.input[self.pos + bytes.len()];
if next.is_ascii_alphanumeric() || next == b'_' {
return false;
}
}
true
}
fn parse_comparison(&mut self) -> Result<Filter, JqError> {
let left = self.parse_add_sub()?;
self.skip_ws();
match self.peek() {
Some(b'=') if self.pos + 1 < self.input.len() && self.input[self.pos + 1] == b'=' => {
self.pos += 2;
self.skip_ws();
let right = self.parse_add_sub()?;
Ok(Filter::Equal(Box::new(left), Box::new(right)))
}
Some(b'!') if self.pos + 1 < self.input.len() && self.input[self.pos + 1] == b'=' => {
self.pos += 2;
self.skip_ws();
let right = self.parse_add_sub()?;
Ok(Filter::NotEqual(Box::new(left), Box::new(right)))
}
Some(b'<') if self.pos + 1 < self.input.len() && self.input[self.pos + 1] == b'=' => {
self.pos += 2;
self.skip_ws();
let right = self.parse_add_sub()?;
Ok(Filter::LessEq(Box::new(left), Box::new(right)))
}
Some(b'>') if self.pos + 1 < self.input.len() && self.input[self.pos + 1] == b'=' => {
self.pos += 2;
self.skip_ws();
let right = self.parse_add_sub()?;
Ok(Filter::GreaterEq(Box::new(left), Box::new(right)))
}
Some(b'<') => {
self.pos += 1;
self.skip_ws();
let right = self.parse_add_sub()?;
Ok(Filter::Less(Box::new(left), Box::new(right)))
}
Some(b'>') => {
self.pos += 1;
self.skip_ws();
let right = self.parse_add_sub()?;
Ok(Filter::Greater(Box::new(left), Box::new(right)))
}
_ => Ok(left),
}
}
fn parse_add_sub(&mut self) -> Result<Filter, JqError> {
let mut left = self.parse_mul_div_mod()?;
self.skip_ws();
loop {
match self.peek() {
Some(b'+') => {
self.pos += 1;
self.skip_ws();
let right = self.parse_mul_div_mod()?;
left = Filter::FilterAdd(Box::new(left), Box::new(right));
self.skip_ws();
}
Some(b'-') => {
if self.pos + 1 < self.input.len()
&& self.input[self.pos + 1].is_ascii_digit()
{
self.pos += 1;
self.skip_ws();
let right = self.parse_mul_div_mod()?;
left = Filter::FilterSub(Box::new(left), Box::new(right));
self.skip_ws();
} else {
self.pos += 1;
self.skip_ws();
let right = self.parse_mul_div_mod()?;
left = Filter::FilterSub(Box::new(left), Box::new(right));
self.skip_ws();
}
}
_ => break,
}
}
Ok(left)
}
fn parse_mul_div_mod(&mut self) -> Result<Filter, JqError> {
let mut left = self.parse_unary()?;
self.skip_ws();
loop {
match self.peek() {
Some(b'*') => {
self.pos += 1;
self.skip_ws();
let right = self.parse_unary()?;
left = Filter::FilterMul(Box::new(left), Box::new(right));
self.skip_ws();
}
Some(b'/') => {
self.pos += 1;
self.skip_ws();
let right = self.parse_unary()?;
left = Filter::FilterDiv(Box::new(left), Box::new(right));
self.skip_ws();
}
Some(b'%') => {
self.pos += 1;
self.skip_ws();
let right = self.parse_unary()?;
left = Filter::FilterMod(Box::new(left), Box::new(right));
self.skip_ws();
}
_ => break,
}
}
Ok(left)
}
fn parse_unary(&mut self) -> Result<Filter, JqError> {
self.parse_primary()
}
fn parse_primary(&mut self) -> Result<Filter, JqError> {
self.skip_ws();
if self.peek() == Some(b'-')
&& self.pos + 1 < self.input.len()
&& self.input[self.pos + 1].is_ascii_digit()
{
return self.parse_number();
}
match self.peek() {
Some(b'.') => {
self.pos += 1;
if self.peek() == Some(b'.') {
self.pos += 1;
return Ok(Filter::RecursiveDescentShorthand);
}
self.skip_ws();
let base = Filter::Identity;
self.parse_suffix(Box::new(base))
}
Some(b'"') => self.parse_string_literal(),
Some(b'0'..=b'9') => self.parse_number(),
Some(b't') if self.peek_word_no_skip("true") => {
self.pos += 4;
Ok(Filter::TrueLiteral)
}
Some(b'f') if self.peek_word_no_skip("false") => {
self.pos += 5;
Ok(Filter::FalseLiteral)
}
Some(b'n') if self.peek_word_no_skip("null") => {
self.pos += 4;
Ok(Filter::NullLiteral)
}
Some(b'(') => {
self.pos += 1;
self.skip_ws();
let expr = self.parse_pipe()?;
self.skip_ws();
self.expect(b')')?;
let mut result = Filter::Paren(Box::new(expr));
self.skip_ws();
result = self.try_parse_suffixes(result)?;
Ok(result)
}
Some(b'{') => self.parse_object_constructor(),
Some(b'[') => self.parse_array_constructor(),
Some(b'i') if self.peek_word_no_skip("if") => self.parse_if_then_else(),
Some(b't') if self.peek_word_no_skip("try") => self.parse_try_catch(),
Some(b'e') if self.peek_word_no_skip("empty") => {
self.pos += 5;
Ok(Filter::Empty)
}
Some(b'r') if self.peek_word_no_skip("recurse") => {
self.pos += 7;
self.skip_ws();
if self.peek() == Some(b'(') {
self.pos += 1;
self.skip_ws();
let expr = self.parse_pipe()?;
self.skip_ws();
self.expect(b')')?;
Ok(Filter::Recurse(Some(Box::new(expr))))
} else {
Ok(Filter::Recurse(None))
}
}
Some(b's') if self.peek_word_no_skip("select") => {
self.pos += 6;
self.parse_call(Filter::Select)
}
Some(b'm') if self.peek_word_no_skip("map") => {
self.pos += 3;
if self.peek() == Some(b'(') {
self.parse_call(Filter::Map)
} else {
return Err(JqError::with_pos(
"expected '(' after map".to_string(),
self.pos,
));
}
}
Some(b'k') if self.peek_word_no_skip("keys") => {
self.pos += 4;
Ok(Filter::Keys)
}
Some(b'l') => {
if self.peek_word_no_skip("length") {
self.pos += 6;
Ok(Filter::Length)
} else if self.peek_word_no_skip("limit") {
self.pos += 5;
self.skip_ws();
self.expect(b'(')?;
self.skip_ws();
let n = self.parse_pipe()?;
self.skip_ws();
self.expect(b';')?;
self.skip_ws();
let expr = self.parse_pipe()?;
self.skip_ws();
self.expect(b')')?;
Ok(Filter::Limit(Box::new(n), Box::new(expr)))
} else if self.peek_word_no_skip("last") {
self.pos += 4;
Ok(Filter::Last)
} else {
Err(JqError::with_pos(
format!("unexpected identifier starting with 'l' at {}", self.pos),
self.pos,
))
}
}
Some(b'h') if self.peek_word_no_skip("has") => {
self.pos += 3;
self.parse_string_arg(Filter::Has)
}
Some(b'd') if self.peek_word_no_skip("del") => {
self.pos += 3;
self.skip_ws();
self.expect(b'(')?;
self.skip_ws();
let expr = self.parse_pipe()?;
self.skip_ws();
self.expect(b')')?;
Ok(Filter::Del(Box::new(expr)))
}
Some(b't') if self.peek_word_no_skip("to_entries") => {
self.pos += 10;
Ok(Filter::ToEntries)
}
Some(b'f') if self.peek_word_no_skip("from_entries") => {
self.pos += 12;
Ok(Filter::FromEntries)
}
Some(b'a') if self.peek_word_no_skip("add") => {
self.pos += 3;
Ok(Filter::Add)
}
Some(b's') if self.peek_word_no_skip("sort_by") => {
self.pos += 7;
self.parse_call(Filter::SortBy)
}
Some(b's') if self.peek_word_no_skip("sort") => {
self.pos += 4;
Ok(Filter::Sort)
}
Some(b'u') if self.peek_word_no_skip("unique") => {
self.pos += 6;
Ok(Filter::Unique)
}
Some(b'r') if self.peek_word_no_skip("reverse") => {
self.pos += 7;
Ok(Filter::Reverse)
}
Some(b'g') if self.peek_word_no_skip("group_by") => {
self.pos += 8;
self.parse_call(Filter::GroupBy)
}
Some(b'm') if self.peek_word_no_skip("min") => {
let word = &self.input[self.pos..std::cmp::min(self.pos + 4, self.input.len())];
if word == b"min" && (self.pos + 3 >= self.input.len() || !self.input[self.pos + 3].is_ascii_alphanumeric()) {
self.pos += 3;
return Ok(Filter::Min);
}
Err(JqError::with_pos("unexpected identifier".to_string(), self.pos))
}
Some(b'm') if self.peek_word_no_skip("max") => {
self.pos += 3;
Ok(Filter::Max)
}
Some(b'f') if self.peek_word_no_skip("first") => {
self.pos += 5;
Ok(Filter::First)
}
Some(b'n') if self.peek_word_no_skip("nth") => {
self.pos += 3;
self.parse_call(Filter::Nth)
}
Some(b'n') if self.peek_word_no_skip("not") => {
self.pos += 3;
let inner = Filter::Identity;
Ok(Filter::Not(Box::new(inner)))
}
Some(b'r') if self.peek_word_no_skip("range") => {
self.pos += 5;
self.skip_ws();
self.expect(b'(')?;
self.skip_ws();
let from = self.parse_pipe()?;
self.skip_ws();
self.expect(b';')?;
self.skip_ws();
let to = self.parse_pipe()?;
self.skip_ws();
self.expect(b')')?;
Ok(Filter::Range(Box::new(from), Box::new(to)))
}
Some(c) => Err(JqError::with_pos(
format!("unexpected character '{}'", c as char),
self.pos,
)),
None => Err(JqError::new("unexpected end of filter")),
}
}
fn peek_word_no_skip(&self, word: &str) -> bool {
let bytes = word.as_bytes();
if self.pos + bytes.len() > self.input.len() {
return false;
}
let slice = &self.input[self.pos..self.pos + bytes.len()];
if !slice.eq_ignore_ascii_case(bytes) {
return false;
}
if self.pos + bytes.len() < self.input.len() {
let next = self.input[self.pos + bytes.len()];
if next.is_ascii_alphanumeric() || next == b'_' {
return false;
}
}
true
}
fn parse_call(&mut self, constructor: fn(Box<Filter>) -> Filter) -> Result<Filter, JqError> {
self.skip_ws();
self.expect(b'(')?;
self.skip_ws();
let expr = self.parse_pipe()?;
self.skip_ws();
self.expect(b')')?;
Ok(constructor(Box::new(expr)))
}
fn parse_string_arg(
&mut self,
constructor: fn(String) -> Filter,
) -> Result<Filter, JqError> {
self.skip_ws();
self.expect(b'(')?;
self.skip_ws();
if self.peek() != Some(b'"') {
return Err(JqError::with_pos(
"expected string argument".to_string(),
self.pos,
));
}
self.pos += 1; let s = self.parse_quoted_string_content()?;
self.skip_ws();
self.expect(b')')?;
Ok(constructor(s))
}
fn parse_quoted_string_content(&mut self) -> Result<String, JqError> {
let mut s = String::new();
loop {
match self.advance() {
None => return Err(JqError::new("unterminated string in filter")),
Some(b'"') => return Ok(s),
Some(b'\\') => match self.advance() {
Some(b'"') => s.push('"'),
Some(b'\\') => s.push('\\'),
Some(b'n') => s.push('\n'),
Some(b'r') => s.push('\r'),
Some(b't') => s.push('\t'),
Some(b'(') => s.push('('),
Some(b')') => s.push(')'),
Some(c) => {
s.push('\\');
s.push(c as char);
}
None => return Err(JqError::new("unterminated escape in filter string")),
},
Some(c) => s.push(c as char),
}
}
}
fn parse_string_literal(&mut self) -> Result<Filter, JqError> {
self.expect(b'"')?; let mut parts: Vec<InterpPart> = Vec::new();
let mut text = String::new();
loop {
match self.advance() {
None => return Err(JqError::new("unterminated string literal")),
Some(b'"') => {
if !text.is_empty() {
parts.push(InterpPart::Text(std::mem::take(&mut text)));
}
if parts.is_empty() {
return Ok(Filter::StringLiteral(String::new()));
}
if parts.len() == 1 {
if let InterpPart::Text(s) = &parts[0] {
return Ok(Filter::StringLiteral(s.clone()));
}
}
return Ok(Filter::StringInterpolation(parts));
}
Some(b'\\') => {
if self.peek() == Some(&b'(') {
self.pos += 1; if !text.is_empty() {
parts.push(InterpPart::Text(std::mem::take(&mut text)));
}
let expr = self.parse_pipe()?;
self.expect(b')')?;
parts.push(InterpPart::Expr(Box::new(expr)));
} else {
match self.advance() {
Some(b'"') => text.push('"'),
Some(b'\\') => text.push('\\'),
Some(b'n') => text.push('\n'),
Some(b'r') => text.push('\r'),
Some(b't') => text.push('\t'),
Some(c) => {
text.push('\\');
text.push(c as char);
}
None => {
return Err(JqError::new("unterminated escape in string literal"))
}
}
}
}
Some(c) => text.push(c as char),
}
}
}
fn parse_number(&mut self) -> Result<Filter, JqError> {
let start = self.pos;
let mut num_str = String::new();
if self.peek() == Some(&b'-') {
num_str.push('-');
self.pos += 1;
}
match self.peek() {
Some(b'0') => {
num_str.push('0');
self.pos += 1;
}
Some(b'1'..=b'9') => {
while let Some(b) = self.peek() {
if b.is_ascii_digit() {
num_str.push(b as char);
self.pos += 1;
} else {
break;
}
}
}
_ => {
return Err(JqError::with_pos(
"expected digit in number".to_string(),
self.pos,
));
}
}
if self.peek() == Some(&b'.')
&& self.pos + 1 < self.input.len()
&& self.input[self.pos + 1].is_ascii_digit()
{
num_str.push('.');
self.pos += 1;
while let Some(b) = self.peek() {
if b.is_ascii_digit() {
num_str.push(b as char);
self.pos += 1;
} else {
break;
}
}
}
if self.peek() == Some(&b'e') || self.peek() == Some(&b'E') {
let peek_pos = self.pos + 1;
if peek_pos < self.input.len() {
let next = self.input[peek_pos];
if next.is_ascii_digit() || next == b'+' || next == b'-' {
num_str.push(self.advance().unwrap() as char);
if self.peek() == Some(&b'+') || self.peek() == Some(&b'-') {
num_str.push(self.advance().unwrap() as char);
}
while let Some(b) = self.peek() {
if b.is_ascii_digit() {
num_str.push(b as char);
self.pos += 1;
} else {
break;
}
}
}
}
}
let n: f64 = num_str.parse().map_err(|_| {
JqError::with_pos(format!("invalid number '{}'", num_str), start)
})?;
Ok(Filter::NumberLiteral(n))
}
fn parse_object_constructor(&mut self) -> Result<Filter, JqError> {
self.expect(b'{')?;
self.skip_ws();
if self.peek() == Some(&b'}') {
self.pos += 1;
return Ok(Filter::ObjectConstruction(Vec::new()));
}
let mut fields: Vec<(String, Box<Filter>)> = Vec::new();
loop {
self.skip_ws();
let key = if self.peek() == Some(&b'"') {
self.pos += 1;
let s = self.parse_quoted_string_content()?;
s
} else if let Some(b) = self.peek() {
if b.is_ascii_alphabetic() || b == b'_' {
let mut ident = String::new();
while let Some(b) = self.peek() {
if b.is_ascii_alphanumeric() || b == b'_' {
ident.push(b as char);
self.pos += 1;
} else {
break;
}
}
ident
} else {
return Err(JqError::with_pos(
format!("expected key in object constructor, got '{}'", b as char),
self.pos,
));
}
} else {
return Err(JqError::new("unterminated object constructor"));
};
self.skip_ws();
if self.peek() == Some(&b'}') || self.peek() == Some(&b',') {
let key_clone = key.clone();
fields.push((key, Box::new(Filter::FieldAccess(
Box::new(Filter::Identity),
key_clone,
))));
} else {
self.expect(b':')?;
self.skip_ws();
let val = self.parse_pipe()?;
fields.push((key, Box::new(val)));
}
self.skip_ws();
match self.peek() {
Some(b',') => {
self.pos += 1;
continue;
}
Some(b'}') => {
self.pos += 1;
break;
}
Some(c) => {
return Err(JqError::with_pos(
format!("expected ',' or '}}' in object constructor, got '{}'", c as char),
self.pos,
));
}
None => {
return Err(JqError::new("unterminated object constructor"));
}
}
}
Ok(Filter::ObjectConstruction(fields))
}
fn parse_array_constructor(&mut self) -> Result<Filter, JqError> {
self.expect(b'[')?;
self.skip_ws();
if self.peek() == Some(&b']') {
self.pos += 1;
return Ok(Filter::ArrayConstruction(Vec::new()));
}
let mut elements: Vec<Box<Filter>> = Vec::new();
loop {
self.skip_ws();
let expr = self.parse_pipe()?;
elements.push(Box::new(expr));
self.skip_ws();
match self.peek() {
Some(b',') => {
self.pos += 1;
continue;
}
Some(b']') => {
self.pos += 1;
break;
}
Some(c) => {
return Err(JqError::with_pos(
format!("expected ',' or ']' in array constructor, got '{}'", c as char),
self.pos,
));
}
None => return Err(JqError::new("unterminated array constructor")),
}
}
Ok(Filter::ArrayConstruction(elements))
}
fn parse_if_then_else(&mut self) -> Result<Filter, JqError> {
self.pos += 2; self.skip_ws();
let cond = self.parse_pipe()?;
self.skip_ws();
self.expect_keyword(b"then")?;
self.skip_ws();
let then_expr = self.parse_pipe()?;
self.skip_ws();
let mut else_expr = None;
if self.peek_word("elif") {
self.pos += 4;
self.skip_ws();
let elif_cond = self.parse_pipe()?;
self.skip_ws();
self.expect_keyword(b"then")?;
self.skip_ws();
let elif_then = self.parse_pipe()?;
self.skip_ws();
self.expect_keyword(b"else")?;
self.skip_ws();
let elif_else = self.parse_pipe()?;
else_expr = Some(Filter::IfThenElse(
Box::new(elif_cond),
Box::new(elif_then),
Box::new(elif_else),
));
} else {
self.expect_keyword(b"else")?;
self.skip_ws();
else_expr = Some(self.parse_pipe()?);
}
self.skip_ws();
self.expect_keyword(b"end")?;
Ok(Filter::IfThenElse(
Box::new(cond),
Box::new(then_expr),
Box::new(else_expr.unwrap()),
))
}
fn parse_try_catch(&mut self) -> Result<Filter, JqError> {
self.pos += 3; self.skip_ws();
let try_expr = self.parse_pipe()?;
self.skip_ws();
if self.peek_word("catch") {
self.pos += 5;
self.skip_ws();
let catch_expr = self.parse_pipe()?;
Ok(Filter::TryCatch(Box::new(try_expr), Box::new(catch_expr)))
} else {
Ok(Filter::TryCatch(
Box::new(try_expr),
Box::new(Filter::Empty),
))
}
}
fn expect_keyword(&mut self, kw: &[u8]) -> Result<(), JqError> {
self.skip_ws();
for &expected in kw.iter() {
match self.advance() {
Some(b) if b == expected => {}
_ => {
return Err(JqError::with_pos(
format!("expected keyword '{}'", String::from_utf8_lossy(kw)),
self.pos,
));
}
}
}
Ok(())
}
fn parse_suffix(&mut self, base: Box<Filter>) -> Result<Filter, JqError> {
let mut result = *base;
loop {
self.skip_ws();
match self.peek() {
Some(b'.') => {
self.pos += 1;
self.skip_ws();
if self.peek() == Some(&b'[') {
self.pos += 1;
self.skip_ws();
if self.peek() == Some(&b']') {
self.pos += 1;
result = Filter::Iterator(Box::new(result));
} else {
let index_expr = self.parse_pipe()?;
self.skip_ws();
self.expect(b']')?;
result = Filter::Index(Box::new(result), Box::new(index_expr));
}
} else if let Some(b) = self.peek() {
if b.is_ascii_alphabetic() || b == b'_' {
let mut key = String::new();
while let Some(b) = self.peek() {
if b.is_ascii_alphanumeric() || b == b'_' {
key.push(b as char);
self.pos += 1;
} else {
break;
}
}
result = Filter::FieldAccess(Box::new(result), key);
} else {
break;
}
} else {
break;
}
}
Some(b'[') => {
self.pos += 1;
self.skip_ws();
if self.peek() == Some(&b']') {
self.pos += 1;
result = Filter::Iterator(Box::new(result));
} else {
let index_expr = self.parse_pipe()?;
self.skip_ws();
self.expect(b']')?;
result = Filter::Index(Box::new(result), Box::new(index_expr));
}
}
Some(b'|') => {
break;
}
_ => break,
}
}
Ok(result)
}
fn try_parse_suffixes(&mut self, mut result: Filter) -> Result<Filter, JqError> {
loop {
self.skip_ws();
match self.peek() {
Some(b'.') => {
self.pos += 1;
self.skip_ws();
if self.peek() == Some(&b'[') {
self.pos += 1;
self.skip_ws();
if self.peek() == Some(&b']') {
self.pos += 1;
result = Filter::Iterator(Box::new(result));
} else {
let index_expr = self.parse_pipe()?;
self.skip_ws();
self.expect(b']')?;
result = Filter::Index(Box::new(result), Box::new(index_expr));
}
} else if let Some(b) = self.peek() {
if b.is_ascii_alphabetic() || b == b'_' {
let mut key = String::new();
while let Some(b) = self.peek() {
if b.is_ascii_alphanumeric() || b == b'_' {
key.push(b as char);
self.pos += 1;
} else {
break;
}
}
result = Filter::FieldAccess(Box::new(result), key);
} else {
break;
}
} else {
break;
}
}
Some(b'[') => {
self.pos += 1;
self.skip_ws();
if self.peek() == Some(&b']') {
self.pos += 1;
result = Filter::Iterator(Box::new(result));
} else {
let index_expr = self.parse_pipe()?;
self.skip_ws();
self.expect(b']')?;
result = Filter::Index(Box::new(result), Box::new(index_expr));
}
}
_ => break,
}
}
Ok(result)
}
fn expect(&mut self, expected: u8) -> Result<(), JqError> {
self.skip_ws();
match self.advance() {
Some(b) if b == expected => Ok(()),
Some(b) => Err(JqError::with_pos(
format!(
"expected '{}', found '{}'",
expected as char, b as char
),
self.pos - 1,
)),
None => Err(JqError::with_pos(
format!("expected '{}', found end of input", expected as char),
self.pos,
)),
}
}
}
pub fn parse_filter(input: &str) -> Result<Filter, JqError> {
let mut parser = FilterParser::new(input);
parser.parse_filter()
}
pub fn evaluate(filter: &Filter, input: &Value) -> Result<Vec<Value>, JqError> {
let mut context = EvalContext::new();
let results = eval_one(filter, input, &mut context)?;
Ok(results)
}
struct EvalContext {
}
impl EvalContext {
fn new() -> Self {
EvalContext {}
}
}
fn eval_one(filter: &Filter, input: &Value, ctx: &mut EvalContext) -> Result<Vec<Value>, JqError> {
match filter {
Filter::Identity => Ok(vec![input.clone()]),
Filter::FieldAccess(base, key) => {
let bases = eval_one(base, input, ctx)?;
let mut results = Vec::new();
for b in &bases {
match b {
Value::Object(map) => {
if let Some(v) = map.get(key) {
results.push(v.clone());
}
}
_ => {
}
}
}
Ok(results)
}
Filter::Index(base, index_expr) => {
let bases = eval_one(base, input, ctx)?;
let mut results = Vec::new();
for b in &bases {
let indices = eval_one(index_expr, b, ctx)?;
for idx in &indices {
match (b, idx) {
(Value::Array(arr), Value::Number(n)) => {
let i = *n as i64;
let len = arr.len() as i64;
let actual = if i < 0 { len + i } else { i };
if actual >= 0 && actual < len {
results.push(arr[actual as usize].clone());
}
}
(Value::Object(map), Value::String(k)) => {
if let Some(v) = map.get(k) {
results.push(v.clone());
}
}
_ => {
}
}
}
}
Ok(results)
}
Filter::Iterator(base) => {
let bases = eval_one(base, input, ctx)?;
let mut results = Vec::new();
for b in &bases {
match b {
Value::Array(arr) => {
for v in arr {
results.push(v.clone());
}
}
Value::Object(map) => {
for v in map.values() {
results.push(v.clone());
}
}
_ => {
results.push(b.clone());
}
}
}
Ok(results)
}
Filter::Pipe(left, right) => {
let left_results = eval_one(left, input, ctx)?;
let mut results = Vec::new();
for lr in &left_results {
let right_results = eval_one(right, lr, ctx)?;
results.extend(right_results);
}
Ok(results)
}
Filter::Comma(left, right) => {
let mut results = eval_one(left, input, ctx)?;
results.extend(eval_one(right, input, ctx)?);
Ok(results)
}
Filter::Select(cond) => {
let val = eval_one(cond, input, ctx)?;
for v in &val {
if v.is_truthy() {
return Ok(vec![input.clone()]);
}
}
Ok(Vec::new())
}
Filter::Map(expr) => {
match input {
Value::Array(arr) => {
let mut results = Vec::new();
for v in arr {
let mapped = eval_one(expr, v, ctx)?;
results.extend(mapped);
}
Ok(vec![Value::Array(results)])
}
_ => {
let mapped = eval_one(expr, input, ctx)?;
Ok(vec![Value::Array(mapped)])
}
}
}
Filter::Keys => {
match input {
Value::Object(map) => {
let keys: Vec<Value> = map
.keys()
.map(|k| Value::String(k.clone()))
.collect();
Ok(vec![Value::Array(keys)])
}
Value::Array(arr) => {
let indices: Vec<Value> = (0..arr.len())
.map(|i| Value::Number(i as f64))
.collect();
Ok(vec![Value::Array(indices)])
}
_ => Ok(Vec::new()),
}
}
Filter::Length => {
let len = input.jq_len();
Ok(vec![Value::Number(len as f64)])
}
Filter::Has(key) => match input {
Value::Object(map) => {
if map.contains_key(key) {
Ok(vec![Value::Bool(true)])
} else {
Ok(vec![Value::Bool(false)])
}
}
Value::Array(arr) => {
if let Ok(idx) = key.parse::<usize>() {
Ok(vec![Value::Bool(idx < arr.len())])
} else {
Ok(vec![Value::Bool(false)])
}
}
_ => Ok(vec![Value::Bool(false)]),
},
Filter::Del(path_expr) => {
match path_expr.as_ref() {
Filter::FieldAccess(base, key) => {
if matches!(base.as_ref(), Filter::Identity) {
match input {
Value::Object(map) => {
let mut new_map = map.clone();
new_map.remove(key);
Ok(vec![Value::Object(new_map)])
}
_ => Ok(vec![input.clone()]),
}
} else {
Ok(vec![input.clone()])
}
}
_ => {
Ok(vec![input.clone()])
}
}
}
Filter::ToEntries => match input {
Value::Object(map) => {
let entries: Vec<Value> = map
.iter()
.map(|(k, v)| {
let mut entry = BTreeMap::new();
entry.insert("key".to_string(), Value::String(k.clone()));
entry.insert("value".to_string(), v.clone());
Value::Object(entry)
})
.collect();
Ok(vec![Value::Array(entries)])
}
_ => Ok(Vec::new()),
},
Filter::FromEntries => match input {
Value::Array(arr) => {
let mut map = BTreeMap::new();
for entry in arr {
match entry {
Value::Object(ref obj_map) => {
let key = obj_map
.get("key")
.or_else(|| obj_map.get("Key"))
.cloned()
.unwrap_or(Value::Null);
let value = obj_map
.get("value")
.or_else(|| obj_map.get("Value"))
.cloned()
.unwrap_or(Value::Null);
if let Value::String(k) = key {
map.insert(k, value);
}
}
_ => {}
}
}
Ok(vec![Value::Object(map)])
}
_ => Ok(Vec::new()),
},
Filter::Add => match input {
Value::Array(arr) => {
if arr.is_empty() {
return Ok(vec![Value::Null]);
}
let all_strings = arr.iter().all(|v| matches!(v, Value::String(_)));
if all_strings {
let concat: String = arr
.iter()
.filter_map(|v| v.as_str())
.collect();
Ok(vec![Value::String(concat)])
} else {
let sum: f64 = arr
.iter()
.filter_map(|v| v.as_f64())
.sum();
Ok(vec![Value::Number(sum)])
}
}
_ => Ok(Vec::new()),
},
Filter::Sort => match input {
Value::Array(arr) => {
let mut sorted = arr.clone();
sorted.sort();
Ok(vec![Value::Array(sorted)])
}
_ => Ok(vec![input.clone()]),
},
Filter::SortBy(key_expr) => match input {
Value::Array(arr) => {
let mut pairs: Vec<(Value, Value)> = Vec::new();
for v in arr {
let keys = eval_one(key_expr, v, ctx)?;
for k in keys {
pairs.push((k, v.clone()));
}
}
pairs.sort_by(|a, b| a.0.cmp(&b.0));
let sorted: Vec<Value> = pairs.into_iter().map(|(_, v)| v).collect();
Ok(vec![Value::Array(sorted)])
}
_ => Ok(vec![input.clone()]),
},
Filter::Unique => match input {
Value::Array(arr) => {
let seen: HashSet<String> = HashSet::new();
let mut result: Vec<Value> = Vec::new();
let mut seen_set: Vec<Value> = Vec::new();
for v in arr {
let key = compact_print(v);
if !seen.contains(&key) {
result.push(v.clone());
seen_set.push(v.clone());
}
}
let mut unique: Vec<Value> = Vec::new();
let mut seen_keys: HashSet<String> = HashSet::new();
for v in arr {
let key = compact_print(v);
if seen_keys.insert(key) {
unique.push(v.clone());
}
}
Ok(vec![Value::Array(unique)])
}
_ => Ok(vec![input.clone()]),
},
Filter::Reverse => match input {
Value::Array(arr) => {
let mut reversed = arr.clone();
reversed.reverse();
Ok(vec![Value::Array(reversed)])
}
_ => Ok(vec![input.clone()]),
},
Filter::GroupBy(key_expr) => match input {
Value::Array(arr) => {
let mut groups: BTreeMap<String, Vec<Value>> = BTreeMap::new();
for v in arr {
let keys = eval_one(key_expr, v, ctx)?;
for k in keys {
let key_str = compact_print(&k);
groups.entry(key_str).or_default().push(v.clone());
}
}
let result: Vec<Value> = groups
.into_values()
.map(Value::Array)
.collect();
Ok(vec![Value::Array(result)])
}
_ => Ok(Vec::new()),
},
Filter::Min => match input {
Value::Array(arr) => {
if arr.is_empty() {
return Ok(vec![Value::Null]);
}
let min = arr.iter().min().unwrap().clone();
Ok(vec![min])
}
_ => Ok(vec![input.clone()]),
},
Filter::Max => match input {
Value::Array(arr) => {
if arr.is_empty() {
return Ok(vec![Value::Null]);
}
let max = arr.iter().max().unwrap().clone();
Ok(vec![max])
}
_ => Ok(vec![input.clone()]),
},
Filter::First => match input {
Value::Array(arr) => {
if arr.is_empty() {
Ok(vec![Value::Null])
} else {
Ok(vec![arr[0].clone()])
}
}
_ => Ok(vec![input.clone()]),
},
Filter::Last => match input {
Value::Array(arr) => {
if arr.is_empty() {
Ok(vec![Value::Null])
} else {
Ok(vec![arr[arr.len() - 1].clone()])
}
}
_ => Ok(vec![input.clone()]),
},
Filter::Nth(n_expr) => {
let indices = eval_one(n_expr, input, ctx)?;
let mut results = Vec::new();
for idx in &indices {
if let Value::Number(n) = idx {
let i = *n as i64;
match input {
Value::Array(arr) => {
let len = arr.len() as i64;
if i >= 0 && i < len {
results.push(arr[i as usize].clone());
}
}
_ => {
if i == 0 {
results.push(input.clone());
}
}
}
}
}
Ok(results)
}
Filter::Limit(n_expr, filter_expr) => {
let n_vals = eval_one(n_expr, input, ctx)?;
let mut results = Vec::new();
for n_val in &n_vals {
if let Value::Number(n) = n_val {
let limit = *n as usize;
let inner = eval_one(filter_expr, input, ctx)?;
for v in inner.iter().take(limit) {
results.push(v.clone());
}
}
}
Ok(results)
}
Filter::Range(from_expr, to_expr) => {
let from_vals = eval_one(from_expr, input, ctx)?;
let to_vals = eval_one(to_expr, input, ctx)?;
let mut results = Vec::new();
for f in &from_vals {
for t in &to_vals {
if let (Value::Number(fn), Value::Number(tn)) = (f, t) {
let start = *fn as i64;
let end = *tn as i64;
for i in start..end {
results.push(Value::Number(i as f64));
}
}
}
}
Ok(results)
}
Filter::Empty => Ok(Vec::new()),
Filter::IfThenElse(cond, then_expr, else_expr) => {
let cond_vals = eval_one(cond, input, ctx)?;
for cv in &cond_vals {
if cv.is_truthy() {
return eval_one(then_expr, input, ctx);
}
}
eval_one(else_expr, input, ctx)
}
Filter::Equal(left, right) => {
let left_vals = eval_one(left, input, ctx)?;
let right_vals = eval_one(right, input, ctx)?;
for l in &left_vals {
for r in &right_vals {
if l == r {
return Ok(vec![Value::Bool(true)]);
}
}
}
Ok(vec![Value::Bool(false)])
}
Filter::NotEqual(left, right) => {
let eq_result = eval_one(&Filter::Equal(left.clone(), right.clone()), input, ctx)?;
if eq_result.first() == Some(&Value::Bool(true)) {
Ok(vec![Value::Bool(false)])
} else {
Ok(vec![Value::Bool(true)])
}
}
Filter::Less(left, right) => compare_values(filter, left, right, input, ctx),
Filter::Greater(left, right) => compare_values(filter, left, right, input, ctx),
Filter::LessEq(left, right) => compare_values(filter, left, right, input, ctx),
Filter::GreaterEq(left, right) => compare_values(filter, left, right, input, ctx),
Filter::FilterAdd(left, right) => {
let left_vals = eval_one(left, input, ctx)?;
let right_vals = eval_one(right, input, ctx)?;
let mut results = Vec::new();
for l in &left_vals {
for r in &right_vals {
results.push(add_values(l, r));
}
}
Ok(results)
}
Filter::FilterSub(left, right) => arithmetic_op(left, right, input, ctx, |a, b| a - b),
Filter::FilterMul(left, right) => arithmetic_op(left, right, input, ctx, |a, b| a * b),
Filter::FilterDiv(left, right) => arithmetic_op(left, right, input, ctx, |a, b| a / b),
Filter::FilterMod(left, right) => arithmetic_op(left, right, input, ctx, |a, b| a % b),
Filter::And(left, right) => {
let left_vals = eval_one(left, input, ctx)?;
for lv in &left_vals {
if !lv.is_truthy() {
return Ok(vec![Value::Bool(false)]);
}
}
let right_vals = eval_one(right, input, ctx)?;
for rv in &right_vals {
if !rv.is_truthy() {
return Ok(vec![Value::Bool(false)]);
}
}
Ok(vec![Value::Bool(true)])
}
Filter::Or(left, right) => {
let left_vals = eval_one(left, input, ctx)?;
for lv in &left_vals {
if lv.is_truthy() {
return Ok(vec![Value::Bool(true)]);
}
}
let right_vals = eval_one(right, input, ctx)?;
for rv in &right_vals {
if rv.is_truthy() {
return Ok(vec![Value::Bool(true)]);
}
}
Ok(vec![Value::Bool(false)])
}
Filter::Not(expr) => {
let vals = eval_one(expr, input, ctx)?;
for v in &vals {
if v.is_truthy() {
return Ok(vec![Value::Bool(false)]);
}
}
Ok(vec![Value::Bool(true)])
}
Filter::StringLiteral(s) => Ok(vec![Value::String(s.clone())]),
Filter::NumberLiteral(n) => Ok(vec![Value::Number(*n)]),
Filter::TrueLiteral => Ok(vec![Value::Bool(true)]),
Filter::FalseLiteral => Ok(vec![Value::Bool(false)]),
Filter::NullLiteral => Ok(vec![Value::Null]),
Filter::StringInterpolation(parts) => {
let mut result = String::new();
for part in parts {
match part {
InterpPart::Text(s) => result.push_str(s),
InterpPart::Expr(expr) => {
let vals = eval_one(expr, input, ctx)?;
if let Some(v) = vals.first() {
result.push_str(&value_to_string(v));
}
}
}
}
Ok(vec![Value::String(result)])
}
Filter::ObjectConstruction(fields) => {
let mut map = BTreeMap::new();
for (key, expr) in fields {
let vals = eval_one(expr, input, ctx)?;
if let Some(v) = vals.first() {
map.insert(key.clone(), v.clone());
}
}
Ok(vec![Value::Object(map)])
}
Filter::ArrayConstruction(elements) => {
let mut arr = Vec::new();
for expr in elements {
let vals = eval_one(expr, input, ctx)?;
arr.extend(vals);
}
Ok(vec![Value::Array(arr)])
}
Filter::Paren(expr) => eval_one(expr, input, ctx),
Filter::TryCatch(try_expr, catch_expr) => match eval_one(try_expr, input, ctx) {
Ok(vals) => Ok(vals),
Err(_) => eval_one(catch_expr, input, ctx),
},
Filter::Recurse(expr) => {
let mut results = Vec::new();
results.push(input.clone());
let mut stack = vec![input.clone()];
while let Some(current) = stack.pop() {
let children = if let Some(ref e) = expr {
eval_one(e, ¤t, ctx)?
} else {
match ¤t {
Value::Array(arr) => arr.clone(),
Value::Object(map) => map.values().cloned().collect(),
_ => Vec::new(),
}
};
for child in children {
results.push(child.clone());
stack.push(child);
}
}
Ok(results)
}
Filter::RecursiveDescentShorthand => {
let mut results = Vec::new();
results.push(input.clone());
let mut stack = vec![input.clone()];
while let Some(current) = stack.pop() {
let children = match ¤t {
Value::Array(arr) => arr.clone(),
Value::Object(map) => map.values().cloned().collect(),
_ => Vec::new(),
};
for child in children {
results.push(child.clone());
stack.push(child);
}
}
Ok(results)
}
Filter::RecursiveDescent => {
let mut results = Vec::new();
results.push(input.clone());
let mut stack = vec![input.clone()];
while let Some(current) = stack.pop() {
let children = match ¤t {
Value::Array(arr) => arr.clone(),
Value::Object(map) => map.values().cloned().collect(),
_ => Vec::new(),
};
for child in children {
results.push(child.clone());
stack.push(child);
}
}
Ok(results)
}
}
}
fn eval_stream(filter: &Filter, inputs: &[Value], ctx: &mut EvalContext) -> Result<Vec<Value>, JqError> {
let mut results = Vec::new();
for input in inputs {
let vals = eval_one(filter, input, ctx)?;
results.extend(vals);
}
Ok(results)
}
fn compare_values(
filter: &Filter,
left: &Filter,
right: &Filter,
input: &Value,
ctx: &mut EvalContext,
) -> Result<Vec<Value>, JqError> {
let left_vals = eval_one(left, input, ctx)?;
let right_vals = eval_one(right, input, ctx)?;
for l in &left_vals {
for r in &right_vals {
let cmp = l.partial_cmp(r);
let result = match filter {
Filter::Less(..) => cmp == Some(std::cmp::Ordering::Less),
Filter::Greater(..) => cmp == Some(std::cmp::Ordering::Greater),
Filter::LessEq(..) => {
cmp == Some(std::cmp::Ordering::Less)
|| cmp == Some(std::cmp::Ordering::Equal)
}
Filter::GreaterEq(..) => {
cmp == Some(std::cmp::Ordering::Greater)
|| cmp == Some(std::cmp::Ordering::Equal)
}
_ => false,
};
if result {
return Ok(vec![Value::Bool(true)]);
}
}
}
Ok(vec![Value::Bool(false)])
}
fn arithmetic_op(
left: &Filter,
right: &Filter,
input: &Value,
ctx: &mut EvalContext,
op: fn(f64, f64) -> f64,
) -> Result<Vec<Value>, JqError> {
let left_vals = eval_one(left, input, ctx)?;
let right_vals = eval_one(right, input, ctx)?;
let mut results = Vec::new();
for l in &left_vals {
for r in &right_vals {
if let (Value::Number(a), Value::Number(b)) = (l, r) {
results.push(Value::Number(op(*a, *b)));
}
}
}
Ok(results)
}
fn add_values(left: &Value, right: &Value) -> Value {
match (left, right) {
(Value::Number(a), Value::Number(b)) => Value::Number(a + b),
(Value::String(a), Value::String(b)) => Value::String(format!("{}{}", a, b)),
(Value::Array(a), Value::Array(b)) => {
let mut result = a.clone();
result.extend(b.clone());
Value::Array(result)
}
(Value::Object(a), Value::Object(b)) => {
let mut result = a.clone();
for (k, v) in b {
result.insert(k.clone(), v.clone());
}
Value::Object(result)
}
(a, b) => {
let sa = value_to_string(a);
let sb = value_to_string(b);
Value::String(format!("{}{}", sa, sb))
}
}
}
fn value_to_string(value: &Value) -> String {
match value {
Value::String(s) => s.clone(),
Value::Number(n) => {
if *n == n.trunc() && n.is_finite() && n.abs() < 1e15 {
format!("{}", *n as i64)
} else {
format!("{}", n)
}
}
Value::Bool(true) => "true".to_string(),
Value::Bool(false) => "false".to_string(),
Value::Null => "null".to_string(),
Value::Object(_) => compact_print(value),
Value::Array(_) => compact_print(value),
}
}
pub fn run_jq(json_input: &str, filter_str: &str) -> Result<Vec<Value>, JqError> {
let value = parse_json(json_input)?;
let filter = parse_filter(filter_str)?;
evaluate(&filter, &value)
}
#[derive(Debug)]
pub struct JqConfig {
pub filter: String,
pub pretty: bool,
pub compact: bool,
pub raw_output: bool,
pub raw_input: bool,
pub null_output: bool,
pub indent: usize,
pub help: bool,
pub color: bool,
pub sort_keys: bool,
pub slurp: bool,
}
impl Default for JqConfig {
fn default() -> Self {
JqConfig {
filter: String::new(),
pretty: false,
compact: true,
raw_output: false,
raw_input: false,
null_output: true,
indent: 2,
help: false,
color: false,
sort_keys: true, slurp: false,
}
}
}
pub fn parse_args(args: &[String]) -> Result<JqConfig, String> {
let mut config = JqConfig::default();
let mut i = 1;
while i < args.len() {
let arg = &args[i];
match arg.as_str() {
"-r" | "--raw-output" => {
config.raw_output = true;
}
"--raw-input" | "-R" => {
config.raw_input = true;
}
"-c" | "--compact-output" => {
config.compact = true;
config.pretty = false;
}
"--pretty" => {
config.pretty = true;
config.compact = false;
}
"-n" | "--null-input" => {
config.null_output = true;
}
"-s" | "--slurp" => {
config.slurp = true;
}
"--indent" => {
i += 1;
if i >= args.len() {
return Err("missing argument for --indent".to_string());
}
config.indent = args[i]
.parse()
.map_err(|_| "invalid indent value".to_string())?;
}
"-C" | "--color-output" => {
config.color = true;
}
"-M" | "--monochrome-output" => {
config.color = false;
}
"-h" | "--help" => {
config.help = true;
return Ok(config);
}
"--" => {
i += 1;
if i < args.len() {
config.filter = args[i..].join(" ");
}
break;
}
s if s.starts_with('-') && s.len() > 1 && !s.starts_with("--") => {
let flags = &s[1..];
for c in flags.chars() {
match c {
'r' => config.raw_output = true,
'R' => config.raw_input = true,
'c' => {
config.compact = true;
config.pretty = false;
}
'n' => config.null_output = true,
's' => config.slurp = true,
'C' => config.color = true,
'M' => config.color = false,
'h' => {
config.help = true;
return Ok(config);
}
_ => return Err(format!("unknown flag: -{}", c)),
}
}
}
_ => {
if config.filter.is_empty() {
config.filter = arg.clone();
} else {
config.filter.push(' ');
config.filter.push_str(arg);
}
}
}
i += 1;
}
if config.filter.is_empty() && !config.help {
config.filter = ".".to_string();
}
Ok(config)
}
pub fn print_help() {
println!("jq - command-line JSON processor (native Rust reimplementation)");
println!();
println!("Usage: jq [options] <filter> [file...]");
println!();
println!("Options:");
println!(" -c, --compact-output Compact output (no extra whitespace)");
println!(" -r, --raw-output Output raw strings (no JSON quotes)");
println!(" -R, --raw-input Read input as raw strings (not JSON)");
println!(" -n, --null-input Use null as single input value");
println!(" -s, --slurp Read entire input into a JSON array");
println!(" --pretty Pretty-print output");
println!(" --indent N Indent size for pretty printing (default: 2)");
println!(" -C, --color-output Force color output");
println!(" -M, --monochrome-output Disable color output");
println!(" -h, --help Show this help message");
println!();
println!("Basic filters:");
println!(" . Identity (pass input through)");
println!(" .key Field access");
println!(" .[index] Array index access");
println!(" .[] Array/object value iterator");
println!(" expr | expr Pipe output as input to next filter");
println!(" select(expr) Filter by condition");
println!(" map(expr) Transform each array element");
println!(" length Length of input");
println!(" keys Object keys or array indices");
println!(" has(\"key\") Check if object has key");
println!(" del(.key) Delete key from object");
println!();
println!("Examples:");
println!(" echo '{\"a\": 1}' | jq .a");
println!(" echo '[1,2,3]' | jq '.[] | select(. > 1)'");
println!(" echo '[1,2,3]' | jq 'map(. * 2)'");
println!(" echo '{\"x\": 1, \"y\": 2}' | jq keys");
}
pub fn read_stdin() -> Result<String, String> {
let stdin = io::stdin();
let mut input = String::new();
stdin
.lock()
.read_to_string(&mut input)
.map_err(|e| format!("failed to read stdin: {}", e))?;
Ok(input)
}
pub fn run_with_config(config: &JqConfig) -> Result<(), String> {
if config.help {
print_help();
return Ok(());
}
let input_str = read_stdin()?;
let filter = parse_filter(&config.filter).map_err(|e| e.to_string())?;
let input_value = if config.slurp {
let mut values = Vec::new();
for line in input_str.lines() {
let trimmed = line.trim();
if !trimmed.is_empty() {
match parse_json(trimmed) {
Ok(v) => values.push(v),
Err(e) => return Err(format!("parse error: {}", e)),
}
}
}
if values.is_empty() {
Value::Array(Vec::new())
} else {
Value::Array(values)
}
} else if config.raw_input {
Value::String(input_str)
} else {
let trimmed = input_str.trim();
if trimmed.is_empty() {
Value::Null
} else {
parse_json(trimmed).map_err(|e| format!("parse error: {}", e))?
}
};
let mut ctx = EvalContext::new();
let results = eval_one(&filter, &input_value, &mut ctx).map_err(|e| e.to_string())?;
let stdout = io::stdout();
let mut handle = stdout.lock();
for value in &results {
if config.raw_output {
match value {
Value::String(s) => {
writeln!(handle, "{}", s).map_err(|e| format!("write error: {}", e))?;
}
_ => {
let s = compact_print(value);
writeln!(handle, "{}", s).map_err(|e| format!("write error: {}", e))?;
}
}
} else if config.pretty {
let s = pretty_print(value, config.indent);
write!(handle, "{}", s).map_err(|e| format!("write error: {}", e))?;
} else {
let s = compact_print(value);
writeln!(handle, "{}", s).map_err(|e| format!("write error: {}", e))?;
}
}
if results.is_empty() && config.null_output {
writeln!(handle, "null").map_err(|e| format!("write error: {}", e))?;
}
Ok(())
}
pub fn main_jq(args: Vec<String>) -> Result<(), String> {
let config = parse_args(&args)?;
run_with_config(&config)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_null() {
let v = parse_json("null").unwrap();
assert_eq!(v, Value::Null);
}
#[test]
fn test_parse_true() {
let v = parse_json("true").unwrap();
assert_eq!(v, Value::Bool(true));
}
#[test]
fn test_parse_false() {
let v = parse_json("false").unwrap();
assert_eq!(v, Value::Bool(false));
}
#[test]
fn test_parse_integer() {
let v = parse_json("42").unwrap();
assert_eq!(v, Value::Number(42.0));
}
#[test]
fn test_parse_negative_integer() {
let v = parse_json("-17").unwrap();
assert_eq!(v, Value::Number(-17.0));
}
#[test]
fn test_parse_float() {
let v = parse_json("3.14").unwrap();
assert_eq!(v, Value::Number(3.14));
}
#[test]
fn test_parse_exponent() {
let v = parse_json("1e10").unwrap();
assert_eq!(v, Value::Number(1e10));
}
#[test]
fn test_parse_negative_exponent() {
let v = parse_json("2.5e-3").unwrap();
assert_eq!(v, Value::Number(0.0025));
}
#[test]
fn test_parse_string_simple() {
let v = parse_json("\"hello\"").unwrap();
assert_eq!(v, Value::String("hello".to_string()));
}
#[test]
fn test_parse_string_escape() {
let v = parse_json(r#""hello\nworld""#).unwrap();
assert_eq!(v, Value::String("hello\nworld".to_string()));
}
#[test]
fn test_parse_string_unicode() {
let v = parse_json(r#""\u0048\u0065\u006c\u006c\u006f""#).unwrap();
assert_eq!(v, Value::String("Hello".to_string()));
}
#[test]
fn test_parse_empty_array() {
let v = parse_json("[]").unwrap();
assert_eq!(v, Value::Array(Vec::new()));
}
#[test]
fn test_parse_array_simple() {
let v = parse_json("[1, 2, 3]").unwrap();
assert_eq!(
v,
Value::Array(vec![
Value::Number(1.0),
Value::Number(2.0),
Value::Number(3.0),
])
);
}
#[test]
fn test_parse_array_nested() {
let v = parse_json("[[1,2],[3,4]]").unwrap();
assert_eq!(
v,
Value::Array(vec![
Value::Array(vec![Value::Number(1.0), Value::Number(2.0)]),
Value::Array(vec![Value::Number(3.0), Value::Number(4.0)]),
])
);
}
#[test]
fn test_parse_empty_object() {
let v = parse_json("{}").unwrap();
assert_eq!(v, Value::Object(BTreeMap::new()));
}
#[test]
fn test_parse_object_simple() {
let v = parse_json(r#"{"a": 1, "b": 2}"#).unwrap();
let mut map = BTreeMap::new();
map.insert("a".to_string(), Value::Number(1.0));
map.insert("b".to_string(), Value::Number(2.0));
assert_eq!(v, Value::Object(map));
}
#[test]
fn test_parse_object_nested() {
let v = parse_json(r#"{"a": {"b": 1}}"#).unwrap();
let mut inner = BTreeMap::new();
inner.insert("b".to_string(), Value::Number(1.0));
let mut outer = BTreeMap::new();
outer.insert("a".to_string(), Value::Object(inner));
assert_eq!(v, Value::Object(outer));
}
#[test]
fn test_parse_complex() {
let json = r#"{
"name": "John",
"age": 30,
"scores": [95, 87, 92],
"address": {
"street": "123 Main St",
"city": "Anytown"
},
"active": true,
"spouse": null
}"#;
let v = parse_json(json).unwrap();
assert!(matches!(v, Value::Object(_)));
if let Value::Object(ref map) = v {
assert_eq!(map.get("name").unwrap(), &Value::String("John".to_string()));
assert_eq!(map.get("age").unwrap(), &Value::Number(30.0));
assert_eq!(map.get("active").unwrap(), &Value::Bool(true));
assert_eq!(map.get("spouse").unwrap(), &Value::Null);
}
}
#[test]
fn test_parse_invalid_json() {
assert!(parse_json("{bad}").is_err());
assert!(parse_json("[1, 2,]").is_err());
assert!(parse_json("'single quotes'").is_err());
}
#[test]
fn test_compact_print_null() {
assert_eq!(compact_print(&Value::Null), "null");
}
#[test]
fn test_compact_print_bool() {
assert_eq!(compact_print(&Value::Bool(true)), "true");
assert_eq!(compact_print(&Value::Bool(false)), "false");
}
#[test]
fn test_compact_print_number() {
assert_eq!(compact_print(&Value::Number(42.0)), "42");
assert_eq!(compact_print(&Value::Number(3.14)), "3.14");
}
#[test]
fn test_compact_print_string() {
assert_eq!(compact_print(&Value::String("hello".to_string())), r#""hello""#);
}
#[test]
fn test_compact_print_array() {
let arr = Value::Array(vec![
Value::Number(1.0),
Value::Number(2.0),
Value::Number(3.0),
]);
assert_eq!(compact_print(&arr), "[1,2,3]");
}
#[test]
fn test_compact_print_object() {
let mut map = BTreeMap::new();
map.insert("x".to_string(), Value::Number(1.0));
map.insert("y".to_string(), Value::Number(2.0));
let obj = Value::Object(map);
assert_eq!(compact_print(&obj), r#"{"x":1,"y":2}"#);
}
#[test]
fn test_pretty_print_object() {
let mut map = BTreeMap::new();
map.insert("name".to_string(), Value::String("Alice".to_string()));
let obj = Value::Object(map);
let pretty = pretty_print(&obj, 2);
assert!(pretty.contains("\"name\""));
assert!(pretty.contains("Alice"));
assert!(pretty.contains("\n"));
}
#[test]
fn test_roundtrip_parse_print() {
let json = r#"{"a":1,"b":[2,3],"c":{"d":true}}"#;
let v = parse_json(json).unwrap();
let printed = compact_print(&v);
let v2 = parse_json(&printed).unwrap();
assert_eq!(v, v2);
}
#[test]
fn test_parse_identity() {
let f = parse_filter(".").unwrap();
assert!(matches!(f, Filter::Identity));
}
#[test]
fn test_parse_field_access() {
let f = parse_filter(".name").unwrap();
assert!(matches!(f, Filter::FieldAccess(_, _)));
if let Filter::FieldAccess(base, key) = &f {
assert!(matches!(base.as_ref(), Filter::Identity));
assert_eq!(key, "name");
}
}
#[test]
fn test_parse_nested_field_access() {
let f = parse_filter(".a.b.c").unwrap();
assert!(matches!(f, Filter::FieldAccess(_, _)));
}
#[test]
fn test_parse_array_index() {
let f = parse_filter(".[0]").unwrap();
assert!(matches!(f, Filter::Index(_, _)));
}
#[test]
fn test_parse_iterator() {
let f = parse_filter(".[]").unwrap();
assert!(matches!(f, Filter::Iterator(_)));
}
#[test]
fn test_parse_pipe() {
let f = parse_filter(".a | .b").unwrap();
assert!(matches!(f, Filter::Pipe(_, _)));
}
#[test]
fn test_parse_select() {
let f = parse_filter("select(. > 1)").unwrap();
assert!(matches!(f, Filter::Select(_)));
}
#[test]
fn test_parse_map() {
let f = parse_filter("map(. + 1)").unwrap();
assert!(matches!(f, Filter::Map(_)));
}
#[test]
fn test_parse_string_literal() {
let f = parse_filter(r#""hello""#).unwrap();
assert!(matches!(f, Filter::StringLiteral(_)));
if let Filter::StringLiteral(s) = &f {
assert_eq!(s, "hello");
}
}
#[test]
fn test_parse_if_then_else() {
let f = parse_filter("if . > 0 then . else 0 end").unwrap();
assert!(matches!(f, Filter::IfThenElse(_, _, _)));
}
#[test]
fn test_parse_comparison() {
let f = parse_filter(".a == .b").unwrap();
assert!(matches!(f, Filter::Equal(_, _)));
}
#[test]
fn test_parse_range() {
let f = parse_filter("range(0; 10)").unwrap();
assert!(matches!(f, Filter::Range(_, _)));
}
#[test]
fn test_parse_limit() {
let f = parse_filter("limit(5; .[])").unwrap();
assert!(matches!(f, Filter::Limit(_, _)));
}
#[test]
fn test_parse_object_constructor() {
let f = parse_filter("{name: .name, age: .age}").unwrap();
assert!(matches!(f, Filter::ObjectConstruction(_)));
}
#[test]
fn test_parse_array_constructor() {
let f = parse_filter("[.a, .b, .c]").unwrap();
assert!(matches!(f, Filter::ArrayConstruction(_)));
}
#[test]
fn test_eval_identity() {
let input = parse_json("42").unwrap();
let result = run_jq("42", ".").unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0], Value::Number(42.0));
}
#[test]
fn test_eval_field_access() {
let input = r#"{"name": "Alice", "age": 30}"#;
let result = run_jq(input, ".name").unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0], Value::String("Alice".to_string()));
}
#[test]
fn test_eval_nested_field_access() {
let input = r#"{"a": {"b": {"c": 42}}}"#;
let result = run_jq(input, ".a.b.c").unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0], Value::Number(42.0));
}
#[test]
fn test_eval_array_index() {
let input = r#"[10, 20, 30, 40]"#;
let result = run_jq(input, ".[2]").unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0], Value::Number(30.0));
}
#[test]
fn test_eval_array_negative_index() {
let input = r#"[10, 20, 30, 40]"#;
let result = run_jq(input, ".[-1]").unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0], Value::Number(40.0));
}
#[test]
fn test_eval_iterator() {
let input = r#"[1, 2, 3]"#;
let result = run_jq(input, ".[]").unwrap();
assert_eq!(result.len(), 3);
assert_eq!(result[0], Value::Number(1.0));
assert_eq!(result[1], Value::Number(2.0));
assert_eq!(result[2], Value::Number(3.0));
}
#[test]
fn test_eval_pipe() {
let input = r#"{"items": [1, 2, 3]}"#;
let result = run_jq(input, ".items | .[]").unwrap();
assert_eq!(result.len(), 3);
}
#[test]
fn test_eval_select() {
let input = r#"[1, 2, 3, 4, 5]"#;
let result = run_jq(input, ".[] | select(. > 3)").unwrap();
assert_eq!(result.len(), 2);
assert_eq!(result[0], Value::Number(4.0));
assert_eq!(result[1], Value::Number(5.0));
}
#[test]
fn test_eval_map() {
let input = r#"[1, 2, 3]"#;
let result = run_jq(input, "map(. * 2)").unwrap();
assert_eq!(result.len(), 1);
assert!(matches!(&result[0], Value::Array(_)));
if let Value::Array(arr) = &result[0] {
assert_eq!(arr[0], Value::Number(2.0));
assert_eq!(arr[1], Value::Number(4.0));
assert_eq!(arr[2], Value::Number(6.0));
}
}
#[test]
fn test_eval_keys_object() {
let input = r#"{"x": 1, "y": 2, "z": 3}"#;
let result = run_jq(input, "keys").unwrap();
assert_eq!(result.len(), 1);
if let Value::Array(ref arr) = result[0] {
assert_eq!(arr.len(), 3);
assert!(arr.contains(&Value::String("x".to_string())));
assert!(arr.contains(&Value::String("y".to_string())));
assert!(arr.contains(&Value::String("z".to_string())));
}
}
#[test]
fn test_eval_length_array() {
let input = r#"[1, 2, 3, 4, 5]"#;
let result = run_jq(input, "length").unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0], Value::Number(5.0));
}
#[test]
fn test_eval_length_string() {
let input = r#""hello""#;
let result = run_jq(input, "length").unwrap();
assert_eq!(result[0], Value::Number(5.0));
}
#[test]
fn test_eval_length_object() {
let input = r#"{"a": 1, "b": 2, "c": 3}"#;
let result = run_jq(input, "length").unwrap();
assert_eq!(result[0], Value::Number(3.0));
}
#[test]
fn test_eval_has_true() {
let input = r#"{"name": "Alice", "age": 30}"#;
let result = run_jq(input, r#"has("name")"#).unwrap();
assert_eq!(result[0], Value::Bool(true));
}
#[test]
fn test_eval_has_false() {
let input = r#"{"name": "Alice"}"#;
let result = run_jq(input, r#"has("email")"#).unwrap();
assert_eq!(result[0], Value::Bool(false));
}
#[test]
fn test_eval_del() {
let input = r#"{"a": 1, "b": 2, "c": 3}"#;
let result = run_jq(input, "del(.b)").unwrap();
assert_eq!(result.len(), 1);
if let Value::Object(ref map) = result[0] {
assert!(map.contains_key("a"));
assert!(!map.contains_key("b"));
assert!(map.contains_key("c"));
}
}
#[test]
fn test_eval_to_entries() {
let input = r#"{"x": 1, "y": 2}"#;
let result = run_jq(input, "to_entries").unwrap();
assert_eq!(result.len(), 1);
if let Value::Array(ref arr) = result[0] {
assert_eq!(arr.len(), 2);
if let Value::Object(ref entry) = arr[0] {
assert!(entry.get("key").unwrap().as_str().unwrap() == "x"
|| entry.get("key").unwrap().as_str().unwrap() == "y");
}
}
}
#[test]
fn test_eval_from_entries() {
let input = r#"[{"key":"a","value":1},{"key":"b","value":2}]"#;
let result = run_jq(input, "from_entries").unwrap();
assert_eq!(result.len(), 1);
if let Value::Object(ref map) = result[0] {
assert_eq!(map.get("a").unwrap(), &Value::Number(1.0));
assert_eq!(map.get("b").unwrap(), &Value::Number(2.0));
}
}
#[test]
fn test_eval_add_numbers() {
let input = r#"[1, 2, 3, 4]"#;
let result = run_jq(input, "add").unwrap();
assert_eq!(result[0], Value::Number(10.0));
}
#[test]
fn test_eval_add_strings() {
let input = r#"["hello", " ", "world"]"#;
let result = run_jq(input, "add").unwrap();
assert_eq!(result[0], Value::String("hello world".to_string()));
}
#[test]
fn test_eval_sort() {
let input = r#"[3, 1, 4, 1, 5, 9]"#;
let result = run_jq(input, "sort").unwrap();
if let Value::Array(ref arr) = result[0] {
assert_eq!(arr[0], Value::Number(1.0));
assert_eq!(arr[arr.len() - 1], Value::Number(9.0));
}
}
#[test]
fn test_eval_sort_by() {
let input = r#"[{"v": 3}, {"v": 1}, {"v": 2}]"#;
let result = run_jq(input, "sort_by(.v)").unwrap();
if let Value::Array(ref arr) = result[0] {
if let Value::Object(ref m) = arr[0] {
assert_eq!(m.get("v").unwrap(), &Value::Number(1.0));
}
if let Value::Object(ref m) = arr[2] {
assert_eq!(m.get("v").unwrap(), &Value::Number(3.0));
}
}
}
#[test]
fn test_eval_unique() {
let input = r#"[1, 2, 2, 3, 3, 3, 1]"#;
let result = run_jq(input, "unique").unwrap();
if let Value::Array(ref arr) = result[0] {
assert_eq!(arr.len(), 3);
}
}
#[test]
fn test_eval_reverse() {
let input = r#"[1, 2, 3]"#;
let result = run_jq(input, "reverse").unwrap();
if let Value::Array(ref arr) = result[0] {
assert_eq!(arr[0], Value::Number(3.0));
assert_eq!(arr[2], Value::Number(1.0));
}
}
#[test]
fn test_eval_min() {
let input = r#"[5, 2, 8, 1, 9]"#;
let result = run_jq(input, "min").unwrap();
assert_eq!(result[0], Value::Number(1.0));
}
#[test]
fn test_eval_max() {
let input = r#"[5, 2, 8, 1, 9]"#;
let result = run_jq(input, "max").unwrap();
assert_eq!(result[0], Value::Number(9.0));
}
#[test]
fn test_eval_first() {
let input = r#"["a", "b", "c"]"#;
let result = run_jq(input, "first").unwrap();
assert_eq!(result[0], Value::String("a".to_string()));
}
#[test]
fn test_eval_last() {
let input = r#"["a", "b", "c"]"#;
let result = run_jq(input, "last").unwrap();
assert_eq!(result[0], Value::String("c".to_string()));
}
#[test]
fn test_eval_nth() {
let input = r#"["a", "b", "c", "d"]"#;
let result = run_jq(input, "nth(2)").unwrap();
assert_eq!(result[0], Value::String("c".to_string()));
}
#[test]
fn test_eval_limit() {
let input = r#"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"#;
let result = run_jq(input, "limit(3; .[])").unwrap();
assert_eq!(result.len(), 3);
assert_eq!(result[0], Value::Number(1.0));
assert_eq!(result[2], Value::Number(3.0));
}
#[test]
fn test_eval_range() {
let input = "null";
let result = run_jq(input, "range(0; 5)").unwrap();
assert_eq!(result.len(), 5);
for (i, v) in result.iter().enumerate() {
assert_eq!(*v, Value::Number(i as f64));
}
}
#[test]
fn test_eval_empty() {
let input = "42";
let result = run_jq(input, "empty").unwrap();
assert!(result.is_empty());
}
#[test]
fn test_eval_if_then_else_true() {
let input = "10";
let result = run_jq(input, "if . > 5 then \"big\" else \"small\" end").unwrap();
assert_eq!(result[0], Value::String("big".to_string()));
}
#[test]
fn test_eval_if_then_else_false() {
let input = "3";
let result = run_jq(input, "if . > 5 then \"big\" else \"small\" end").unwrap();
assert_eq!(result[0], Value::String("small".to_string()));
}
#[test]
fn test_eval_equal() {
let input = r#"{"a": 1, "b": 1}"#;
let result = run_jq(input, ".a == .b").unwrap();
assert_eq!(result[0], Value::Bool(true));
}
#[test]
fn test_eval_not_equal() {
let input = r#"{"a": 1, "b": 2}"#;
let result = run_jq(input, ".a != .b").unwrap();
assert_eq!(result[0], Value::Bool(true));
}
#[test]
fn test_eval_less_than() {
let input = r#"{"a": 1, "b": 2}"#;
let result = run_jq(input, ".a < .b").unwrap();
assert_eq!(result[0], Value::Bool(true));
}
#[test]
fn test_eval_greater_than() {
let input = r#"{"a": 5, "b": 3}"#;
let result = run_jq(input, ".a > .b").unwrap();
assert_eq!(result[0], Value::Bool(true));
}
#[test]
fn test_eval_arithmetic_add() {
let input = r#"{"x": 10, "y": 7}"#;
let result = run_jq(input, ".x + .y").unwrap();
assert_eq!(result[0], Value::Number(17.0));
}
#[test]
fn test_eval_arithmetic_sub() {
let input = r#"{"x": 10, "y": 3}"#;
let result = run_jq(input, ".x - .y").unwrap();
assert_eq!(result[0], Value::Number(7.0));
}
#[test]
fn test_eval_arithmetic_mul() {
let input = r#"{"x": 6, "y": 7}"#;
let result = run_jq(input, ".x * .y").unwrap();
assert_eq!(result[0], Value::Number(42.0));
}
#[test]
fn test_eval_arithmetic_div() {
let input = r#"{"x": 15, "y": 3}"#;
let result = run_jq(input, ".x / .y").unwrap();
assert_eq!(result[0], Value::Number(5.0));
}
#[test]
fn test_eval_arithmetic_mod() {
let input = r#"{"x": 10, "y": 3}"#;
let result = run_jq(input, ".x % .y").unwrap();
assert_eq!(result[0], Value::Number(1.0));
}
#[test]
fn test_eval_and_true() {
let input = r#"{"a": true, "b": true}"#;
let result = run_jq(input, ".a and .b").unwrap();
assert_eq!(result[0], Value::Bool(true));
}
#[test]
fn test_eval_and_false() {
let input = r#"{"a": true, "b": false}"#;
let result = run_jq(input, ".a and .b").unwrap();
assert_eq!(result[0], Value::Bool(false));
}
#[test]
fn test_eval_or_true() {
let input = r#"{"a": false, "b": true}"#;
let result = run_jq(input, ".a or .b").unwrap();
assert_eq!(result[0], Value::Bool(true));
}
#[test]
fn test_eval_not() {
let input = "false";
let result = run_jq(input, "false | not").unwrap();
assert_eq!(result[0], Value::Bool(true));
}
#[test]
fn test_eval_string_interpolation() {
let input = r#"{"name": "Alice", "age": 30}"#;
let result = run_jq(input, r#""Hello \(.name), you are \(.age) years old""#).unwrap();
assert_eq!(
result[0],
Value::String("Hello Alice, you are 30 years old".to_string())
);
}
#[test]
fn test_eval_array_concat() {
let input = r#"{"a": [1,2], "b": [3,4]}"#;
let result = run_jq(input, ".a + .b").unwrap();
if let Value::Array(ref arr) = result[0] {
assert_eq!(arr.len(), 4);
assert_eq!(arr[0], Value::Number(1.0));
assert_eq!(arr[3], Value::Number(4.0));
}
}
#[test]
fn test_eval_object_merge() {
let input = r#"{"a": {"x": 1}, "b": {"y": 2}}"#;
let result = run_jq(input, ".a + .b").unwrap();
if let Value::Object(ref map) = result[0] {
assert_eq!(map.get("x").unwrap(), &Value::Number(1.0));
assert_eq!(map.get("y").unwrap(), &Value::Number(2.0));
}
}
#[test]
fn test_eval_group_by() {
let input = r#"["apple", "banana", "apricot", "blueberry"]"#;
let result = run_jq(input, "group_by(.[0:1])").unwrap();
assert_eq!(result.len(), 1);
}
#[test]
fn test_eval_group_by_length() {
let input = r#"["a", "bb", "ccc", "dd"]"#;
let result = run_jq(input, "group_by(length)").unwrap();
if let Value::Array(ref groups) = result[0] {
assert!(groups.len() >= 2);
}
}
#[test]
fn test_eval_try_catch() {
let input = r#"{"a": 1}"#;
let result = run_jq(input, "try .b catch 0").unwrap();
assert_eq!(result[0], Value::Number(0.0));
}
#[test]
fn test_eval_recursive_descent() {
let input = r#"{"a": {"b": 1}, "c": [2, 3]}"#;
let result = run_jq(input, "..").unwrap();
assert!(result.len() > 1);
}
#[test]
fn test_eval_object_constructor() {
let input = r#"{"name": "Alice", "age": 30}"#;
let result = run_jq(input, "{full_name: .name, years: .age}").unwrap();
if let Value::Object(ref map) = result[0] {
assert_eq!(map.get("full_name").unwrap(), &Value::String("Alice".to_string()));
assert_eq!(map.get("years").unwrap(), &Value::Number(30.0));
}
}
#[test]
fn test_eval_array_constructor() {
let input = r#"{"a": 1, "b": 2, "c": 3}"#;
let result = run_jq(input, "[.a, .b, .c]").unwrap();
if let Value::Array(ref arr) = result[0] {
assert_eq!(arr.len(), 3);
assert_eq!(arr[0], Value::Number(1.0));
assert_eq!(arr[1], Value::Number(2.0));
assert_eq!(arr[2], Value::Number(3.0));
}
}
#[test]
fn test_eval_comma_operator() {
let input = r#"{"a": 1, "b": 2}"#;
let result = run_jq(input, ".a, .b").unwrap();
assert_eq!(result.len(), 2);
assert_eq!(result[0], Value::Number(1.0));
assert_eq!(result[1], Value::Number(2.0));
}
#[test]
fn test_eval_complex_pipeline() {
let input = r#"[{"name": "Alice", "score": 95}, {"name": "Bob", "score": 87}, {"name": "Charlie", "score": 92}]"#;
let result = run_jq(
input,
".[] | select(.score > 90) | .name",
)
.unwrap();
assert_eq!(result.len(), 2);
assert!(result.contains(&Value::String("Alice".to_string())));
assert!(result.contains(&Value::String("Charlie".to_string())));
}
#[test]
fn test_value_ordering() {
assert!(Value::Null < Value::Bool(false));
assert!(Value::Bool(false) < Value::Bool(true));
assert!(Value::Bool(true) < Value::Number(0.0));
assert!(Value::Number(1.0) < Value::Number(2.0));
assert!(Value::Number(2.0) < Value::String("a".to_string()));
assert!(Value::String("a".to_string()) < Value::String("b".to_string()));
}
#[test]
fn test_truthy_values() {
assert!(Value::Bool(true).is_truthy());
assert!(!Value::Bool(false).is_truthy());
assert!(!Value::Null.is_truthy());
assert!(Value::Number(0.0).is_truthy());
assert!(Value::Number(1.0).is_truthy());
assert!(Value::String("".to_string()).is_truthy());
assert!(Value::String("false".to_string()).is_truthy());
}
#[test]
fn test_filter_parse_errors() {
assert!(parse_filter("select(").is_err());
assert!(parse_filter("[").is_err());
assert!(parse_filter("{").is_err());
}
#[test]
fn test_multiple_values_stream() {
let input = r#"[1, 2, 3, 4, 5]"#;
let result = run_jq(
input,
".[] | select(. % 2 == 0)",
)
.unwrap();
assert_eq!(result.len(), 2);
assert_eq!(result[0], Value::Number(2.0));
assert_eq!(result[1], Value::Number(4.0));
}
#[test]
fn test_slurp_behavior() {
let input = r#"["line1", "line2", "line3"]"#;
let result = run_jq(input, ".[] | select(. == \"line2\")").unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0], Value::String("line2".to_string()));
}
#[test]
fn test_null_output() {
let input = "null";
let result = run_jq(input, ".").unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0], Value::Null);
}
#[test]
fn test_empty_array_output() {
let input = "[]";
let result = run_jq(input, ".[] | select(. > 100)").unwrap();
assert!(result.is_empty());
}
#[test]
fn test_parse_args_default() {
let args = vec!["jq".to_string(), ".".to_string()];
let config = parse_args(&args).unwrap();
assert_eq!(config.filter, ".");
assert!(!config.help);
assert!(!config.raw_output);
assert!(!config.pretty);
}
#[test]
fn test_parse_args_help() {
let args = vec!["jq".to_string(), "--help".to_string()];
let config = parse_args(&args).unwrap();
assert!(config.help);
}
#[test]
fn test_parse_args_raw_output() {
let args = vec!["jq".to_string(), "-r".to_string(), ".".to_string()];
let config = parse_args(&args).unwrap();
assert!(config.raw_output);
assert_eq!(config.filter, ".");
}
#[test]
fn test_parse_args_compact() {
let args = vec!["jq".to_string(), "-c".to_string(), ".".to_string()];
let config = parse_args(&args).unwrap();
assert!(config.compact);
}
#[test]
fn test_parse_args_pretty() {
let args = vec!["jq".to_string(), "--pretty".to_string(), ".".to_string()];
let config = parse_args(&args).unwrap();
assert!(config.pretty);
assert!(!config.compact);
}
#[test]
fn test_roundtrip_complex_json() {
let json = r#"{"users":[{"id":1,"name":"Alice","roles":["admin","user"]},{"id":2,"name":"Bob","roles":["user"]}],"metadata":{"count":2,"active":true}}"#;
let v = parse_json(json).unwrap();
let printed = compact_print(&v);
let v2 = parse_json(&printed).unwrap();
let result = run_jq(json, ".users[].name").unwrap();
assert_eq!(result.len(), 2);
assert_eq!(result[0], Value::String("Alice".to_string()));
assert_eq!(result[1], Value::String("Bob".to_string()));
let result = run_jq(json, ".users | map(.roles | length) | add").unwrap();
assert_eq!(result[0], Value::Number(3.0));
}
}