Documentation
¶
Overview ¶
Package functy implements an imperative language whose values are cty values and whose expressions are HCL. A functy source file is a sequence of function declarations; compiling it yields ordinary cty function.Function values that can be added to an *hcl.EvalContext and called from any HCL expression.
The statement grammar (func, var, if/else, for/while, switch, ...) is parsed by functy itself, while every embedded expression is handed to HCL (hclsyntax.ParseExpression), so operators, templates, and function calls behave exactly as they do elsewhere in HCL. Type annotations are resolved by functy's own TypeResolver — a superset of the ext/typeexpr grammar that also supports host-registered capsule and open (predicate-backed) named types.
Index ¶
- Constants
- func BuildFunction(fn *FuncDecl, evalCtxFn func() *hcl.EvalContext) function.Function
- type Assign
- type Block
- type Break
- type CaptureAssign
- type CatchClause
- type Clause
- type CondBranch
- type Continue
- type Decl
- type Defer
- type Directive
- type ExprStmt
- type Fallthrough
- type For
- type ForKind
- type FuncDecl
- type IfChain
- type Param
- type Parser
- func (p *Parser) AllowTopLevelConst(v bool) *Parser
- func (p *Parser) AllowTopLevelVar(v bool) *Parser
- func (p *Parser) Parse(src []byte, filename string) (*Result, hcl.Diagnostics)
- func (p *Parser) ParseAll(sources []Source) (*Result, hcl.Diagnostics)
- func (p *Parser) RegisterOpenType(name string, pred func(cty.Value) error) *Parser
- func (p *Parser) RegisterType(name string, ty cty.Type) *Parser
- func (p *Parser) RequireDeclaredTypes(v bool) *Parser
- func (p *Parser) RequireParamTypes(v bool) *Parser
- func (p *Parser) RequireReturnType(v bool) *Parser
- func (p *Parser) Types() *TypeResolver
- type Result
- type Return
- type Scope
- type Signal
- type SignalKind
- type Source
- type Statement
- type Switch
- type Throw
- type ThrownError
- type Try
- type TypeAlias
- type TypeConstraint
- type TypeResolver
- func (r *TypeResolver) ParseType(src []byte, filename string) (TypeConstraint, hcl.Diagnostics)
- func (r *TypeResolver) RegisterOpenType(name string, pred func(cty.Value) error) *TypeResolver
- func (r *TypeResolver) RegisterType(name string, ty cty.Type) *TypeResolver
- func (r *TypeResolver) ResolveType(expr hcl.Expression) (TypeConstraint, hcl.Diagnostics)
- type VarDecl
Constants ¶
const Extension = ".cty"
Extension is the file extension for functy source files.
Variables ¶
This section is empty.
Functions ¶
func BuildFunction ¶
func BuildFunction(fn *FuncDecl, evalCtxFn func() *hcl.EvalContext) function.Function
BuildFunction builds a single cty function from a parsed declaration.
cty has no native optional parameters, so only the required parameters go in Spec.Params; optional parameters and the variadic parameter are collected via a VarParam and mapped back to names (applying defaults) inside the Impl.
Types ¶
type Assign ¶
type Assign struct {
Name string
Expr hcl.Expression
SrcRange hcl.Range
}
Assign reassigns an existing binding found by walking the scope chain.
type CaptureAssign ¶ added in v0.3.0
type CaptureAssign struct {
ValName string // "_" to discard the value
ErrName string // "_" to discard the error
Declare bool // true for the `:=` declare-and-capture form
Expr hcl.Expression
ValRange hcl.Range // the value target, for diagnostics
ErrRange hcl.Range // the error target, for diagnostics
SrcRange hcl.Range
}
CaptureAssign is the two-target error-capture assignment `val, err = expr`. It evaluates Expr exactly once; on success it assigns the value to ValName and null to ErrName, and on failure it assigns null to ValName and the caught error to ErrName. Either target may be "_" (the blank identifier) to discard it. It is statement-level sugar for a try/catch — the function never unwinds.
When Declare is false (the `val, err = expr` operator) both non-blank targets must already be declared, like a plain `=`. When Declare is true (the `val, err := expr` shorthand) each non-blank target is newly declared (untyped) in the current scope, like a pair of `var`s.
type CatchClause ¶ added in v0.3.0
type CatchClause struct {
Name string // "" when omitted
Type TypeConstraint // nil = no type filter
Guard hcl.Expression // nil = no guard
Body []Statement
SrcRange hcl.Range
}
CatchClause is one `catch [name] [: type] [if guard] { ... }` clause. It matches a raised error iff its type filter's Coerce succeeds (Type == nil matches any shape) and its guard evaluates true (Guard == nil is unconditional); a clause with both Type and Guard nil is the catch-all. The bound name receives the raw error value (the filter is a gate, not a cast).
type Clause ¶ added in v0.3.0
Clause is one case or default clause of a switch. For a case clause Values holds the match expressions (it runs if any equals the subject, or — in the expression-less form — if any is true); the default clause has IsDefault true and no Values. A body whose final statement is Fallthrough transfers control to the next clause in source order.
type CondBranch ¶
type CondBranch struct {
Condition hcl.Expression
Body []Statement
SrcRange hcl.Range
}
CondBranch is one condition-guarded branch of an if chain.
type Continue ¶
Continue skips to the next iteration of an enclosing loop: the innermost one, or the loop named by Label.
type Decl ¶
type Decl struct {
Name string
Type TypeConstraint // from an optional `: T`; nil if unannotated
Expr hcl.Expression // initializer, lazily evaluated (nil if none)
DefRange hcl.Range
}
Decl is a collected top-level var/const declaration, returned unevaluated so a host can fold it into its own dependency-sorting and evaluation pass. Expr.Variables() exposes the references needed for that sort.
type Defer ¶
type Defer struct {
Expr hcl.Expression
SrcRange hcl.Range
}
Defer schedules Expr to run when the enclosing function exits, in LIFO order.
type Directive ¶ added in v0.2.0
Directive is a collected directive comment, following Go's convention: a line comment with no space after `//`, of the form `//<namespace>:<name> [args]`. A space after `//` (`// functy: …`) makes it ordinary prose, not a directive.
functy interprets only its own `functy:` namespace (strict, require); every other namespace is collected and passed through untouched for the host to act on (e.g. `//vinculum:cache 5m`).
type ExprStmt ¶
type ExprStmt struct {
Expr hcl.Expression
SrcRange hcl.Range
}
ExprStmt evaluates an expression purely for its side effects; the value is discarded.
type Fallthrough ¶ added in v0.3.0
Fallthrough transfers control to the next clause of the enclosing switch, running its body without testing. It is legal only as the final statement of a case or default body, and not in the last clause.
type For ¶
type For struct {
Kind ForKind
// Label is the loop's label ("" if unlabeled); a labeled break/continue whose
// target equals this label is consumed by this loop rather than an inner one.
Label string
// ForCond / ForClause:
Init Statement // ForClause init clause (nil otherwise)
Cond hcl.Expression // condition (nil = always true)
Post Statement // ForClause post clause (nil otherwise)
// ForRange:
KeyName string // first range variable ("" if absent)
ValName string // second range variable ("" if only one is given)
Collection hcl.Expression // collection being ranged over
Body []Statement
SrcRange hcl.Range
}
For is the unified loop node covering all loop forms.
type FuncDecl ¶
type FuncDecl struct {
Name string
Params []Param
RetType TypeConstraint // nil when no return type is annotated (dynamic)
Body []Statement
DefRange hcl.Range
}
FuncDecl is a top-level function declaration.
type IfChain ¶
type IfChain struct {
Branches []CondBranch
Else []Statement
SrcRange hcl.Range
}
IfChain is an if / else-if / else chain. Else is nil when there is no final else clause.
type Param ¶
type Param struct {
Name string
Type TypeConstraint // nil when unannotated (dynamic)
Default hcl.Expression // non-nil for an optional parameter
Variadic bool // true for the trailing *rest parameter
DefRange hcl.Range
}
Param is a single function parameter.
A parameter is required unless it has a Default or is Variadic. A typed parameter (Type != cty.NilType) converts its argument to that type. For a variadic parameter, Type is the *element* type: `*rest: T` collects the extra arguments into a list(T), while an untyped `*rest` collects them into a tuple.
type Parser ¶
type Parser struct {
// contains filtered or unexported fields
}
Parser parses functy source into a Result. Options accrue via chained setters; the zero value (via NewParser) accepts only function declarations and the built-in type grammar.
func (*Parser) AllowTopLevelConst ¶
AllowTopLevelConst controls whether a top-level `const` declaration is collected into Result.Consts (true) or reported as a parse error (false, the default).
func (*Parser) AllowTopLevelVar ¶
AllowTopLevelVar controls whether a top-level `var` declaration is collected into Result.Vars (true) or reported as a parse error (false, the default).
func (*Parser) Parse ¶
Parse parses a single functy source. The returned Result holds the parsed declarations even when diagnostics contain errors (best-effort recovery), so callers should check diags before using it.
func (*Parser) ParseAll ¶
func (p *Parser) ParseAll(sources []Source) (*Result, hcl.Diagnostics)
ParseAll parses several sources together and merges their declarations into one Result. The sources share one namespace: type aliases declared in any source are visible to every source (see parseSources), and per-source function/var/ const declarations are concatenated in order (duplicate function names across sources are detected later by Result.Compile).
func (*Parser) RegisterOpenType ¶ added in v0.2.0
RegisterOpenType registers a named open type backed by a predicate. An annotation naming it requires the value to satisfy pred and otherwise passes it through untouched (non-destructive), so extra attributes survive — suitable for marker-capsule objects (e.g. a ctx carrying _ctx plus other fields) and required-attribute objects (e.g. an error with at least a message). Returns the Parser for chaining.
func (*Parser) RegisterType ¶ added in v0.2.0
RegisterType registers a named (capsule) type. An annotation naming it is enforced by type identity: a value must already be of ty (or null), unless ty itself defines a conversion. Hosts use this for their cty capsule and rich-object types. Returns the Parser for chaining.
func (*Parser) RequireDeclaredTypes ¶ added in v0.2.0
RequireDeclaredTypes requires every var/const declaration to carry a type (`: T`). See RequireParamTypes for the off-by-default and tighten-only semantics.
func (*Parser) RequireParamTypes ¶ added in v0.2.0
RequireParamTypes, when set, requires every function parameter to carry an explicit type annotation (`: T`; `any` is allowed but must be written). Off by default. A file may additionally request this via a //functy: directive, but a file can never relax a host-set requirement. Returns the Parser for chaining.
func (*Parser) RequireReturnType ¶ added in v0.2.0
RequireReturnType requires every function to declare a return type (`-> T`). See RequireParamTypes for the off-by-default and tighten-only semantics.
func (*Parser) Types ¶ added in v0.2.0
func (p *Parser) Types() *TypeResolver
Types returns the parser's TypeResolver. Named types registered through the Parser are registered on it, so a host can resolve standalone type annotations (TypeResolver.ResolveType / ParseType) against the very same named types it uses for parsing `.cty` files.
type Result ¶
type Result struct {
Funcs []*FuncDecl // parsed function declarations
Consts []Decl // top-level const declarations (only when enabled)
Vars []Decl // top-level var declarations (only when enabled)
Types []TypeAlias // top-level type aliases (project-scoped across all sources)
// Directives are the directive comments from each source's leading comment
// block (file-scope), across all sources. functy acts on its own `functy:`
// namespace; others are passed through for the host.
Directives []Directive
}
Result is the outcome of parsing one or more functy sources. It is a struct (rather than a bare map) so additional collected output can be added without breaking callers.
func (*Result) Compile ¶
func (r *Result) Compile(evalCtxFn func() *hcl.EvalContext) (map[string]function.Function, hcl.Diagnostics)
Compile turns the parsed function declarations into cty functions. Each function captures evalCtxFn and calls it at invocation time (late binding), so a function may call sibling functions and reference host globals that are finalized after compilation — enabling recursion and mutual recursion.
Duplicate function names within the result are reported as errors; the host is responsible for detecting collisions against its own built-in functions when it merges the returned map into its registry.
type Return ¶
type Return struct {
Expr hcl.Expression
SrcRange hcl.Range
}
Return exits the enclosing function. Expr is nil for a bare return.
type Scope ¶
type Scope struct {
// contains filtered or unexported fields
}
Scope is a chained lexical scope. Variable lookup walks outward through the parent chain; the nearest binding wins. Declare always creates a binding in the innermost scope (shadowing any outer one); Set reassigns the nearest existing binding, converting to its pinned type.
dirty supports the interpreter's eval-context cache: it is set whenever a binding in this scope changes, so the interpreter knows to rebuild the merged *hcl.EvalContext before the next statement. Statements that cannot mutate a binding (a bare expression evaluated for its side effects, for instance) leave it clear, so consecutive such statements reuse one context.
func NewScope ¶
NewScope creates a scope with the given parent (nil for a function root). A fresh scope is dirty so the interpreter builds its context once before use.
func (*Scope) Declare ¶
Declare introduces a new binding in this (innermost) scope, shadowing any binding of the same name in an enclosing scope. For a typed declaration (a non-nil constraint) the initial value is coerced through it.
type Signal ¶
type Signal struct {
Kind SignalKind
Value cty.Value // meaningful only for SignalReturn
Label string // target loop label for SignalBreak / SignalContinue ("" = innermost)
}
Signal carries a control-flow transfer up the call stack. A nil *Signal means normal completion (fall-through to the next statement).
type SignalKind ¶
type SignalKind int
SignalKind classifies a non-local control-flow transfer that propagates out of statement execution.
const ( // SignalReturn unwinds to the enclosing function with a value. SignalReturn SignalKind = iota // SignalBreak exits the innermost enclosing loop. SignalBreak // SignalContinue skips to the next iteration of the innermost loop. SignalContinue // SignalError unwinds a raised error (from throw or a failing expression) // until a try/catch handles it or it leaves the function. SignalError // SignalFallthrough transfers control to the next clause of the enclosing // switch. It is produced only by a Fallthrough statement and is always // consumed by execSwitch, never escaping the switch. SignalFallthrough )
type Source ¶
Source is a single functy source: its filename (used in diagnostics) and raw bytes. ParseSources collects these from files, directories, and embedded filesystems.
func ParseSources ¶
func ParseSources(inputs ...any) ([]Source, hcl.Diagnostics)
ParseSources collects functy sources from a heterogeneous set of inputs, returning the raw bytes of each (it does not parse them — call Parser.Parse). Each argument may be:
- a string path to a .cty file, or to a directory (walked recursively, skipping dot-directories, collecting every .cty file);
- a []string of such paths;
- an embed.FS (walked recursively for .cty files);
- a Source, used as-is;
- a []byte, treated as the bytes of one anonymous source.
This mirrors how a host discovers .vcl/.vinit files, but yields raw bytes because functy has its own front-end.
type Statement ¶
type Statement interface {
// contains filtered or unexported methods
}
Statement is implemented by every functy statement node.
type Switch ¶
type Switch struct {
Subject hcl.Expression
Clauses []Clause
SrcRange hcl.Range
}
Switch is a switch statement. Subject is nil for the expression-less form, whose case values are boolean expressions evaluated like an if/else chain. Clauses are in source order; at most one is the default.
type Throw ¶
type Throw struct {
Expr hcl.Expression
SrcRange hcl.Range
}
Throw raises an error whose value is Expr (a string becomes { message = <string>, value = null }; an object is used directly).
type ThrownError ¶ added in v0.3.0
ThrownError is the Go error a functy function returns at its cty.Function boundary when an uncaught throw unwinds out of it. It carries the raw error value so a functy catch (or a Go host, via errors.As) recovers the full error object; Error() renders the message for any other caller.
func (*ThrownError) Error ¶ added in v0.3.0
func (e *ThrownError) Error() string
type Try ¶
type Try struct {
Body []Statement
Catches []CatchClause
Finally []Statement
SrcRange hcl.Range
}
Try runs Body, routing a raised error through its catch clauses (first match wins) and always running a finally block. At least one of Catches/Finally is present.
type TypeAlias ¶ added in v0.2.0
type TypeAlias struct {
Name string
Type TypeConstraint
DefRange hcl.Range
}
TypeAlias is a resolved top-level `type Name = <type>` declaration. Aliases are project-scoped: every alias collected from the sources parsed together is visible to every source (like the function namespace), so a function in one file may use a type declared in another.
type TypeConstraint ¶ added in v0.2.0
type TypeConstraint interface {
// Coerce applies the constraint to a value, returning the value to store or
// an error if it does not satisfy the constraint.
Coerce(cty.Value) (cty.Value, error)
// Cty returns the underlying cty.Type (dynamic for `any`, void, and open
// predicate types).
Cty() cty.Type
String() string
}
TypeConstraint is a resolved type annotation: it knows how to coerce a value to satisfy the annotation and exposes the underlying cty.Type. A nil TypeConstraint means "dynamic" (no annotation) — no coercion is applied.
It is the single source of truth for a declared type. Cty() derives the cty.Type, so callers that only need the type (host introspection, generated docs) need not carry it separately; Coerce() applies the enforcement.
Three coercion disciplines exist: structural/primitive annotations convert the value (cty/convert); a named (capsule) type is checked by identity; an open type is checked by a predicate and otherwise passed through untouched. This is why functy owns its resolver rather than delegating to ext/typeexpr, whose closed grammar cannot name capsule types or express open ("at least these attributes") types — and why a bare cty.Type cannot represent every constraint (a predicate type's Cty() is only dynamic).
func ConvertType ¶ added in v0.2.0
func ConvertType(ty cty.Type) TypeConstraint
ConvertType wraps a concrete cty.Type as a TypeConstraint enforced by conversion (cty/convert). Useful for a host that already has a cty.Type — for example a backward-compatible path for a type that was specified some other way.
type TypeResolver ¶ added in v0.2.0
type TypeResolver struct {
// contains filtered or unexported fields
}
TypeResolver is functy's type system as a standalone, reusable component. It resolves functy type annotations (the same grammar used in `.cty` source) into TypeConstraints, against the built-in types plus any host-registered named types. It is usable independently of parsing functy programs — for example as a richer alternative to ext/typeexpr (a TypeConstraint carries enforcement via Coerce, not just a cty.Type), or for a host to type-check its own configuration (e.g. resolving a declared type and enforcing a value with Coerce).
A Parser holds one (see Parser.Types); register named types once and use them both for parsing `.cty` files and for resolving standalone annotations.
func NewTypeResolver ¶ added in v0.2.0
func NewTypeResolver() *TypeResolver
NewTypeResolver returns a resolver with functy's built-in types (including the `error` open type) and no host registrations.
func (*TypeResolver) ParseType ¶ added in v0.2.0
func (r *TypeResolver) ParseType(src []byte, filename string) (TypeConstraint, hcl.Diagnostics)
ParseType lexes a type annotation from source bytes and resolves it — a convenience for annotations stored as strings (e.g. a host config field).
func (*TypeResolver) RegisterOpenType ¶ added in v0.2.0
func (r *TypeResolver) RegisterOpenType(name string, pred func(cty.Value) error) *TypeResolver
RegisterOpenType registers a named open type backed by a predicate. See Parser.RegisterOpenType. Returns the resolver for chaining.
func (*TypeResolver) RegisterType ¶ added in v0.2.0
func (r *TypeResolver) RegisterType(name string, ty cty.Type) *TypeResolver
RegisterType registers a named (capsule) type, enforced by type identity. See Parser.RegisterType. Returns the resolver for chaining.
func (*TypeResolver) ResolveType ¶ added in v0.2.0
func (r *TypeResolver) ResolveType(expr hcl.Expression) (TypeConstraint, hcl.Diagnostics)
ResolveType resolves a parsed type-annotation expression (e.g. an HCL attribute value) into a TypeConstraint — the analog of typeexpr.TypeConstraint, but yielding a constraint that can enforce values, not just a cty.Type. `null` is not accepted here (it is only meaningful as a function return type).
type VarDecl ¶
type VarDecl struct {
Name string
Type TypeConstraint // nil when unannotated (dynamic)
Init hcl.Expression
SrcRange hcl.Range
}
VarDecl declares a new local binding in the current scope.
Type is cty.NilType for a dynamic variable. Init is nil for a declaration with no initializer, which defaults to null (of the declared type, if any).