Module: Lutaml::Xml::PlanCompiler
- Defined in:
- lib/lutaml/xml/plan_compiler.rb
Overview
Phase 5 slice: compile a model's XML mapping into a Leptris::XML::Descriptor plan and materialize whole documents in one native pass (leptris_plan_walk) — no moxml wrapper tree, no per-element Ruby dispatch. Measured on the 200-item probe: walk+hydrate 8.7x faster, 82% fewer allocations than the interpretive path, hydration-equal output.
A model compiles when EVERY rule is plan-shaped; richer rows defer their subtree verbatim and interpret post-walk rather than opting the whole model out:
- map_attribute / map_element / content / raw rows
- Value-scalar types or nested Serializables
- custom methods, polymorphism, unions, and ordered/mixed
children capture :raw and interpret post-walk
- ordered/mixed root mappings compile; the entry points
reconstruct element_order from the node surface
- attributes not derived/union/polymorphic
The fast path is opt-in (Config.xml_plan_fast_path) while the full semantics audit (parent links, consolidation, ordering metadata) completes.
Constant Summary collapse
- PLAN_CACHE =
Isolated holder: suites freeze model classes; a cache on a frozen constant would be immutable (TypeProbeCache precedent).
::Class.new do class << self def cache @cache ||= {} end end end.cache
Class Method Summary collapse
Class Method Details
.compile(model_class, register) ⇒ Object
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/lutaml/xml/plan_compiler.rb', line 38 def compile(model_class, register) # A child model's declared lutaml_default_register takes # precedence over the ambient (parent) register — the same # contract the interpretive path applies through # Register.resolve_for_child. Without this, plan compilation # resolves the child's symbol attribute types in the parent # context and raises UnknownTypeError for ids registered only # in the child's own register (#876). register = Lutaml::Model::Register.resolve_for_child( model_class, register ) key = [model_class, register] return PLAN_CACHE[key] if PLAN_CACHE.key?(key) # Cycle guard: a self-referential model (JATS sec-in-sec) must # resolve to nil — the interpretive pipeline owns it. The guard # is THREAD-LOCAL (a shared in-progress set races: a concurrent # same-key compile would cache false permanently — the #828 # lesson); the nil at the cycle point is NOT cached — the # outermost build completes and caches the real verdict. stack = (Thread.current[:plan_compiler_stack] ||= []) return nil if stack.include?(key) stack.push(key) begin PLAN_CACHE[key] = build(model_class, register) ensure stack.pop end end |