use std::marker::PhantomData;
use crate::syntax::ast::common::{Ident, ModulePath};
use crate::syntax::dimension::Rational;
use crate::syntax::names::{
ConstructorName, DeclName, FieldName, IndexName, IndexVariantName, LocalName, NamePath,
ScopedName, UnitRef,
};
use crate::syntax::non_empty::NonEmpty;
use crate::syntax::phase::{Phase, Raw};
use crate::syntax::span::{Span, Spanned};
#[derive(Debug, Clone)]
pub enum RawExprSugar {
TableLiteral {
indexes: Vec<TableIndexSpec>,
entries: Vec<MapEntry<Raw>>,
},
}
#[derive(Debug, Clone)]
pub enum UnresolvedRef {
Path(IdentPath),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct IdentPath {
pub segments: NonEmpty<Ident>,
}
impl IdentPath {
#[must_use]
pub const fn new(segments: NonEmpty<Ident>) -> Self {
Self { segments }
}
#[must_use]
pub fn bare(ident: Ident) -> Self {
Self::new(NonEmpty::singleton(ident))
}
#[must_use]
pub fn segments(&self) -> &[Ident] {
self.segments.as_slice()
}
#[must_use]
pub fn into_segments(self) -> NonEmpty<Ident> {
self.segments
}
#[must_use]
pub fn into_vec(self) -> Vec<Ident> {
self.segments.into_vec()
}
#[must_use]
pub const fn len(&self) -> usize {
self.segments.len()
}
#[must_use]
pub const fn is_empty(&self) -> bool {
false
}
#[must_use]
pub const fn is_bare(&self) -> bool {
self.segments.len() == 1
}
#[must_use]
pub fn span(&self) -> Span {
self.segments.first().span.merge(self.segments.last().span)
}
#[must_use]
pub fn to_name_path(&self) -> crate::syntax::names::NamePath {
crate::syntax::names::NamePath::new(self.segments.clone().map(|ident| ident.name))
}
#[must_use]
pub fn leaf(&self) -> &Ident {
self.segments.last()
}
#[must_use]
pub fn split_last(&self) -> (&[Ident], &Ident) {
let (leaf, qualifier) = self.segments.split_last();
(qualifier, leaf)
}
#[must_use]
pub fn qualifier_segments(&self) -> &[Ident] {
self.split_last().0
}
#[must_use]
pub fn qualifier_and_leaf(&self) -> Option<(&[Ident], &Ident)> {
let (qualifier, leaf) = self.split_last();
(!qualifier.is_empty()).then_some((qualifier, leaf))
}
#[must_use]
pub fn as_bare(&self) -> Option<&Ident> {
match self.segments.as_slice() {
[ident] => Some(ident),
_ => None,
}
}
pub fn as_bare_mut(&mut self) -> Option<&mut Ident> {
match self.segments.as_mut_slice() {
[ident] => Some(ident),
_ => None,
}
}
pub fn into_bare(self) -> Result<Ident, Self> {
if self.is_bare() {
let mut segments = self.segments.into_vec();
Ok(segments.remove(0))
} else {
Err(self)
}
}
#[must_use]
pub fn into_name_path(self) -> NamePath {
NamePath::new(self.segments.map(|ident| ident.name))
}
#[must_use]
pub fn into_spanned_name_path(self) -> Spanned<NamePath> {
let span = self.span();
Spanned::new(self.into_name_path(), span)
}
#[must_use]
pub fn display_path(&self) -> String {
self.segments
.iter()
.map(|segment| segment.name.as_str())
.collect::<Vec<_>>()
.join(".")
}
}
impl From<Ident> for IdentPath {
fn from(ident: Ident) -> Self {
Self::bare(ident)
}
}
impl std::fmt::Display for IdentPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (idx, segment) in self.segments.iter().enumerate() {
if idx > 0 {
f.write_str(".")?;
}
f.write_str(segment.name.as_str())?;
}
Ok(())
}
}
impl UnresolvedRef {
#[must_use]
pub fn span(&self) -> Span {
match self {
Self::Path(path) => path.span(),
}
}
}
#[derive(Debug, Clone)]
pub struct ParamBinding<P: Phase = Raw> {
pub name: Ident,
pub value: Expr<P>,
pub span: Span,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DomainBoundKind {
Min,
Max,
}
impl std::fmt::Display for DomainBoundKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Min => write!(f, "min"),
Self::Max => write!(f, "max"),
}
}
}
#[derive(Debug, Clone)]
pub struct DomainBound<P: Phase = Raw> {
pub kind: DomainBoundKind,
pub kind_span: Span,
pub value: Expr<P>,
pub span: Span,
}
#[derive(Debug, Clone)]
pub enum IndexExpr {
Name(Spanned<NamePath>),
NatExpr(NatExpr),
}
impl IndexExpr {
#[must_use]
pub const fn span(&self) -> Span {
match self {
Self::Name(name) => name.span,
Self::NatExpr(nat_expr) => nat_expr.span(),
}
}
}
#[derive(Debug, Clone)]
pub struct TypeExpr<P: Phase = Raw> {
pub kind: TypeExprKind<P>,
pub constraints: Vec<DomainBound<P>>,
pub span: Span,
}
impl<P: Phase> TypeExpr<P> {
#[must_use]
pub fn domain_bounds(&self) -> &[DomainBound<P>] {
if !self.constraints.is_empty() {
return &self.constraints;
}
match &self.kind {
TypeExprKind::Indexed { base, .. } => &base.constraints,
_ => &[],
}
}
}
#[derive(Debug, Clone)]
pub enum TypeExprKind<P: Phase = Raw> {
Dimensionless,
Bool,
Int,
Datetime,
DatetimeApplication { type_args: Vec<TypeExpr<P>> },
DimExpr(DimExpr),
Indexed {
base: Box<TypeExpr<P>>,
indexes: Vec<IndexExpr>,
},
TypeApplication {
name: Spanned<NamePath>,
type_args: Vec<TypeExpr<P>>,
},
}
#[derive(Debug, Clone)]
pub struct DimExpr {
pub terms: Vec<DimExprItem>,
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct DimExprItem {
pub op: MulDivOp,
pub term: DimTerm,
}
#[derive(Debug, Clone)]
pub struct DimTerm {
pub name: Spanned<NamePath>,
pub power: Option<Rational>,
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct UnitExpr {
pub terms: Vec<UnitExprItem>,
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct UnitExprItem {
pub op: MulDivOp,
pub name: Spanned<UnitRef>,
pub power: Option<Rational>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MulDivOp {
Mul,
Div,
}
#[derive(Debug)]
pub struct Expr<P: Phase = Raw> {
pub kind: ExprKind<P>,
pub span: Span,
_phase: PhantomData<fn() -> P>,
}
impl<P: Phase> Clone for Expr<P>
where
ExprKind<P>: Clone,
{
fn clone(&self) -> Self {
crate::stack::with_stack_growth(|| Self {
kind: self.kind.clone(),
span: self.span,
_phase: PhantomData,
})
}
}
impl<P: Phase> Expr<P> {
#[must_use]
pub const fn new(kind: ExprKind<P>, span: Span) -> Self {
Self {
kind,
span,
_phase: PhantomData,
}
}
}
#[derive(Debug, Clone)]
pub enum ExprKind<P: Phase = Raw> {
Number(f64),
Integer(i64),
Bool(bool),
StringLiteral(String),
GraphRef(Spanned<ScopedName>),
BinOp {
op: BinOp,
lhs: Box<Expr<P>>,
rhs: Box<Expr<P>>,
},
UnaryOp { op: UnaryOp, operand: Box<Expr<P>> },
FnCall {
callee: IdentPath,
type_args: Vec<GenericArg<P>>,
args: Vec<Expr<P>>,
},
If {
condition: Box<Expr<P>>,
then_branch: Box<Expr<P>>,
else_branch: Box<Expr<P>>,
},
UnitLiteral { value: f64, unit: UnitExpr },
Convert {
expr: Box<Expr<P>>,
target: UnitExpr,
},
DisplayTimezone {
expr: Box<Expr<P>>,
timezone: String,
},
FieldAccess {
expr: Box<Expr<P>>,
field: Spanned<FieldName>,
},
ConstructorCall {
callee: IdentPath,
generic_args: Vec<GenericArg<P>>,
fields: Vec<FieldInit<P>>,
},
MapLiteral { entries: Vec<MapEntry<P>> },
ForComp {
bindings: Vec<ForBinding>,
body: Box<Expr<P>>,
},
IndexAccess {
expr: Box<Expr<P>>,
args: Vec<IndexArg<P>>,
},
Scan {
source: Box<Expr<P>>,
init: Box<Expr<P>>,
acc_name: Spanned<LocalName>,
val_name: Spanned<LocalName>,
body: Box<Expr<P>>,
},
Unfold {
init: Box<Expr<P>>,
prev_name: Spanned<LocalName>,
curr_name: Spanned<LocalName>,
body: Box<Expr<P>>,
},
Match {
scrutinee: Box<Expr<P>>,
arms: Vec<MatchArm<P>>,
},
InlineDagRef {
path: ModulePath,
args: Vec<ParamBinding<P>>,
output: Spanned<DeclName>,
},
UnresolvedRef(UnresolvedRef),
Sugar(P::ExprSugar),
}
#[derive(Debug, Clone)]
pub enum TableIndexSpec {
Named(Spanned<NamePath>),
NatRange(u64, Span),
}
#[derive(Debug, Clone)]
pub struct MultiDeclSharedAxes {
slice_axes: Vec<TableIndexSpec>,
row_axis: TableIndexSpec,
}
impl MultiDeclSharedAxes {
#[must_use]
pub const fn new(slice_axes: Vec<TableIndexSpec>, row_axis: TableIndexSpec) -> Self {
Self {
slice_axes,
row_axis,
}
}
pub fn try_from_vec(
mut axes: Vec<TableIndexSpec>,
) -> Result<Self, crate::syntax::non_empty::EmptyVecError> {
let row_axis = axes.pop().ok_or(crate::syntax::non_empty::EmptyVecError)?;
Ok(Self::new(axes, row_axis))
}
#[must_use]
pub fn slice_axes(&self) -> &[TableIndexSpec] {
&self.slice_axes
}
#[must_use]
pub const fn row_axis(&self) -> &TableIndexSpec {
&self.row_axis
}
#[must_use]
pub const fn len(&self) -> usize {
self.slice_axes.len() + 1
}
#[must_use]
pub const fn is_empty(&self) -> bool {
false
}
pub fn iter(&self) -> impl Iterator<Item = &TableIndexSpec> {
self.slice_axes
.iter()
.chain(std::iter::once(&self.row_axis))
}
}
impl std::ops::Index<usize> for MultiDeclSharedAxes {
type Output = TableIndexSpec;
#[expect(
clippy::panic,
reason = "Index implementations conventionally panic on out-of-bounds access"
)]
fn index(&self, index: usize) -> &Self::Output {
match index.cmp(&self.slice_axes.len()) {
std::cmp::Ordering::Less => &self.slice_axes[index],
std::cmp::Ordering::Equal => &self.row_axis,
std::cmp::Ordering::Greater => {
panic!("multi-decl shared axis index out of bounds")
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MapEntryIndex {
Named(NamePath),
NatRange(u64),
}
impl MapEntryIndex {
#[must_use]
pub fn named_registry_name(&self) -> Option<IndexName> {
match self {
Self::Named(name) => Some(IndexName::from(name.leaf().clone())),
Self::NatRange(_) => None,
}
}
}
impl From<IndexName> for MapEntryIndex {
fn from(value: IndexName) -> Self {
Self::Named(NamePath::from(value))
}
}
impl From<NamePath> for MapEntryIndex {
fn from(value: NamePath) -> Self {
Self::Named(value)
}
}
impl std::fmt::Display for MapEntryIndex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Named(name) => write!(f, "{name}"),
Self::NatRange(size) => write!(f, "range({size})"),
}
}
}
impl TableIndexSpec {
#[must_use]
pub const fn span(&self) -> Span {
match self {
Self::Named(spanned) => spanned.span,
Self::NatRange(_, span) => *span,
}
}
#[must_use]
pub const fn is_nat_range(&self) -> bool {
matches!(self, Self::NatRange(..))
}
}
#[derive(Debug, Clone)]
pub struct MapEntryKey {
pub index: Spanned<MapEntryIndex>,
pub variant: Spanned<IndexVariantName>,
}
#[derive(Debug, Clone)]
pub struct MapEntry<P: Phase = Raw> {
pub keys: NonEmpty<MapEntryKey>,
pub value: Expr<P>,
}
#[derive(Debug, Clone)]
pub struct ForBinding {
pub var: Spanned<LocalName>,
pub index: ForBindingIndex,
}
#[derive(Debug, Clone)]
pub enum ForBindingIndex {
Named(Spanned<NamePath>),
Range {
arg: NatExpr,
span: Span,
},
}
#[derive(Debug, Clone)]
pub enum NatExpr {
Literal(u64, Span),
Var(Ident),
Add(Box<Self>, Box<Self>, Span),
Mul(Box<Self>, Box<Self>, Span),
}
impl NatExpr {
#[must_use]
pub const fn span(&self) -> Span {
match self {
Self::Literal(_, span) | Self::Add(_, _, span) | Self::Mul(_, _, span) => *span,
Self::Var(ident) => ident.span,
}
}
}
impl std::fmt::Display for NatExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Literal(n, _) => write!(f, "{n}"),
Self::Var(ident) => f.write_str(&ident.name),
Self::Add(lhs, rhs, _) => write!(f, "{lhs} + {rhs}"),
Self::Mul(lhs, rhs, _) => write!(f, "{lhs} * {rhs}"),
}
}
}
#[derive(Debug, Clone)]
pub enum GenericArg<P: Phase = Raw> {
Type(TypeExpr<P>),
Nat(NatExpr),
}
impl<P: Phase> GenericArg<P> {
#[must_use]
pub const fn span(&self) -> Span {
match self {
Self::Type(te) => te.span,
Self::Nat(ne) => ne.span(),
}
}
}
#[derive(Debug, Clone)]
pub enum IndexArg<P: Phase = Raw> {
Variant {
index: Spanned<NamePath>,
variant: Spanned<IndexVariantName>,
},
Var(Ident),
Expr(Box<Expr<P>>),
}
#[derive(Debug, Clone)]
pub struct FieldInit<P: Phase = Raw> {
pub name: Spanned<FieldName>,
pub value: Expr<P>,
}
#[derive(Debug, Clone)]
pub struct MatchArm<P: Phase = Raw> {
pub pattern: MatchPattern,
pub body: Expr<P>,
pub span: Span,
}
#[derive(Debug, Clone)]
pub enum MatchPattern {
Path {
path: IdentPath,
bindings: Vec<PatternBinding>,
span: Span,
},
Constructor {
name: Spanned<ConstructorName>,
bindings: Vec<PatternBinding>,
span: Span,
},
IndexLabel {
index: Spanned<NamePath>,
variant: Spanned<IndexVariantName>,
span: Span,
},
}
impl MatchPattern {
#[must_use]
pub const fn span(&self) -> Span {
match self {
Self::Path { span, .. }
| Self::Constructor { span, .. }
| Self::IndexLabel { span, .. } => *span,
}
}
#[must_use]
pub fn bindings(&self) -> &[PatternBinding] {
match self {
Self::Path { bindings, .. } | Self::Constructor { bindings, .. } => bindings,
Self::IndexLabel { .. } => &[],
}
}
}
#[derive(Debug, Clone)]
pub enum PatternBinding {
Bind {
field: Spanned<FieldName>,
var: Ident,
},
Wildcard {
field: Spanned<FieldName>,
span: Span,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinOp {
Add,
Sub,
Mul,
Div,
Mod,
Pow,
Eq,
Ne,
Lt,
Gt,
Le,
Ge,
And,
Or,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnaryOp {
Neg,
Not,
}