pub mod adapter;
mod ast;
pub mod bibref;
pub mod encoding;
pub mod env;
pub mod error;
mod exec;
#[cfg(feature = "kaiv")]
pub mod kaiv_out;
mod lexer;
pub mod library;
pub mod locale;
pub mod number;
mod parser;
pub mod reflect;
pub mod source;
pub mod trace;
pub use encoding::{base64, base64_decode, sha256, sha256_hex};
pub mod complete;
pub mod highlight;
pub mod quantity;
mod stdlib;
pub use stdlib::{tabulate, tabulate_in, tabulate_with};
pub mod table;
pub use table::{
TABLE_CSS, table_cells, table_columns, table_html, table_html_plain, table_node_records,
table_rows_of,
};
pub mod names;
pub mod plan;
pub mod registry;
pub use quarb_time::datenames;
pub use quarb_time::{period, temporal, timex};
pub mod target;
pub mod translit;
mod unparse;
mod value;
pub use adapter::{
Ask, AstAdapter, Cap, Catalog, CatalogLookup, Cmp, Confidence, Conjunct, Estimate, ExternalRef,
InstantFrom, Latency, LinkDir, NodeId, PlanAxis, Provenance, ProvenanceList, Selection,
StepPlan, Unopened,
};
pub mod conformance;
pub use error::{Kind as ErrorKind, QuarbError, Result};
pub mod koine;
mod pinned {
use std::cell::Cell;
thread_local! {
pub(super) static NOW: Cell<Option<(i64, u32)>> = const { Cell::new(None) };
}
}
pub fn set_invocation_instant(secs: i64, nanos: u32) {
pinned::NOW.with(|c| c.set(Some((secs, nanos))));
}
pub trait SentenceBonds {
fn unicode_sentences<'a>(&self, text: &'a str) -> Vec<&'a str>;
fn as_any(&self) -> &dyn std::any::Any;
}
#[cfg(feature = "desm")]
impl SentenceBonds for syndesmos::Syndesmos {
fn unicode_sentences<'a>(&self, text: &'a str) -> Vec<&'a str> {
syndesmos::Syndesmos::unicode_sentences(self, text)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
thread_local! {
static SENTENCE_BONDS: std::cell::RefCell<Option<std::rc::Rc<dyn SentenceBonds>>> =
const { std::cell::RefCell::new(None) };
}
pub fn set_sentence_bonds(bonds: Option<std::rc::Rc<dyn SentenceBonds>>) {
SENTENCE_BONDS.with(|b| *b.borrow_mut() = bonds);
}
pub fn sentence_bonds() -> Option<std::rc::Rc<dyn SentenceBonds>> {
SENTENCE_BONDS.with(|b| b.borrow().clone())
}
pub fn invocation_instant() -> Option<(i64, u32)> {
pinned::NOW.with(|c| c.get())
}
pub fn now_secs() -> i64 {
match invocation_instant() {
Some((secs, _)) => secs,
None => std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0),
}
}
pub use env::{EnvKind, Environment};
pub use exec::{
ORIGIN_CAP, Origin, Origins, Prov, QueryResult, RunOptions as Run, Traced, canonical_identity,
canonical_identity_at_open, fetched, fetched_opt, is_url, name_path, record_refusal,
resolved_provenance,
};
pub use library::CommandLibrary;
pub use parser::{Defs, parse_scope_path};
pub use value::{Foreign, ForeignValue, Value, Table};
pub fn step_body_query(body: &str) -> String {
let body = body.trim();
if body.starts_with(['<', '[']) {
format!("*//*{body}")
} else {
format!("*//{body}")
}
}
pub struct StepBody(ast::PathElem);
pub fn parse_step_body(body: &str) -> Result<StepBody> {
let q = parser::parse(&lexer::lex(&step_body_query(body))?)?;
let refuse = |what: &str| {
Err(QuarbError::parse(format!(
"a trait declaration takes one step's body (a name, a pattern, \
a trait expression, predicates), not {what}: '{body}'"
)))
};
if !q.correlations.is_empty() || !q.pipeline.is_empty() {
return refuse("a pipeline");
}
if q.branches.len() != 1 || q.branches[0].projection.is_some() {
return refuse("a projection or a union");
}
if q.branches[0].steps.len() != 1 {
return refuse("a path");
}
let elem = q
.branches
.into_iter()
.next()
.unwrap()
.steps
.into_iter()
.next()
.unwrap();
let preds = match &elem {
ast::PathElem::Step(s) => &s.predicates,
ast::PathElem::Group(g) => &g.predicates,
_ => return refuse("that"),
};
if preds
.iter()
.any(|p| matches!(p, ast::Predicate::Index(_) | ast::Predicate::Range(_, _)))
{
return refuse("a positional predicate");
}
Ok(StepBody(elem))
}
pub fn validate_step_body(body: &str) -> Result<()> {
parse_step_body(body).map(|_| ())
}
pub fn node_matches_step_body(adapter: &impl AstAdapter, node: NodeId, body: &StepBody) -> bool {
exec::node_matches_elem(adapter, node, &body.0)
}
fn checked<T>(result: T) -> Result<T> {
match exec::take_refusal() {
Some(msg) => Err(QuarbError::refused(msg)),
None => Ok(result),
}
}
impl Run {
pub fn ambient() -> Run {
Run {
now: invocation_instant(),
bonds: sentence_bonds(),
..Run::default()
}
}
}
fn inherited() -> Run {
if exec::in_run() {
exec::run_option(|o| o.clone())
} else {
Run::ambient()
}
}
#[derive(Debug, Clone)]
pub struct ClosingTable {
pub csv: bool,
pub columns: Vec<String>,
pub delimiter: Option<u8>,
}
#[derive(Debug, Clone)]
pub struct Outcome {
pub result: QueryResult,
pub closing: Option<ClosingTable>,
pub refs: Vec<String>,
pub plan: Option<plan::Plan>,
}
#[derive(Debug, Clone)]
pub struct Query {
ast: ast::Query,
closing: Option<ClosingTable>,
}
pub fn closes_with_table(text: &str) -> bool {
lexer::lex(text)
.and_then(|tokens| parser::parse(&tokens))
.ok()
.and_then(|q| match q.pipeline.last() {
Some(ast::Command::Agg(call)) => Some(matches!(call.name.as_str(), "table" | "pretty")),
_ => None,
})
.unwrap_or(false)
}
impl Query {
pub fn parse(text: &str, adapter: &impl AstAdapter) -> Result<Query> {
Query::parse_with(text, &Defs::default(), adapter, &inherited())
}
pub fn parse_with(
text: &str,
defs: &Defs,
adapter: &impl AstAdapter,
run: &Run,
) -> Result<Query> {
let _scope = exec::begin_run(Some(run.clone()));
let (tokens, spans) = lexer::lex_spanned(text)?;
let ast =
parser::parse_with_data_spanned(&tokens, Some(&spans), defs.clone(), Some(adapter))?;
Ok(Query { ast, closing: None })
}
pub fn for_rendering(mut self) -> Query {
if let Some(ast::Command::Agg(call)) = self.ast.pipeline.last_mut()
&& matches!(call.name.as_str(), "csv" | "table" | "pretty")
{
let (delimiter, columns) = stdlib::split_csv_delimiter(call);
self.closing = Some(ClosingTable {
csv: call.name == "csv",
columns: columns
.iter()
.filter_map(|a| match a {
ast::Arg::Lit(Value::Str(s)) => Some(s.clone()),
_ => None,
})
.collect(),
delimiter,
});
let columns: Vec<ast::Arg> = columns.to_vec();
call.name = stdlib::ROWS_FOR_RENDERING.to_string();
call.args = columns;
}
self
}
pub fn without_closing(mut self) -> Query {
if let Some(ast::Command::Agg(call)) = self.ast.pipeline.last()
&& matches!(call.name.as_str(), "table" | "pretty")
{
let (delimiter, columns) = stdlib::split_csv_delimiter(call);
self.closing = Some(ClosingTable {
csv: false,
columns: columns
.iter()
.filter_map(|a| match a {
ast::Arg::Lit(Value::Str(s)) => Some(s.clone()),
_ => None,
})
.collect(),
delimiter,
});
self.ast.pipeline.pop();
}
self
}
pub fn run(&self, adapter: &impl AstAdapter, run: &Run) -> Result<Outcome> {
exec::gate(&self.ast, run)?;
let plan = self.planned(adapter, run)?;
let result = checked(exec::eval_with(&self.ast, adapter, Some(run.clone())))?;
Ok(Outcome {
result,
closing: self.closing.clone(),
refs: exec::take_refs(),
plan,
})
}
fn planned(&self, adapter: &impl AstAdapter, run: &Run) -> Result<Option<plan::Plan>> {
if !run.plan && run.budget.is_none() {
return Ok(None);
}
let plan = match &run.budget {
Some(budget) => plan::of_within(&self.ast, adapter, budget.cap()),
None => plan::of(&self.ast, adapter),
};
if let Some(budget) = &run.budget
&& let Some(why) = budget.exceeded_by(&plan)
{
return Err(QuarbError::refused(why));
}
Ok(Some(plan))
}
pub fn run_traced(
&self,
adapter: &impl AstAdapter,
run: &Run,
) -> Result<Vec<(NodeId, Option<Value>)>> {
Ok(self
.run_traced_prov(adapter, run)?
.into_iter()
.map(|t| (t.node, t.topic))
.collect())
}
pub fn run_traced_prov(&self, adapter: &impl AstAdapter, run: &Run) -> Result<Vec<Traced>> {
exec::gate(&self.ast, run)?;
checked(exec::eval_traced_prov_with(
&self.ast,
adapter,
Some(run.clone()),
))
}
pub fn plan(&self, adapter: &dyn AstAdapter) -> plan::Plan {
plan::of(&self.ast, adapter)
}
pub fn plan_within(&self, adapter: &dyn AstAdapter, budget: &plan::Budget) -> plan::Plan {
plan::of_within(&self.ast, adapter, budget.cap())
}
pub fn canonical(&self) -> String {
unparse::unparse(&self.ast)
}
pub fn ast(&self) -> &ast::Query {
&self.ast
}
}
pub fn run(query: &str, adapter: &impl AstAdapter) -> Result<QueryResult> {
run_with_defs(query, &Defs::default(), adapter)
}
pub fn run_for_rendering(
query: &str,
adapter: &impl AstAdapter,
) -> Result<(QueryResult, Option<ClosingTable>)> {
let r = inherited();
let out = Query::parse_with(query, &Defs::default(), adapter, &r)?
.for_rendering()
.run(adapter, &r)?;
Ok((out.result, out.closing))
}
pub fn run_traced(query: &str, adapter: &impl AstAdapter) -> Result<Vec<(NodeId, Option<Value>)>> {
run_traced_with_defs(query, &Defs::default(), adapter)
}
pub fn run_traced_prov(query: &str, adapter: &impl AstAdapter) -> Result<Vec<exec::Traced>> {
run_traced_prov_with_defs(query, &Defs::default(), adapter)
}
pub fn parse_defs(text: &str) -> Result<Defs> {
let tokens = lexer::lex(&strip_defs_comments(text))?;
parser::parse_defs(&tokens)
}
pub fn strip_defs_comments(text: &str) -> String {
text.lines()
.map(|l| {
if l.trim_start().starts_with('#') {
""
} else {
l
}
})
.collect::<Vec<_>>()
.join("\n")
}
pub fn run_with_defs(query: &str, defs: &Defs, adapter: &impl AstAdapter) -> Result<QueryResult> {
let r = inherited();
Ok(Query::parse_with(query, defs, adapter, &r)?
.run(adapter, &r)?
.result)
}
pub fn run_traced_with_defs(
query: &str,
defs: &Defs,
adapter: &impl AstAdapter,
) -> Result<Vec<(NodeId, Option<Value>)>> {
let r = inherited();
Query::parse_with(query, defs, adapter, &r)?.run_traced(adapter, &r)
}
pub fn run_traced_prov_with_defs(
query: &str,
defs: &Defs,
adapter: &impl AstAdapter,
) -> Result<Vec<exec::Traced>> {
let r = inherited();
Query::parse_with(query, defs, adapter, &r)?.run_traced_prov(adapter, &r)
}
pub fn is_calculator(query: &str) -> bool {
let Ok(tokens) = lexer::lex(query) else {
return false;
};
let Ok(ast) = parser::parse_with_defs(&tokens, Defs::default()) else {
return false;
};
ast.correlations.is_empty()
&& !ast.branches.is_empty()
&& ast.branches.iter().all(|b| {
matches!(b.anchor, ast::Anchor::Root) && b.steps.is_empty() && b.projection.is_none()
})
}
pub fn expand(query: &str, defs: &Defs) -> Result<String> {
let tokens = lexer::lex(query)?;
let ast = parser::parse_with_defs(&tokens, defs.clone())?;
Ok(unparse::unparse(&ast))
}
pub fn expand_first(query: &str, defs: &Defs) -> Result<Vec<String>> {
let tokens = lexer::lex(query)?;
parser::parse_first_steps(&tokens, defs.clone(), None)
}
pub fn expand_first_with(
query: &str,
defs: &Defs,
adapter: &impl AstAdapter,
run: &Run,
) -> Result<Vec<String>> {
let _scope = exec::begin_run(Some(run.clone()));
let tokens = lexer::lex(query)?;
parser::parse_first_steps(&tokens, defs.clone(), Some(adapter))
}
pub fn expand_with(
query: &str,
defs: &Defs,
adapter: &impl AstAdapter,
run: &Run,
) -> Result<String> {
Ok(Query::parse_with(query, defs, adapter, run)?.canonical())
}