diff --git a/crates/sploosh-parser/tests/corpus.rs b/crates/sploosh-parser/tests/corpus.rs index 78ecba6..95508ea 100644 --- a/crates/sploosh-parser/tests/corpus.rs +++ b/crates/sploosh-parser/tests/corpus.rs @@ -1,22 +1,25 @@ use sploosh_parser::parse_program; +/// Parses every `.sp` fixture in `tests/corpus/` at the repo root. Fixtures +/// are discovered, not listed, so a new file cannot be silently skipped +/// (crates/AGENTS.md: corpus tests for every accepted grammar shape). #[test] fn parses_corpus_files() { - for path in [ - "tests/corpus/basic.sp", - "tests/corpus/actor.sp", - "tests/corpus/control_flow.sp", - "tests/corpus/traits_impls.sp", - "tests/corpus/pipes.sp", - "tests/corpus/send_assign.sp", - "tests/corpus/ranges_modifiers.sp", - ] { - let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) - .join("../..") - .join(path); - let source = std::fs::read_to_string(&path).unwrap_or_else(|err| { - panic!("{}: {err}", path.display()); - }); + let corpus_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../tests/corpus"); + let mut paths: Vec<_> = std::fs::read_dir(&corpus_dir) + .unwrap_or_else(|err| panic!("{}: {err}", corpus_dir.display())) + .map(|entry| entry.expect("corpus dir entry").path()) + .filter(|path| path.extension().is_some_and(|ext| ext == "sp")) + .collect(); + paths.sort(); + assert!( + !paths.is_empty(), + "no .sp fixtures found in {}", + corpus_dir.display() + ); + for path in paths { + let source = std::fs::read_to_string(&path) + .unwrap_or_else(|err| panic!("{}: {err}", path.display())); parse_program(&source).unwrap_or_else(|errors| panic!("{}: {errors:#?}", path.display())); } } diff --git a/tests/corpus/async_await.sp b/tests/corpus/async_await.sp new file mode 100644 index 0000000..838a243 --- /dev/null +++ b/tests/corpus/async_await.sp @@ -0,0 +1,18 @@ +/// Async functions, `.await`, and `?` error propagation outside pipes +/// (§6 errors, §8.9 async). `.context(...)` is an ordinary method call. +async fn fetch(url: &str) -> Result { + let response = net::get(url).await?; + Ok(response) +} + +pub async fn retry(url: &str) -> Result { + let first = fetch(url).await; + let second = fetch(url).await?; + Ok(second) +} + +fn propagate(input: &str) -> Result { + let n = parse::(input)?; + let checked = validate(n).context("validating input")?; + Ok(checked) +} diff --git a/tests/corpus/attributes.sp b/tests/corpus/attributes.sp new file mode 100644 index 0000000..11c38ad --- /dev/null +++ b/tests/corpus/attributes.sp @@ -0,0 +1,41 @@ +/// Attribute shapes (§12, §16 attrs/attr_args): bare markers, derive lists, +/// named arguments, and actor-handler attributes. +@derive(Serialize, Clone, Debug) +struct Payload { + pub body: String, +} + +@error +enum AppError { + NotFound, + Denied, +} + +@overflow(wrapping) +fn wrap_add(a: u8, b: u8) -> u8 { + a + b +} + +@fast_math(contract, afn) +fn mix(a: f64, b: f64) -> f64 { + a * b + a +} + +@supervisor(strategy: "one_for_one", max_restarts: 5, window_secs: 60) +actor Sup { + children: i64, +} + +actor Mailer { + queued: i64, + + @mailbox(capacity: 2048) + pub fn enqueue(&mut self, n: i64) { + self.queued = self.queued + n; + } +} + +@test +fn smoke() { + let ok = true; +} diff --git a/tests/corpus/casts_literals.sp b/tests/corpus/casts_literals.sp new file mode 100644 index 0000000..d4569dd --- /dev/null +++ b/tests/corpus/casts_literals.sp @@ -0,0 +1,35 @@ +/// Numeric casts (§3.2 `as`, numeric-only) and the §16.1 literal zoo: based +/// integers, separators, suffixes, float exponents, string escapes, and +/// character literals. Lifetimes appear in generic params and reference types. +fn convert(n: i64, ratio: f64) -> u32 { + let small = n as i32; + let wide = small as i64; + let scaled = ratio as f32; + let back = scaled as f64; + let total = wide + n; + total as u32 +} + +fn literals() -> f64 { + let hex = 0xFF_FFu32; + let oct = 0o777; + let bin = 0b1010_1010u8; + let million = 1_000_000; + let big = 340_282_366_920_938u128; + let chain_cap = 115_792u256; + let pi = 3.14159f64; + let tiny = 2.5e-3; + let large = 1e10f64; + let greeting = "hi\n\t\\\"\x41\u{1F600}"; + let letter = 'a'; + let newline = '\n'; + let quote = '\''; + let escaped = '\u{41}'; + let yes = true; + let no = false; + 3.0 +} + +fn longest<'a>(a: &'a str, b: &'a str) -> &'a str { + a +} diff --git a/tests/corpus/expressions.sp b/tests/corpus/expressions.sp new file mode 100644 index 0000000..9485099 --- /dev/null +++ b/tests/corpus/expressions.sp @@ -0,0 +1,34 @@ +/// Struct literals (§5.1 incl. the parenthesized block-head escape and +/// shorthand field init), nesting, and the boolean/comparison operator set. +struct Point { + pub x: i64, + pub y: i64, +} + +struct Cfg { + pub on: bool, +} + +fn build(x: i64, y: i64) -> Point { + let origin = Point { x: 0, y: 0 }; + let shorthand = Point { x, y }; + let nested = Wrap { inner: Point { x: 1, y: 2 } }; + origin +} + +fn guarded() -> i64 { + if (Cfg { on: true }).on { + 1 + } else { + 2 + } +} + +fn logic(a: bool, b: bool, lo: i64, hi: i64) -> bool { + let both = a && b; + let either = a || b; + let cmp = lo <= hi; + let ne = lo != hi; + let modulo = hi % 7 == 0; + both && either && cmp && ne && modulo +} diff --git a/tests/corpus/extern_onchain.sp b/tests/corpus/extern_onchain.sp new file mode 100644 index 0000000..ebd3a3d --- /dev/null +++ b/tests/corpus/extern_onchain.sp @@ -0,0 +1,34 @@ +/// FFI and on-chain surfaces: extern "C" blocks (§4.9), string-target async +/// extern blocks, extern onchain mod declarations (§11.4a), a top-level +/// onchain enum, and an onchain mod with a storage block (§11.1a). +extern "C" { + pub fn puts(s: &str) -> i32; + fn c_open(path: &str, flags: i32) -> Result; +} + +extern "C" async { + fn poll_events(mask: u32) -> u64; +} + +extern onchain mod token { + pub fn balance_of(account: Address) -> Result; +} + +onchain enum TokenError { + InsufficientBalance, + Unauthorized, +} + +onchain mod vault { + storage { + balances: Map, + supply: u256, + } + + pub fn deposit(amount: u256) -> Result { + let caller = ctx::caller(); + let current = storage::get(&self.balances, caller)?; + storage::set(&mut self.balances, caller, current + amount); + Ok(current + amount) + } +} diff --git a/tests/corpus/modules_use.sp b/tests/corpus/modules_use.sp new file mode 100644 index 0000000..894d63c --- /dev/null +++ b/tests/corpus/modules_use.sp @@ -0,0 +1,23 @@ +/// Module trees and use declarations (§10): inline modules, file modules, +/// nested paths, brace imports, contextual `crate`/`super` heads, and +/// re-exports. +mod auth { + pub mod login; + pub mod token; + + pub fn is_enabled() -> bool { + true + } +} + +mod models; + +use std::collections::Map; +use crate::models::{User, Role}; +pub use crate::models::User; +use super::shared; +use crate::api; + +fn wire() -> bool { + true +}