compilers

package
v0.0.0-...-8d9931c Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 29, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package compilers defines the contract between spec compilers and the engine: a Compiler lowers source documents of its formats into an ir.Document plus diagnostics, purely and reentrantly.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Compiler

type Compiler interface {
	Formats() []SourceFormat
	// Detect reports the format src declares, and whether this compiler
	// recognizes it at all. Recognition is not support: a compiler may name a
	// format it does not serve — a version it has yet to implement — so that the
	// caller can say so rather than report the source as unrecognized.
	//
	// An ok answer must name a format. Recognizing a source is knowing what it
	// is, so the zero format with ok true is no answer, and Registry.Detect
	// passes over a compiler that gives one rather than let it end the search.
	//
	// opts is what this compiler would compile src with, so that recognizing a
	// source is held to the bounds the caller set for reading it rather than to
	// a ceiling of the compiler's choosing: the read that recognition makes may
	// be the most expensive read of the source there is. A FormatOptions value
	// of a type this compiler does not take configures some other compiler, and
	// here means this one's defaults; Compile is where such a value is an error,
	// since only there is it certain the caller meant it for this compiler.
	//
	// A compiler that parsed src to recognize it may return that parse in
	// Recognition.Parsed, and read it back from Source.Parsed in Compile. It is
	// an optimization and never a protocol: a compiler must compile a source
	// whose Parsed is nil or another compiler's, since a caller calling Compile
	// directly never went through Detect at all.
	//
	// diags is what this compiler can say about a source it declines, and is read
	// only when ok is false. Bytes of another format are ordinary input here, so
	// declining them is silent: a compiler that reported every source it did not
	// take would bury the one report that matters under one per registered
	// format. It is for the narrower cases where the source is recognizably this
	// compiler's own and cannot be read — a malformed document in its own
	// serialization — or where opts forbid reading it at all, a source past the
	// caller's size budget. No other compiler is in a position to say either,
	// and the caller would otherwise have to report the source as unrecognized.
	Detect(src Source, opts Options) (rec Recognition, diags []ir.Diagnostic, ok bool)
	// DecodeOptions turns textual settings into the value this compiler expects
	// in Options.FormatOptions. An empty set yields defaults. An unknown key, an
	// unusable value, or a file that cannot be read is an error — a setting that
	// is silently ignored leaves the caller believing they configured something.
	DecodeOptions(set OptionSet) (any, error)
	Compile(ctx context.Context, sources []Source, opts Options) (*ir.Document, []ir.Diagnostic, error)
}

Compiler lowers source documents into the IR. Implementations must be pure: no package-level mutable state, no writes to stderr; spec problems are returned as ir.Diagnostic values and the error return is reserved for I/O-level and programmer errors.

Purity is also the concurrency contract. One Compiler value must accept overlapping Compile calls, which is what lets a caller share a single engine across goroutines; a Compiler that memoizes into package state would break that caller's guarantee without changing this signature.

The guarantee is the compiler's, not the argument's. Two Compile calls may run at once over Sources holding the same bytes; they may not run at once over one Source carrying a Parsed, nor may one such Source be compiled twice, because a Parsed is the compiler's working state and a compile writes through it. Source.Parsed says so, and nothing here can enforce it: a Source is the caller's value, and the type system cannot tell one that has been compiled from one that has not.

Detect and DecodeOptions are what keep the layers above format-agnostic: a compiler says what its own input looks like and what its own options are called, so registering one is the whole of adding a format. Both are required rather than optional interfaces on purpose — a compiler that answered neither would be registered and unreachable, which is a hole no caller can see.

A compiler reports through the returned slice. It may also store the same findings on the Document it returns — that copy is what the persisted IR JSON carries — but nothing obliges it to, and one that fills both must fill them alike. Neither list is guaranteed to hold the other, so a caller holding both unions them, as the engine does, rather than take one for the whole set.

type Configure

type Configure func(c Compiler) (Options, error)

Configure resolves the options one compiler would compile a source with. It is asked per compiler because what configures a run is, in general, one compiler's vocabulary — textual settings only that compiler can decode — and which compiler a source belongs to is what detection is still finding out.

