eval

package
v3.11.0 Latest Latest
Warning

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

Go to latest
Published: Jan 24, 2023 License: MIT Imports: 6 Imported by: 160

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

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

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.

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.

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, 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.

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 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.

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.

Jump to

Keyboard shortcuts

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