Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit3bb1da2647

README.md

luau_introspect (module)

Parses Luau/asset source TEXT into structured declarations. Pure string/pattern parsing (string.find / string.match / string.gmatch) over the literal source — no compiling, no VM spawn, no runtime state, no engine FFI. Given source text, it extracts what is written in the file.

This is the shared parser every per-assetType M.ref.inspect hook uses to turn a component/module/tool's own source into the structured record asset.inspect returns.

Exports

  • docstrings(src) -> DocMap — scans --!desc / --!arg / --!return / --!example comment runs and binds each run to the name of the declaration on the next non-comment line (the identifier after function, public:, local function, or M.).
  • publicFields(src) -> { FieldEntry } — parses public = { name = Field.<kind>(default, mode), ... } table-literal entries and module-scope public.<name> = Field.<kind>(...) assignments.
  • events(src) -> { EventEntry } — parses events = { name = Event(payloadSchema?, syncMode?), ... } table-literal entries.
  • methods(src) -> { MethodEntry } — parses function public:<name>(...) / typed function public:<name>(...) declarations (params + optional return type).
  • lifecycleHooks(src, catalog) -> { string } — top-level function <name>( declarations whose name is in the caller-supplied catalog array.
  • moduleExports(src) -> { ExportEntry } — parses M.<name> = function assignments, function M.<name>(...) declarations, and a trailing return { foo = foo, ... } export table.
  • forSource(src, computeFn) -> any — memoises computeFn(src) keyed on src itself in a module-local table. The same source text returns the cached result without re-running computeFn; different source text recomputes. Keyed on the source content (not an asset checksum) because composite/container assets (component folders, scene folders, etc.) all hash to the empty-string checksum — a checksum-keyed cache collapses every such asset's detail onto whichever one was inspected first.

Usage

local Introspect = require("modules.luau_introspect")

local src = self:getInitScript()
local detail = Introspect.forSource(src, function(s)
    return {
        fields = Introspect.publicFields(s),
        events = Introspect.events(s),
        methods = Introspect.methods(s),
        hooks = Introspect.lifecycleHooks(s, lifecycleCatalog),
    }
end)

Notes

  • Every function is a pure string→table transform: same source text in, same structured table out, every time. No reads of live component state, no entity/world queries.
  • Structural scanning runs over a comment- and string-aware mask of the source (a same-length copy where comment bodies and string-literal contents are blanked to spaces, delimiters and newlines preserved). Pattern matches and brace/paren balancing run on the mask — so a brace, Field., Event(, or a whole entry that appears inside a comment or a string literal never registers — while captured values are sliced from the original source at the same (aligned) indices. This makes the parser robust to apostrophes in comments, commented-out entries, string defaults containing } / Field. / Event(, and [[ ]] long strings.
  • default / category are raw trimmed source-text slices (with any trailing comment stripped), not evaluated values.
    • For a ref constructor (Field.assetRef / dataRef / resource / componentRef) the FIRST positional argument is the category / contract / componentType selector, captured under category; the SECOND argument is captured under default.
    • For every other Field kind the first argument is the default and category is nil.
  • A trailing Sync / NoSync identifier is captured as sync, tolerating a trailing comma or a trailing comment after the last argument.
  • Field/Event entries whose key is a computed or indirect expression ([expr] = Field.number(...)) are omitted — only literal identifier keys are recognised.
  • methods captures a ... variadic parameter (as name = "..."), with its : <type> annotation when present.
  • A --!desc written above a field/event declaration (<name> = Field.<kind>(...) / <name> = Event(...)) or a public.<name> = assignment binds to that name in docstrings, and is folded into the corresponding publicFields / events entry's desc.
  • lifecycleHooks never hardcodes the lifecycle-callback catalog; the caller passes the list of names to match against.
  • forSource's memo is bounded (oldest-key eviction) so a long session touching many distinct asset versions can't grow it without limit.