pub mod registry;
use std::sync::{Arc, OnceLock};
use indexmap::IndexMap;
use crate::ast::{SourceSpan, Sources};
use crate::desugar;
use crate::grammar::parse_file;
use crate::resolve::{resolve, Def, NameKind, Resolved};
use crate::shared::Fail;
use registry::Kind;
pub const SOURCES: &[(&str, &str)] = &[
("stdlib/table.alc", include_str!("../../stdlib/table.alc")),
("stdlib/csv.alc", include_str!("../../stdlib/csv.alc")),
];
pub struct Stdlib {
pub files: Vec<Resolved>,
pub defs: IndexMap<Arc<str>, Def>,
}
impl Stdlib {
pub fn get(&self, name: &str) -> Option<&Def> {
self.defs.get(name)
}
pub fn names(&self) -> impl Iterator<Item = &str> {
self.defs.keys().map(|k| &**k)
}
}
pub fn native_kind(name: &str) -> Option<NameKind> {
registry::native(name).map(|n| match n.kind {
Kind::Function => NameKind::Function,
Kind::Constant => NameKind::Constant,
Kind::Constructor => NameKind::Constructor,
})
}
pub fn load() -> Result<Stdlib, Fail> {
let mut parsed = Vec::with_capacity(SOURCES.len());
let mut names: Vec<Arc<str>> = Vec::new();
for (file, src) in SOURCES {
let forms = desugar::program(parse_file(src, file)?, src)?;
for form in &forms {
if let crate::ast::Expr::List { items, .. } = form {
if let Some(crate::ast::Expr::Symbol { name, .. }) = items.get(1) {
names.push(Arc::from(name.as_str()));
}
}
}
parsed.push((*file, *src, forms));
}
let mut files = Vec::with_capacity(parsed.len());
let mut defs: IndexMap<Arc<str>, Def> = IndexMap::new();
for (file, src, forms) in parsed {
let outer = |name: &str| -> Option<NameKind> {
if names.iter().any(|n| &**n == name) {
return Some(NameKind::Value);
}
native_kind(name)
};
let resolved = resolve(forms, &Sources::one(file, src), &outer)?;
crate::check::stdlib_file(&resolved, src)?;
for (name, def) in &resolved.defs {
if defs.insert(name.clone(), def.clone()).is_some() {
return Err(Fail::new(
crate::shared::Code::DslTypeError,
format!("duplicate_def: {name} is defined in two standard library files"),
));
}
}
files.push(resolved);
}
Ok(Stdlib { files, defs })
}
pub fn stdlib() -> &'static Stdlib {
static LIB: OnceLock<Stdlib> = OnceLock::new();
LIB.get_or_init(|| match load() {
Ok(lib) => lib,
Err(fail) => panic!("the embedded standard library does not load: {fail}"),
})
}
pub fn source(file: &str) -> Option<&'static str> {
SOURCES
.iter()
.find(|(name, _)| *name == file)
.map(|(_, src)| *src)
}
pub fn file_of(span: &SourceSpan) -> Option<(&'static str, &'static str)> {
stdlib()
.files
.iter()
.zip(SOURCES)
.find(|(resolved, _)| {
resolved
.defs
.values()
.next()
.is_some_and(|def| Arc::ptr_eq(&def.span.file, &span.file))
})
.map(|(_, (name, src))| (*name, *src))
}
pub fn outer(name: &str) -> Option<NameKind> {
if stdlib().get(name).is_some() {
return Some(NameKind::Value);
}
native_kind(name)
}
#[cfg(test)]
mod tests {
use std::fs;
use std::path::Path;
use super::*;
#[test]
fn packaged_sources_match_the_repository_sources() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("rs/ has a parent");
if !root.join(".tabnas-kind").is_file() {
return;
}
for (path, embedded) in SOURCES {
let canonical = fs::read_to_string(root.join(path))
.unwrap_or_else(|error| panic!("cannot read canonical {path}: {error}"));
assert_eq!(
canonical, *embedded,
"rs/{path} is not the canonical {path}: copy it into rs/stdlib"
);
}
}
#[test]
fn the_library_loads_with_the_spec_definitions() {
let lib = stdlib();
let names: Vec<&str> = lib.names().collect();
assert_eq!(
names,
[
"public-column",
"table-inferred-column",
"table-row",
"table-first-row",
"table-step",
"table-finish",
"table-finish-for",
"table-captures",
"table-from-json",
"csv-options",
"csv-field",
"csv-row",
"csv",
]
);
assert_eq!(lib.files.len(), 2);
assert_eq!(
lib.get("table-from-json").unwrap().params(),
Some(vec!["binding", "input"])
);
assert_eq!(lib.get("csv-options").unwrap().params(), None);
}
#[test]
fn library_names_and_natives_are_outer_to_a_program() {
assert_eq!(outer("csv"), Some(NameKind::Value));
assert_eq!(outer("map"), Some(NameKind::Function));
assert_eq!(outer("table-end"), Some(NameKind::Constant));
assert_eq!(outer("selected"), Some(NameKind::Constructor));
assert_eq!(outer("nope"), None);
assert_eq!(source("stdlib/csv.alc"), Some(SOURCES[1].1));
assert_eq!(source("nope"), None);
}
}