Skip to content

feat: Opal boot file + lazy registration for v2/v3/v4 - #31

Open
ronaldtse wants to merge 1 commit into
mainfrom
feat/opal-boot-and-workflow
Open

feat: Opal boot file + lazy registration for v2/v3/v4#31
ronaldtse wants to merge 1 commit into
mainfrom
feat/opal-boot-and-workflow

Conversation

@ronaldtse

Copy link
Copy Markdown
Contributor

Summary

Adds Opal compatibility to mml so consumers like plurimath-js can compile the gem with -r mml/opal. Also fixes a fallback-resolution bug that affected any caller creating a derived context before the first parse.

  • Adds lib/mml/opal.rb boot file that eager-requires every autoload entry point in dependency order (Opal autoloads do not lazy-execute).
  • Converts lib/mml/v{2,3,4}.rb from require_relative to Ruby autoload (per the global rule: no require_relative for internal library code).
  • Replaces v2/v4 module-body registration side-effects with a lazy register_models! class method, so the version module body stays side-effect-free at load time and the Opal boot file can eager-load element files in any order without hitting NameError.
  • Adds VersionedParser#ensure_registered! (memoized lazy trigger) and ContextConfiguration#ensure_version_registered, which is called from create_context / populate_context! to guarantee the built-in context is populated before any derived context snapshots its fallback chain.
  • Adds a GHA workflow (.github/workflows/opal.yml) and a boot-file spec (spec/mml/opal_boot_spec.rb) covering both static structure and a real Opal::Builder compile.

Why lazy registration

Lutaml::Model::TypeContext.derived resolves fallback context IDs at creation time (GlobalContext.registry.lookup(:mml_v4)). If a caller invokes Mml::V4::Configuration.create_context(id: :custom, ...) before any parse, the :mml_v4 context doesn't exist yet — the resulting derived context snapshots an empty fallback chain and Mml.parse(..., context: :custom) later fails with Unknown type "math" in context "custom".

The fix: Configuration.create_context (and populate_context!) now trigger the parent version module's ensure_registered! first. The parent is derived from Configuration.name (e.g. "Mml::V4::Configuration"Mml::V4) so each version doesn't need an explicit back-reference.

Test plan

  • bundle exec rspec — 2878 examples, 0 failures (36 pre-existing pending)
  • bundle exec rubocop — clean
  • bundle exec rspec spec/mml/opal_boot_spec.rb — 6 examples, 0 failures
  • Manual: bundle exec ruby -e "require 'mml/opal'; ..." parses V2/V3/V4 MathML successfully
  • GHA opal workflow runs the boot-file spec on Ubuntu / Ruby 3.3

Under Opal, autoload declarations do not lazy-execute, so consumers
(plurimath-js) need an explicit boot file that eager-requires every
entry point in dependency order. This adds lib/mml/opal.rb plus a GHA
workflow + spec that verifies the boot file stays in sync with the
autoload declarations.

To support the boot file cleanly:

- Convert lib/mml/v{2,3,4}.rb from `require_relative` to `autoload`.
  Per global rule (no require_relative for internal library code),
  autoloads are declared inside the immediate parent namespace file.

- Replace v2/v4 module-body registration side-effects with a lazy
  `register_models!` class method. The version module body now only
  declares autoloads and defines methods, so the Opal boot file can
  eager-load element files in any order without hitting a NameError
  from a registration block firing too early.

- Add `VersionedParser#ensure_registered!` as the lazy trigger. The
  first call to `parse` (or any path that needs registered types)
  populates the version's context and memoizes the result.

- Add `ContextConfiguration#ensure_version_registered` and call it
  from `create_context` and `populate_context!`. Without this, a user
  who creates a derived context before calling `parse` would get an
  empty fallback chain — TypeContext snapshots fallback_contexts at
  creation time, so the built-in context must already be populated
  for fallback resolution to see version types.

- Remove the `require_relative "../context_configuration"` line from
  each version's Configuration module; the parent autoload handles it.

The boot file spec verifies:
  - every autoload has a matching eager require,
  - all referenced files exist on disk,
  - version module files load after their per-version elements,
  - Opal::Builder compiles the boot file end-to-end (native deps
    stubbed).
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.

1 participant