type Detection

type Detection struct {
	// Compiler is the compiler registered for the format the source was
	// recognized as, or nil when none is.
	Compiler Compiler
	// Recognition is what recognizing the source found. Its Format is the zero
	// format when nothing recognized it; see Registry.Detect.
	Recognition Recognition
	// Options is what Compiler compiles the source with: the answer Configure
	// gave for it, which the caller hands on rather than resolving again.
	Options Options
	// Declined collects what the compilers that declined the source had to say,
	// in the order they were asked, and is meaningful only when none took it.
	Declined []ir.Diagnostic
}

Detection is what Registry.Detect found for one source.

type OptionSet

type OptionSet struct {
	// Settings maps an option name to its textual value. A nil or empty map asks
	// for defaults.
	Settings map[string]string
	// ReadFile loads a file a setting names, e.g. an overlay document. A
	// compiler does no file I/O of its own — Source is the whole of its input,
	// which is what keeps compilation pure and reentrant — so the caller supplies
	// the reader and the read stays the caller's. A nil ReadFile means no setting
	// may name a file.
	ReadFile func(name string) ([]byte, error)
}

OptionSet is one compile's configuration as text: settings named in the compiler's own option vocabulary, which DecodeOptions turns into that compiler's FormatOptions value.

It exists so a caller can configure a compiler it does not import. The CLI collects key=value pairs without knowing which compiler will read them, and the compiler names and validates every key, so no layer above holds a list of another format's options.

type Options

type Options struct {
	FormatOptions any
}

Options carries per-compile configuration. FormatOptions is the compiler-specific options value; each compiler documents the concrete type it accepts and treats nil as defaults.

type Recognition

type Recognition struct {
	// Format is the dialect the source declares. An ok recognition names one.
	Format SourceFormat
	// Parsed is the value to put in Source.Parsed before compiling this source,
	// or nil when the compiler parsed nothing worth keeping — it read the bytes
	// some other way, or it declined.
	Parsed any
}

Recognition is what one compiler made of a source it recognized: the format it names, and whatever it parsed while deciding. The parse is carried so it can be handed to the same compiler's Compile rather than repeated there; see Source.Parsed for what a consumer may assume about it.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry maps source formats to compilers. It is a plain instance — there is no package-level default and no init()-time self-registration; the engine composes its registry explicitly. The zero value is a usable empty registry.

Concurrent Lookup is safe once registration is complete. Register is not: it writes an unsynchronized map, so every Register must happen before the first concurrent Lookup. Compose a Registry fully before publishing it, the way engine.NewWith registers into a fresh one and only then wraps it.

func NewRegistry

func NewRegistry() *Registry

NewRegistry returns an empty registry.

func (*Registry) Detect

func (r *Registry) Detect(ctx context.Context, src Source, configure Configure) (Detection, bool, error)

Detect asks each registered compiler in turn to recognize src, under the options configure resolves for it, and returns the first that does: the compiler registered for the format it named, what that recognition found, the options it compiles with, and whether such a compiler exists. Nothing recognizing src is reported as the zero format, which is what tells "no compiler takes these bytes" from "this format is recognized but unsupported". A nil configure gives every compiler the zero Options, its defaults.

Options a compiler cannot be configured with are an error only when that compiler is the one taking src. Until then they may well be another's: a setting in one compiler's vocabulary fails to decode in every other, and a compiler that the settings do not describe is configured by its defaults, which is what it recognizes under. A compiler that takes src with options it could not decode ends the search with the error, because those are the options it would have been asked to compile with.

The parse a recognition carries survives only when the compiler that produced it is the one registered for the format it named. A compiler may recognize a format another serves — an OpenAPI compiler names Swagger so the caller hears "unsupported" rather than "unreadable" — and one compiler's parse is not another's to read, whatever its type happens to be. The owner is configured in its own right then, since the recognizer's options are not its.

A compiler that recognizes src ends the search, so nothing after it is asked and nothing it might have said is collected — the answer to "who takes this" makes any account of why others did not moot.

