Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/stdlib.dj
Original file line number Diff line number Diff line change
Expand Up @@ -2222,6 +2222,13 @@ $ jaq -n '1, halt(42), 2'; echo $?
`jq` does not implement `halt($exit_code)`, only `halt`.
:::

::: Advanced
Note that not all integer values for `$exit_code` work as intended on all platforms; see
[`std::process::exit`](https://doc.rust-lang.org/stable/std/process/fn.exit.html#platform-specific-behavior).
As a general rule of thumb, the 8-bit signed integers (-128 to 127 inclusive) are safe to use and
anything outside of that range _may_ be silently truncated by the operating system.
:::

{#halt_error}
### `halt_error`, `halt_error($exit_code)`

Expand Down
3 changes: 2 additions & 1 deletion jaq-all/examples/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use jaq_all::{data, fmts, load};
use jaq_core::unwrap_valr;
use std::io::{self, Error, ErrorKind};

fn main() -> io::Result<()> {
Expand All @@ -18,7 +19,7 @@ fn main() -> io::Result<()> {
let fi = |e| Error::new(ErrorKind::InvalidData, e);

data::run(&runner, &filter, vars, inputs, fi, |v| {
let v = v.map_err(|e| Error::new(ErrorKind::Other, e.to_string()));
let v = unwrap_valr(v).map_err(|e| Error::new(ErrorKind::Other, e.to_string()));
fmts::write::write(stdout, &runner.writer, &v?)
})
}
8 changes: 4 additions & 4 deletions jaq-all/src/data.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! Commonly used data for filter compilation & execution.
use crate::{compile_with, FileReports, Fun};
use jaq_core::{data, unwrap_valr, DataT, Lut, Vars};
use jaq_core::{data, DataT, Lut, Vars};
use jaq_fmts::write::Writer;
use jaq_json::{Val, ValR};
use jaq_json::{Val, ValX};
use jaq_std::input::{self, Inputs, RcIter};

/// Filter for given kind of data.
Expand Down Expand Up @@ -93,7 +93,7 @@ pub fn run<E>(
vars: Vars<Val>,
inputs: impl Iterator<Item = Result<Val, impl ToString>>,
fi: impl Fn(String) -> E,
mut f: impl FnMut(ValR) -> Result<(), E>,
mut f: impl FnMut(ValX) -> Result<(), E>,
) -> Result<(), E> {
let inputs = Box::new(inputs.map(|r| r.map_err(|e| e.to_string())));
let null = Box::new(core::iter::once(Ok(Val::Null)));
Expand All @@ -109,7 +109,7 @@ pub fn run<E>(

let outputs = |x| filter.id.run((ctx.clone(), x));
(if runner.null_input { null } else { data.inputs }).try_for_each(|x| match x {
Ok(x) => outputs(x).try_for_each(|y| f(unwrap_valr(y))),
Ok(x) => outputs(x).try_for_each(&mut f),
Err(e) => Err(fi(e)),
})
}
24 changes: 20 additions & 4 deletions jaq-core/src/exn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ use core::fmt::{self, Display};

/// Exception.
///
/// This is either an error or control flow data internal to jaq.
/// Users should only be able to observe errors.
/// This is either an error, a runtime halt, or control flow data internal to jaq.
/// Users should only be able to observe the first two cases.
///
/// Use [`crate::val::unwrap_valr`] to convert a [`crate::val::ValX`] to an error.
/// Use [`crate::unwrap_valr`] to convert a [`crate::ValX`] to an error.
#[derive(Clone, Debug)]
pub struct Exn<'a, V>(pub(crate) Inner<'a, V>);

Expand All @@ -22,6 +22,7 @@ pub(crate) enum Inner<'a, V> {
/// If this can be observed by users, then this is a bug.
TailCall(Box<(&'a TermId, Vars<V>, CallInput<V>)>),
Break(usize),
Halt(i32),
}

#[derive(Clone, Debug)]
Expand All @@ -48,12 +49,27 @@ impl<V> CallInput<V> {

impl<V> Exn<'_, V> {
/// If the exception is an error, yield it, else yield the exception.
pub(crate) fn get_err(self) -> Result<Error<V>, Self> {
pub fn get_err(self) -> Result<Error<V>, Self> {
match self.0 {
Inner::Err(e) => Ok(*e),
_ => Err(self),
}
}

/// If the exception halts, yield the exit code, else yield the exception.
pub fn get_halt(self) -> Result<i32, Self> {
match self.0 {
Inner::Halt(code) => Ok(code),
_ => Err(self),
}
}

/// Create an exception intended to halt filter execution.
///
/// This is used by the `halt/1` filter.
pub fn halt(exit_code: i32) -> Self {
Self(Inner::Halt(exit_code))
}
}

impl<V> From<Error<V>> for Exn<'_, V> {
Expand Down
14 changes: 13 additions & 1 deletion jaq-core/src/val.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,20 @@ pub type ValXs<'a, T, V = T> = BoxIter<'a, ValX<'a, T, V>>;
///
/// If you are writing a native filter, e.g. `f(f1; ...; fn)`,
/// do not use this function on outputs of `fi`!
///
/// This function will exit the current process if
/// the value exception results from a call to the filter `halt`.
/// If the `std` feature is disabled, this function panics instead.
/// In a future jaq 3.0, this function should only be provided if
/// the `std` feature is enabled.
pub fn unwrap_valr<T, V>(v: ValX<T, V>) -> ValR<T, V> {
v.map_err(|e| e.get_err().ok().unwrap())
#[cfg(feature = "std")]
let exit = |exit_code| std::process::exit(exit_code);
#[cfg(not(feature = "std"))]
let exit = |exit_code| panic!("halt({})", exit_code);

let halt = |e: crate::Exn<_>| exit(e.get_halt().ok().unwrap());
v.map_err(|e| e.get_err().unwrap_or_else(halt))
}

/// Range of options, used for iteration operations.
Expand Down
11 changes: 8 additions & 3 deletions jaq-play/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ use alloc::{boxed::Box, string::String, vec::Vec};
use core::fmt::{self, Debug, Display, Formatter};
use jaq_all::data::{self, compile, Runner};
use jaq_all::fmts::{read, write};
use jaq_all::jaq_core::Exn;
use jaq_all::json::write::{Pp, Styles};
use jaq_all::json::{self, bstr, style, write_bytes, write_utf8, Val, ValR};
use jaq_all::json::{self, bstr, style, write_bytes, write_utf8, Val, ValX};
use jaq_all::load::{Color, FileReportsDisp};
use wasm_bindgen::prelude::{wasm_bindgen, JsValue};
use web_sys::DedicatedWorkerGlobalScope as Scope;
Expand Down Expand Up @@ -134,6 +135,7 @@ impl Settings {
enum Error {
Hifijson(String),
Jaq(json::Error),
Halt(i32),
}

#[wasm_bindgen]
Expand All @@ -146,8 +148,10 @@ pub fn run(filter: &str, input: &str, settings: &JsValue, scope: &Scope) {
let runner = &settings.runner();

let post = |s: String| scope.post_message(&s.into()).unwrap();
let post_value = |y: ValR| {
let y = y.map_err(Error::Jaq)?;
let post_value = |y: ValX| {
let halt = |e: Exn<_>| Error::Halt(e.get_halt().ok().unwrap());
let y = y.map_err(|e| e.get_err().map_or_else(halt, Error::Jaq))?;

let s = FormatterFn(|f: &mut Formatter| match &y {
Val::TStr(s) | Val::BStr(s) if settings.raw_output => bstr(&escape_bytes(s)).fmt(f),
y => fmt_json(f, &runner.writer.pp, 0, y),
Expand All @@ -166,6 +170,7 @@ pub fn run(filter: &str, input: &str, settings: &JsValue, scope: &Scope) {
Ok(()) => (),
Err(Error::Hifijson(e)) => post(format!("Parse error: {e}")),
Err(Error::Jaq(e)) => post(format!("Error: {e}")),
Err(Error::Halt(exit_code)) => post(format!("Exited with code {exit_code}")),
},
}

Expand Down
10 changes: 4 additions & 6 deletions jaq-std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,6 @@ trait ValTx: ValT + Sized {
.ok_or_else(|| Error::typ(self.clone(), "integer"))
}

#[cfg(feature = "math")]
/// Use as an i32 to be given as an argument to a libm function.
fn try_as_i32(&self) -> Result<i32, Error<Self>> {
self.try_as_isize()?.try_into().map_err(Error::str)
}
Expand Down Expand Up @@ -442,6 +440,10 @@ where
.map(|s| ValT::from_utf8_bytes(s.replace(b"'", b"'\\''"))),
)
}),
("halt", v(1), |mut cv| {
let exit_code = cv.0.pop_var().try_as_i32().map_err(Exn::from);
box_once(exit_code.and_then(|exit_code| Err(Exn::halt(exit_code))))
}),
])
}

Expand All @@ -467,10 +469,6 @@ where
))
}),
("now", v(0), |_| bome(now().map(D::V::from))),
("halt", v(1), |mut cv| {
let exit_code = cv.0.pop_var().try_as_isize();
bome(exit_code.map(|exit_code| std::process::exit(exit_code as i32)))
}),
])
}

Expand Down
6 changes: 4 additions & 2 deletions jaq/src/filter.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Filter parsing, compilation, and execution.
use crate::{funs, read, Error, Runner, Val};
use jaq_all::data::Filter;
use jaq_all::jaq_core::{compile, load, ValT, Vars};
use jaq_all::jaq_core::{compile, load, Exn, ValT, Vars};
use jaq_all::load::{compile_errors, load_errors, FileReports};
use std::{io, path::PathBuf};

Expand Down Expand Up @@ -51,7 +51,9 @@ pub(crate) fn run(
) -> Result<Option<bool>, Error> {
let mut last = None;
jaq_all::data::run(runner, filter, vars, inputs, Error::Parse, |v| {
let v = v.map_err(Error::Jaq)?;
let halt = |e: Exn<_>| Error::Halt(e.get_halt().ok().unwrap());
let v = v.map_err(|e| e.get_err().map_or_else(halt, Error::Jaq))?;

last = Some(v.as_bool());
f(v).map_err(Into::into)
})?;
Expand Down
5 changes: 4 additions & 1 deletion jaq/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ enum Error {
Report(Vec<FileReports<PathBuf>>),
Parse(String),
Jaq(jaq_all::json::Error),
Halt(i32),
FalseOrNull,
NoOutput,
}
Expand All @@ -249,7 +250,7 @@ impl fmt::Display for ErrorColor<'_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let Self(error, color) = self;
match error {
Error::FalseOrNull | Error::NoOutput => Ok(()),
Error::FalseOrNull | Error::NoOutput | Error::Halt(_) => Ok(()),
Error::Io(prefix, e) => {
write!(f, "Error: ")?;
if let Some(p) = prefix {
Expand Down Expand Up @@ -277,6 +278,8 @@ impl Termination for Error {
Self::Report(_) => 3,
Self::NoOutput => 4,
Self::Parse(_) | Self::Jaq(_) => 5,
// ExitCode ~= u8, but exit_code: i32
Self::Halt(exit_code) => std::process::exit(exit_code),
})
}
}
Expand Down
4 changes: 3 additions & 1 deletion jaq/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use crate::Error;
use alloc::vec::Vec;
use jaq_all::jaq_core::Exn;
use jaq_all::json::Val;
use std::io::{self, BufRead, Write};
use std::process::ExitCode;
Expand Down Expand Up @@ -51,7 +52,8 @@ fn run_test(test: Test<String>) -> Result<(Val, Val), Error> {
let vars = Default::default();
let input = core::iter::once(parse_single(test.input.as_bytes()).map_err(|e| e.to_string()));
run(runner, &filter, vars, input, Error::Parse, |v| {
obtain.push(v.map_err(Error::Jaq)?);
let halt = |e: Exn<_>| Error::Halt(e.get_halt().ok().unwrap());
obtain.push(v.map_err(|e| e.get_err().map_or_else(halt, Error::Jaq))?);
Ok(())
})?;

Expand Down
Loading