use jaq_core::load::{Arena, File, Loader};
use jaq_core::{Compiler, Ctx, Vars, data, unwrap_valr};
use jaq_json::write::Pp;
use jaq_json::{Val, read};
#[derive(Debug)]
pub(crate) enum Failure {
Input(String),
Program(String),
Run(String),
}
pub(crate) fn select(program: &str, input: &[u8]) -> Result<Vec<Vec<u8>>, Failure> {
let value = read::parse_single(input).map_err(|err| Failure::Input(err.to_string()))?;
let defs = jaq_core::defs()
.chain(jaq_std::defs())
.chain(jaq_json::defs());
let funs = jaq_core::funs()
.chain(jaq_std::funs())
.chain(jaq_json::funs());
let arena = Arena::default();
let file = File {
code: program,
path: (),
};
let modules = Loader::new(defs)
.load(&arena, file)
.map_err(|errs| Failure::Program(load_errors(&errs)))?;
let filter = Compiler::default()
.with_funs(funs)
.compile(modules)
.map_err(|errs| Failure::Program(compile_errors(&errs)))?;
let ctx = Ctx::<data::JustLut<Val>>::new(&filter.lut, Vars::new([]));
let mut rendered = Vec::new();
for output in filter.id.run((ctx, value)).map(unwrap_valr) {
let value = output.map_err(|err| Failure::Run(err.to_string()))?;
rendered.push(render(&value));
}
Ok(rendered)
}
fn render(value: &Val) -> Vec<u8> {
let pp = Pp::<String> {
indent: None,
sort_keys: true,
sep_space: false,
styles: Pp::default().styles,
};
let mut out = Vec::new();
if jaq_json::write::write(&mut out, &pp, 0, value).is_err() {
return Vec::new();
}
out
}
fn load_errors(errors: &jaq_core::load::Errors<&str, ()>) -> String {
use jaq_core::load::Error;
let mut lines: Vec<String> = Vec::new();
for (_file, error) in errors {
match error {
Error::Io(errs) => lines.extend(
errs.iter()
.map(|(what, err)| format!("cannot read `{what}`: {err}")),
),
Error::Lex(errs) => lines.extend(
errs.iter()
.map(|(expect, found)| expected(expect.as_str(), found)),
),
Error::Parse(errs) => lines.extend(
errs.iter()
.map(|(expect, found)| expected(expect.as_str(), found)),
),
}
}
join(&lines)
}
fn compile_errors(errors: &jaq_core::compile::Errors<&str, ()>) -> String {
let lines: Vec<String> = errors
.iter()
.flat_map(|(_file, errs)| errs.iter())
.map(|(name, undefined)| format!("undefined {} `{name}`", undefined.as_str()))
.collect();
join(&lines)
}
fn expected(what: &str, found: &str) -> String {
if found.is_empty() {
return format!("expected {what}, found end of program");
}
format!("expected {what}, found `{found}`")
}
fn join(lines: &[String]) -> String {
if lines.is_empty() {
return "the program could not be compiled".to_owned();
}
lines.join("; ")
}
#[cfg(test)]
#[path = "json_tests.rs"]
mod tests;