use std::{cmp::Ordering, ffi::OsStr, fmt, path::Path};
use malachite::base::num::basic::traits::Zero;
use crate::{
error::ParseError,
identifier::{Ident, LocIdent},
impl_display_from_bytecode_pretty,
position::TermPos,
traverse::*,
};
pub type Number = malachite::rational::Rational;
pub mod alloc;
pub mod builder;
pub mod combine;
pub mod pattern;
pub mod pretty;
pub mod primop;
pub mod record;
pub mod typ;
pub use alloc::AstAlloc;
use pattern::*;
use primop::PrimOp;
use record::*;
use serde::{Serialize, Serializer};
use typ::*;
#[derive(Default, Clone, Copy, Eq, Debug, PartialEq, Hash)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
pub enum InputFormat {
#[default]
Nickel,
Json,
Yaml,
Toml,
#[cfg(feature = "nix-experimental")]
Nix,
Text,
}
impl InputFormat {
pub fn from_path(path: impl AsRef<Path>) -> Option<InputFormat> {
match path.as_ref().extension().and_then(OsStr::to_str) {
Some("ncl") => Some(InputFormat::Nickel),
Some("json") => Some(InputFormat::Json),
Some("yaml") | Some("yml") => Some(InputFormat::Yaml),
Some("toml") => Some(InputFormat::Toml),
#[cfg(feature = "nix-experimental")]
Some("nix") => Some(InputFormat::Nix),
Some("txt") => Some(InputFormat::Text),
_ => None,
}
}
pub fn to_str(&self) -> &'static str {
match self {
InputFormat::Nickel => "Nickel",
InputFormat::Json => "Json",
InputFormat::Yaml => "Yaml",
InputFormat::Toml => "Toml",
InputFormat::Text => "Text",
#[cfg(feature = "nix-experimental")]
InputFormat::Nix => "Nix",
}
}
}
impl fmt::Display for InputFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_str())
}
}
impl std::str::FromStr for InputFormat {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"Json" => InputFormat::Json,
"Nickel" => InputFormat::Nickel,
"Text" => InputFormat::Text,
"Yaml" => InputFormat::Yaml,
"Toml" => InputFormat::Toml,
#[cfg(feature = "nix-experimental")]
"Nix" => InputFormat::Nix,
_ => return Err(()),
})
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum StringChunk<E> {
Literal(String),
Expr(
E,
usize,
),
}
impl<E> StringChunk<E> {
#[doc(hidden)]
pub fn expr(e: E) -> Self {
StringChunk::Expr(e, 0)
}
pub fn try_chunks_as_static_str<'a, I>(chunks: I) -> Option<String>
where
I: IntoIterator<Item = &'a StringChunk<E>>,
E: 'a,
{
chunks
.into_iter()
.try_fold(String::new(), |mut acc, next| match next {
StringChunk::Literal(lit) => {
acc.push_str(lit);
Some(acc)
}
_ => None,
})
}
}
#[derive(Debug, Clone, Default)]
pub enum MergePriority {
Bottom,
#[default]
Neutral,
Numeral(Number),
Top,
}
impl PartialOrd for MergePriority {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for MergePriority {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(MergePriority::Bottom, MergePriority::Bottom)
| (MergePriority::Neutral, MergePriority::Neutral)
| (MergePriority::Top, MergePriority::Top) => true,
(MergePriority::Numeral(p1), MergePriority::Numeral(p2)) => p1 == p2,
(MergePriority::Neutral, MergePriority::Numeral(p))
| (MergePriority::Numeral(p), MergePriority::Neutral)
if p == &Number::ZERO =>
{
true
}
_ => false,
}
}
}
impl Eq for MergePriority {}
impl Ord for MergePriority {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(MergePriority::Bottom, MergePriority::Bottom)
| (MergePriority::Top, MergePriority::Top)
| (MergePriority::Neutral, MergePriority::Neutral) => Ordering::Equal,
(MergePriority::Numeral(p1), MergePriority::Numeral(p2)) => p1.cmp(p2),
(MergePriority::Bottom, _) | (_, MergePriority::Top) => Ordering::Less,
(MergePriority::Top, _) | (_, MergePriority::Bottom) => Ordering::Greater,
(MergePriority::Neutral, MergePriority::Numeral(n)) => Number::ZERO.cmp(n),
(MergePriority::Numeral(n), MergePriority::Neutral) => n.cmp(&Number::ZERO),
}
}
}
impl fmt::Display for MergePriority {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
MergePriority::Bottom => write!(f, "default"),
MergePriority::Neutral => write!(f, "{}", Number::ZERO),
MergePriority::Numeral(p) => write!(f, "{p}"),
MergePriority::Top => write!(f, "force"),
}
}
}
impl Serialize for MergePriority {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
#[derive(Clone, Copy, Eq, PartialEq, Debug, Default)]
pub enum MergeKind {
#[default]
Standard,
PiecewiseDef,
}
#[derive(Clone, Debug, PartialEq, Eq, Copy, Default)]
pub enum RecordOpKind {
#[default]
IgnoreEmptyOpt,
ConsiderAllFields,
}
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub enum Node<'ast> {
#[default]
Null,
Bool(bool),
Number(&'ast Number),
String(&'ast str),
StringChunks(&'ast [StringChunk<Ast<'ast>>]),
Fun {
args: &'ast [Pattern<'ast>],
body: &'ast Ast<'ast>,
},
Let {
bindings: &'ast [LetBinding<'ast>],
body: &'ast Ast<'ast>,
rec: bool,
},
App {
head: &'ast Ast<'ast>,
args: &'ast [Ast<'ast>],
},
Var(LocIdent),
EnumVariant {
tag: LocIdent,
arg: Option<&'ast Ast<'ast>>,
},
Record(&'ast Record<'ast>),
IfThenElse {
cond: &'ast Ast<'ast>,
then_branch: &'ast Ast<'ast>,
else_branch: &'ast Ast<'ast>,
},
Match(Match<'ast>),
Array(&'ast [Ast<'ast>]),
PrimOpApp {
op: &'ast PrimOp,
args: &'ast [Ast<'ast>],
},
Annotated {
annot: &'ast Annotation<'ast>,
inner: &'ast Ast<'ast>,
},
Import(Import<'ast>),
Type(&'ast Type<'ast>),
ParseError(&'ast ParseError),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LetBinding<'ast> {
pub pattern: Pattern<'ast>,
pub metadata: LetMetadata<'ast>,
pub value: Ast<'ast>,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct LetMetadata<'ast> {
pub doc: Option<&'ast str>,
pub annotation: Annotation<'ast>,
}
impl<'ast> From<LetMetadata<'ast>> for FieldMetadata<'ast> {
fn from(let_metadata: LetMetadata<'ast>) -> Self {
FieldMetadata {
annotation: let_metadata.annotation,
doc: let_metadata.doc,
..Default::default()
}
}
}
impl<'ast> TryFrom<FieldMetadata<'ast>> for LetMetadata<'ast> {
type Error = ();
fn try_from(field_metadata: FieldMetadata<'ast>) -> Result<Self, Self::Error> {
if let FieldMetadata {
doc,
annotation,
opt: false,
not_exported: false,
priority: MergePriority::Neutral,
} = field_metadata
{
Ok(LetMetadata { doc, annotation })
} else {
Err(())
}
}
}
impl<'ast> Node<'ast> {
pub fn try_str_chunk_as_static_str(&self) -> Option<String> {
match self {
Node::StringChunks(chunks) => StringChunk::try_chunks_as_static_str(*chunks),
_ => None,
}
}
pub fn spanned(self, pos: TermPos) -> Ast<'ast> {
Ast { node: self, pos }
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Ast<'ast> {
pub node: Node<'ast>,
pub pos: TermPos,
}
impl<'ast> Ast<'ast> {
pub fn with_pos(self, pos: TermPos) -> Self {
Ast { pos, ..self }
}
#[cfg(test)]
pub fn without_pos(self, alloc: &'ast AstAlloc) -> Self {
self.traverse(
alloc,
&mut |t: Type| -> Result<_, std::convert::Infallible> {
Ok(Type {
pos: TermPos::None,
..t
})
},
TraverseOrder::BottomUp,
)
.unwrap()
.traverse(
alloc,
&mut |t: Ast<'_>| -> Result<_, std::convert::Infallible> {
let node = match t.node {
Node::Record(r) => Node::Record(alloc.alloc(Record {
field_defs: alloc.alloc_many(r.field_defs.iter().map(|fd| FieldDef {
pos: TermPos::None,
..fd.clone()
})),
..r.clone()
})),
n => n,
};
Ok(Ast {
pos: TermPos::None,
node,
})
},
TraverseOrder::BottomUp,
)
.unwrap()
}
}
impl Default for Ast<'_> {
fn default() -> Self {
Ast {
node: Node::Null,
pos: TermPos::None,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct MatchBranch<'ast> {
pub pattern: Pattern<'ast>,
pub guard: Option<Ast<'ast>>,
pub body: Ast<'ast>,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct Match<'ast> {
pub branches: &'ast [MatchBranch<'ast>],
}
#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub struct Annotation<'ast> {
pub typ: Option<Type<'ast>>,
pub contracts: &'ast [Type<'ast>],
}
impl Annotation<'_> {
pub fn contracts_to_string(&self) -> Option<String> {
todo!("requires pretty printing first")
}
pub fn is_empty(&self) -> bool {
self.typ.is_none() && self.contracts.is_empty()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Import<'ast> {
Path {
path: &'ast OsStr,
format: InputFormat,
},
Package { id: Ident },
}
impl<'ast> TraverseAlloc<'ast, Ast<'ast>> for Ast<'ast> {
fn traverse<F, E>(
self,
alloc: &'ast AstAlloc,
f: &mut F,
order: TraverseOrder,
) -> Result<Ast<'ast>, E>
where
F: FnMut(Ast<'ast>) -> Result<Ast<'ast>, E>,
{
let ast = match order {
TraverseOrder::TopDown => f(self)?,
TraverseOrder::BottomUp => self,
};
let pos = ast.pos;
let result = match &ast.node {
Node::Fun { args, body } => {
let args = traverse_alloc_many(alloc, args.iter().cloned(), f, order)?;
let body = alloc.alloc((*body).clone().traverse(alloc, f, order)?);
Ast {
node: Node::Fun { args, body },
pos,
}
}
Node::Let {
bindings,
body,
rec,
} => {
let bindings = traverse_alloc_many(alloc, bindings.iter().cloned(), f, order)?;
let body = alloc.alloc((*body).clone().traverse(alloc, f, order)?);
Ast {
node: Node::Let {
bindings,
body,
rec: *rec,
},
pos,
}
}
Node::App { head, args } => {
let head = alloc.alloc((*head).clone().traverse(alloc, f, order)?);
let args = traverse_alloc_many(alloc, args.iter().cloned(), f, order)?;
Ast {
node: Node::App { head, args },
pos,
}
}
Node::Match(data) => {
let branches = traverse_alloc_many(alloc, data.branches.iter().cloned(), f, order)?;
Ast {
node: Node::Match(Match { branches }),
pos,
}
}
Node::PrimOpApp { op, args } => {
let args = traverse_alloc_many(alloc, args.iter().cloned(), f, order)?;
Ast {
node: Node::PrimOpApp { op, args },
pos,
}
}
Node::Record(record) => {
let field_defs =
traverse_alloc_many(alloc, record.field_defs.iter().cloned(), f, order)?;
Ast {
node: Node::Record(alloc.alloc(Record {
field_defs,
includes: record.includes,
open: record.open,
})),
pos,
}
}
Node::Array(elts) => {
let elts = traverse_alloc_many(alloc, elts.iter().cloned(), f, order)?;
Ast {
node: Node::Array(elts),
pos,
}
}
Node::StringChunks(chunks) => {
let chunks_res: Result<Vec<StringChunk<Ast<'ast>>>, E> = chunks
.iter()
.cloned()
.map(|chunk| match chunk {
chunk @ StringChunk::Literal(_) => Ok(chunk),
StringChunk::Expr(ast, indent) => {
Ok(StringChunk::Expr(ast.traverse(alloc, f, order)?, indent))
}
})
.collect();
Ast {
node: Node::StringChunks(alloc.alloc_many(chunks_res?)),
pos,
}
}
Node::Annotated { annot, inner } => {
let annot = alloc.alloc((*annot).clone().traverse(alloc, f, order)?);
let inner = alloc.alloc((*inner).clone().traverse(alloc, f, order)?);
Ast {
node: Node::Annotated { annot, inner },
pos,
}
}
Node::Type(typ) => {
let typ = alloc.alloc((*typ).clone().traverse(alloc, f, order)?);
Ast {
node: Node::Type(typ),
pos,
}
}
Node::IfThenElse {
cond,
then_branch,
else_branch,
} => {
let cond = alloc.alloc((*cond).clone().traverse(alloc, f, order)?);
let then_branch = alloc.alloc((*then_branch).clone().traverse(alloc, f, order)?);
let else_branch = alloc.alloc((*else_branch).clone().traverse(alloc, f, order)?);
Ast {
node: Node::IfThenElse {
cond,
then_branch,
else_branch,
},
pos,
}
}
_ => ast,
};
match order {
TraverseOrder::TopDown => Ok(result),
TraverseOrder::BottomUp => f(result),
}
}
fn traverse_ref<S, U>(
&'ast self,
f: &mut dyn FnMut(&'ast Ast<'ast>, &S) -> TraverseControl<S, U>,
state: &S,
) -> Option<U> {
let child_state = match f(self, state) {
TraverseControl::Continue => None,
TraverseControl::ContinueWithScope(s) => Some(s),
TraverseControl::SkipBranch => {
return None;
}
TraverseControl::Return(ret) => {
return Some(ret);
}
};
let state = child_state.as_ref().unwrap_or(state);
match self.node {
Node::Null
| Node::Bool(_)
| Node::Number(_)
| Node::String(_)
| Node::Var(_)
| Node::Import(_)
| Node::ParseError(_) => None,
Node::IfThenElse {
cond,
then_branch,
else_branch,
} => cond
.traverse_ref(f, state)
.or_else(|| then_branch.traverse_ref(f, state))
.or_else(|| else_branch.traverse_ref(f, state)),
Node::EnumVariant { tag: _, arg } => arg?.traverse_ref(f, state),
Node::StringChunks(chunks) => chunks.iter().find_map(|chk| {
if let StringChunk::Expr(term, _) = chk {
term.traverse_ref(f, state)
} else {
None
}
}),
Node::Fun { args, body } => args
.iter()
.find_map(|arg| arg.traverse_ref(f, state))
.or_else(|| body.traverse_ref(f, state)),
Node::PrimOpApp { op: _, args } => {
args.iter().find_map(|arg| arg.traverse_ref(f, state))
}
Node::Let {
bindings,
body,
rec: _,
} => bindings
.iter()
.find_map(|binding| binding.traverse_ref(f, state))
.or_else(|| body.traverse_ref(f, state)),
Node::App { head, args } => head
.traverse_ref(f, state)
.or_else(|| args.iter().find_map(|arg| arg.traverse_ref(f, state))),
Node::Record(data) => data
.field_defs
.iter()
.find_map(|field_def| field_def.traverse_ref(f, state)),
Node::Match(data) => data.branches.iter().find_map(
|MatchBranch {
pattern,
guard,
body,
}| {
pattern
.traverse_ref(f, state)
.or_else(|| {
if let Some(cond) = guard.as_ref() {
cond.traverse_ref(f, state)
} else {
None
}
})
.or_else(|| body.traverse_ref(f, state))
},
),
Node::Array(elts) => elts.iter().find_map(|t| t.traverse_ref(f, state)),
Node::Annotated { annot, inner } => annot
.traverse_ref(f, state)
.or_else(|| inner.traverse_ref(f, state)),
Node::Type(typ) => typ.traverse_ref(f, state),
}
}
}
impl<'ast> TraverseAlloc<'ast, Type<'ast>> for Ast<'ast> {
fn traverse<F, E>(
self,
alloc: &'ast AstAlloc,
f: &mut F,
order: TraverseOrder,
) -> Result<Ast<'ast>, E>
where
F: FnMut(Type<'ast>) -> Result<Type<'ast>, E>,
{
self.traverse(
alloc,
&mut |ast: Ast<'ast>| match &ast.node {
Node::Type(typ) => {
let typ = alloc.alloc((*typ).clone().traverse(alloc, f, order)?);
Ok(Ast {
node: Node::Type(typ),
pos: ast.pos,
})
}
_ => Ok(ast),
},
order,
)
}
fn traverse_ref<S, U>(
&'ast self,
f: &mut dyn FnMut(&'ast Type<'ast>, &S) -> TraverseControl<S, U>,
state: &S,
) -> Option<U> {
self.traverse_ref(
&mut |ast: &'ast Ast<'ast>, state: &S| match &ast.node {
Node::Type(typ) => typ.traverse_ref(f, state).into(),
_ => TraverseControl::Continue,
},
state,
)
}
}
impl<'ast> TraverseAlloc<'ast, Ast<'ast>> for Annotation<'ast> {
fn traverse<F, E>(
self,
alloc: &'ast AstAlloc,
f: &mut F,
order: TraverseOrder,
) -> Result<Self, E>
where
F: FnMut(Ast<'ast>) -> Result<Ast<'ast>, E>,
{
let typ = self
.typ
.map(|typ| typ.traverse(alloc, f, order))
.transpose()?;
let contracts = traverse_alloc_many(alloc, self.contracts.iter().cloned(), f, order)?;
Ok(Annotation { typ, contracts })
}
fn traverse_ref<S, U>(
&'ast self,
f: &mut dyn FnMut(&'ast Ast<'ast>, &S) -> TraverseControl<S, U>,
scope: &S,
) -> Option<U> {
self.typ
.iter()
.chain(self.contracts.iter())
.find_map(|c| c.traverse_ref(f, scope))
}
}
impl<'ast> TraverseAlloc<'ast, Ast<'ast>> for LetBinding<'ast> {
fn traverse<F, E>(
self,
alloc: &'ast AstAlloc,
f: &mut F,
order: TraverseOrder,
) -> Result<Self, E>
where
F: FnMut(Ast<'ast>) -> Result<Ast<'ast>, E>,
{
let pattern = self.pattern.traverse(alloc, f, order)?;
let metadata = LetMetadata {
annotation: self.metadata.annotation.traverse(alloc, f, order)?,
doc: self.metadata.doc,
};
let value = self.value.traverse(alloc, f, order)?;
Ok(LetBinding {
pattern,
metadata,
value,
})
}
fn traverse_ref<S, U>(
&'ast self,
f: &mut dyn FnMut(&'ast Ast<'ast>, &S) -> TraverseControl<S, U>,
scope: &S,
) -> Option<U> {
self.metadata
.annotation
.traverse_ref(f, scope)
.or_else(|| self.value.traverse_ref(f, scope))
}
}
impl<'ast> TraverseAlloc<'ast, Ast<'ast>> for MatchBranch<'ast> {
fn traverse<F, E>(
self,
alloc: &'ast AstAlloc,
f: &mut F,
order: TraverseOrder,
) -> Result<Self, E>
where
F: FnMut(Ast<'ast>) -> Result<Ast<'ast>, E>,
{
let pattern = self.pattern.traverse(alloc, f, order)?;
let body = self.body.traverse(alloc, f, order)?;
let guard = self
.guard
.map(|guard| guard.traverse(alloc, f, order))
.transpose()?;
Ok(MatchBranch {
pattern,
guard,
body,
})
}
fn traverse_ref<S, U>(
&'ast self,
f: &mut dyn FnMut(&'ast Ast<'ast>, &S) -> TraverseControl<S, U>,
scope: &S,
) -> Option<U> {
self.pattern
.traverse_ref(f, scope)
.or_else(|| self.body.traverse_ref(f, scope))
.or_else(|| {
self.guard
.as_ref()
.and_then(|guard| guard.traverse_ref(f, scope))
})
}
}
impl<'ast> From<Node<'ast>> for Ast<'ast> {
fn from(node: Node<'ast>) -> Self {
Ast {
node,
pos: TermPos::None,
}
}
}
pub trait TryConvert<'ast, T>
where
Self: Sized,
{
type Error;
fn try_convert(alloc: &'ast AstAlloc, from: T) -> Result<Self, Self::Error>;
}
impl_display_from_bytecode_pretty!(Node<'_>);
impl_display_from_bytecode_pretty!(Ast<'_>);