Documentation ¶
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, Preparer, 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 prepared implement the Preparer interface.
- 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 four 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 prepares all the preparer expressions. In the third 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 on demand.
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
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
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 ¶
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 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.
Types ¶
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.
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.
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, Preparer, 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 prepares them (for the ones that implement Preparer), then validates them (for the ones that implement Validator), and finalizes them (for the ones that implement Finalizer).
func ToExpressionSet ¶ added in v3.2.0
func ToExpressionSet(slice interface{}) ExpressionSet
ToExpressionSet is a convenience function that accepts a slice of expressions and builds the corresponding ExpressionSet.
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.
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 returns 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.
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.