github.com/goadesign/goa/eval
eval
import "github.com/goadesign/goa/eval"
Overview
Package eval implements a DSL engine for executing arbitrary Go DSLs.
DSLs executed via eval consist of package functions that build up expressions upon execution.
A DSL that allows describing a service and its methods could look like this:
var _ = Service("service name") // Defines the service "service name"
var _ = Method("method name", func() { // Defines the method "method name"
Description("some method description") // Sets the method description
})
DSL keywords are simply package functions that can be nested using anonymous functions as last argument. Upon execution the DSL functions create expression structs. The expression structs created by the top level functions on process start (both Service and Method in this example) should be stored in special expressions called root expressions. The DSL implements both the expression and root expression structs, the only requirement is that they implement the eval package Expression and Root interfaces respectively.
Keeping with the example above, Method creates instances of the following MethodExpression struct:
type MethodExpression struct {
Name string
DSLFunc func()
}
where Name gets initialized with the first argument and DSLFunc with the second. ServiceExpression is the root expression that contains the instances of MethodExpression created by the Method function:
type ServiceExpression struct {
Name string
Methods []eval.Expression
}
The Method DSL function simply initializes a MethodExpression and stores it in the Methods field of the root ServiceExpression:
func Method(name string, fn func()) {
ep := &MethodExpression{Name: name, DSLFunc: fn}
Design.Methods = append(Design.Methods, ep)
}
where Design is a package variable holding the ServiceExpression root expression:
// Design is the DSL root expression.
var Design *ServiceExpression = &ServiceExpression{}
The Service function simply sets the Name field of Service:
func Service(name string) {
Design.Name = name
}
Once the process is loaded the Design package variable contains an instance of ServiceExpression which in turn contains all the instances of MethodExpression that were created via the Method function. Note that at this point the Description function used in the Method DSL hasn’t run yet as it is called by the anonymous function stored in the DSLFunc field of each MethodExpression instance. This is where the RunDSL function of package eval comes in.
RunDSL iterates over the initial set of root expressions and calls the WalkSets method exposed by the Root interface. This method lets the DSL engine iterate over the sub-expressions that were initialized when the process loaded.
In this example the ServiceExpression implementation of WalkSets simply passes the Methods field to the iterator:
func (se *ServiceExpression) WalkSets(it eval.SetWalker) {
it(se.Methods)
}
Each expression in an expression set may optionally implement the Source, Validator and Finalizer interfaces:
-
Expressions that are initialized via a child DSL implement Source which
provides RunDSL with the corresponding anonymous function.
-
Expressions that need to be validated implement the Validator interface.
-
Expressions that require an additional pass after validation implement the
Finalizer interface.
In our example MethodExpression implements Source and return its DSLFunc member in the implementation of the Source interface DSL function:
func (ep *MethodExpression) Source() func() {
return ep.DSLFunc
}
MethodExpression could also implement the Validator Validate method to check that the name of the method is not empty for example.
The execution of the DSL thus happens in three phases: in the first phase RunDSL executes all the DSLs of all the source expressions in each expression set. In this initial phase the DSLs being executed may append to the expression set and/or may register new expression roots. In the second phase RunDSL validates all the validator expressions and in the last phase it calls Finalize on all the finalizer expressions.
The eval package exposes functions that the implementation of the DSL can take advantage of to report errors, such as ReportError, InvalidArg and IncompatibleDSL. The engine records the errors being reported but keeps running the current phase so that multiple errors may be reported at once. This means that the DSL implementation must maintain a consistent state for the duration of one iteration even though some input may be incorrect (for example it may elect to create default value expressions instead of leaving them nil to avoid panics later on).
The package exposes other helper functions such as Execute which allows running a DSL function manually or IsTop which reports whether the expression being currently built is a top level expression (such as Service and Method in our example).
Index
- func Execute(fn func(), def Expression) bool
- func IncompatibleDSL()
- func InvalidArgError(expected string, actual interface{})
- func Register(r Root) error
- func ReportError(fm string, vals …interface{})
- func Reset()
- func RunDSL() error
- type DSLContext
- type DSLFunc
- type Error
- type Expression
- type ExpressionSet
- type Finalizer
- type MultiError
- type Preparer
- type Root
- type SetWalker
- type Source
- type Stack
- type TopExpr
- type ValidationErrors
- type Validator
Package files
context.go doc.go error.go eval.go expression.go
func Execute
func Execute(fn func(), def Expression) bool
Execute runs the given DSL to initialize the given expression. It returns true on success. It returns false and appends to Context.Errors on failure. Note that Run takes care of calling Execute on all expressions that implement Source. This function is intended for use by expressions that run the DSL at declaration time rather than store the DSL for execution by the dsl engine (usually simple independent expressions). The DSL should use ReportError to record DSL execution errors.
func IncompatibleDSL
func IncompatibleDSL()
IncompatibleDSL should be called by DSL functions when they are invoked in an incorrect context (e.g. “Params” in “Service”).
func InvalidArgError
func InvalidArgError(expected string, actual interface{})
InvalidArgError records an invalid argument error. It is used by DSL functions that take dynamic arguments.
func Register
func Register(r Root) error
Register appends a root expression to the current Context root expressions. Each root expression may only be registered once.
func ReportError
func ReportError(fm string, vals ...interface{})
ReportError records a DSL error for reporting post DSL execution. It accepts a format and values a la fmt.Printf.
func Reset
func Reset()
Reset resets the eval context, mostly useful for tests.
func RunDSL
func RunDSL() error
RunDSL iterates through the root expressions and calls WalkSets on each to retrieve the expression sets. It iterates over the expression sets multiple times to first execute the DSL, then validate the resulting expressions and lastly to finalize them. The executed DSL may register additional roots during initial execution via Register to have them be executed (last) in the same run.
type DSLContext
type DSLContext struct {
// Stack represents the current execution stack.
Stack Stack
// Errors contains the DSL execution errors for the current
// expression set.
// Errors is an instance of MultiError.
Errors error
// contains filtered or unexported fields
}
DSLContext is the data structure that contains the DSL execution state.
var Context *DSLContext
Context contains the state used by the engine to execute the DSL.
func (*DSLContext) Error
func (c *DSLContext) Error() string
Error builds the error message from the current context errors.
func (*DSLContext) Record
func (c *DSLContext) Record(err *Error)
Record appends an error to the context Errors field.
func (*DSLContext) Roots
func (c *DSLContext) Roots() ([]Root, error)
Roots orders the DSL roots making sure dependencies are last. It returns an error if there is a dependency cycle.
type DSLFunc
type DSLFunc func()
DSLFunc is a type that DSL expressions may embed to store DSL. It implements Source.
func (DSLFunc) DSL
func (f DSLFunc) DSL() func()
DSL returns the DSL function.
type Error
type Error struct {
// GoError is the original error returned by the DSL function.
GoError error
// File is the path to the file containing the user code that
// caused the error.
File string
// Line is the line number that caused the error.
Line int
}
Error represents an error that occurred while evaluating the DSL. It contains the name of the file and line number of where the error occurred as well as the original Go error.
func (*Error) Error
func (e *Error) Error() string
Error returns the underlying error message.
type Expression
type Expression interface {
// EvalName is the qualified name of the DSL expression e.g.
// "service bottle".
EvalName() string
}
Expression built by the engine through the DSL functions.
func Current
func Current() Expression
Current returns the expression whose DSL is currently being executed. As a special case Current returns Top when the execution stack is empty.
type ExpressionSet
type ExpressionSet []Expression
ExpressionSet is a sequence of expressions processed in order. Each DSL implementation provides an arbitrary number of expression sets to the engine via iterators (see the Root interface WalkSets method).
The items in the set may implement the Source, Validator and/or Finalizer interfaces to enable the corresponding behaviors during DSL execution. The engine first runs the expression DSLs (for the ones that implement Source) then validates them (for the ones that implement Validator) and finalizes them (for the ones that implement Finalizer).
type Finalizer
type Finalizer interface {
// Finalize is run by the engine as the last step. Finalize
// cannot fail, any potential failure should be returned by
// implementing Validator instead.
Finalize()
}
A Finalizer expression requires an additional pass after the DSL has executed and has been validated (e.g. to merge generated expressions or initialize default values).
type MultiError
type MultiError []*Error
MultiError collects multiple DSL errors. It implements error.
func (MultiError) Error
func (m MultiError) Error() string
Error returns the error message.
type Preparer
type Preparer interface {
// Prepare is run by the engine right after the DSL has run.
// Prepare cannot fail, any potential failure should be returned
// by implementing Validator instead.
Prepare()
}
A Preparer expression requires an additional pass after the DSL has executed and BEFORE it is validated (e.g. to flatten inheritance)
type Root
type Root interface {
Expression
// WalkSets implements the visitor pattern: is is called by
// the engine so the DSL can control the order of execution.
// WalkSets calls back the engine via the given iterator as
// many times as needed providing the expression sets on each
// callback.
WalkSets(SetWalker)
// DependsOn returns the list of other DSL roots this root
// depends on. The engine uses this function to order the
// execution of the DSL roots.
DependsOn() []Root
// Packages returns the import path to the Go packages that make
// up the DSL. This is used to skip frames that point to files
// in these packages when computing the location of errors.
Packages() []string
}
A Root expression represents an entry point to the executed DSL: upon execution the DSL engine iterates over all root expressions and calls their WalkSets methods to iterate over the sub-expressions.
type SetWalker
type SetWalker func(s ExpressionSet) error
SetWalker is the function signature used to iterate over expression sets with WalkSets.
type Source
type Source interface {
// DSL returns the DSL used to initialize the expression in a
// second pass.
DSL() func()
}
A Source expression embeds DSL to be executed after the process is loaded.
type Stack
type Stack []Expression
Stack represents the expression evaluation stack. The stack is appended to each time the initiator executes an expression source DSL.
func (Stack) Current
func (s Stack) Current() Expression
Current evaluation context, i.e. object being currently built by DSL
type TopExpr
type TopExpr string
TopExpr is the type of Top.
const Top TopExpr = "top-level"
Top is the expression returned by Current when the execution stack is empty.
func (TopExpr) EvalName
func (t TopExpr) EvalName() string
EvalName is the name is the qualified name of the expression.
type ValidationErrors
type ValidationErrors struct {
Errors []error
Expressions []Expression
}
ValidationErrors records the errors encountered when running Validate.
func (*ValidationErrors) Add
func (verr *ValidationErrors) Add(def Expression, format string, vals ...interface{})
Add adds a validation error to the target.
func (*ValidationErrors) AddError
func (verr *ValidationErrors) AddError(def Expression, err error)
AddError adds a validation error to the target. It “flattens” validation errors so that the recorded errors are never ValidationErrors themselves.
func (*ValidationErrors) Error
func (verr *ValidationErrors) Error() string
Error implements the error interface.
func (*ValidationErrors) Merge
func (verr *ValidationErrors) Merge(err *ValidationErrors)
Merge merges validation errors into the target.
type Validator
type Validator interface {
// Validate runs after Prepare if the expression is a Preparer.
// It returns nil if the expression contains no validation
// error. The Validate implementation may take advantage of
// ValidationErrors to report more than one errors at a time.
Validate() error
}
A Validator expression can be validated.
Generated by godoc2md