Class: Kobako::Catalog::Snippets
- Inherits:
-
Object
- Object
- Kobako::Catalog::Snippets
- Defined in:
- lib/kobako/catalog/snippets.rb,
sig/kobako/catalog/snippets.rbs
Overview
Kobako::Catalog::Snippets — per-Sandbox insertion-ordered registry of preloaded snippets.
Entries replay against the fresh mrb_state before per-invocation
source / entrypoint resolution. Each Snippet::Source entry's name
is its canonical identity — the filename baked into the loaded IREP's
debug_info that surfaces in every backtrace frame originating from
the snippet as (snippet:Name):line. Duplicate names within the
code: form would produce ambiguous attribution and are rejected at
registration time.
Snippet::Binary entries carry no host-side name — their canonical
name lives in the bytecode's debug_info and is read by the guest at
load time; the host does not extract it.
Sealing is governed by the owning Sandbox — the registry itself
is append-only and exposes no mutation API beyond #register; the
Sandbox guards #register behind the seal check before delegating.
Constant Summary collapse
- NAME_PATTERN =
Ruby constant-name pattern enforced on snippet names.
/\A[A-Z]\w*\z/
Instance Method Summary collapse
-
#encode ⇒ String
Serialize the registered snippets to wire bytes.
-
#ensure_source_args!(code, name) ⇒ [String, Symbol | String]
Shape-only validation for the
code:+name:pair. -
#entry_payload(entry) ⇒ Hash[String, untyped]
Build the msgpack-ready Hash for one entry.
-
#initialize ⇒ Snippets
constructor
A new instance of Snippets.
- #normalize_name(name) ⇒ Symbol
-
#register(code: nil, name: nil, binary: nil) ⇒ Symbol?
Register one preloaded snippet in either of two forms.
-
#register_binary!(bytes) ⇒ nil
Binary-form register path.
-
#register_source!(code, name) ⇒ Symbol
Source-form register path.
Constructor Details
#initialize ⇒ Snippets
Returns a new instance of Snippets.
29 30 31 32 |
# File 'lib/kobako/catalog/snippets.rb', line 29 def initialize @entries = [] # : Array[Kobako::Snippet::Source | Kobako::Snippet::Binary] @encoded = nil # : String? end |
Instance Method Details
#encode ⇒ String
Serialize the registered snippets to wire bytes. Each entry
contributes a msgpack map shape; the collection rides as a single
msgpack array. An empty registry serializes to an empty array, never
absent. The wire codec is an implementation detail — callers
receive a binary String that the Kobako::Runtime layer ships
through the invocation channel. The entry value objects stay pure
carriers — this collection-tier method reads their attributes
externally via entry_payload rather than asking each entry to
self-encode.
The bytes are memoized — the table is replayed verbatim on every
invocation after sealing, so Frame 3 never changes between
encodes; #register drops the memo while the table is still open.
Unlike Catalog::Namespaces#encode, which gates its memo on the
seal, this one can fill eagerly and invalidate in #register
because every mutation funnels through that single method — there is
no out-of-sight child object to change the result behind its back.
51 52 53 54 55 |
# File 'lib/kobako/catalog/snippets.rb', line 51 def encode return @encoded if @encoded @encoded = Codec::Encoder.encode(@entries.map { |entry| entry_payload(entry) }).freeze end |
#ensure_source_args!(code, name) ⇒ [String, Symbol | String]
Shape-only validation for the code: + name: pair. Returns
the pair with nil narrowed away so callers can treat both as
present. The code: type check runs before the name:
presence check so callers passing code: nil explicitly see
the type error rather than the "missing keyword" error.
108 109 110 111 112 113 114 |
# File 'lib/kobako/catalog/snippets.rb', line 108 def ensure_source_args!(code, name) raise ArgumentError, "missing keyword: code: + name:, or binary:" if code.nil? && name.nil? raise ArgumentError, "code must be a String, got #{code.class}" unless code.is_a?(String) raise ArgumentError, "missing keyword: name:" if name.nil? [code, name] end |
#entry_payload(entry) ⇒ Hash[String, untyped]
Build the msgpack-ready Hash for one entry. Source entries
contribute their host-side name; Binary entries omit it
because the canonical name lives in the bytecode's embedded
debug_info and is read by the guest at load time
(docs/wire-codec.md Invocation channels).
131 132 133 134 135 136 137 138 |
# File 'lib/kobako/catalog/snippets.rb', line 131 def entry_payload(entry) case entry when Snippet::Source { "name" => entry.name.to_s, "kind" => Snippet::Source::KIND, "body" => entry.body } when Snippet::Binary { "kind" => Snippet::Binary::KIND, "body" => entry.body } end end |
#normalize_name(name) ⇒ Symbol
140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/kobako/catalog/snippets.rb', line 140 def normalize_name(name) unless name.is_a?(Symbol) || name.is_a?(String) raise ArgumentError, "snippet name must be a Symbol or String, got #{name.class}" end name_str = name.to_s unless NAME_PATTERN.match?(name_str) raise ArgumentError, "snippet name must match #{NAME_PATTERN.inspect} (got #{name.inspect})" end name_str.to_sym end |
#register(code: nil, name: nil, binary: nil) ⇒ Symbol?
Register one preloaded snippet in either of two forms.
* Source form +register(code: src, name: Name)+ — +src+ is the
mruby source as a String; the bytes are re-encoded as UTF-8
and detached from the caller's reference. +name+ is a Symbol
or String matching +NAME_PATTERN+. Returns the Symbol form
of +name+.
* Binary form +register(binary: bytes)+ — +bytes+ is
precompiled RITE bytecode as a String, duplicated and forced
to ASCII-8BIT so msgpack-ruby ships it as +bin+. Returns
+nil+ — bytecode entries are anonymous on the host side; any
structural validation is deferred to the guest at first replay.
The two forms are mutually exclusive: shape validation lives
here so callers (chiefly Kobako::Sandbox#preload) collapse to
a single delegation. Raises ArgumentError on mixed forms,
missing keywords, wrong types, malformed name, or
duplicate code: name.
75 76 77 78 79 80 81 82 83 84 |
# File 'lib/kobako/catalog/snippets.rb', line 75 def register(code: nil, name: nil, binary: nil) @encoded = nil if binary raise ArgumentError, "cannot combine binary: with code: / name:" if code || name register_binary!(binary) else register_source!(code, name) end end |
#register_binary!(bytes) ⇒ nil
Binary-form register path. Validates the binary: payload type
and appends the Binary entry. The bytes are duplicated and forced
to ASCII-8BIT so msgpack-ruby picks the bin family on the wire.
119 120 121 122 123 124 |
# File 'lib/kobako/catalog/snippets.rb', line 119 def register_binary!(bytes) raise ArgumentError, "binary must be a String, got #{bytes.class}" unless bytes.is_a?(String) @entries << Snippet::Binary.new(body: bytes.dup.force_encoding(Encoding::ASCII_8BIT)) nil end |
#register_source!(code, name) ⇒ Symbol
Source-form register path. Delegates argument-shape checks to
ensure_source_args! (which returns the narrowed [code, name]
pair), normalises name to a Symbol, rejects duplicates,
and appends the Source entry.
92 93 94 95 96 97 98 99 100 101 |
# File 'lib/kobako/catalog/snippets.rb', line 92 def register_source!(code, name) code, name = ensure_source_args!(code, name) name_sym = normalize_name(name) if @entries.any? { |e| e.is_a?(Snippet::Source) && e.name == name_sym } raise ArgumentError, "snippet #{name_sym.inspect} already preloaded" end @entries << Snippet::Source.new(name: name_sym, body: code.dup.force_encoding(Encoding::UTF_8)) name_sym end |