diff --git a/zcl/eval_context.go b/zcl/eval_context.go new file mode 100644 index 0000000..f305db7 --- /dev/null +++ b/zcl/eval_context.go @@ -0,0 +1,19 @@ +package zcl + +import ( + "github.com/apparentlymart/go-cty/cty" + "github.com/apparentlymart/go-cty/cty/function" +) + +// An EvalContext provides the variables and functions that should be used +// to evaluate an expression. +type EvalContext struct { + Variables map[string]cty.Value + Functions map[string]function.Function + parent *EvalContext +} + +// NewChild returns a new EvalContext that is a child of the receiver. +func (ctx *EvalContext) NewChild() *EvalContext { + return &EvalContext{parent: ctx} +} diff --git a/zcl/json/structure.go b/zcl/json/structure.go index 92230e9..6ad28ab 100644 --- a/zcl/json/structure.go +++ b/zcl/json/structure.go @@ -226,7 +226,36 @@ func (b *body) unpackBlock(v node, typeName string, typeRange *zcl.Range, labels return } -func (e *expression) LiteralValue() (cty.Value, zcl.Diagnostics) { - // TODO: Implement - return cty.NilVal, nil +func (e *expression) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) { + // TEMP: Since we've not yet implemented the zcl native template language + // parser, for the moment we'll support only literal values here. + // FIXME: Once the template language parser is implemented, parse string + // values as templates and evaluate them. + + switch v := e.src.(type) { + case *stringVal: + return cty.StringVal(v.Value), nil + case *numberVal: + return cty.NumberVal(v.Value), nil + case *booleanVal: + return cty.BoolVal(v.Value), nil + case *arrayVal: + vals := []cty.Value{} + for _, jsonVal := range v.Values { + val, _ := (&expression{src: jsonVal}).Value(ctx) + vals = append(vals, val) + } + return cty.TupleVal(vals), nil + case *objectVal: + attrs := map[string]cty.Value{} + for name, jsonAttr := range v.Attrs { + val, _ := (&expression{src: jsonAttr.Value}).Value(ctx) + attrs[name] = val + } + return cty.ObjectVal(attrs), nil + default: + // Default to DynamicVal so that ASTs containing invalid nodes can + // still be partially-evaluated. + return cty.DynamicVal, nil + } } diff --git a/zcl/structure.go b/zcl/structure.go index 11f8165..877b33d 100644 --- a/zcl/structure.go +++ b/zcl/structure.go @@ -82,8 +82,28 @@ type Attribute struct { // Expression is a literal value or an expression provided in the // configuration, which can be evaluated within a scope to produce a value. type Expression interface { - LiteralValue() (cty.Value, Diagnostics) - // TODO: evaluation of non-literal expressions + // Value returns the value resulting from evaluating the expression + // in the given evaluation context. + // + // The context may be nil, in which case the expression may contain + // only constants and diagnostics will be produced for any non-constant + // sub-expressions. (The exact definition of this depends on the source + // language.) + // + // The context may instead be set but have either its Variables or + // Functions maps set to nil, in which case only use of these features + // will return diagnostics. + // + // Different diagnostics are provided depending on whether the given + // context maps are nil or empty. In the former case, the message + // tells the user that variables/functions are not permitted at all, + // while in the latter case usage will produce a "not found" error for + // the specific symbol in question. + Value(ctx *EvalContext) (cty.Value, Diagnostics) + + // TODO: A "Variables" method that returns a description of all of the + // variables used in the expression, so callers can populate the scope + // only with variables that are actually used. } // OfType filters the receiving block sequence by block type name,