pub mod error;
mod graph;
mod pipeline;
mod stdlib;
mod syntax;
mod thread;
mod value;
pub use graph::complete;
pub use graph::host::{QueryResult, Traced, resolved_provenance};
pub use graph::identity::{canonical_identity, canonical_identity_at_open, is_url, name_path};
pub use graph::run::{Run, inherited};
pub use graph::tabula::{table_node_records, table_rows_of};
pub use graph::{adapter, conformance, koine, names, plan, reflect, source, target, trace};
#[cfg(feature = "kaiv")]
pub use pipeline::kaiv_out;
pub use pipeline::{env, library};
pub use quarb_time::{calendar, calnames, datenames, durnames, regional};
pub use quarb_time::{period, temporal, timex};
pub use stdlib::{pretty_precision, tabulate, tabulate_in, tabulate_pretty, tabulate_with};
pub use pipeline::{PrettyMode, SessionMemory, SessionStore, session_memory};
pub use stdlib::pretty_value;
pub use value::pretty_float;
use syntax::{ast, lexer, parser, unparse};
pub use syntax::{highlight, man, registry};
pub use thread::prov::{ORIGIN_CAP, Origin, Origins, Prov};
pub use value::encoding::{base64, base64_decode, sha256, sha256_hex};
pub use value::split_seconds_pub;
pub use value::table::{TABLE_CSS, table_cells, table_columns, table_html, table_html_plain};
pub use value::{bibref, encoding, locale, number, quantity, table, translit};
pub use error::{Kind as ErrorKind, QuarbError, Result};
pub use graph::adapter::{
Ask, AstAdapter, Cap, Catalog, CatalogLookup, Cmp, Confidence, Conjunct, Estimate, ExternalRef,
InstantFrom, Latency, LinkDir, NodeId, PlanAxis, Provenance, ProvenanceList, Selection,
StepPlan, Unopened,
};
pub use pipeline::env::{EnvKind, Environment};
pub use pipeline::library::CommandLibrary;
pub use syntax::parser::{Defs, parse_scope_path};
pub use value::ambient::{
SentenceBonds, fetched, fetched_opt, invocation_instant, now_secs, record_refusal,
sentence_bonds, set_invocation_instant, set_sentence_bonds,
};
pub use value::{ColumnType, Foreign, ForeignValue, NodeRef, Table, Typing, Value};
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(_, _) | ast::Predicate::Positions(_) | ast::Predicate::Computed(_)))
{
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 {
graph::steps::node_matches_elem(adapter, node, &body.0)
}
fn checked<T>(result: T) -> Result<T> {
match value::ambient::take_refusal() {
Some(msg) => Err(QuarbError::refused(msg)),
None => Ok(result),
}
}
#[derive(Debug, Clone)]
pub struct ClosingTable {
pub csv: bool,
pub columns: Vec<String>,
pub delimiter: Option<u8>,
pub pretty: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct Outcome {
pub result: QueryResult,
pub closing: Option<ClosingTable>,
pub refs: Vec<String>,
pub plan: Option<plan::Plan>,
pub html: Option<String>,
}
#[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)
}
pub fn closes_with_drawing(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(), "tree" | "histogram")),
_ => 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 = graph::run::enter(Some(run.clone()));
let (tokens, spans) = lexer::lex_spanned(text)?;
let ast = parser::parse_with_data_spanned(
&tokens,
Some(&spans),
defs.clone(),
&graph::reflect::Reflector::new(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,
pretty: (call.name == "pretty").then(|| stdlib::pretty_precision(call)),
});
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,
pretty: (call.name == "pretty").then(|| stdlib::pretty_precision(call)),
});
self.ast.pipeline.pop();
}
self
}
pub fn run(&self, adapter: &impl AstAdapter, run: &Run) -> Result<Outcome> {
pipeline::gate(&self.ast, &run.options())?;
let plan = self.planned(adapter, run)?;
let result = checked(graph::host::eval_with(
&self.ast,
adapter,
Some(run.clone()),
))?;
Ok(Outcome {
result,
closing: self.closing.clone(),
refs: pipeline::take_refs(),
plan,
html: pipeline::take_drawing_html(),
})
}
pub fn run_calc(&self, run: &Run) -> Result<Outcome> {
pipeline::gate(&self.ast, &run.options())?;
let values = checked(graph::host::eval_calc(&self.ast, Some(run.clone())))?;
Ok(Outcome {
result: QueryResult::Values(values),
closing: self.closing.clone(),
refs: pipeline::take_refs(),
plan: None,
html: pipeline::take_drawing_html(),
})
}
pub fn run_traced_prov_calc(&self, run: &Run) -> Result<Vec<Traced>> {
pipeline::gate(&self.ast, &run.options())?;
checked(graph::host::eval_calc_traced(&self.ast, Some(run.clone())))
}
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>> {
pipeline::gate(&self.ast, &run.options())?;
checked(graph::host::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<graph::host::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 defs_only(text: &str) -> Result<bool> {
let tokens = lexer::lex(&strip_defs_comments(text))?;
parser::defs_only(&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<graph::host::Traced>> {
let r = inherited();
Query::parse_with(query, defs, adapter, &r)?.run_traced_prov(adapter, &r)
}
pub fn calculator_reads(query: &str) -> Option<String> {
let tokens = lexer::lex(query).ok()?;
let ast = parser::parse_with_defs(&tokens, Defs::default()).ok()?;
struct Reads(Option<String>);
impl ast::visit::Visitor for Reads {
fn operand(&mut self, o: &ast::Operand) {
if self.0.is_none()
&& let ast::Operand::Rel { steps, .. } = o
&& !steps.is_empty()
{
self.0 = Some(unparse::operand(o));
}
ast::visit::walk_operand(self, o);
}
}
let mut v = Reads(None);
ast::visit::walk_query(&mut v, &ast);
v.0
}
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(), &graph::reflect::Reflector::new(None))
}
pub fn expand_first_with(
query: &str,
defs: &Defs,
adapter: &impl AstAdapter,
run: &Run,
) -> Result<Vec<String>> {
let _scope = graph::run::enter(Some(run.clone()));
let tokens = lexer::lex(query)?;
parser::parse_first_steps(
&tokens,
defs.clone(),
&graph::reflect::Reflector::new(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())
}