Expression Evaluation

Each argument attribute in a configuration file is interpreted as an expression. In the HCL native syntax, certain basic expression functionality is always available, such as arithmetic and template strings, and the calling application can extend this by making available specific variables and/or functions via an evaluation context.

We saw in Decoding Into Native Go Values and Decoding With Dynamic Schema some basic examples of populating an evaluation context to make a variable available. This section will look more closely at the hcl.EvalContext type and how HCL expression evaluation behaves in different cases.

This section does not discuss in detail the expression syntax itself. For more information on that, see the HCL Native Syntax specification.

EvalContext

hcl.EvalContext is the type used to describe the variables and functions available during expression evaluation, if any. Its usage is described in the following sections.

Defining Variables

As we saw in Decoding With Dynamic Schema, HCL represents values using an underlying library called cty. When defining variables, their values must be given as cty.Value values.

A full description of the types and value constructors in cty is in the reference documentation. Variables in HCL are defined by assigning values into a map from string names to cty.Value:

ctx := &hcl.EvalContext{
     Variables: map[string]cty.Value{
         "name": cty.StringVal("Ermintrude"),
         "age":  cty.NumberIntVal(32),
     },
}

If this evaluation context were passed to one of the evaluation functions we saw in previous sections, the user would be able to refer to these variable names in any argument expression appearing in the evaluated portion of configuration:

message = "${name} is ${age} ${age == 1 ? "year" : "years"} old!"

If you place cty’s object values in the evaluation context, then their attributes can be referenced using the HCL attribute syntax, allowing for more complex structures:

ctx := &hcl.EvalContext{
     Variables: map[string]cty.Value{
         "path": cty.ObjectVal(map[string]cty.Value{
             "root":    cty.StringVal(rootDir),
             "module":  cty.StringVal(moduleDir),
             "current": cty.StringVal(currentDir),
         }),
     },
}
source_file = "${path.module}/foo.txt"

Defining Functions

Custom functions can be defined by you application to allow users of its language to transform data in application-specific ways. The underlying function mechanism is also provided by cty, allowing you to define the arguments a given function expects, what value type it will return for given argument types, etc. The full functions model is described in the cty documentation section Functions System.

There are a number of “standard library” functions available in a stdlib package within the cty repository, avoiding the need for each application to re-implement basic functions for string manipulation, list manipulation, etc. It also includes function-shaped versions of several operations that are native operators in HCL, which should generally not be exposed as functions in HCL-based configurationf formats to avoid user confusion.

You can define functions in the Functions field of hcl.EvalContext:

ctx := &hcl.EvalContext{
     Variables: map[string]cty.Value{
         "name": cty.StringVal("Ermintrude"),
     },
     Functions: map[string]function.Function{
         "upper":  stdlib.UpperFunc,
         "lower":  stdlib.LowerFunc,
         "min":    stdlib.MinFunc,
         "max":    stdlib.MaxFunc,
         "strlen": stdlib.StrlenFunc,
         "substr": stdlib.SubstrFunc,
     },
}

If this evaluation context were passed to one of the evaluation functions we saw in previous sections, the user would be able to call any of these functions in any argument expression appearing in the evaluated portion of configuration:

message = "HELLO, ${upper(name)}!"

Expression Evaluation Modes

HCL uses a different expression evaluation mode depending on the evaluation context provided. In HCL native syntax, evaluation modes are used to provide more relevant error messages. In JSON syntax, which embeds the native expression syntax in strings using “template” syntax, the evaluation mode determines whether strings are evaluated as templates at all.

If the given hcl.EvalContext is nil, native syntax expressions will react to users attempting to refer to variables or functions by producing errors indicating that these features are not available at all, rather than by saying that the specific variable or function does not exist. JSON syntax strings will not be evaluated as templates at all in this mode, making them function as literal strings.

If the evaluation context is non-nil but either Variables or Functions within it is nil, native syntax will similarly produce “not supported” error messages. JSON syntax strings will parse templates in this case, but can also generate “not supported” messages if e.g. the user accesses a variable when the variables map is nil.

If neither map is nil, HCL assumes that both variables and functions are supported and will instead produce error messages stating that the specific variable or function accessed by the user is not defined.