use polydat::ast::PortType;
use polydat::dsl::ast::{
Arg, Binding, BindingModifier, BinOpKind, CallExpr, CursorDecl, Expr,
ExternPort, InputDecl, ModuleDef, PolydatFile, Statement, TypedParam,
};
use polydat::dsl::compile_polydat;
use polydat::dsl::lexer::Span;
use polydat::dsl::pprint::pp_file;
use polydat::dsl::{lexer, parser};
const GRAMMAR_DOC: &str = include_str!("../docs/design/polydat_grammar.md");
const PROG_DOC: &str = include_str!("../docs/design/polydat_grammar_programmatic.md");
struct Block {
directives: Vec<String>,
body: String,
start_line: usize,
}
fn extract_blocks(md: &str, lang: &str) -> Vec<Block> {
let mut out = Vec::new();
let mut lines = md.lines().enumerate();
while let Some((idx, line)) = lines.next() {
let trimmed = line.trim_start();
if !trimmed.starts_with("```") {
continue;
}
let info = trimmed.trim_start_matches('`');
let mut tokens = info.split([' ', ',']).filter(|t| !t.is_empty());
let is_match = tokens.next() == Some(lang);
let directives: Vec<String> = tokens.map(|t| t.to_string()).collect();
let mut body = String::new();
let mut closed = false;
for (_, body_line) in lines.by_ref() {
if body_line.trim_start().starts_with("```") {
closed = true;
break;
}
body.push_str(body_line);
body.push('\n');
}
debug_assert!(closed, "unterminated fence at line {}", idx + 1);
if is_match {
out.push(Block { directives, body, start_line: idx + 1 });
}
}
out
}
fn parse(src: &str) -> Result<PolydatFile, String> {
let tokens = lexer::lex(src)?;
parser::parse(tokens)
}
fn project(src: &str, label: &str) -> String {
let ast = parse(src)
.unwrap_or_else(|e| panic!("{label} failed to parse: {e}\n--- source ---\n{src}"));
pp_file(&ast)
}
#[test]
fn polydat_blocks_round_trip_idempotently() {
let blocks = extract_blocks(GRAMMAR_DOC, "polydat");
assert!(
!blocks.is_empty(),
"no ```polydat blocks found in polydat_grammar.md — the spec must \
carry at least one verifiable example"
);
for b in &blocks {
let loc = format!("```polydat block at line {}", b.start_line);
let p1 = project(&b.body, &loc);
let p2 = project(&p1, &format!("re-projection of {loc}"));
assert_eq!(
p1, p2,
"{loc} is not idempotent under projection.\n\
--- original ---\n{}\n--- first projection ---\n{p1}\n\
--- second projection ---\n{p2}",
b.body
);
}
}
#[test]
fn polydat_compile_blocks_compile() {
let mut compiled = 0;
for b in extract_blocks(GRAMMAR_DOC, "polydat") {
if !b.directives.iter().any(|d| d == "compile") {
continue;
}
if b.directives.iter().any(|d| d == "vectordata") && !cfg!(feature = "vectordata") {
continue;
}
compile_polydat(&b.body).unwrap_or_else(|e| {
panic!(
"```polydat compile block at line {} failed to compile: {e}\n\
--- source ---\n{}",
b.start_line, b.body
)
});
compiled += 1;
}
assert!(
compiled > 0,
"expected at least one ```polydat compile block to compile"
);
}
struct Paired {
anchor: &'static str,
grammar_src: &'static str,
doc_snippet: &'static str,
build: fn() -> PolydatFile,
}
#[test]
fn programmatic_examples_match_grammar() {
let spec_projections: Vec<String> = extract_blocks(GRAMMAR_DOC, "polydat")
.iter()
.map(|b| project(&b.body, "spec block"))
.collect();
let examples = paired_examples();
assert!(!examples.is_empty());
for p in &examples {
let canonical = project(p.grammar_src, p.anchor);
let built = pp_file(&(p.build)());
assert_eq!(
built, canonical,
"builder `{}` does not project to its grammar source.\n\
--- builder projection ---\n{built}\n--- grammar projection ---\n{canonical}",
p.anchor
);
assert!(
spec_projections.iter().any(|s| s == &canonical),
"paired example `{}` is not present as a ```polydat block in \
polydat_grammar.md",
p.anchor
);
assert!(
PROG_DOC.contains(p.anchor),
"anchor `{}` missing from polydat_grammar_programmatic.md",
p.anchor
);
assert!(
PROG_DOC.contains(p.doc_snippet),
"builder snippet for `{}` not found verbatim in \
polydat_grammar_programmatic.md — doc and code have drifted.\n\
expected substring:\n{}",
p.anchor, p.doc_snippet
);
}
}
fn sp() -> Span {
Span { line: 0, col: 0 }
}
fn input(name: &str, ty: &str) -> Statement {
Statement::InputDecl(InputDecl { name: name.into(), ty: Some(ty.into()), span: sp() })
}
fn id(n: &str) -> Expr {
Expr::Ident(n.into(), sp())
}
fn int(n: u64) -> Expr {
Expr::IntLit(n, sp())
}
fn flt(n: f64) -> Expr {
Expr::FloatLit(n, sp())
}
fn call(func: &str, args: Vec<Expr>) -> Expr {
Expr::Call(CallExpr {
func: func.into(),
args: args.into_iter().map(Arg::Positional).collect(),
span: sp(),
})
}
fn binop(l: Expr, op: BinOpKind, r: Expr) -> Expr {
Expr::BinOp(Box::new(l), op, Box::new(r))
}
fn bind(target: &str, value: Expr) -> Statement {
Statement::Binding(Binding {
targets: vec![target.into()],
value,
modifier: BindingModifier::NONE,
type_annotation: None,
span: sp(),
})
}
fn bind_multi(targets: &[&str], value: Expr) -> Statement {
Statement::Binding(Binding {
targets: targets.iter().map(|s| s.to_string()).collect(),
value,
modifier: BindingModifier::NONE,
type_annotation: None,
span: sp(),
})
}
fn file(statements: Vec<Statement>) -> PolydatFile {
PolydatFile { statements }
}
fn build_minimal() -> PolydatFile {
file(vec![
input("cycle", "u64"),
bind("hashed", call("hash", vec![id("cycle")])),
bind("user_id", call("mod", vec![id("hashed"), int(1_000_000)])),
])
}
fn build_destructure() -> PolydatFile {
file(vec![
input("cycle", "u64"),
bind_multi(&["region", "store", "tx"], call("mixed_radix", vec![id("cycle"), int(50), int(200), int(0)])),
bind("region_id", call("mod", vec![call("hash", vec![id("region")]), int(10_000)])),
bind("store_id", call("mod", vec![call("hash", vec![call("interleave", vec![id("region"), id("store")])]), int(100_000)])),
])
}
fn build_tuple_input() -> PolydatFile {
file(vec![
input("cycle", "u64"),
input("thread", "u64"),
bind("combined", call("interleave", vec![id("cycle"), id("thread")])),
bind("row_key", call("mod", vec![call("hash", vec![id("combined")]), int(1_000_000)])),
])
}
fn build_extern() -> PolydatFile {
file(vec![
input("cycle", "u64"),
Statement::ExternPort(ExternPort { name: "scale".into(), typ: "u64".into(), default: None, span: sp() }),
bind("result", binop(id("cycle"), BinOpKind::Mul, id("scale"))),
])
}
fn build_cursor_over() -> PolydatFile {
file(vec![
Statement::Cursor(CursorDecl {
name: "q".into(),
constructor: call("range", vec![int(0), int(100)]),
over: Some(id("p")),
span: sp(),
}),
bind("i", Expr::FieldAccess { source: "q__cursor".into(), field: "idx".into(), span: sp() }),
bind("ratio", binop(Expr::Cast(Box::new(id("i")), PortType::F64, sp()), BinOpKind::Div, flt(100.0))),
])
}
fn build_module() -> PolydatFile {
let body = vec![
bind("pos", call("to_f64", vec![binop(id("input"), BinOpKind::Mod, id("period"))])),
bind("per", call("to_f64", vec![id("period")])),
bind("value", call("sin", vec![binop(binop(id("pos"), BinOpKind::Div, id("per")), BinOpKind::Mul, flt(std::f64::consts::TAU))])),
];
file(vec![Statement::ModuleDef(ModuleDef {
name: "sine_wave".into(),
params: vec![
TypedParam { name: "input".into(), typ: "u64".into() },
TypedParam { name: "period".into(), typ: "u64".into() },
],
outputs: vec![TypedParam { name: "value".into(), typ: "f64".into() }],
body,
span: sp(),
})])
}
fn paired_examples() -> Vec<Paired> {
vec![
Paired {
anchor: "p-minimal",
grammar_src: "input cycle: u64\nhashed := hash(cycle)\nuser_id := mod(hashed, 1000000)\n",
doc_snippet: "bind(\"user_id\", call(\"mod\", vec![id(\"hashed\"), int(1_000_000)])),",
build: build_minimal,
},
Paired {
anchor: "p-destructure",
grammar_src: "input cycle: u64\n(region, store, tx) := mixed_radix(cycle, 50, 200, 0)\nregion_id := mod(hash(region), 10000)\nstore_id := mod(hash(interleave(region, store)), 100000)\n",
doc_snippet: "bind_multi(&[\"region\", \"store\", \"tx\"], call(\"mixed_radix\", vec![id(\"cycle\"), int(50), int(200), int(0)])),",
build: build_destructure,
},
Paired {
anchor: "p-tuple-input",
grammar_src: "input (cycle: u64, thread: u64)\ncombined := interleave(cycle, thread)\nrow_key := mod(hash(combined), 1000000)\n",
doc_snippet: "bind(\"row_key\", call(\"mod\", vec![call(\"hash\", vec![id(\"combined\")]), int(1_000_000)])),",
build: build_tuple_input,
},
Paired {
anchor: "p-extern",
grammar_src: "input cycle: u64\nextern scale: u64\nresult := cycle * scale\n",
doc_snippet: "ExternPort { name: \"scale\".into(), typ: \"u64\".into(), default: None, span: sp() }",
build: build_extern,
},
Paired {
anchor: "p-cursor-over",
grammar_src: "cursor q = range(0, 100) over p\ni := q.cursor.idx\nratio := (i as f64) / 100.0\n",
doc_snippet: "over: Some(id(\"p\")),",
build: build_cursor_over,
},
Paired {
anchor: "p-module",
grammar_src: "sine_wave(input: u64, period: u64) -> (value: f64) := {\n pos := to_f64(input % period)\n per := to_f64(period)\n value := sin((pos / per) * 6.283185307179586)\n}\n",
doc_snippet: "name: \"sine_wave\".into(),",
build: build_module,
},
]
}