The order is registration order, because it is the only order a caller controls — the variadic that composes the registry fixes it — and a map's would make two compilers that both claim a source resolve differently from run to run.

ctx is consulted before each compiler is asked, since recognizing a source may mean parsing it, and a canceled ctx is returned as the error.

func (*Registry) Formats

func (r *Registry) Formats() []SourceFormat

Formats returns every format some registered compiler serves.

It is sorted rather than in registration order, because this answers "what does this build accept" for a reader, and a set rendered in a different order from run to run reads as a different set.

Sorting is by name, then by version as a string. That matches numeric order only while minor versions stay single-digit: a 3.10 would sort ahead of 3.2, not behind it. The deviation is left rather than fixed because this order is read, never compared against — nothing selects a compiler by position here — and a version comparator that guessed at every format's scheme would be a larger thing to get wrong than a list one line out of order.

func (*Registry) Lookup

func (r *Registry) Lookup(format SourceFormat) (Compiler, bool)

Lookup returns the compiler registered for format.

func (*Registry) Register

func (r *Registry) Register(c Compiler) error

Register adds c under every format it reports. It rejects a nil compiler and a compiler reporting no formats, and it fails if any format is already claimed; on failure nothing is registered.

Rejecting nil is what keeps a caller's programmer error a Go error instead of a segmentation fault raised inside this package, which is why the check comes before the only call this method makes on c.

type Source

type Source struct {
	Path string
	Data []byte
	// Parsed is what a compiler's own Detect already made of Data, for its
	// Compile to use instead of reading the bytes a second time. Recognizing a
	// source and lowering it both begin by parsing it, and the two calls are
	// back to back over the same bytes, so without this every compile parses
	// its input twice.
	//
	// It is never required. A nil Parsed — what a caller assembling a Source by
	// hand leaves — means the compiler parses Data itself, and so does a value
	// of a type it does not recognize, which is the only thing it may assume
	// about one: the type is the producing compiler's own, and a compiler must
	// type-assert with comma-ok rather than trust what it is handed.
	//
	// A Parsed value belongs to one Compile. What a compiler leaves here is live
	// state it writes through while compiling, so a Source carrying one may not
	// be compiled twice or shared between concurrent Compile calls. Data may
	// change under it, on the other hand, and a compiler must notice: bytes that
	// no longer match the parse beside them are read afresh rather than lowered
	// from a tree that does not describe them. Registry.Detect produces one per
	// source, and drops it unless the compiler that produced it is the one that
	// will consume it.
	Parsed any
}

Source is one pre-read input document. Compilers perform no file I/O; the caller loads bytes so compilation stays pure and reentrant.

type SourceFormat

type SourceFormat struct {
	Name    string // "openapi", "swagger", "typespec", "smithy", ...
	Version string // "3.0", "3.1", "2.0", ...
}

SourceFormat identifies one spec dialect a compiler accepts.

func (SourceFormat) String

func (f SourceFormat) String() string

String renders the canonical "name@version" form used in diagnostics and registry errors.

Directories

