From 14e8740e0c72ab79e0a5f396cf645132b2dbc74d Mon Sep 17 00:00:00 2001 From: Jeremy HERGAULT Date: Fri, 6 Mar 2026 08:46:09 +0100 Subject: [PATCH 1/3] feat: add CI Signed-off-by: Jeremy HERGAULT --- .github/workflows/ci.yml | 109 ++++++++++++++++++++++++++++++++++++ .github/workflows/pages.yml | 49 ++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/pages.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..70b2a9f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,109 @@ +--- +name: Rust + +# yamllint disable-line rule:truthy +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + schedule: [cron: "40 1 * * *"] + +env: + RUSTFLAGS: -Dwarnings + RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + +permissions: + contents: read + +jobs: + clippy: + name: Clippy + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + components: clippy,rustc-dev + - run: > + cargo clippy --color always --verbose + --workspace --all-targets + --all-features --tests + + fmt: + name: Rust fmt + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + components: rustfmt + - run: cargo fmt -- --check + + deny: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - uses: EmbarkStudios/cargo-deny-action@v2 + + doc: + name: Documentation + runs-on: ubuntu-latest + timeout-minutes: 30 + env: + RUSTDOCFLAGS: -Dwarnings + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + components: rustfmt + - run: cargo doc --all-features --color=always --verbose --workspace --no-deps + + build: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + components: rustc-dev + - run: cargo check --workspace --tests --all-features --release + + doc_test: + name: Doc tests + needs: build + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + with: + components: llvm-tools, rustc-dev + - run: > + cargo test --doc + --verbose --color always + --workspace --no-fail-fast + + test: + name: Tests + needs: build + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + with: + components: llvm-tools, rustc-dev + - run: > + cargo test --release --tests + --verbose --color always + --workspace --no-fail-fast diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..95712e2 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,49 @@ +--- +name: Pages + +# yamllint disable-line rule:truthy +on: + push: + tags: + - '*.*.*' + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: "Checkout" + uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Setup mdBook + uses: peaceiris/actions-mdbook@v2 + with: + mdbook-version: 'latest' + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + - name: Build mbBook + run: | + cargo install mdbook-mermaid + prosa_book/init.sh + mdbook build prosa_book + - name: Deploy + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./prosa_book/book From cb260b354b7772624f6b4de2dc8800f72b75cb42 Mon Sep 17 00:00:00 2001 From: Jeremy HERGAULT Date: Fri, 6 Mar 2026 08:53:40 +0100 Subject: [PATCH 2/3] fix: CI, fmt, clippy --- .github/workflows/pages.yml | 49 -------------------- deny.toml | 28 ++++++++++++ src/ast/mod.rs | 91 ++++++++++++++++++++++++++++++------- src/ast/parse.rs | 30 +++++++----- 4 files changed, 122 insertions(+), 76 deletions(-) delete mode 100644 .github/workflows/pages.yml create mode 100644 deny.toml diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml deleted file mode 100644 index 95712e2..0000000 --- a/.github/workflows/pages.yml +++ /dev/null @@ -1,49 +0,0 @@ ---- -name: Pages - -# yamllint disable-line rule:truthy -on: - push: - tags: - - '*.*.*' - workflow_dispatch: - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write - -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - build-and-deploy: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: "Checkout" - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Setup mdBook - uses: peaceiris/actions-mdbook@v2 - with: - mdbook-version: 'latest' - - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable - - name: Build mbBook - run: | - cargo install mdbook-mermaid - prosa_book/init.sh - mdbook build prosa_book - - name: Deploy - uses: peaceiris/actions-gh-pages@v4 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./prosa_book/book diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..4c51cd8 --- /dev/null +++ b/deny.toml @@ -0,0 +1,28 @@ +[advisories] +db-path = "~/.cargo/advisory-db" +db-urls = ["https://github.com/rustsec/advisory-db"] +yanked = "deny" +ignore = [ +] + +[licenses] +allow = [ + "Apache-2.0", + "MIT", + "ISC", + "Zlib", + "Unicode-3.0", +] +confidence-threshold = 0.8 + +[bans] +multiple-versions = "allow" +wildcards = "allow" +highlight = "all" +workspace-default-features = "allow" +external-default-features = "allow" + +[sources] +unknown-registry = "deny" +unknown-git = "deny" +allow-registry = ["https://github.com/rust-lang/crates.io-index"] diff --git a/src/ast/mod.rs b/src/ast/mod.rs index f0aec1b..fcdb0b4 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -137,7 +137,13 @@ mod tests { } let main_function = main_item_iter.next(); - if let Some(Item::Fn(ItemFn { attrs, vis, sig, block })) = main_function { + if let Some(Item::Fn(ItemFn { + attrs, + vis, + sig, + block, + })) = main_function + { assert!(attrs.is_empty()); assert_eq!(Visibility::Inherited, *vis); @@ -152,12 +158,21 @@ mod tests { assert_eq!(ty.span.src(), "int"); assert_eq!(ty.kind, FundamentalKind::Int); } else { - panic!("Expected a fundamental return type, got {:#?}", sig.return_type); + panic!( + "Expected a fundamental return type, got {:#?}", + sig.return_type + ); } assert_eq!(sig.ident.span.src(), "main"); assert_eq!(sig.ident.sym, "main"); let mut sig_inputs_iter = sig.inputs.iter(); - if let Some(FnArg { attrs, ty: Type::Fundamental(fn_arg_ty), ident: Some(fn_arg_ident), default_value }) = sig_inputs_iter.next() { + if let Some(FnArg { + attrs, + ty: Type::Fundamental(fn_arg_ty), + ident: Some(fn_arg_ident), + default_value, + }) = sig_inputs_iter.next() + { assert!(attrs.is_empty()); assert_eq!(fn_arg_ty.span.src(), "int"); assert_eq!(fn_arg_ty.kind, FundamentalKind::Int); @@ -165,9 +180,18 @@ mod tests { assert_eq!(fn_arg_ident.sym, "argc"); assert_eq!(&None, default_value); } else { - panic!("Expected a typed argument for argc, got {:#?}", sig.inputs.iter().next()); + panic!( + "Expected a typed argument for argc, got {:#?}", + sig.inputs.iter().next() + ); } - if let Some(FnArg { attrs, ty: Type::Array(TypeArray { element, size }), ident: Some(fn_arg_ident), default_value }) = sig_inputs_iter.next() { + if let Some(FnArg { + attrs, + ty: Type::Array(TypeArray { element, size }), + ident: Some(fn_arg_ident), + default_value, + }) = sig_inputs_iter.next() + { assert!(attrs.is_empty()); if let Type::Ptr(TypePtr { cv, pointee }) = &**element { assert_eq!(cv.const_token, false); @@ -176,17 +200,26 @@ mod tests { assert_eq!(fn_arg_ty.span.src(), "char"); assert_eq!(fn_arg_ty.kind, FundamentalKind::Char); } else { - panic!("Expected a fundamental pointee type for argv, got {:#?}", pointee); + panic!( + "Expected a fundamental pointee type for argv, got {:#?}", + pointee + ); } } else { - panic!("Expected a fundamental element type for argv, got {:#?}", element); + panic!( + "Expected a fundamental element type for argv, got {:#?}", + element + ); } assert_eq!(size, &None); assert_eq!(fn_arg_ident.span.src(), "argv"); assert_eq!(fn_arg_ident.sym, "argv"); assert_eq!(&None, default_value); } else { - panic!("Expected a typed argument for argv, got {:#?}", sig.inputs.iter().next()); + panic!( + "Expected a typed argument for argv, got {:#?}", + sig.inputs.iter().next() + ); } assert_eq!(false, sig.variadic); // Trailing qualifiers @@ -204,7 +237,10 @@ mod tests { // stmt 1: std::cout << "Hello, world" << std::endl; let stmt1 = stmts.next().unwrap(); - if let Stmt::Expr(StmtExpr { expr: Expr::Binary(ExprBinary { lhs, op, rhs }) }) = stmt1 { + if let Stmt::Expr(StmtExpr { + expr: Expr::Binary(ExprBinary { lhs, op, rhs }), + }) = stmt1 + { assert_eq!(*op, BinaryOp::ShiftLeft); if let Expr::Path(ExprPath { path }) = rhs.as_ref() { assert_eq!(path.segments[0].ident.sym, "std"); @@ -212,7 +248,12 @@ mod tests { } else { panic!("Expected std::endl, got {:#?}", rhs); } - if let Expr::Binary(ExprBinary { lhs: inner_lhs, op: inner_op, rhs: inner_rhs }) = lhs.as_ref() { + if let Expr::Binary(ExprBinary { + lhs: inner_lhs, + op: inner_op, + rhs: inner_rhs, + }) = lhs.as_ref() + { assert_eq!(*inner_op, BinaryOp::ShiftLeft); if let Expr::Path(ExprPath { path }) = inner_lhs.as_ref() { assert_eq!(path.segments[0].ident.sym, "std"); @@ -245,7 +286,10 @@ mod tests { let mut switch_stmts = body.stmts.iter(); // case 1: - if let Stmt::Case(StmtCase { value: Expr::Lit(ExprLit { span, kind }) }) = switch_stmts.next().unwrap() { + if let Stmt::Case(StmtCase { + value: Expr::Lit(ExprLit { span, kind }), + }) = switch_stmts.next().unwrap() + { assert_eq!(*kind, LitKind::Integer); assert_eq!(span.src(), "1"); } else { @@ -253,7 +297,10 @@ mod tests { } // case 2: - if let Stmt::Case(StmtCase { value: Expr::Lit(ExprLit { span, kind }) }) = switch_stmts.next().unwrap() { + if let Stmt::Case(StmtCase { + value: Expr::Lit(ExprLit { span, kind }), + }) = switch_stmts.next().unwrap() + { assert_eq!(*kind, LitKind::Integer); assert_eq!(span.src(), "2"); } else { @@ -261,7 +308,10 @@ mod tests { } // std::cout << "first and second" << std::endl; - if let Stmt::Expr(StmtExpr { expr: Expr::Binary(ExprBinary { lhs, op, rhs }) }) = switch_stmts.next().unwrap() { + if let Stmt::Expr(StmtExpr { + expr: Expr::Binary(ExprBinary { lhs, op, rhs }), + }) = switch_stmts.next().unwrap() + { assert_eq!(*op, BinaryOp::ShiftLeft); if let Expr::Path(ExprPath { path }) = rhs.as_ref() { assert_eq!(path.segments[1].ident.sym, "endl"); @@ -286,7 +336,10 @@ mod tests { assert_eq!(switch_stmts.next().unwrap(), &Stmt::Empty); // case 3: - if let Stmt::Case(StmtCase { value: Expr::Lit(ExprLit { span, kind }) }) = switch_stmts.next().unwrap() { + if let Stmt::Case(StmtCase { + value: Expr::Lit(ExprLit { span, kind }), + }) = switch_stmts.next().unwrap() + { assert_eq!(*kind, LitKind::Integer); assert_eq!(span.src(), "3"); } else { @@ -294,7 +347,10 @@ mod tests { } // std::cout << "fallthrough" << std::endl; - if let Stmt::Expr(StmtExpr { expr: Expr::Binary(ExprBinary { lhs, .. }) }) = switch_stmts.next().unwrap() { + if let Stmt::Expr(StmtExpr { + expr: Expr::Binary(ExprBinary { lhs, .. }), + }) = switch_stmts.next().unwrap() + { if let Expr::Binary(ExprBinary { rhs: inner_rhs, .. }) = lhs.as_ref() { if let Expr::Lit(ExprLit { span, kind }) = inner_rhs.as_ref() { assert_eq!(*kind, LitKind::String); @@ -317,7 +373,10 @@ mod tests { // stmt 3: return 0; let stmt3 = stmts.next().unwrap(); - if let Stmt::Return(StmtReturn { expr: Some(Expr::Lit(ExprLit { span, kind })) }) = stmt3 { + if let Stmt::Return(StmtReturn { + expr: Some(Expr::Lit(ExprLit { span, kind })), + }) = stmt3 + { assert_eq!(*kind, LitKind::Integer); assert_eq!(span.src(), "0"); } else { diff --git a/src/ast/parse.rs b/src/ast/parse.rs index b7cef20..221c6b8 100644 --- a/src/ast/parse.rs +++ b/src/ast/parse.rs @@ -236,7 +236,9 @@ fn parse_attribute<'de>(p: &mut Parser<'de>) -> Result, AstError> fn parse_item<'de>(p: &mut Parser<'de>) -> Result, AstError> { // Skip preprocessor directives (lines starting with #) if p.peek_kind() == Some(TokenKind::NumberSign) { - if p.peek_nth(1).is_some_and(|t| t.src_span().src() == "include") { + if p.peek_nth(1) + .is_some_and(|t| t.src_span().src() == "include") + { return parse_item_include(p).map(Item::Include); } return parse_item_macro(p).map(Item::Macro); @@ -363,7 +365,11 @@ fn parse_item_include<'de>(p: &mut Parser<'de>) -> Result, AstE // Local include: "..." — strip surrounding quotes let str_tok = p.bump()?; let str_range: core::ops::Range = str_tok.src_span().into(); - IncludePath::Local(SourceSpan::new(p.src, str_range.start + 1, str_range.len() - 2)) + IncludePath::Local(SourceSpan::new( + p.src, + str_range.start + 1, + str_range.len() - 2, + )) }; let span = p.span_since(start); @@ -2084,7 +2090,9 @@ fn parse_block<'de>(p: &mut Parser<'de>) -> Result, AstError> { fn parse_stmt<'de>(p: &mut Parser<'de>) -> Result, AstError> { // Skip preprocessor directives inside function bodies if p.peek_kind() == Some(TokenKind::NumberSign) { - if p.peek_nth(1).is_some_and(|t| t.src_span().src() == "include") { + if p.peek_nth(1) + .is_some_and(|t| t.src_span().src() == "include") + { return parse_item_include(p).map(|i| Stmt::Item(Item::Include(i))); } let macro_item = parse_item_macro(p)?; @@ -3406,14 +3414,14 @@ fn parse_expr_primary<'de>(p: &mut Parser<'de>) -> Result, AstError> { }; let mut span = tok.src_span(); // User-defined literal suffix: 0_potato, 1.0_sec, etc. - if let Some(suffix) = p.peek() { - if suffix.kind() == TokenKind::Ident && suffix.src().starts_with('_') { - let s = p.bump()?; - let s_range: core::ops::Range = s.src_span().into(); - let start_range: core::ops::Range = span.into(); - span = - SourceSpan::new(p.src, start_range.start, s_range.end - start_range.start); - } + if let Some(suffix) = p.peek() + && suffix.kind() == TokenKind::Ident + && suffix.src().starts_with('_') + { + let s = p.bump()?; + let s_range: core::ops::Range = s.src_span().into(); + let start_range: core::ops::Range = span.into(); + span = SourceSpan::new(p.src, start_range.start, s_range.end - start_range.start); } Ok(Expr::Lit(ExprLit { span, kind })) } From 14414d3ef61d01c2a0b37c39fab44fa480a8458e Mon Sep 17 00:00:00 2001 From: Jeremy HERGAULT Date: Fri, 6 Mar 2026 09:07:25 +0100 Subject: [PATCH 3/3] fix: clippy --- src/ast/mod.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index fcdb0b4..88b6bd4 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -148,12 +148,12 @@ mod tests { assert_eq!(Visibility::Inherited, *vis); // Check signature - assert_eq!(false, sig.constexpr_token); - assert_eq!(false, sig.consteval_token); - assert_eq!(false, sig.inline_token); - assert_eq!(false, sig.virtual_token); - assert_eq!(false, sig.static_token); - assert_eq!(false, sig.explicit_token); + assert!(!sig.constexpr_token); + assert!(!sig.consteval_token); + assert!(!sig.inline_token); + assert!(!sig.virtual_token); + assert!(!sig.static_token); + assert!(!sig.explicit_token); if let Type::Fundamental(ty) = &sig.return_type { assert_eq!(ty.span.src(), "int"); assert_eq!(ty.kind, FundamentalKind::Int); @@ -194,8 +194,8 @@ mod tests { { assert!(attrs.is_empty()); if let Type::Ptr(TypePtr { cv, pointee }) = &**element { - assert_eq!(cv.const_token, false); - assert_eq!(cv.volatile_token, false); + assert!(!cv.const_token); + assert!(!cv.volatile_token); if let Type::Fundamental(fn_arg_ty) = &**pointee { assert_eq!(fn_arg_ty.span.src(), "char"); assert_eq!(fn_arg_ty.kind, FundamentalKind::Char); @@ -221,15 +221,15 @@ mod tests { sig.inputs.iter().next() ); } - assert_eq!(false, sig.variadic); + assert!(!sig.variadic); // Trailing qualifiers - assert_eq!(false, sig.const_token); - assert_eq!(false, sig.noexcept_token); - assert_eq!(false, sig.override_token); - assert_eq!(false, sig.final_token); - assert_eq!(false, sig.pure_virtual); - assert_eq!(false, sig.defaulted); - assert_eq!(false, sig.deleted); + assert!(!sig.const_token); + assert!(!sig.noexcept_token); + assert!(!sig.override_token); + assert!(!sig.final_token); + assert!(!sig.pure_virtual); + assert!(!sig.defaulted); + assert!(!sig.deleted); let block = block.as_ref().expect("main function should have a block"); assert_eq!(block.stmts.len(), 3);