use std::io::Write;
use std::sync::Arc;
use crate::ast::{Expr, Sources};
use crate::interp::{Runtime, MAX_PLAN_STEPS};
use crate::lower::{EventSink, Lowering, Out};
use crate::resolve::{resolve, Resolved};
use crate::shared::{
AbortFlag, Code, Duplicates, Fail, Limits, Metrics, Renderers, Routers, Selector, Sink, TextOut,
};
use crate::value::{Plan, Protocol, Seq, Val};
use crate::{desugar, parse_file, stdlib};
pub use crate::lower::Renderer;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Output {
Text,
TableRows,
JsonEvents,
}
impl Output {
pub fn as_str(self) -> &'static str {
match self {
Output::Text => "Text",
Output::TableRows => "TableRows/1",
Output::JsonEvents => "JsonEvents/1",
}
}
}
#[derive(Clone)]
pub struct Program {
resolved: Arc<Resolved>,
sources: Sources,
output: Output,
native: bool,
duplicates: Duplicates,
abort: AbortFlag,
result: Val,
injected: Injected,
}
#[derive(Clone)]
struct Injected {
routers: Arc<dyn Routers<Val>>,
renderers: Arc<dyn Renderers>,
}
impl std::fmt::Debug for Program {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Program")
.field("file", &self.resolved.file)
.field("output", &self.output())
.field("native", &self.native)
.finish()
}
}
pub fn compile(
src: &str,
file: &str,
routers: Arc<dyn Routers<Val>>,
renderers: Arc<dyn Renderers>,
) -> Result<Program, Fail> {
compile_sources(&[Source::new(file, src)], routers, renderers)
}
#[derive(Clone, Copy, Debug)]
pub struct Source<'a> {
pub file: &'a str,
pub text: &'a str,
pub export_as: Option<&'a str>,
}
impl<'a> Source<'a> {
pub fn new(file: &'a str, text: &'a str) -> Source<'a> {
Source {
file,
text,
export_as: None,
}
}
pub fn export_as(self, name: &'a str) -> Source<'a> {
Source {
export_as: Some(name),
..self
}
}
}
pub fn compile_sources(
sources: &[Source<'_>],
routers: Arc<dyn Routers<Val>>,
renderers: Arc<dyn Renderers>,
) -> Result<Program, Fail> {
let injected = Injected { routers, renderers };
on_stack(|| {
let mut named: Vec<(Arc<str>, Arc<str>)> = Vec::with_capacity(sources.len());
for source in sources {
if named.iter().any(|(file, _)| &**file == source.file) {
return Err(Fail::new(
Code::DslTypeError,
format!(
"duplicate_file: {} is given twice; each source has its own name",
source.file
),
));
}
named.push((Arc::from(source.file), Arc::from(source.text)));
}
if named.is_empty() {
return Err(crate::check::no_export());
}
let linked = Sources::several(named);
let mut forms = Vec::new();
for source in sources {
let in_file = |fail: Fail| {
if linked.names_files() {
fail.in_file(source.file)
} else {
fail
}
};
let parsed = parse_file(source.text, source.file).map_err(in_file)?;
let mut own = desugar::program(parsed, source.text).map_err(in_file)?;
if let Some(name) = source.export_as {
if !own.iter().any(|form| defines(form, "export")) {
return Err(in_file(Fail::new(
Code::DslTypeError,
format!(
"no_export: {} defines no export to link as {name}",
source.file
),
)));
}
if let Some(taken) = own.iter().find_map(|form| mentions(form, name)) {
return Err(in_file(
Fail::new(
Code::DslTypeError,
format!(
"duplicate_def: {} already names {name}, so its export cannot \
be linked under it; link it under another name",
source.file
),
)
.at(
linked.position(taken).0 as u64,
linked.position(taken).1 as u64,
),
));
}
for form in &mut own {
rename_symbol(form, "export", name);
}
}
forms.extend(own);
}
let resolved = Arc::new(resolve(forms, &linked, &stdlib::outer)?);
let checked = crate::check::program(&resolved, &linked)?;
Program::build_here(
resolved,
linked,
checked.output,
true,
Duplicates::Reject,
AbortFlag::new(),
injected.clone(),
)
})
}
fn defines(form: &Expr, name: &str) -> bool {
match form {
Expr::List { items, .. } => {
items.len() == 3 && items[0].symbol() == Some("def") && items[1].symbol() == Some(name)
}
_ => false,
}
}
fn mentions<'f>(form: &'f Expr, name: &str) -> Option<&'f crate::ast::SourceSpan> {
match form {
Expr::Symbol { name: n, span } if n == name => Some(span),
Expr::List { items, .. } | Expr::Vector { items, .. } => {
items.iter().find_map(|item| mentions(item, name))
}
_ => None,
}
}
fn rename_symbol(form: &mut Expr, from: &str, to: &str) {
match form {
Expr::Symbol { name, .. } if name == from => *name = to.to_string(),
Expr::List { items, .. } | Expr::Vector { items, .. } => {
for item in items {
rename_symbol(item, from, to);
}
}
_ => {}
}
}
fn on_stack<T: Send>(work: impl FnOnce() -> Result<T, Fail> + Send) -> Result<T, Fail> {
std::thread::scope(|scope| {
let thread = std::thread::Builder::new()
.name("alchemy-compile".into())
.stack_size(crate::STACK_BYTES)
.spawn_scoped(scope, work)
.map_err(|error| {
Fail::new(
Code::ResourceLimitExceeded,
format!("no thread could be started to compile the program: {error}"),
)
})?;
thread
.join()
.unwrap_or_else(|panic| std::panic::resume_unwind(panic))
})
}
impl Program {
fn build(
resolved: Arc<Resolved>,
sources: Sources,
output: Output,
native: bool,
duplicates: Duplicates,
abort: AbortFlag,
injected: Injected,
) -> Result<Program, Fail> {
on_stack(|| {
Program::build_here(
resolved, sources, output, native, duplicates, abort, injected,
)
})
}
fn build_here(
resolved: Arc<Resolved>,
sources: Sources,
output: Output,
native: bool,
duplicates: Duplicates,
abort: AbortFlag,
injected: Injected,
) -> Result<Program, Fail> {
let result = Runtime::new(resolved.clone(), sources.clone())
.with_native(native)
.with_duplicates(duplicates)
.with_fuel(Some(MAX_PLAN_STEPS))
.with_abort(abort.clone())
.export()?;
Ok(Program {
resolved,
sources,
output,
native,
duplicates,
abort,
result,
injected,
})
}
pub fn with_native(&self, native: bool) -> Result<Program, Fail> {
Program::build(
self.resolved.clone(),
self.sources.clone(),
self.output,
native,
self.duplicates,
self.abort.clone(),
self.injected.clone(),
)
}
pub fn with_duplicates(&self, duplicates: Duplicates) -> Result<Program, Fail> {
Program::build(
self.resolved.clone(),
self.sources.clone(),
self.output,
self.native,
duplicates,
self.abort.clone(),
self.injected.clone(),
)
}
pub fn with_abort(&self, abort: AbortFlag) -> Program {
Program {
abort,
..self.clone()
}
}
pub fn native(&self) -> bool {
self.native
}
pub fn duplicates(&self) -> Duplicates {
self.duplicates
}
pub fn file(&self) -> &str {
&self.resolved.file
}
pub fn resolved(&self) -> &Arc<Resolved> {
&self.resolved
}
pub fn result(&self) -> &Val {
&self.result
}
pub fn output(&self) -> Output {
self.output
}
pub fn plan_output(&self) -> Output {
match &self.result {
Val::Stream(plan) => match plan.protocol() {
Protocol::JsonEvents => Output::JsonEvents,
_ => Output::TableRows,
},
_ => Output::Text,
}
}
pub fn row_selector(&self) -> Option<&Selector> {
match root_stage(self.plan()?) {
Plan::TableFromJson {
binding: Val::Record(fields),
..
} => match fields.get("rows") {
Some(Val::Selector(s)) => Some(&**s),
_ => None,
},
Plan::Select { selector, .. } => Some(selector),
Plan::Route { specs, .. } => {
let multi: Vec<&Selector> = specs
.iter()
.map(|s| &s.selector)
.filter(|s| s.is_multi())
.collect();
match (multi.as_slice(), specs.len()) {
([one], _) => Some(one),
([], 1) => Some(&specs[0].selector),
_ => None,
}
}
_ => None,
}
}
pub fn explain(&self) -> String {
crate::effects::explain(self)
}
pub fn explain_json(&self) -> serde_json::Value {
crate::effects::explain_json(self)
}
fn plan(&self) -> Option<&Arc<Plan>> {
match &self.result {
Val::Stream(p) | Val::Text(p) => Some(p),
_ => None,
}
}
pub fn sink(
&self,
out: Box<dyn Write + Send>,
render: Option<Renderer>,
limits: &Limits,
metrics: Arc<Metrics>,
) -> Result<Box<dyn Sink + Send>, Fail> {
let out = self
.injected
.renderers
.write_out(out, limits, metrics.clone());
self.sink_out(out, render, limits, metrics)
}
pub fn sink_out(
&self,
out: Box<dyn TextOut + Send>,
render: Option<Renderer>,
limits: &Limits,
metrics: Arc<Metrics>,
) -> Result<EventSink, Fail> {
let rt = Arc::new(
Runtime::new(self.resolved.clone(), self.sources.clone())
.with_native(self.native)
.with_duplicates(self.duplicates)
.with_limits(limits)
.with_abort(self.abort.clone()),
);
let out: Out = out;
Lowering::new(
rt,
limits,
metrics,
self.injected.routers.clone(),
self.injected.renderers.clone(),
)
.sink(&self.result, out, render)
}
}
fn root_stage(plan: &Plan) -> &Plan {
let mut here = plan;
loop {
let next: &Plan = match here {
Plan::Input => return here,
Plan::Route { source, .. }
| Plan::Select { source, .. }
| Plan::Events { source }
| Plan::ScanEmit { source, .. }
| Plan::Map { source, .. }
| Plan::Filter { source, .. }
| Plan::TableFromJson { source, .. }
| Plan::Records { source }
| Plan::CsvTable { source, .. }
| Plan::Csv { source, .. }
| Plan::Json { source } => {
if matches!(**source, Plan::Input) {
return here;
}
source
}
Plan::ConcatMap {
items: Seq::Stream(source),
..
}
| Plan::Join {
items: Seq::Stream(source),
..
} => source,
Plan::Concat { items, live } => match live.and_then(|i| items.get(i)) {
Some(Val::Text(inner)) | Some(Val::Stream(inner)) => inner,
_ => return here,
},
Plan::Replace {
source: Val::Text(inner),
..
} => inner,
_ => return here,
};
here = next;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lower::tests::{compile, PROGRAM};
#[test]
fn a_program_knows_its_output_and_row_selector() {
let p = compile(PROGRAM, "t.alc").unwrap();
assert_eq!(p.output(), Output::Text);
assert_eq!(
p.row_selector().unwrap().to_string(),
".response.payload.deep.records[*]"
);
assert!(p.native());
let slow = p.with_native(false).unwrap();
assert!(!slow.native());
assert_eq!(
slow.row_selector().map(ToString::to_string).as_deref(),
Some(".response.payload.deep.records[*]")
);
let table = compile(&PROGRAM.replace(" csv csv-options\n", ""), "t.alc").unwrap();
assert_eq!(table.output(), Output::TableRows);
let echo = compile("def export [input] input", "t.alc").unwrap();
assert_eq!(echo.output(), Output::JsonEvents);
assert_eq!(echo.row_selector(), None);
let select = compile(
"def export [input] (join \",\" (select (path \"a\" each-index) input))",
"t.alc",
)
.unwrap();
assert_eq!(select.output(), Output::Text);
assert_eq!(select.row_selector().unwrap().to_string(), ".a[*]");
}
#[test]
fn compile_reports_reader_resolver_and_export_failures() {
assert_eq!(
compile("(a b", "t.alc").unwrap_err().code,
crate::shared::Code::DslParseError
);
let f = compile("def export [input] (nope input)", "t.alc").unwrap_err();
assert!(f.message.starts_with("unknown_name: "), "{f}");
let f = compile("def x 1", "t.alc").unwrap_err();
assert!(f.message.starts_with("no_export: "), "{f}");
}
}