#[cfg(feature = "arbitrary")]
mod arbitrary;
pub mod lex;
pub mod parse;
mod prec_climb;
use crate::{ops, path};
#[cfg(feature = "std")]
use alloc::boxed::Box;
use alloc::{string::String, vec::Vec};
pub use lex::Lexer;
use lex::Token;
pub use parse::Parser;
use parse::{Def, Term};
#[cfg(feature = "std")]
use std::path::{Path, PathBuf};
#[cfg(feature = "std")]
extern crate std;
pub type Arena = typed_arena::Arena<String>;
pub struct Loader<S, P, R> {
#[allow(clippy::type_complexity)]
mods: Vec<(File<S, P>, Result<Module<S>, Error<S>>)>,
read: R,
open: Vec<P>,
}
#[derive(Clone, Debug, Default)]
pub struct File<C, P> {
pub code: C,
pub path: P,
}
pub struct Import<'a, S, P> {
pub parent: &'a P,
pub path: &'a S,
pub meta: &'a Option<Term<S>>,
}
impl<C, P> File<C, P> {
pub fn map_code<C2>(self, f: impl Fn(C) -> C2) -> File<C2, P> {
File {
code: f(self.code),
path: self.path,
}
}
pub fn map_path<P2>(self, f: impl Fn(P) -> P2) -> File<C, P2> {
File {
code: self.code,
path: f(self.path),
}
}
}
#[derive(Debug)]
pub enum Error<S> {
Io(Vec<(S, String)>),
Lex(Vec<lex::Error<S>>),
Parse(Vec<parse::Error<S>>),
}
type Vars<S> = Vec<(S, S, Option<Term<S>>)>;
#[derive(Default)]
pub struct Module<S, B = Vec<Def<S>>> {
#[allow(dead_code)]
meta: Option<Term<S>>,
pub(crate) mods: Vec<(usize, Option<S>)>,
pub(crate) vars: Vars<S>,
pub(crate) body: B,
}
pub struct Modules<S, P> {
pub(crate) deps: Vec<(File<S, P>, Module<S>)>,
pub(crate) main: (File<S, P>, Module<S, Term<S>>),
}
impl<S, P> Modules<S, P> {
pub(crate) fn file_vars(&self) -> impl Iterator<Item = (&File<S, P>, &Vars<S>)> {
let mod_vars = self.deps.iter().map(|(file, module)| (file, &module.vars));
mod_vars.chain([(&self.main.0, &self.main.1.vars)])
}
}
pub type Errors<S, P, E = Error<S>> = Vec<(File<S, P>, E)>;
impl<S: core::ops::Deref<Target = str>, B> parse::Module<S, B> {
fn map(
self,
mut f: impl FnMut(&S, Option<Term<S>>) -> Result<usize, String>,
) -> Result<Module<S, B>, Error<S>> {
let mut mods = Vec::from([(0, None)]);
let mut vars = Vec::new();
let mut errs = Vec::new();
for (path, as_, meta) in self.deps {
match as_ {
Some(x) if x.starts_with('$') => vars.push((path, x, meta)),
as_ => match f(&path, meta) {
Ok(mid) => mods.push((mid, as_)),
Err(e) => errs.push((path, e)),
},
}
}
if errs.is_empty() {
Ok(Module {
meta: self.meta,
mods,
vars,
body: self.body,
})
} else {
Err(Error::Io(errs))
}
}
}
type ReadResult<P> = Result<File<String, P>, String>;
type ReadFn<P> = fn(Import<&str, P>) -> ReadResult<P>;
impl<'s, P: Default> Loader<&'s str, P, ReadFn<P>> {
pub fn new(prelude: impl IntoIterator<Item = Def<&'s str>>) -> Self {
let defs = [Def::new("!empty", Vec::new(), Term::empty())];
let prelude = Module {
body: defs.into_iter().chain(prelude).collect(),
..Module::default()
};
Self {
mods: Vec::from([(File::default(), Ok(prelude))]),
read: |_path| Err("module loading not supported".into()),
open: Vec::new(),
}
}
}
#[cfg(feature = "std")]
impl<S: PartialEq> Term<S> {
fn obj_key(&self, key: S) -> Option<&Self> {
if let Term::Obj(kvs) = self {
kvs.iter().find_map(|(k, v)| {
if *k.as_str()? == key {
v.as_ref()
} else {
None
}
})
} else {
None
}
}
fn unconcat(&self) -> Box<dyn Iterator<Item = &Self> + '_> {
match self {
Self::BinOp(l, parse::BinaryOp::Comma, r) => Box::new(l.unconcat().chain(r.unconcat())),
_ => Box::new(core::iter::once(self)),
}
}
}
#[cfg(feature = "std")]
fn expand_prefix(path: &Path, pre: &str, f: impl FnOnce() -> Option<PathBuf>) -> Option<PathBuf> {
let rest = path.strip_prefix(pre).ok()?;
let mut replace = f()?;
replace.push(rest);
Some(replace)
}
#[cfg(feature = "std")]
impl<'a> Import<'a, &'a str, PathBuf> {
fn meta_paths(&self) -> impl Iterator<Item = PathBuf> + '_ {
let paths = self.meta.as_ref().and_then(|meta| {
let v = meta.obj_key("search")?;
let iter = if let Term::Arr(Some(a)) = v {
Box::new(a.unconcat().filter_map(|v| v.as_str()))
} else if let Some(s) = v.as_str() {
Box::new(core::iter::once(s))
} else {
Box::new(core::iter::empty()) as Box<dyn Iterator<Item = _>>
};
Some(iter.map(|s| Path::new(*s).to_path_buf()))
});
paths.into_iter().flatten()
}
pub fn find(self, paths: &[PathBuf], ext: &str) -> Result<PathBuf, String> {
let parent = Path::new(self.parent).parent().unwrap_or(Path::new("."));
let mut rel = Path::new(self.path).to_path_buf();
if !rel.is_relative() {
Err("non-relative path")?
}
rel.set_extension(ext);
#[cfg(target_os = "windows")]
let home = "USERPROFILE";
#[cfg(not(target_os = "windows"))]
let home = "HOME";
use std::env;
let home = || env::var_os(home).map(PathBuf::from);
let origin = || env::current_exe().ok()?.parent().map(PathBuf::from);
let expand = |path: &PathBuf| {
let home = expand_prefix(path, "~", home);
let orig = expand_prefix(path, "$ORIGIN", origin);
home.or(orig).unwrap_or_else(|| path.clone())
};
let meta = self.meta_paths().map(|p| parent.join(expand(&p)));
meta.chain(paths.iter().map(expand))
.map(|path| path.join(&rel))
.filter_map(|path| path.canonicalize().ok())
.find(|path| path.is_file())
.ok_or_else(|| "file not found".into())
}
fn read(self, paths: &[PathBuf], ext: &str) -> ReadResult<PathBuf> {
use alloc::string::ToString;
let path = self.find(paths, ext)?;
let code = std::fs::read_to_string(&path).map_err(|e| e.to_string())?;
Ok(File { code, path })
}
}
pub fn import<S: Copy, P: Clone>(
mods: &Modules<S, P>,
mut f: impl FnMut(Import<S, P>) -> Result<(), String>,
) -> Result<(), Errors<S, P>> {
let mut errs = Vec::new();
let mut vals = Vec::new();
for (mod_file, vars) in mods.file_vars() {
let mut mod_errs = Vec::new();
for (path, _name, meta) in vars {
let parent = &mod_file.path;
match f(Import { parent, path, meta }) {
Ok(v) => vals.push(v),
Err(e) => mod_errs.push((*path, e)),
}
}
if !mod_errs.is_empty() {
errs.push((mod_file.clone(), Error::Io(mod_errs)));
}
}
errs.is_empty().then_some(()).ok_or(errs)
}
impl<S, P, R> Loader<S, P, R> {
pub fn with_read<R2>(self, read: R2) -> Loader<S, P, R2> {
let Self { mods, open, .. } = self;
Loader { mods, read, open }
}
}
#[cfg(feature = "std")]
impl<S, R> Loader<S, PathBuf, R> {
pub fn with_std_read(
self,
paths: &[PathBuf],
) -> Loader<S, PathBuf, impl FnMut(Import<&str, PathBuf>) -> ReadResult<PathBuf> + '_> {
self.with_read(|import: Import<&str, PathBuf>| import.read(paths, "jq"))
}
}
impl<'s, P: Clone + Eq, R: FnMut(Import<&'s str, P>) -> ReadResult<P>> Loader<&'s str, P, R> {
pub fn load(
mut self,
arena: &'s Arena,
file: File<&'s str, P>,
) -> Result<Modules<&'s str, P>, Errors<&'s str, P>> {
let result = parse_main(file.code).and_then(|m| {
m.map(|path, meta| {
let (parent, meta) = (&file.path, &meta);
self.find(arena, Import { parent, path, meta })
})
});
let mut main = None;
let mut deps = Vec::new();
let mut errs = Vec::new();
match result {
Ok(m) => main = Some((file, m)),
Err(e) => errs.push((file, e)),
};
for (file, result) in self.mods {
match result {
Ok(m) => deps.push((file, m)),
Err(e) => errs.push((file, e)),
}
}
match main {
Some(main) if errs.is_empty() => Ok(Modules { main, deps }),
_ => Err(errs),
}
}
fn find(&mut self, arena: &'s Arena, import: Import<&'s str, P>) -> Result<usize, String> {
let file = (self.read)(import)?;
let mut mods = self.mods.iter();
if let Some(id) = mods.position(|(file_, _)| file.path == file_.path) {
return Ok(id);
};
if self.open.contains(&file.path) {
return Err("circular include/import".into());
}
let code = &**arena.alloc(file.code);
self.open.push(file.path.clone());
let defs = parse_defs(code).and_then(|m| {
m.map(|path, meta| {
let (parent, meta) = (&file.path, &meta);
self.find(arena, Import { parent, path, meta })
})
});
assert!(self.open.pop().as_ref() == Some(&file.path));
let id = self.mods.len();
let path = file.path;
self.mods.push((File { path, code }, defs));
Ok(id)
}
}
fn parse_main(code: &str) -> Result<parse::Module<&str, Term<&str>>, Error<&str>> {
let tokens = lex::Lexer::new(code).lex().map_err(Error::Lex)?;
let conv_err = |(expected, found)| (expected, Token::opt_as_str(found, code));
parse::Parser::new(&tokens)
.parse(|p| p.module(|p| p.term()))
.map_err(|e| Error::Parse(e.into_iter().map(conv_err).collect()))
}
fn parse_defs(code: &str) -> Result<parse::Module<&str, Vec<Def<&str>>>, Error<&str>> {
let tokens = lex::Lexer::new(code).lex().map_err(Error::Lex)?;
let conv_err = |(expected, found)| (expected, Token::opt_as_str(found, code));
parse::Parser::new(&tokens)
.parse(|p| p.module(|p| p.defs()))
.map_err(|e| Error::Parse(e.into_iter().map(conv_err).collect()))
}
pub fn parse<'s, T: Default, F>(s: &'s str, f: F) -> Option<T>
where
F: for<'t> FnOnce(&mut Parser<'s, 't>) -> parse::Result<'s, 't, T>,
{
Parser::new(&Lexer::new(s).lex().ok()?).parse(f).ok()
}
pub fn span(whole: &str, part: &str) -> core::ops::Range<usize> {
let start = part.as_ptr() as usize - whole.as_ptr() as usize;
start..start + part.len()
}