Path Synopsis
Package compile holds the state every spec compiler needs and the invariants that state carries, so each compiler does not reimplement them.
Package compile holds the state every spec compiler needs and the invariants that state carries, so each compiler does not reimplement them.
Package openapi lowers OpenAPI 3.0/3.1/3.2 documents into the Morphic IR.
Package openapi lowers OpenAPI 3.0/3.1/3.2 documents into the Morphic IR.
internal/annotation
Package annotation reads the documentation-adjacent facts a schema or a carrier declares — descriptions, deprecation, visibility, XML hints, extensions — and the validation-only JSON Schema keywords the IR keeps verbatim rather than models.
Package annotation reads the documentation-adjacent facts a schema or a carrier declares — descriptions, deprecation, visibility, XML hints, extensions — and the validation-only JSON Schema keywords the IR keeps verbatim rather than models.
internal/auth
Package auth lowers what a document says about authentication: the security schemes it declares, and the requirements that name them.
Package auth lowers what a document says about authentication: the security schemes it declares, and the requirements that name them.
internal/diag
Package diag holds the OpenAPI compiler's diagnostic vocabulary: the stable codes it reports under, and the single constructor that builds a diagnostic from them.
Package diag holds the OpenAPI compiler's diagnostic vocabulary: the stable codes it reports under, and the single constructor that builds a diagnostic from them.
internal/ids
Package ids builds the RFC 6901 pointer that names a position — a jsontext.Pointer from construction on, read back through its methods, never split on '/' — and derives IR identifiers and namespaces from it.
Package ids builds the RFC 6901 pointer that names a position — a jsontext.Pointer from construction on, read back through its methods, never split on '/' — and derives IR identifiers and namespaces from it.
internal/load
Package load turns one source document into a parsed, reference-resolved OpenAPI document plus the identity metadata the rest of the compiler stamps into the IR.
Package load turns one source document into a parsed, reference-resolved OpenAPI document plus the identity metadata the rest of the compiler stamps into the IR.
internal/lowering
Package lowering holds the immutable context every OpenAPI lowering reads.
Package lowering holds the immutable context every OpenAPI lowering reads.
internal/merge
Package merge reconciles the properties more than one allOf branch declares: either folding two declarations into one or reporting that they disagree.
Package merge reconciles the properties more than one allOf branch declares: either folding two declarations into one or reporting that they disagree.
internal/nodeview
Package nodeview reads a YAML mapping the way the resolver will: through aliases, through `<<` merge keys, and with duplicate keys resolved the way the parser resolves them.
Package nodeview reads a YAML mapping the way the resolver will: through aliases, through `<<` merge keys, and with duplicate keys resolved the way the parser resolves them.
internal/openapitest
Package openapitest holds the test scaffolding every test package under compilers/openapi would otherwise carry as its own copy.
Package openapitest holds the test scaffolding every test package under compilers/openapi would otherwise carry as its own copy.
internal/operation
Package operation lowers what a document says an API does: its path items, webhooks and callbacks, the parameters merged onto each operation, and the content of every request body, response and header.
Package operation lowers what a document says an API does: its path items, webhooks and callbacks, the parameters merged onto each operation, and the content of every request body, response and header.
internal/overlay
Package overlay applies an OpenAPI Overlay document to the parsed node tree, and records which positions in the result the overlay is answerable for.
Package overlay applies an OpenAPI Overlay document to the parsed node tree, and records which positions in the result the overlay is answerable for.
internal/resolve
Package resolve answers what a $ref names: which same-document pointer it addresses, which interned type, if any, already lives there, and — for the components that are not schemas — which concrete value and declaration site a reference-or-inline entry stands for.
Package resolve answers what a $ref names: which same-document pointer it addresses, which interned type, if any, already lives there, and — for the components that are not schemas — which concrete value and declaration site a reference-or-inline entry stands for.
internal/scan
Package scan refuses a source document before any of it is lowered.
Package scan refuses a source document before any of it is lowered.
internal/schema
Package schema lowers OpenAPI schemas into IR types: the shape walk itself, the compositions written around it, the references that reach other schemas, and the preservation of what the IR has no field for.
Package schema lowers OpenAPI schemas into IR types: the shape walk itself, the compositions written around it, the references that reach other schemas, and the preservation of what the IR has no field for.
internal/sourceindex
Package sourceindex answers, in one walk, the questions asked of a decoded source tree before any of it is lowered.
Package sourceindex answers, in one walk, the questions asked of a decoded source tree before any of it is lowered.
internal/value
Package value lowers source scalars into ir.Value, keeping numeric literals as their exact source text so nothing rounds through float64.
Package value lowers source scalars into ir.Value, keeping numeric literals as their exact source text so nothing rounds through float64.
internal/ynode
Package ynode spells yaml.v3 nodes: the tag a resolved `<<` merge key carries, the constructors for the node kinds a parse produces, and the merge chain the compiler's depth bounds are measured against.
Package ynode spells yaml.v3 nodes: the tag a resolved `<<` merge key carries, the constructors for the node kinds a parse produces, and the merge chain the compiler's depth bounds are measured against.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL