use crate::lexer::Span;
#[derive(Debug, Clone)]
pub struct PolydatFile {
pub statements: Vec<Statement>,
}
#[derive(Debug, Clone)]
pub enum Statement {
InputDecl(InputDecl),
Binding(Binding),
ModuleDef(ModuleDef),
ExternPort(ExternPort),
Cursor(CursorDecl),
Pragma {
name: String,
span: Span,
},
For(ForStmt),
Tile(TileDef),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TileOptions {
pub open: String,
pub close: String,
pub sigil: String,
pub strict: bool,
pub in_string: bool,
}
impl Default for TileOptions {
fn default() -> Self {
Self {
open: "${".into(),
close: "}".into(),
sigil: "@".into(),
strict: false,
in_string: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TileBodyKind {
Block,
Heredoc,
Literal,
}
#[derive(Debug, Clone)]
pub struct TileDef {
pub name: String,
pub encoding: Option<String>,
pub options: TileOptions,
pub body_kind: TileBodyKind,
pub body: String,
pub pieces: Vec<TilePiece>,
pub span: Span,
}
#[derive(Debug, Clone)]
pub enum TilePiece {
Static(String),
Hole(TileHole),
Projection {
source: ForSource,
sep: Option<String>,
body: Vec<TilePiece>,
span: Span,
},
Branch {
cond: Expr,
then: Vec<TilePiece>,
otherwise: Option<Vec<TilePiece>>,
span: Span,
},
}
#[derive(Debug, Clone)]
pub struct TileHole {
pub text: String,
pub expr: Expr,
pub decl_type: Option<String>,
pub format: Option<String>,
pub raw: bool,
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct ForSource {
pub text: String,
pub kind: ForSourceKind,
pub span: Span,
}
#[derive(Debug, Clone)]
pub enum ForSourceKind {
Producer(String),
Comprehension(crate::comprehension::Comprehension),
Derived {
base: String,
filter: Option<String>,
order: Option<String>,
},
}
impl ForSource {
pub fn element_names(&self) -> Vec<String> {
match &self.kind {
ForSourceKind::Producer(_) | ForSourceKind::Derived { .. } => Vec::new(),
ForSourceKind::Comprehension(c) => c.coordinate_names(),
}
}
}
#[derive(Debug, Clone)]
pub struct ForStmt {
pub source: ForSource,
pub body: Vec<Statement>,
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct ExternPort {
pub name: String,
pub typ: String,
pub default: Option<Expr>,
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct InputDecl {
pub name: String,
pub ty: Option<String>,
pub span: Span,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WireModifier {
Const,
Shared,
Volatile,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct BindingModifier {
bits: u8,
}
impl BindingModifier {
pub const NONE: Self = Self { bits: 0 };
pub const CONST: Self = Self {
bits: Self::bit(WireModifier::Const),
};
pub const SHARED: Self = Self {
bits: Self::bit(WireModifier::Shared),
};
pub const VOLATILE: Self = Self {
bits: Self::bit(WireModifier::Volatile),
};
pub const fn has(&self, m: WireModifier) -> bool {
self.bits & Self::bit(m) != 0
}
pub const fn has_any(&self) -> bool {
self.bits != 0
}
pub fn insert(&mut self, m: WireModifier) {
self.bits |= Self::bit(m);
}
pub fn try_from_iter<I: IntoIterator<Item = WireModifier>>(
items: I,
) -> Result<Self, &'static str> {
let mut out = Self::NONE;
for m in items {
out.insert(m);
}
if out.has(WireModifier::Const) && out.has(WireModifier::Volatile) {
return Err(
"modifier conflict: `const` and `volatile` are contradictory \
— `const` materializes the value once and freezes it; \
`volatile` excludes the wire from const-fold and signals \
per-cycle variability. Drop one.",
);
}
Ok(out)
}
pub fn iter(&self) -> impl Iterator<Item = WireModifier> + '_ {
const ORDER: &[WireModifier] = &[
WireModifier::Const,
WireModifier::Shared,
WireModifier::Volatile,
];
ORDER.iter().copied().filter(move |m| self.has(*m))
}
#[inline]
pub const fn is_const(&self) -> bool {
self.has(WireModifier::Const)
}
#[inline]
pub const fn is_shared(&self) -> bool {
self.has(WireModifier::Shared)
}
#[inline]
pub const fn is_volatile(&self) -> bool {
self.has(WireModifier::Volatile)
}
const fn bit(m: WireModifier) -> u8 {
match m {
WireModifier::Const => 1 << 0,
WireModifier::Shared => 1 << 1,
WireModifier::Volatile => 1 << 2,
}
}
}
#[derive(Debug, Clone)]
pub struct Binding {
pub targets: Vec<String>,
pub value: Expr,
pub modifier: BindingModifier,
pub type_annotation: Option<String>,
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct CursorDecl {
pub name: String,
pub constructor: Expr,
pub over: Option<Expr>,
pub span: Span,
}
#[derive(Debug, Clone)]
pub enum Expr {
Ident(String, Span),
IntLit(u64, Span),
FloatLit(f64, Span),
StringLit(String, Span),
ArrayLit(Vec<Expr>, Span),
Call(CallExpr),
BinOp(Box<Expr>, BinOpKind, Box<Expr>),
UnaryNeg(Box<Expr>, Span),
UnaryBitNot(Box<Expr>, Span),
FieldAccess {
source: String,
field: String,
span: Span,
},
Cast(Box<Expr>, crate::PortType, Span),
For(Box<ForSource>),
}
#[derive(Debug, Clone, Copy)]
pub enum BinOpKind {
Add,
Sub,
Mul,
Div,
Mod,
Pow,
BitAnd,
BitOr,
BitXor,
Shl,
Shr,
Eq,
Ne,
Lt,
Gt,
Le,
Ge,
And,
Or,
}
#[derive(Debug, Clone)]
pub struct TypedParam {
pub name: String,
pub typ: String, }
#[derive(Debug, Clone)]
pub struct ModuleDef {
pub name: String,
pub params: Vec<TypedParam>,
pub outputs: Vec<TypedParam>,
pub body: Vec<Statement>,
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct CallExpr {
pub func: String,
pub args: Vec<Arg>,
pub span: Span,
}
#[derive(Debug, Clone)]
pub enum Arg {
Positional(Expr),
Named(String, Expr),
}
#[cfg(test)]
mod modifier_tests {
use super::*;
#[test]
fn empty_set_has_no_modifiers() {
let m = BindingModifier::NONE;
assert!(!m.has_any());
assert!(!m.is_const() && !m.is_shared() && !m.is_volatile());
}
#[test]
fn single_modifier_consts_match_expected_flags() {
assert!(BindingModifier::CONST.is_const());
assert!(!BindingModifier::CONST.is_shared());
assert!(!BindingModifier::CONST.is_volatile());
assert!(BindingModifier::SHARED.is_shared());
assert!(!BindingModifier::SHARED.is_const());
assert!(BindingModifier::VOLATILE.is_volatile());
assert!(!BindingModifier::VOLATILE.is_const());
}
#[test]
fn from_iter_collects_combinations() {
let m = BindingModifier::try_from_iter([WireModifier::Const, WireModifier::Shared])
.expect("const+shared is valid");
assert!(m.is_const() && m.is_shared());
assert!(!m.is_volatile());
let m = BindingModifier::try_from_iter([WireModifier::Shared, WireModifier::Volatile])
.expect("shared+volatile is valid");
assert!(m.is_shared() && m.is_volatile());
}
#[test]
fn from_iter_rejects_const_plus_volatile() {
let err = BindingModifier::try_from_iter([WireModifier::Const, WireModifier::Volatile])
.expect_err("const+volatile must be rejected");
assert!(
err.contains("const") && err.contains("volatile"),
"error should name both keywords: {err}"
);
}
#[test]
fn from_iter_rejects_const_shared_volatile() {
let err = BindingModifier::try_from_iter([
WireModifier::Const,
WireModifier::Shared,
WireModifier::Volatile,
])
.expect_err("triple combo includes the contradictory pair");
assert!(err.contains("const") && err.contains("volatile"));
}
#[test]
fn iter_yields_modifiers_in_stable_order() {
let m =
BindingModifier::try_from_iter([WireModifier::Volatile, WireModifier::Shared]).unwrap();
let collected: Vec<_> = m.iter().collect();
assert_eq!(
collected,
vec![WireModifier::Shared, WireModifier::Volatile]
);
}
#[test]
fn equality_distinguishes_combinations() {
let const_only = BindingModifier::CONST;
let const_shared =
BindingModifier::try_from_iter([WireModifier::Const, WireModifier::Shared]).unwrap();
assert_ne!(
const_only, const_shared,
"const-only must not equal const+shared"
);
}
}