Skip to content

Initial v0.1.0 scaffold: S-expression generator, FFI bindings, smoke tests, and CI#1

Merged
55728 merged 5 commits intomainfrom
feat/v0.1.0-initial-scaffold
May 3, 2026
Merged

Initial v0.1.0 scaffold: S-expression generator, FFI bindings, smoke tests, and CI#1
55728 merged 5 commits intomainfrom
feat/v0.1.0-initial-scaffold

Conversation

@55728
Copy link
Copy Markdown
Member

@55728 55728 commented May 2, 2026

Initial v0.1.0 scaffold: S-expression generator, FFI bindings, smoke tests, and CI

This PR bootstraps the brst-binding-ruby gem as agreed with @dmitrys99 in libBeresta/libBeresta#19.

What's included

S-expression generator (generator/)

  • Parses gen/data/*.lsp directly — the canonical S-expression definitions remain the source of truth (JSON/YAML are used only for cross-checking, not as a dependency).
  • Pipeline: SexpParserSymbolTableRenderer.
  • Implements all four design improvements from the planning phase: consolidated :types renderer, FFI enums, Struct.by_value aliases, and load-order-independent types.rb.
  • Tolerant of upstream quirks (Cyrillic :ру typo — fix submitted as Fix Cyrillic keyword typo and stray closing paren in gen/data libBeresta#47; stray ) in encrypt.lsp).

Generated FFI bindings (lib/brst/binding/ruby/)

  • 56 .lsp files processed → 36 per-file FFI modules.
  • 246 functions attached; 22 skipped with recorded MISSING_SYMBOLS (caption–symbol drift and build-flag-gated features like Asian fonts and PNG loaders).
  • Consolidated types.rb with 39 enums, 22 opaque pointers, 8 type aliases, 3 structs, 6 constants, plus a synthesised :PageSizes enum from ISO 216 entries.

Smoke tests (spec/smoke/)

  • Port of demo/minimal.c: creates Doc → adds Page → sets A4 landscape → saves PDF → verifies %PDF header.
  • "Hello, Beresta from Ruby!" test: loads Helvetica, writes text to an A4 portrait page.
  • Both produce real PDF files on disk via libbrst.dylib.

Structural cross-check (spec/cross_check_spec.rb)

  • Asserts that per-file counts of functions / enums / pointers / definitions / structs / consts / sizes match gen/json/*.json — a sanity net without making JSON a dependency.

CI (.github/workflows/ci.yml)

  • macOS-14, Ruby 3.2 / 3.3 / 3.4 matrix.
  • Clones libBeresta, builds libbrst.dylib, regenerates bindings (fails on drift), runs rspec, then gem build.

Mandatory baseline

  • README.md + CMakeLists.txt per the brst-binding-<lang> family convention.
  • Three standard CMake targets for cross-language CI/CD (per @dmitrys99's guidance): binding (regenerate FFI from gen/data/*.lsp), check (run rspec), bundle (build .gem).

Not included (intentional)

  • Linux support (planned for next release, noted in README).
  • High-level idiomatic Ruby API (planned as a separate gem).
  • gem push / rubygems release (coordinated with libBeresta 1.0.0 per the agreed roadmap).

Related

55728 added 4 commits May 2, 2026 11:01
Establishes the libBeresta org-wide mandatory baseline for the Ruby member
of the brst-binding-<lang> family: a top-level README plus a CMakeLists.txt
that builds libBeresta into a shared library the FFI loader can dlopen.
On top of that baseline we drop in the Ruby gem skeleton (gemspec, Gemfile,
Rakefile, LICENSE under zlib/libpng, CHANGELOG, .rspec, .gitignore,
ext/extconf.rb stub, top-level entrypoint module, and a Library loader
that resolves libbrst across vendored / in-tree / system locations).

Per agreement with @dmitrys99 (libBeresta Issue #19, danbako 399): the
gem name matches the repo name (brst-binding-ruby), license matches
upstream (zlib/libpng), and the high-level idiomatic API stays out of
this gem — v0.1.0 is intentionally a faithful low-level FFI surface.
The generator parses libBeresta's canonical S-expression definitions
directly (gen/json/*.json is treated as a derived artifact only, used for
cross-checking — see spec/cross_check_spec.rb). Pipeline:

  gen/data/*.lsp
     |  SexpParser   (StringScanner; UTF-8; tolerant of upstream quirks
     |                like Cyrillic-letter keywords `:ру` in doc_font.lsp
     |                and a stray trailing `)` in encrypt.lsp)
     v
   parsed Ruby Hash/Array tree
     |  SymbolTable  (consolidates :types / :pointers / :definitions /
     |                :enums / :structs / :consts / :sizes; synthesises
     |                a :PageSizes enum from page_sizes.lsp ISO 216 entries
     |                since the .lsp data never declares that enum
     |                explicitly even though base.lsp / page_routines.lsp
     |                reference it)
     v
   Renderer  ->  lib/brst/binding/ruby/types.rb        (consolidated)
                 lib/brst/binding/ruby/<file>.rb        (per-file FFI module)

Implements the four design improvements from danbako 366:
  #1 Consolidated :types renderer (typedef'd from inner->outer mapping).
  #2 Enums emitted as FFI enums (effectively :int-backed typedefs).
  #3 Structs registered with FFI::Struct.by_value aliases.
  #4 Every typedef lives in types.rb so per-file modules have no
     load-order dependency.
Generated from libBeresta master @ HEAD (post-#26 merge, gen/data layout):
  - 56 .lsp files processed
  - lib/brst/binding/ruby/types.rb consolidates 39 enums, 22 pointers,
    8 definitions, 3 structs, 6 constants, plus the synthesised
    :PageSizes enum (ISO 216 build).
  - 36 per-file modules, 246 attached_function bindings; 22 captions are
    skipped with a recorded MISSING_SYMBOLS entry (mostly Asian font
    captions whose .lsp caption drifted from the C symbol, plus PNG
    image loaders compiled out of the default macOS build, plus the
    `Image` opaque type which upstream never declared in .lsp data).

Smoke specs port demo/minimal.c to RSpec and add a "Hello, Beresta from
Ruby!" text-write test using the built-in Helvetica path. Both produce
a real %PDF-marked file on disk via libbrst.dylib.

Cross-check spec asserts that our parser's per-file counts of
functions / enums / pointers / definitions / structs / consts / sizes
match libBeresta's auto-generated gen/json/*.json view of the same
data — a structural sanity net that doesn't make us depend on JSON.

CI runs on macos-14 across Ruby 3.2 / 3.3 / 3.4: clones libBeresta,
builds the dylib, regenerates bindings, fails if the regeneration
diff is non-empty (drift guard), runs rspec, then `gem build`.
Per dmitrys99's guidance (libBeresta/libBeresta#19), all
brst-binding-<lang> repositories should expose three
standard CMake targets for cross-language CI/CD:

  - binding: regenerate FFI bindings from gen/data/*.lsp
  - check:   run rspec to verify the bindings work
  - bundle:  build the .gem archive

These are implemented as custom targets that shell out to
the Ruby toolchain (ruby, bundle, gem).
@55728 55728 self-assigned this May 2, 2026
@55728 55728 requested a review from dmitrys99 May 2, 2026 10:03
@55728
Copy link
Copy Markdown
Member Author

55728 commented May 2, 2026

@dmitrys99 PR opened, would love your review when you have a moment 👀✨

Copy link
Copy Markdown

@dmitrys99 dmitrys99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing job!
I have a lot to learn from you :)
The only question is: did you use "ISO 216" paper sizes intentionally?
I use it since in my project it is an external restriction.
Please take a look if you missed it out.

@dmitrys99
Copy link
Copy Markdown

@55728
Copy link
Copy Markdown
Member Author

55728 commented May 2, 2026

Thank you so much for the kind words and for the review, @dmitrys99! 😊

Good catch on the ISO 216 restriction — that was not intentional. The generator currently filters page_sizes.lsp entries by :origin "ISO 216" when synthesising the PageSizes enum, but it should include all origins (US Letter, US Arch, JIS B, etc.) to match the full set available in the C headers.

I'll push a fix to include all page sizes before merging. Thanks for pointing this out!

Per review feedback from @dmitrys99: the generator was filtering
page_sizes.lsp entries to ISO 216 only, but should include all
origins (US Letter, US Arch, JIS B, etc.) to match the full set
available in the C headers.
@55728
Copy link
Copy Markdown
Member Author

55728 commented May 3, 2026

One thing I noticed: since the default CMake build uses LIBBRST_ISO_216_ONLY=ON, the enum indices will differ between the Ruby binding (all origins) and the compiled library (ISO 216 only). I'll set LIBBRST_ISO_216_ONLY=OFF in the gem's CMakeLists.txt — does that sound right to you?

@dmitrys99
Copy link
Copy Markdown

dmitrys99 commented May 3, 2026

The intent of LIBBRST_ISO_216_ONLY was twofold.

First, there are plans to make library configurable as much as possible (make it modular through configuration).

Second, I had external requirement outside library (in other project) to have only ISO 216 paper sizes included.

I implemented this option and managed it to work. But after I made it and use it, I think it was incorrect decision. The more valuable solution will be to have all paper sizes available and LIBBRST_ISO_216_ONLY option removal.

To reduce library size I can propose CMake option, which will drop BRST_PREDEFINED_PAGE_SIZE_NAMES and make function BRST_PageSizeName() return empty string and, probably, return error.

But from interface point of view it'll be the same amount of page sizes despite of CMake option(s).

So, short answer for your question, you made right decision.

Copy link
Copy Markdown

@dmitrys99 dmitrys99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@55728
Copy link
Copy Markdown
Member Author

55728 commented May 3, 2026

Thank you for the detailed explanation, @dmitrys99 — that context really helps.

The direction of keeping all page sizes available at the interface level and removing LIBBRST_ISO_216_ONLY makes a lot of sense. I'll keep the binding aligned with that: all origins included, and if a future CMake option drops BRST_PREDEFINED_PAGE_SIZE_NAMES, the FFI enum values will still be valid since they're index-based.

@55728 55728 merged commit f5ad495 into main May 3, 2026
3 checks passed
@55728
Copy link
Copy Markdown
Member Author

55728 commented May 3, 2026

One small housekeeping request, @dmitrys99: could you switch the default branch of brst-binding-ruby from feat/v0.1.0-initial-scaffold to main? I don't have admin access to change it myself. The PR is already merged, so main now has all the code — it's just the default branch setting that needs updating.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants