vendor hashicorp/hil

This commit is contained in:
Mitchell Hashimoto 2016-01-31 08:41:53 +01:00 committed by James Nugent
parent 5f3de02fa9
commit 09409a1a2b
36 changed files with 4668 additions and 0 deletions

3
vendor/github.com/hashicorp/hil/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,3 @@
sudo: false
language: go
go: 1.5

102
vendor/github.com/hashicorp/hil/README.md generated vendored Normal file
View File

@ -0,0 +1,102 @@
# HIL
[![GoDoc](https://godoc.org/github.com/hashicorp/hil?status.png)](https://godoc.org/github.com/hashicorp/hil) [![Build Status](https://travis-ci.org/hashicorp/hil.svg?branch=master)](https://travis-ci.org/hashicorp/hil)
HIL (HashiCorp Interpolation Language) is a lightweight embedded language used
primarily for configuration interpolation. The goal of HIL is to make a simple
language for interpolations in the various configurations of HashiCorp tools.
HIL is built to interpolate any string, but is in use by HashiCorp primarily
with [HCL](https://github.com/hashicorp/hcl). HCL is _not required_ in any
way for use with HIL.
HIL isn't meant to be a general purpose language. It was built for basic
configuration interpolations. Therefore, you can't currently write functions,
have conditionals, set intermediary variables, etc. within HIL itself. It is
possible some of these may be added later but the right use case must exist.
## Why?
Many of our tools have support for something similar to templates, but
within the configuration itself. The most prominent requirement was in
[Terraform](https://github.com/hashicorp/terraform) where we wanted the
configuration to be able to reference values from elsewhere in the
configuration. Example:
foo = "hi ${var.world}"
We originally used a full templating language for this, but found it
was too heavy weight. Additionally, many full languages required bindings
to C (and thus the usage of cgo) which we try to avoid to make cross-compilation
easier. We then moved to very basic regular expression based
string replacement, but found the need for basic arithmetic and function
calls resulting in overly complex regular expressions.
Ultimately, we wrote our own mini-language within Terraform itself. As
we built other projects such as [Nomad](https://nomadproject.io) and
[Otto](https://ottoproject.io), the need for basic interpolations arose
again.
Thus HIL was born. It is extracted from Terraform, cleaned up, and
better tested for general purpose use.
## Syntax
For a complete grammar, please see the parser itself. A high-level overview
of the syntax and grammer is listed here.
Code begins within `${` and `}`. Outside of this, text is treated
literally. For example, `foo` is a valid HIL program that is just the
string "foo", but `foo ${bar}` is an HIL program that is the string "foo "
concatened with the value of `bar`. For the remainder of the syntax
docs, we'll assume you're within `${}`.
* Identifiers are any text in the format of `[a-zA-Z0-9-.]`. Example
identifiers: `foo`, `var.foo`, `foo-bar`.
* Strings are double quoted and can contain any UTF-8 characters.
Example: `"Hello, World"`
* Numbers are assumed to be base 10. If you prefix a number with 0x,
it is treated as a hexadecimal. If it is prefixed with 0, it is
treated as an octal. Numbers can be in scientific notation: "1e10".
* Unary `-` can be used for negative numbers. Example: `-10` or `-0.2`
* Boolean values: `true`, `false`
* The following arithmetic operations are allowed: +, -, *, /, %.
* Function calls are in the form of `name(arg1, arg2, ...)`. Example:
`add(1, 5)`. Arguments can be any valid HIL expression, example:
`add(1, var.foo)` or even nested function calls:
`add(1, get("some value"))`.
* Witin strings, further interpolations can be opened with `${}`.
Example: `"Hello ${nested}"`. A full example including the
original `${}` (remember this list assumes were inside of one
already) could be: `foo ${func("hello ${var.foo}")}`.
## Language Changes
We've used this mini-language in Terraform for years. For backwards compatibility
reasons, we're unlikely to make an incompatible change to the language but
we're not currently making that promise, either.
The internal API of this project may very well change as we evolve it
to work with more of our projects. We recommend using some sort of dependency
management solution with this package.
## Future Changes
The following changes are already planned to be made at some point:
* Richer types: lists, maps, etc.
* Convert to a more standard Go parser structure similar to HCL. This
will improve our error messaging as well as allow us to have automatic
formatting.
* Allow interpolations to result in more types than just a string. While
within the interpolation basic types are honored, the result is always
a string.

43
vendor/github.com/hashicorp/hil/ast/arithmetic.go generated vendored Normal file
View File

@ -0,0 +1,43 @@
package ast
import (
"bytes"
"fmt"
)
// Arithmetic represents a node where the result is arithmetic of
// two or more operands in the order given.
type Arithmetic struct {
Op ArithmeticOp
Exprs []Node
Posx Pos
}
func (n *Arithmetic) Accept(v Visitor) Node {
for i, expr := range n.Exprs {
n.Exprs[i] = expr.Accept(v)
}
return v(n)
}
func (n *Arithmetic) Pos() Pos {
return n.Posx
}
func (n *Arithmetic) GoString() string {
return fmt.Sprintf("*%#v", *n)
}
func (n *Arithmetic) String() string {
var b bytes.Buffer
for _, expr := range n.Exprs {
b.WriteString(fmt.Sprintf("%s", expr))
}
return b.String()
}
func (n *Arithmetic) Type(Scope) (Type, error) {
return TypeInt, nil
}

13
vendor/github.com/hashicorp/hil/ast/arithmetic_op.go generated vendored Normal file
View File

@ -0,0 +1,13 @@
package ast
// ArithmeticOp is the operation to use for the math.
type ArithmeticOp int
const (
ArithmeticOpInvalid ArithmeticOp = 0
ArithmeticOpAdd ArithmeticOp = iota
ArithmeticOpSub
ArithmeticOpMul
ArithmeticOpDiv
ArithmeticOpMod
)

55
vendor/github.com/hashicorp/hil/ast/ast.go generated vendored Normal file
View File

@ -0,0 +1,55 @@
package ast
import (
"fmt"
)
// Node is the interface that all AST nodes must implement.
type Node interface {
// Accept is called to dispatch to the visitors. It must return the
// resulting Node (which might be different in an AST transform).
Accept(Visitor) Node
// Pos returns the position of this node in some source.
Pos() Pos
// Type returns the type of this node for the given context.
Type(Scope) (Type, error)
}
// Pos is the starting position of an AST node
type Pos struct {
Column, Line int // Column/Line number, starting at 1
}
func (p Pos) String() string {
return fmt.Sprintf("%d:%d", p.Line, p.Column)
}
// Visitors are just implementations of this function.
//
// The function must return the Node to replace this node with. "nil" is
// _not_ a valid return value. If there is no replacement, the original node
// should be returned. We build this replacement directly into the visitor
// pattern since AST transformations are a common and useful tool and
// building it into the AST itself makes it required for future Node
// implementations and very easy to do.
//
// Note that this isn't a true implementation of the visitor pattern, which
// generally requires proper type dispatch on the function. However,
// implementing this basic visitor pattern style is still very useful even
// if you have to type switch.
type Visitor func(Node) Node
//go:generate stringer -type=Type
// Type is the type of any value.
type Type uint32
const (
TypeInvalid Type = 0
TypeAny Type = 1 << iota
TypeString
TypeInt
TypeFloat
)

47
vendor/github.com/hashicorp/hil/ast/call.go generated vendored Normal file
View File

@ -0,0 +1,47 @@
package ast
import (
"fmt"
"strings"
)
// Call represents a function call.
type Call struct {
Func string
Args []Node
Posx Pos
}
func (n *Call) Accept(v Visitor) Node {
for i, a := range n.Args {
n.Args[i] = a.Accept(v)
}
return v(n)
}
func (n *Call) Pos() Pos {
return n.Posx
}
func (n *Call) String() string {
args := make([]string, len(n.Args))
for i, arg := range n.Args {
args[i] = fmt.Sprintf("%s", arg)
}
return fmt.Sprintf("Call(%s, %s)", n.Func, strings.Join(args, ", "))
}
func (n *Call) Type(s Scope) (Type, error) {
f, ok := s.LookupFunc(n.Func)
if !ok {
return TypeInvalid, fmt.Errorf("unknown function: %s", n.Func)
}
return f.ReturnType, nil
}
func (n *Call) GoString() string {
return fmt.Sprintf("*%#v", *n)
}

36
vendor/github.com/hashicorp/hil/ast/call_test.go generated vendored Normal file
View File

@ -0,0 +1,36 @@
package ast
import (
"testing"
)
func TestCallType(t *testing.T) {
c := &Call{Func: "foo"}
scope := &BasicScope{
FuncMap: map[string]Function{
"foo": Function{ReturnType: TypeString},
},
}
actual, err := c.Type(scope)
if err != nil {
t.Fatalf("err: %s", err)
}
if actual != TypeString {
t.Fatalf("bad: %s", actual)
}
}
func TestCallType_invalid(t *testing.T) {
c := &Call{Func: "bar"}
scope := &BasicScope{
FuncMap: map[string]Function{
"foo": Function{ReturnType: TypeString},
},
}
_, err := c.Type(scope)
if err == nil {
t.Fatal("should error")
}
}

42
vendor/github.com/hashicorp/hil/ast/concat.go generated vendored Normal file
View File

@ -0,0 +1,42 @@
package ast
import (
"bytes"
"fmt"
)
// Concat represents a node where the result of two or more expressions are
// concatenated. The result of all expressions must be a string.
type Concat struct {
Exprs []Node
Posx Pos
}
func (n *Concat) Accept(v Visitor) Node {
for i, expr := range n.Exprs {
n.Exprs[i] = expr.Accept(v)
}
return v(n)
}
func (n *Concat) Pos() Pos {
return n.Posx
}
func (n *Concat) GoString() string {
return fmt.Sprintf("*%#v", *n)
}
func (n *Concat) String() string {
var b bytes.Buffer
for _, expr := range n.Exprs {
b.WriteString(fmt.Sprintf("%s", expr))
}
return b.String()
}
func (n *Concat) Type(Scope) (Type, error) {
return TypeString, nil
}

16
vendor/github.com/hashicorp/hil/ast/concat_test.go generated vendored Normal file
View File

@ -0,0 +1,16 @@
package ast
import (
"testing"
)
func TestConcatType(t *testing.T) {
c := &Concat{}
actual, err := c.Type(nil)
if err != nil {
t.Fatalf("err: %s", err)
}
if actual != TypeString {
t.Fatalf("bad: %s", actual)
}
}

33
vendor/github.com/hashicorp/hil/ast/literal.go generated vendored Normal file
View File

@ -0,0 +1,33 @@
package ast
import (
"fmt"
)
// LiteralNode represents a single literal value, such as "foo" or
// 42 or 3.14159. Based on the Type, the Value can be safely cast.
type LiteralNode struct {
Value interface{}
Typex Type
Posx Pos
}
func (n *LiteralNode) Accept(v Visitor) Node {
return v(n)
}
func (n *LiteralNode) Pos() Pos {
return n.Posx
}
func (n *LiteralNode) GoString() string {
return fmt.Sprintf("*%#v", *n)
}
func (n *LiteralNode) String() string {
return fmt.Sprintf("Literal(%s, %v)", n.Typex, n.Value)
}
func (n *LiteralNode) Type(Scope) (Type, error) {
return n.Typex, nil
}

16
vendor/github.com/hashicorp/hil/ast/literal_test.go generated vendored Normal file
View File

@ -0,0 +1,16 @@
package ast
import (
"testing"
)
func TestLiteralNodeType(t *testing.T) {
c := &LiteralNode{Typex: TypeString}
actual, err := c.Type(nil)
if err != nil {
t.Fatalf("err: %s", err)
}
if actual != TypeString {
t.Fatalf("bad: %s", actual)
}
}

65
vendor/github.com/hashicorp/hil/ast/scope.go generated vendored Normal file
View File

@ -0,0 +1,65 @@
package ast
// Scope is the interface used to look up variables and functions while
// evaluating. How these functions/variables are defined are up to the caller.
type Scope interface {
LookupFunc(string) (Function, bool)
LookupVar(string) (Variable, bool)
}
// Variable is a variable value for execution given as input to the engine.
// It records the value of a variables along with their type.
type Variable struct {
Value interface{}
Type Type
}
// Function defines a function that can be executed by the engine.
// The type checker will validate that the proper types will be called
// to the callback.
type Function struct {
// ArgTypes is the list of types in argument order. These are the
// required arguments.
//
// ReturnType is the type of the returned value. The Callback MUST
// return this type.
ArgTypes []Type
ReturnType Type
// Variadic, if true, says that this function is variadic, meaning
// it takes a variable number of arguments. In this case, the
// VariadicType must be set.
Variadic bool
VariadicType Type
// Callback is the function called for a function. The argument
// types are guaranteed to match the spec above by the type checker.
// The length of the args is strictly == len(ArgTypes) unless Varidiac
// is true, in which case its >= len(ArgTypes).
Callback func([]interface{}) (interface{}, error)
}
// BasicScope is a simple scope that looks up variables and functions
// using a map.
type BasicScope struct {
FuncMap map[string]Function
VarMap map[string]Variable
}
func (s *BasicScope) LookupFunc(n string) (Function, bool) {
if s == nil {
return Function{}, false
}
v, ok := s.FuncMap[n]
return v, ok
}
func (s *BasicScope) LookupVar(n string) (Variable, bool) {
if s == nil {
return Variable{}, false
}
v, ok := s.VarMap[n]
return v, ok
}

39
vendor/github.com/hashicorp/hil/ast/scope_test.go generated vendored Normal file
View File

@ -0,0 +1,39 @@
package ast
import (
"testing"
)
func TestBasicScope_impl(t *testing.T) {
var _ Scope = new(BasicScope)
}
func TestBasicScopeLookupFunc(t *testing.T) {
scope := &BasicScope{
FuncMap: map[string]Function{
"foo": Function{},
},
}
if _, ok := scope.LookupFunc("bar"); ok {
t.Fatal("should not find bar")
}
if _, ok := scope.LookupFunc("foo"); !ok {
t.Fatal("should find foo")
}
}
func TestBasicScopeLookupVar(t *testing.T) {
scope := &BasicScope{
VarMap: map[string]Variable{
"foo": Variable{},
},
}
if _, ok := scope.LookupVar("bar"); ok {
t.Fatal("should not find bar")
}
if _, ok := scope.LookupVar("foo"); !ok {
t.Fatal("should find foo")
}
}

25
vendor/github.com/hashicorp/hil/ast/stack.go generated vendored Normal file
View File

@ -0,0 +1,25 @@
package ast
// Stack is a stack of Node.
type Stack struct {
stack []Node
}
func (s *Stack) Len() int {
return len(s.stack)
}
func (s *Stack) Push(n Node) {
s.stack = append(s.stack, n)
}
func (s *Stack) Pop() Node {
x := s.stack[len(s.stack)-1]
s.stack[len(s.stack)-1] = nil
s.stack = s.stack[:len(s.stack)-1]
return x
}
func (s *Stack) Reset() {
s.stack = nil
}

46
vendor/github.com/hashicorp/hil/ast/stack_test.go generated vendored Normal file
View File

@ -0,0 +1,46 @@
package ast
import (
"reflect"
"testing"
)
func TestStack(t *testing.T) {
var s Stack
if s.Len() != 0 {
t.Fatalf("bad: %d", s.Len())
}
n := &LiteralNode{Value: 42}
s.Push(n)
if s.Len() != 1 {
t.Fatalf("bad: %d", s.Len())
}
actual := s.Pop()
if !reflect.DeepEqual(actual, n) {
t.Fatalf("bad: %#v", actual)
}
if s.Len() != 0 {
t.Fatalf("bad: %d", s.Len())
}
}
func TestStack_reset(t *testing.T) {
var s Stack
n := &LiteralNode{Value: 42}
s.Push(n)
if s.Len() != 1 {
t.Fatalf("bad: %d", s.Len())
}
s.Reset()
if s.Len() != 0 {
t.Fatalf("bad: %d", s.Len())
}
}

38
vendor/github.com/hashicorp/hil/ast/type_string.go generated vendored Normal file
View File

@ -0,0 +1,38 @@
// Code generated by "stringer -type=Type"; DO NOT EDIT
package ast
import "fmt"
const (
_Type_name_0 = "TypeInvalid"
_Type_name_1 = "TypeAny"
_Type_name_2 = "TypeString"
_Type_name_3 = "TypeInt"
_Type_name_4 = "TypeFloat"
)
var (
_Type_index_0 = [...]uint8{0, 11}
_Type_index_1 = [...]uint8{0, 7}
_Type_index_2 = [...]uint8{0, 10}
_Type_index_3 = [...]uint8{0, 7}
_Type_index_4 = [...]uint8{0, 9}
)
func (i Type) String() string {
switch {
case i == 0:
return _Type_name_0
case i == 2:
return _Type_name_1
case i == 4:
return _Type_name_2
case i == 8:
return _Type_name_3
case i == 16:
return _Type_name_4
default:
return fmt.Sprintf("Type(%d)", i)
}
}

36
vendor/github.com/hashicorp/hil/ast/variable_access.go generated vendored Normal file
View File

@ -0,0 +1,36 @@
package ast
import (
"fmt"
)
// VariableAccess represents a variable access.
type VariableAccess struct {
Name string
Posx Pos
}
func (n *VariableAccess) Accept(v Visitor) Node {
return v(n)
}
func (n *VariableAccess) Pos() Pos {
return n.Posx
}
func (n *VariableAccess) GoString() string {
return fmt.Sprintf("*%#v", *n)
}
func (n *VariableAccess) String() string {
return fmt.Sprintf("Variable(%s)", n.Name)
}
func (n *VariableAccess) Type(s Scope) (Type, error) {
v, ok := s.LookupVar(n.Name)
if !ok {
return TypeInvalid, fmt.Errorf("unknown variable: %s", n.Name)
}
return v.Type, nil
}

View File

@ -0,0 +1,36 @@
package ast
import (
"testing"
)
func TestVariableAccessType(t *testing.T) {
c := &VariableAccess{Name: "foo"}
scope := &BasicScope{
VarMap: map[string]Variable{
"foo": Variable{Type: TypeString},
},
}
actual, err := c.Type(scope)
if err != nil {
t.Fatalf("err: %s", err)
}
if actual != TypeString {
t.Fatalf("bad: %s", actual)
}
}
func TestVariableAccessType_invalid(t *testing.T) {
c := &VariableAccess{Name: "bar"}
scope := &BasicScope{
VarMap: map[string]Variable{
"foo": Variable{Type: TypeString},
},
}
_, err := c.Type(scope)
if err == nil {
t.Fatal("should error")
}
}

144
vendor/github.com/hashicorp/hil/builtins.go generated vendored Normal file
View File

@ -0,0 +1,144 @@
package hil
import (
"strconv"
"github.com/hashicorp/hil/ast"
)
// NOTE: All builtins are tested in engine_test.go
func registerBuiltins(scope *ast.BasicScope) *ast.BasicScope {
if scope == nil {
scope = new(ast.BasicScope)
}
if scope.FuncMap == nil {
scope.FuncMap = make(map[string]ast.Function)
}
// Implicit conversions
scope.FuncMap["__builtin_FloatToInt"] = builtinFloatToInt()
scope.FuncMap["__builtin_FloatToString"] = builtinFloatToString()
scope.FuncMap["__builtin_IntToFloat"] = builtinIntToFloat()
scope.FuncMap["__builtin_IntToString"] = builtinIntToString()
scope.FuncMap["__builtin_StringToInt"] = builtinStringToInt()
// Math operations
scope.FuncMap["__builtin_IntMath"] = builtinIntMath()
scope.FuncMap["__builtin_FloatMath"] = builtinFloatMath()
return scope
}
func builtinFloatMath() ast.Function {
return ast.Function{
ArgTypes: []ast.Type{ast.TypeInt},
Variadic: true,
VariadicType: ast.TypeFloat,
ReturnType: ast.TypeFloat,
Callback: func(args []interface{}) (interface{}, error) {
op := args[0].(ast.ArithmeticOp)
result := args[1].(float64)
for _, raw := range args[2:] {
arg := raw.(float64)
switch op {
case ast.ArithmeticOpAdd:
result += arg
case ast.ArithmeticOpSub:
result -= arg
case ast.ArithmeticOpMul:
result *= arg
case ast.ArithmeticOpDiv:
result /= arg
}
}
return result, nil
},
}
}
func builtinIntMath() ast.Function {
return ast.Function{
ArgTypes: []ast.Type{ast.TypeInt},
Variadic: true,
VariadicType: ast.TypeInt,
ReturnType: ast.TypeInt,
Callback: func(args []interface{}) (interface{}, error) {
op := args[0].(ast.ArithmeticOp)
result := args[1].(int)
for _, raw := range args[2:] {
arg := raw.(int)
switch op {
case ast.ArithmeticOpAdd:
result += arg
case ast.ArithmeticOpSub:
result -= arg
case ast.ArithmeticOpMul:
result *= arg
case ast.ArithmeticOpDiv:
result /= arg
case ast.ArithmeticOpMod:
result = result % arg
}
}
return result, nil
},
}
}
func builtinFloatToInt() ast.Function {
return ast.Function{
ArgTypes: []ast.Type{ast.TypeFloat},
ReturnType: ast.TypeInt,
Callback: func(args []interface{}) (interface{}, error) {
return int(args[0].(float64)), nil
},
}
}
func builtinFloatToString() ast.Function {
return ast.Function{
ArgTypes: []ast.Type{ast.TypeFloat},
ReturnType: ast.TypeString,
Callback: func(args []interface{}) (interface{}, error) {
return strconv.FormatFloat(
args[0].(float64), 'g', -1, 64), nil
},
}
}
func builtinIntToFloat() ast.Function {
return ast.Function{
ArgTypes: []ast.Type{ast.TypeInt},
ReturnType: ast.TypeFloat,
Callback: func(args []interface{}) (interface{}, error) {
return float64(args[0].(int)), nil
},
}
}
func builtinIntToString() ast.Function {
return ast.Function{
ArgTypes: []ast.Type{ast.TypeInt},
ReturnType: ast.TypeString,
Callback: func(args []interface{}) (interface{}, error) {
return strconv.FormatInt(int64(args[0].(int)), 10), nil
},
}
}
func builtinStringToInt() ast.Function {
return ast.Function{
ArgTypes: []ast.Type{ast.TypeInt},
ReturnType: ast.TypeString,
Callback: func(args []interface{}) (interface{}, error) {
v, err := strconv.ParseInt(args[0].(string), 0, 0)
if err != nil {
return nil, err
}
return int(v), nil
},
}
}

88
vendor/github.com/hashicorp/hil/check_identifier.go generated vendored Normal file
View File

@ -0,0 +1,88 @@
package hil
import (
"fmt"
"sync"
"github.com/hashicorp/hil/ast"
)
// IdentifierCheck is a SemanticCheck that checks that all identifiers
// resolve properly and that the right number of arguments are passed
// to functions.
type IdentifierCheck struct {
Scope ast.Scope
err error
lock sync.Mutex
}
func (c *IdentifierCheck) Visit(root ast.Node) error {
c.lock.Lock()
defer c.lock.Unlock()
defer c.reset()
root.Accept(c.visit)
return c.err
}
func (c *IdentifierCheck) visit(raw ast.Node) ast.Node {
if c.err != nil {
return raw
}
switch n := raw.(type) {
case *ast.Call:
c.visitCall(n)
case *ast.VariableAccess:
c.visitVariableAccess(n)
case *ast.Concat:
// Ignore
case *ast.LiteralNode:
// Ignore
default:
// Ignore
}
// We never do replacement with this visitor
return raw
}
func (c *IdentifierCheck) visitCall(n *ast.Call) {
// Look up the function in the map
function, ok := c.Scope.LookupFunc(n.Func)
if !ok {
c.createErr(n, fmt.Sprintf("unknown function called: %s", n.Func))
return
}
// Break up the args into what is variadic and what is required
args := n.Args
if function.Variadic && len(args) > len(function.ArgTypes) {
args = n.Args[:len(function.ArgTypes)]
}
// Verify the number of arguments
if len(args) != len(function.ArgTypes) {
c.createErr(n, fmt.Sprintf(
"%s: expected %d arguments, got %d",
n.Func, len(function.ArgTypes), len(n.Args)))
return
}
}
func (c *IdentifierCheck) visitVariableAccess(n *ast.VariableAccess) {
// Look up the variable in the map
if _, ok := c.Scope.LookupVar(n.Name); !ok {
c.createErr(n, fmt.Sprintf(
"unknown variable accessed: %s", n.Name))
return
}
}
func (c *IdentifierCheck) createErr(n ast.Node, str string) {
c.err = fmt.Errorf("%s: %s", n.Pos(), str)
}
func (c *IdentifierCheck) reset() {
c.err = nil
}

View File

@ -0,0 +1,141 @@
package hil
import (
"testing"
"github.com/hashicorp/hil/ast"
)
func TestIdentifierCheck(t *testing.T) {
cases := []struct {
Input string
Scope ast.Scope
Error bool
}{
{
"foo",
&ast.BasicScope{},
false,
},
{
"foo ${bar} success",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: "baz",
Type: ast.TypeString,
},
},
},
false,
},
{
"foo ${bar}",
&ast.BasicScope{},
true,
},
{
"foo ${rand()} success",
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ReturnType: ast.TypeString,
Callback: func([]interface{}) (interface{}, error) {
return "42", nil
},
},
},
},
false,
},
{
"foo ${rand()}",
&ast.BasicScope{},
true,
},
{
"foo ${rand(42)} ",
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ReturnType: ast.TypeString,
Callback: func([]interface{}) (interface{}, error) {
return "42", nil
},
},
},
},
true,
},
{
"foo ${rand()} ",
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ReturnType: ast.TypeString,
Variadic: true,
VariadicType: ast.TypeInt,
Callback: func([]interface{}) (interface{}, error) {
return "42", nil
},
},
},
},
false,
},
{
"foo ${rand(42)} ",
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ReturnType: ast.TypeString,
Variadic: true,
VariadicType: ast.TypeInt,
Callback: func([]interface{}) (interface{}, error) {
return "42", nil
},
},
},
},
false,
},
{
"foo ${rand(\"foo\", 42)} ",
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ArgTypes: []ast.Type{ast.TypeString},
ReturnType: ast.TypeString,
Variadic: true,
VariadicType: ast.TypeInt,
Callback: func([]interface{}) (interface{}, error) {
return "42", nil
},
},
},
},
false,
},
}
for _, tc := range cases {
node, err := Parse(tc.Input)
if err != nil {
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
}
visitor := &IdentifierCheck{Scope: tc.Scope}
err = visitor.Visit(node)
if err != nil != tc.Error {
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
}
}
}

324
vendor/github.com/hashicorp/hil/check_types.go generated vendored Normal file
View File

@ -0,0 +1,324 @@
package hil
import (
"fmt"
"sync"
"github.com/hashicorp/hil/ast"
)
// TypeCheck implements ast.Visitor for type checking an AST tree.
// It requires some configuration to look up the type of nodes.
//
// It also optionally will not type error and will insert an implicit
// type conversions for specific types if specified by the Implicit
// field. Note that this is kind of organizationally weird to put into
// this structure but we'd rather do that than duplicate the type checking
// logic multiple times.
type TypeCheck struct {
Scope ast.Scope
// Implicit is a map of implicit type conversions that we can do,
// and that shouldn't error. The key of the first map is the from type,
// the key of the second map is the to type, and the final string
// value is the function to call (which must be registered in the Scope).
Implicit map[ast.Type]map[ast.Type]string
// Stack of types. This shouldn't be used directly except by implementations
// of TypeCheckNode.
Stack []ast.Type
err error
lock sync.Mutex
}
// TypeCheckNode is the interface that must be implemented by any
// ast.Node that wants to support type-checking. If the type checker
// encounters a node that doesn't implement this, it will error.
type TypeCheckNode interface {
TypeCheck(*TypeCheck) (ast.Node, error)
}
func (v *TypeCheck) Visit(root ast.Node) error {
v.lock.Lock()
defer v.lock.Unlock()
defer v.reset()
root.Accept(v.visit)
return v.err
}
func (v *TypeCheck) visit(raw ast.Node) ast.Node {
if v.err != nil {
return raw
}
var result ast.Node
var err error
switch n := raw.(type) {
case *ast.Arithmetic:
tc := &typeCheckArithmetic{n}
result, err = tc.TypeCheck(v)
case *ast.Call:
tc := &typeCheckCall{n}
result, err = tc.TypeCheck(v)
case *ast.Concat:
tc := &typeCheckConcat{n}
result, err = tc.TypeCheck(v)
case *ast.LiteralNode:
tc := &typeCheckLiteral{n}
result, err = tc.TypeCheck(v)
case *ast.VariableAccess:
tc := &typeCheckVariableAccess{n}
result, err = tc.TypeCheck(v)
default:
tc, ok := raw.(TypeCheckNode)
if !ok {
err = fmt.Errorf("unknown node for type check: %#v", raw)
break
}
result, err = tc.TypeCheck(v)
}
if err != nil {
pos := raw.Pos()
v.err = fmt.Errorf("At column %d, line %d: %s",
pos.Column, pos.Line, err)
}
return result
}
type typeCheckArithmetic struct {
n *ast.Arithmetic
}
func (tc *typeCheckArithmetic) TypeCheck(v *TypeCheck) (ast.Node, error) {
// The arguments are on the stack in reverse order, so pop them off.
exprs := make([]ast.Type, len(tc.n.Exprs))
for i, _ := range tc.n.Exprs {
exprs[len(tc.n.Exprs)-1-i] = v.StackPop()
}
// Determine the resulting type we want. We do this by going over
// every expression until we find one with a type we recognize.
// We do this because the first expr might be a string ("var.foo")
// and we need to know what to implicit to.
mathFunc := "__builtin_IntMath"
mathType := ast.TypeInt
for _, v := range exprs {
exit := true
switch v {
case ast.TypeInt:
mathFunc = "__builtin_IntMath"
mathType = v
case ast.TypeFloat:
mathFunc = "__builtin_FloatMath"
mathType = v
default:
exit = false
}
// We found the type, so leave
if exit {
break
}
}
// Verify the args
for i, arg := range exprs {
if arg != mathType {
cn := v.ImplicitConversion(exprs[i], mathType, tc.n.Exprs[i])
if cn != nil {
tc.n.Exprs[i] = cn
continue
}
return nil, fmt.Errorf(
"operand %d should be %s, got %s",
i+1, mathType, arg)
}
}
// Modulo doesn't work for floats
if mathType == ast.TypeFloat && tc.n.Op == ast.ArithmeticOpMod {
return nil, fmt.Errorf("modulo cannot be used with floats")
}
// Return type
v.StackPush(mathType)
// Replace our node with a call to the proper function. This isn't
// type checked but we already verified types.
args := make([]ast.Node, len(tc.n.Exprs)+1)
args[0] = &ast.LiteralNode{
Value: tc.n.Op,
Typex: ast.TypeInt,
Posx: tc.n.Pos(),
}
copy(args[1:], tc.n.Exprs)
return &ast.Call{
Func: mathFunc,
Args: args,
Posx: tc.n.Pos(),
}, nil
}
type typeCheckCall struct {
n *ast.Call
}
func (tc *typeCheckCall) TypeCheck(v *TypeCheck) (ast.Node, error) {
// Look up the function in the map
function, ok := v.Scope.LookupFunc(tc.n.Func)
if !ok {
return nil, fmt.Errorf("unknown function called: %s", tc.n.Func)
}
// The arguments are on the stack in reverse order, so pop them off.
args := make([]ast.Type, len(tc.n.Args))
for i, _ := range tc.n.Args {
args[len(tc.n.Args)-1-i] = v.StackPop()
}
// Verify the args
for i, expected := range function.ArgTypes {
if expected == ast.TypeAny {
continue
}
if args[i] != expected {
cn := v.ImplicitConversion(args[i], expected, tc.n.Args[i])
if cn != nil {
tc.n.Args[i] = cn
continue
}
return nil, fmt.Errorf(
"%s: argument %d should be %s, got %s",
tc.n.Func, i+1, expected, args[i])
}
}
// If we're variadic, then verify the types there
if function.Variadic && function.VariadicType != ast.TypeAny {
args = args[len(function.ArgTypes):]
for i, t := range args {
if t != function.VariadicType {
realI := i + len(function.ArgTypes)
cn := v.ImplicitConversion(
t, function.VariadicType, tc.n.Args[realI])
if cn != nil {
tc.n.Args[realI] = cn
continue
}
return nil, fmt.Errorf(
"%s: argument %d should be %s, got %s",
tc.n.Func, realI,
function.VariadicType, t)
}
}
}
// Return type
v.StackPush(function.ReturnType)
return tc.n, nil
}
type typeCheckConcat struct {
n *ast.Concat
}
func (tc *typeCheckConcat) TypeCheck(v *TypeCheck) (ast.Node, error) {
n := tc.n
types := make([]ast.Type, len(n.Exprs))
for i, _ := range n.Exprs {
types[len(n.Exprs)-1-i] = v.StackPop()
}
// All concat args must be strings, so validate that
for i, t := range types {
if t != ast.TypeString {
cn := v.ImplicitConversion(t, ast.TypeString, n.Exprs[i])
if cn != nil {
n.Exprs[i] = cn
continue
}
return nil, fmt.Errorf(
"argument %d must be a string", i+1)
}
}
// This always results in type string
v.StackPush(ast.TypeString)
return n, nil
}
type typeCheckLiteral struct {
n *ast.LiteralNode
}
func (tc *typeCheckLiteral) TypeCheck(v *TypeCheck) (ast.Node, error) {
v.StackPush(tc.n.Typex)
return tc.n, nil
}
type typeCheckVariableAccess struct {
n *ast.VariableAccess
}
func (tc *typeCheckVariableAccess) TypeCheck(v *TypeCheck) (ast.Node, error) {
// Look up the variable in the map
variable, ok := v.Scope.LookupVar(tc.n.Name)
if !ok {
return nil, fmt.Errorf(
"unknown variable accessed: %s", tc.n.Name)
}
// Add the type to the stack
v.StackPush(variable.Type)
return tc.n, nil
}
func (v *TypeCheck) ImplicitConversion(
actual ast.Type, expected ast.Type, n ast.Node) ast.Node {
if v.Implicit == nil {
return nil
}
fromMap, ok := v.Implicit[actual]
if !ok {
return nil
}
toFunc, ok := fromMap[expected]
if !ok {
return nil
}
return &ast.Call{
Func: toFunc,
Args: []ast.Node{n},
Posx: n.Pos(),
}
}
func (v *TypeCheck) reset() {
v.Stack = nil
v.err = nil
}
func (v *TypeCheck) StackPush(t ast.Type) {
v.Stack = append(v.Stack, t)
}
func (v *TypeCheck) StackPop() ast.Type {
var x ast.Type
x, v.Stack = v.Stack[len(v.Stack)-1], v.Stack[:len(v.Stack)-1]
return x
}

265
vendor/github.com/hashicorp/hil/check_types_test.go generated vendored Normal file
View File

@ -0,0 +1,265 @@
package hil
import (
"testing"
"github.com/hashicorp/hil/ast"
)
func TestTypeCheck(t *testing.T) {
cases := []struct {
Input string
Scope ast.Scope
Error bool
}{
{
"foo",
&ast.BasicScope{},
false,
},
{
"foo ${bar}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: "baz",
Type: ast.TypeString,
},
},
},
false,
},
{
"foo ${rand()}",
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ReturnType: ast.TypeString,
Callback: func([]interface{}) (interface{}, error) {
return "42", nil
},
},
},
},
false,
},
{
`foo ${rand("42")}`,
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ArgTypes: []ast.Type{ast.TypeString},
ReturnType: ast.TypeString,
Callback: func([]interface{}) (interface{}, error) {
return "42", nil
},
},
},
},
false,
},
{
`foo ${rand(42)}`,
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ArgTypes: []ast.Type{ast.TypeString},
ReturnType: ast.TypeString,
Callback: func([]interface{}) (interface{}, error) {
return "42", nil
},
},
},
},
true,
},
{
`foo ${rand()}`,
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ArgTypes: nil,
ReturnType: ast.TypeString,
Variadic: true,
VariadicType: ast.TypeString,
Callback: func([]interface{}) (interface{}, error) {
return "42", nil
},
},
},
},
false,
},
{
`foo ${rand("42")}`,
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ArgTypes: nil,
ReturnType: ast.TypeString,
Variadic: true,
VariadicType: ast.TypeString,
Callback: func([]interface{}) (interface{}, error) {
return "42", nil
},
},
},
},
false,
},
{
`foo ${rand("42", 42)}`,
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ArgTypes: nil,
ReturnType: ast.TypeString,
Variadic: true,
VariadicType: ast.TypeString,
Callback: func([]interface{}) (interface{}, error) {
return "42", nil
},
},
},
},
true,
},
{
"foo ${bar}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: 42,
Type: ast.TypeInt,
},
},
},
true,
},
{
"foo ${rand()}",
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ReturnType: ast.TypeInt,
Callback: func([]interface{}) (interface{}, error) {
return 42, nil
},
},
},
},
true,
},
}
for _, tc := range cases {
node, err := Parse(tc.Input)
if err != nil {
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
}
visitor := &TypeCheck{Scope: tc.Scope}
err = visitor.Visit(node)
if err != nil != tc.Error {
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
}
}
}
func TestTypeCheck_implicit(t *testing.T) {
implicitMap := map[ast.Type]map[ast.Type]string{
ast.TypeInt: {
ast.TypeString: "intToString",
},
}
cases := []struct {
Input string
Scope *ast.BasicScope
Error bool
}{
{
"foo ${bar}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: 42,
Type: ast.TypeInt,
},
},
},
false,
},
{
"foo ${foo(42)}",
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"foo": ast.Function{
ArgTypes: []ast.Type{ast.TypeString},
ReturnType: ast.TypeString,
},
},
},
false,
},
{
`foo ${foo("42", 42)}`,
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"foo": ast.Function{
ArgTypes: []ast.Type{ast.TypeString},
Variadic: true,
VariadicType: ast.TypeString,
ReturnType: ast.TypeString,
},
},
},
false,
},
}
for _, tc := range cases {
node, err := Parse(tc.Input)
if err != nil {
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
}
// Modify the scope to add our conversion functions.
if tc.Scope.FuncMap == nil {
tc.Scope.FuncMap = make(map[string]ast.Function)
}
tc.Scope.FuncMap["intToString"] = ast.Function{
ArgTypes: []ast.Type{ast.TypeInt},
ReturnType: ast.TypeString,
}
// Do the first pass...
visitor := &TypeCheck{Scope: tc.Scope, Implicit: implicitMap}
err = visitor.Visit(node)
if err != nil != tc.Error {
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
}
if err != nil {
continue
}
// If we didn't error, then the next type check should not fail
// WITHOUT implicits.
visitor = &TypeCheck{Scope: tc.Scope}
err = visitor.Visit(node)
if err != nil {
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
}
}
}

222
vendor/github.com/hashicorp/hil/eval.go generated vendored Normal file
View File

@ -0,0 +1,222 @@
package hil
import (
"bytes"
"fmt"
"sync"
"github.com/hashicorp/hil/ast"
)
// EvalConfig is the configuration for evaluating.
type EvalConfig struct {
// GlobalScope is the global scope of execution for evaluation.
GlobalScope *ast.BasicScope
// SemanticChecks is a list of additional semantic checks that will be run
// on the tree prior to evaluating it. The type checker, identifier checker,
// etc. will be run before these automatically.
SemanticChecks []SemanticChecker
}
// SemanticChecker is the type that must be implemented to do a
// semantic check on an AST tree. This will be called with the root node.
type SemanticChecker func(ast.Node) error
// Eval evaluates the given AST tree and returns its output value, the type
// of the output, and any error that occurred.
func Eval(root ast.Node, config *EvalConfig) (interface{}, ast.Type, error) {
// Copy the scope so we can add our builtins
if config == nil {
config = new(EvalConfig)
}
scope := registerBuiltins(config.GlobalScope)
implicitMap := map[ast.Type]map[ast.Type]string{
ast.TypeFloat: {
ast.TypeInt: "__builtin_FloatToInt",
ast.TypeString: "__builtin_FloatToString",
},
ast.TypeInt: {
ast.TypeFloat: "__builtin_IntToFloat",
ast.TypeString: "__builtin_IntToString",
},
ast.TypeString: {
ast.TypeInt: "__builtin_StringToInt",
},
}
// Build our own semantic checks that we always run
tv := &TypeCheck{Scope: scope, Implicit: implicitMap}
ic := &IdentifierCheck{Scope: scope}
// Build up the semantic checks for execution
checks := make(
[]SemanticChecker,
len(config.SemanticChecks),
len(config.SemanticChecks)+2)
copy(checks, config.SemanticChecks)
checks = append(checks, ic.Visit)
checks = append(checks, tv.Visit)
// Run the semantic checks
for _, check := range checks {
if err := check(root); err != nil {
return nil, ast.TypeInvalid, err
}
}
// Execute
v := &evalVisitor{Scope: scope}
return v.Visit(root)
}
// EvalNode is the interface that must be implemented by any ast.Node
// to support evaluation. This will be called in visitor pattern order.
// The result of each call to Eval is automatically pushed onto the
// stack as a LiteralNode. Pop elements off the stack to get child
// values.
type EvalNode interface {
Eval(ast.Scope, *ast.Stack) (interface{}, ast.Type, error)
}
type evalVisitor struct {
Scope ast.Scope
Stack ast.Stack
err error
lock sync.Mutex
}
func (v *evalVisitor) Visit(root ast.Node) (interface{}, ast.Type, error) {
// Run the actual visitor pattern
root.Accept(v.visit)
// Get our result and clear out everything else
var result *ast.LiteralNode
if v.Stack.Len() > 0 {
result = v.Stack.Pop().(*ast.LiteralNode)
} else {
result = new(ast.LiteralNode)
}
resultErr := v.err
// Clear everything else so we aren't just dangling
v.Stack.Reset()
v.err = nil
t, err := result.Type(v.Scope)
if err != nil {
return nil, ast.TypeInvalid, err
}
return result.Value, t, resultErr
}
func (v *evalVisitor) visit(raw ast.Node) ast.Node {
if v.err != nil {
return raw
}
en, err := evalNode(raw)
if err != nil {
v.err = err
return raw
}
out, outType, err := en.Eval(v.Scope, &v.Stack)
if err != nil {
v.err = err
return raw
}
v.Stack.Push(&ast.LiteralNode{
Value: out,
Typex: outType,
})
return raw
}
// evalNode is a private function that returns an EvalNode for built-in
// types as well as any other EvalNode implementations.
func evalNode(raw ast.Node) (EvalNode, error) {
switch n := raw.(type) {
case *ast.Call:
return &evalCall{n}, nil
case *ast.Concat:
return &evalConcat{n}, nil
case *ast.LiteralNode:
return &evalLiteralNode{n}, nil
case *ast.VariableAccess:
return &evalVariableAccess{n}, nil
default:
en, ok := n.(EvalNode)
if !ok {
return nil, fmt.Errorf("node doesn't support evaluation: %#v", raw)
}
return en, nil
}
}
type evalCall struct{ *ast.Call }
func (v *evalCall) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type, error) {
// Look up the function in the map
function, ok := s.LookupFunc(v.Func)
if !ok {
return nil, ast.TypeInvalid, fmt.Errorf(
"unknown function called: %s", v.Func)
}
// The arguments are on the stack in reverse order, so pop them off.
args := make([]interface{}, len(v.Args))
for i, _ := range v.Args {
node := stack.Pop().(*ast.LiteralNode)
args[len(v.Args)-1-i] = node.Value
}
// Call the function
result, err := function.Callback(args)
if err != nil {
return nil, ast.TypeInvalid, fmt.Errorf("%s: %s", v.Func, err)
}
return result, function.ReturnType, nil
}
type evalConcat struct{ *ast.Concat }
func (v *evalConcat) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type, error) {
// The expressions should all be on the stack in reverse
// order. So pop them off, reverse their order, and concatenate.
nodes := make([]*ast.LiteralNode, 0, len(v.Exprs))
for range v.Exprs {
nodes = append(nodes, stack.Pop().(*ast.LiteralNode))
}
var buf bytes.Buffer
for i := len(nodes) - 1; i >= 0; i-- {
buf.WriteString(nodes[i].Value.(string))
}
return buf.String(), ast.TypeString, nil
}
type evalLiteralNode struct{ *ast.LiteralNode }
func (v *evalLiteralNode) Eval(ast.Scope, *ast.Stack) (interface{}, ast.Type, error) {
return v.Value, v.Typex, nil
}
type evalVariableAccess struct{ *ast.VariableAccess }
func (v *evalVariableAccess) Eval(scope ast.Scope, _ *ast.Stack) (interface{}, ast.Type, error) {
// Look up the variable in the map
variable, ok := scope.LookupVar(v.Name)
if !ok {
return nil, ast.TypeInvalid, fmt.Errorf(
"unknown variable accessed: %s", v.Name)
}
return variable.Value, variable.Type, nil
}

273
vendor/github.com/hashicorp/hil/eval_test.go generated vendored Normal file
View File

@ -0,0 +1,273 @@
package hil
import (
"reflect"
"strconv"
"testing"
"github.com/hashicorp/hil/ast"
)
func TestEval(t *testing.T) {
cases := []struct {
Input string
Scope *ast.BasicScope
Error bool
Result interface{}
ResultType ast.Type
}{
{
"foo",
nil,
false,
"foo",
ast.TypeString,
},
{
"foo ${bar}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: "baz",
Type: ast.TypeString,
},
},
},
false,
"foo baz",
ast.TypeString,
},
{
"foo ${42+1}",
nil,
false,
"foo 43",
ast.TypeString,
},
{
"foo ${42-1}",
nil,
false,
"foo 41",
ast.TypeString,
},
{
"foo ${42*2}",
nil,
false,
"foo 84",
ast.TypeString,
},
{
"foo ${42/2}",
nil,
false,
"foo 21",
ast.TypeString,
},
{
"foo ${42%4}",
nil,
false,
"foo 2",
ast.TypeString,
},
{
"foo ${42.0+1.0}",
nil,
false,
"foo 43",
ast.TypeString,
},
{
"foo ${42.0+1}",
nil,
false,
"foo 43",
ast.TypeString,
},
{
"foo ${42+1.0}",
nil,
false,
"foo 43",
ast.TypeString,
},
{
"foo ${42+2*2}",
nil,
false,
"foo 88",
ast.TypeString,
},
{
"foo ${42+(2*2)}",
nil,
false,
"foo 46",
ast.TypeString,
},
{
"foo ${bar+1}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: 41,
Type: ast.TypeInt,
},
},
},
false,
"foo 42",
ast.TypeString,
},
{
"foo ${bar+1}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: "41",
Type: ast.TypeString,
},
},
},
false,
"foo 42",
ast.TypeString,
},
{
"foo ${bar+baz}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: "41",
Type: ast.TypeString,
},
"baz": ast.Variable{
Value: "1",
Type: ast.TypeString,
},
},
},
false,
"foo 42",
ast.TypeString,
},
{
"foo ${rand()}",
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ReturnType: ast.TypeString,
Callback: func([]interface{}) (interface{}, error) {
return "42", nil
},
},
},
},
false,
"foo 42",
ast.TypeString,
},
{
`foo ${rand("foo", "bar")}`,
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ReturnType: ast.TypeString,
Variadic: true,
VariadicType: ast.TypeString,
Callback: func(args []interface{}) (interface{}, error) {
var result string
for _, a := range args {
result += a.(string)
}
return result, nil
},
},
},
},
false,
"foo foobar",
ast.TypeString,
},
// Testing implicit type conversions
{
"foo ${bar}",
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: 42,
Type: ast.TypeInt,
},
},
},
false,
"foo 42",
ast.TypeString,
},
{
`foo ${foo("42")}`,
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"foo": ast.Function{
ArgTypes: []ast.Type{ast.TypeInt},
ReturnType: ast.TypeString,
Callback: func(args []interface{}) (interface{}, error) {
return strconv.FormatInt(int64(args[0].(int)), 10), nil
},
},
},
},
false,
"foo 42",
ast.TypeString,
},
// Multiline
{
"foo ${42+\n1.0}",
nil,
false,
"foo 43",
ast.TypeString,
},
}
for _, tc := range cases {
node, err := Parse(tc.Input)
if err != nil {
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
}
out, outType, err := Eval(node, &EvalConfig{GlobalScope: tc.Scope})
if err != nil != tc.Error {
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
}
if outType != tc.ResultType {
t.Fatalf("Bad: %s\n\nInput: %s", outType, tc.Input)
}
if !reflect.DeepEqual(out, tc.Result) {
t.Fatalf("Bad: %#v\n\nInput: %s", out, tc.Input)
}
}
}

165
vendor/github.com/hashicorp/hil/lang.y generated vendored Normal file
View File

@ -0,0 +1,165 @@
// This is the yacc input for creating the parser for interpolation
// expressions in Go. To build it, just run `go generate` on this
// package, as the lexer has the go generate pragma within it.
%{
package hil
import (
"github.com/hashicorp/hil/ast"
)
%}
%union {
node ast.Node
nodeList []ast.Node
str string
token *parserToken
}
%token <str> PROGRAM_BRACKET_LEFT PROGRAM_BRACKET_RIGHT
%token <str> PROGRAM_STRING_START PROGRAM_STRING_END
%token <str> PAREN_LEFT PAREN_RIGHT COMMA
%token <token> ARITH_OP IDENTIFIER INTEGER FLOAT STRING
%type <node> expr interpolation literal literalModeTop literalModeValue
%type <nodeList> args
%left ARITH_OP
%%
top:
{
parserResult = &ast.LiteralNode{
Value: "",
Typex: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
}
}
| literalModeTop
{
parserResult = $1
// We want to make sure that the top value is always a Concat
// so that the return value is always a string type from an
// interpolation.
//
// The logic for checking for a LiteralNode is a little annoying
// because functionally the AST is the same, but we do that because
// it makes for an easy literal check later (to check if a string
// has any interpolations).
if _, ok := $1.(*ast.Concat); !ok {
if n, ok := $1.(*ast.LiteralNode); !ok || n.Typex != ast.TypeString {
parserResult = &ast.Concat{
Exprs: []ast.Node{$1},
Posx: $1.Pos(),
}
}
}
}
literalModeTop:
literalModeValue
{
$$ = $1
}
| literalModeTop literalModeValue
{
var result []ast.Node
if c, ok := $1.(*ast.Concat); ok {
result = append(c.Exprs, $2)
} else {
result = []ast.Node{$1, $2}
}
$$ = &ast.Concat{
Exprs: result,
Posx: result[0].Pos(),
}
}
literalModeValue:
literal
{
$$ = $1
}
| interpolation
{
$$ = $1
}
interpolation:
PROGRAM_BRACKET_LEFT expr PROGRAM_BRACKET_RIGHT
{
$$ = $2
}
expr:
PAREN_LEFT expr PAREN_RIGHT
{
$$ = $2
}
| literalModeTop
{
$$ = $1
}
| INTEGER
{
$$ = &ast.LiteralNode{
Value: $1.Value.(int),
Typex: ast.TypeInt,
Posx: $1.Pos,
}
}
| FLOAT
{
$$ = &ast.LiteralNode{
Value: $1.Value.(float64),
Typex: ast.TypeFloat,
Posx: $1.Pos,
}
}
| expr ARITH_OP expr
{
$$ = &ast.Arithmetic{
Op: $2.Value.(ast.ArithmeticOp),
Exprs: []ast.Node{$1, $3},
Posx: $1.Pos(),
}
}
| IDENTIFIER
{
$$ = &ast.VariableAccess{Name: $1.Value.(string), Posx: $1.Pos}
}
| IDENTIFIER PAREN_LEFT args PAREN_RIGHT
{
$$ = &ast.Call{Func: $1.Value.(string), Args: $3, Posx: $1.Pos}
}
args:
{
$$ = nil
}
| args COMMA expr
{
$$ = append($1, $3)
}
| expr
{
$$ = append($$, $1)
}
literal:
STRING
{
$$ = &ast.LiteralNode{
Value: $1.Value.(string),
Typex: ast.TypeString,
Posx: $1.Pos,
}
}
%%

403
vendor/github.com/hashicorp/hil/lex.go generated vendored Normal file
View File

@ -0,0 +1,403 @@
package hil
import (
"bytes"
"fmt"
"strconv"
"unicode"
"unicode/utf8"
"github.com/hashicorp/hil/ast"
)
//go:generate go tool yacc -p parser lang.y
// The parser expects the lexer to return 0 on EOF.
const lexEOF = 0
// The parser uses the type <prefix>Lex as a lexer. It must provide
// the methods Lex(*<prefix>SymType) int and Error(string).
type parserLex struct {
Err error
Input string
mode parserMode
interpolationDepth int
pos int
width int
col, line int
lastLine int
astPos *ast.Pos
}
// parserToken is the token yielded to the parser. The value can be
// determined within the parser type based on the enum value returned
// from Lex.
type parserToken struct {
Value interface{}
Pos ast.Pos
}
// parserMode keeps track of what mode we're in for the parser. We have
// two modes: literal and interpolation. Literal mode is when strings
// don't have to be quoted, and interpolations are defined as ${foo}.
// Interpolation mode means that strings have to be quoted and unquoted
// things are identifiers, such as foo("bar").
type parserMode uint8
const (
parserModeInvalid parserMode = 0
parserModeLiteral = 1 << iota
parserModeInterpolation
)
// The parser calls this method to get each new token.
func (x *parserLex) Lex(yylval *parserSymType) int {
// We always start in literal mode, since programs don't start
// in an interpolation. ex. "foo ${bar}" vs "bar" (and assuming interp.)
if x.mode == parserModeInvalid {
x.mode = parserModeLiteral
}
// Defer an update to set the proper column/line we read the next token.
defer func() {
if yylval.token != nil && yylval.token.Pos.Column == 0 {
yylval.token.Pos = *x.astPos
}
}()
x.astPos = nil
return x.lex(yylval)
}
func (x *parserLex) lex(yylval *parserSymType) int {
switch x.mode {
case parserModeLiteral:
return x.lexModeLiteral(yylval)
case parserModeInterpolation:
return x.lexModeInterpolation(yylval)
default:
x.Error(fmt.Sprintf("Unknown parse mode: %d", x.mode))
return lexEOF
}
}
func (x *parserLex) lexModeLiteral(yylval *parserSymType) int {
for {
c := x.next()
if c == lexEOF {
return lexEOF
}
// Are we starting an interpolation?
if c == '$' && x.peek() == '{' {
x.next()
x.interpolationDepth++
x.mode = parserModeInterpolation
return PROGRAM_BRACKET_LEFT
}
// We're just a normal string that isn't part of any interpolation yet.
x.backup()
result, terminated := x.lexString(yylval, x.interpolationDepth > 0)
// If the string terminated and we're within an interpolation already
// then that means that we finished a nested string, so pop
// back out to interpolation mode.
if terminated && x.interpolationDepth > 0 {
x.mode = parserModeInterpolation
// If the string is empty, just skip it. We're still in
// an interpolation so we do this to avoid empty nodes.
if yylval.token.Value.(string) == "" {
return x.lex(yylval)
}
}
return result
}
}
func (x *parserLex) lexModeInterpolation(yylval *parserSymType) int {
for {
c := x.next()
if c == lexEOF {
return lexEOF
}
// Ignore all whitespace
if unicode.IsSpace(c) {
continue
}
// If we see a double quote then we're lexing a string since
// we're in interpolation mode.
if c == '"' {
result, terminated := x.lexString(yylval, true)
if !terminated {
// The string didn't end, which means that we're in the
// middle of starting another interpolation.
x.mode = parserModeLiteral
// If the string is empty and we're starting an interpolation,
// then just skip it to avoid empty string AST nodes
if yylval.token.Value.(string) == "" {
return x.lex(yylval)
}
}
return result
}
// If we are seeing a number, it is the start of a number. Lex it.
if c >= '0' && c <= '9' {
x.backup()
return x.lexNumber(yylval)
}
switch c {
case '}':
// '}' means we ended the interpolation. Pop back into
// literal mode and reduce our interpolation depth.
x.interpolationDepth--
x.mode = parserModeLiteral
return PROGRAM_BRACKET_RIGHT
case '(':
return PAREN_LEFT
case ')':
return PAREN_RIGHT
case ',':
return COMMA
case '+':
yylval.token = &parserToken{Value: ast.ArithmeticOpAdd}
return ARITH_OP
case '-':
yylval.token = &parserToken{Value: ast.ArithmeticOpSub}
return ARITH_OP
case '*':
yylval.token = &parserToken{Value: ast.ArithmeticOpMul}
return ARITH_OP
case '/':
yylval.token = &parserToken{Value: ast.ArithmeticOpDiv}
return ARITH_OP
case '%':
yylval.token = &parserToken{Value: ast.ArithmeticOpMod}
return ARITH_OP
default:
x.backup()
return x.lexId(yylval)
}
}
}
func (x *parserLex) lexId(yylval *parserSymType) int {
var b bytes.Buffer
var last rune
for {
c := x.next()
if c == lexEOF {
break
}
// We only allow * after a '.' for resource splast: type.name.*.id
// Otherwise, its probably multiplication.
if c == '*' && last != '.' {
x.backup()
break
}
// If this isn't a character we want in an ID, return out.
// One day we should make this a regexp.
if c != '_' &&
c != '-' &&
c != '.' &&
c != '*' &&
!unicode.IsLetter(c) &&
!unicode.IsNumber(c) {
x.backup()
break
}
if _, err := b.WriteRune(c); err != nil {
x.Error(err.Error())
return lexEOF
}
last = c
}
yylval.token = &parserToken{Value: b.String()}
return IDENTIFIER
}
// lexNumber lexes out a number: an integer or a float.
func (x *parserLex) lexNumber(yylval *parserSymType) int {
var b bytes.Buffer
gotPeriod := false
for {
c := x.next()
if c == lexEOF {
break
}
// If we see a period, we might be getting a float..
if c == '.' {
// If we've already seen a period, then ignore it, and
// exit. This will probably result in a syntax error later.
if gotPeriod {
x.backup()
break
}
gotPeriod = true
} else if c < '0' || c > '9' {
// If we're not seeing a number, then also exit.
x.backup()
break
}
if _, err := b.WriteRune(c); err != nil {
x.Error(fmt.Sprintf("internal error: %s", err))
return lexEOF
}
}
// If we didn't see a period, it is an int
if !gotPeriod {
v, err := strconv.ParseInt(b.String(), 0, 0)
if err != nil {
x.Error(fmt.Sprintf("expected number: %s", err))
return lexEOF
}
yylval.token = &parserToken{Value: int(v)}
return INTEGER
}
// If we did see a period, it is a float
f, err := strconv.ParseFloat(b.String(), 64)
if err != nil {
x.Error(fmt.Sprintf("expected float: %s", err))
return lexEOF
}
yylval.token = &parserToken{Value: f}
return FLOAT
}
func (x *parserLex) lexString(yylval *parserSymType, quoted bool) (int, bool) {
var b bytes.Buffer
terminated := false
for {
c := x.next()
if c == lexEOF {
if quoted {
x.Error("unterminated string")
}
break
}
// Behavior is a bit different if we're lexing within a quoted string.
if quoted {
// If its a double quote, we've reached the end of the string
if c == '"' {
terminated = true
break
}
// Let's check to see if we're escaping anything.
if c == '\\' {
switch n := x.next(); n {
case '\\', '"':
c = n
case 'n':
c = '\n'
default:
x.backup()
}
}
}
// If we hit a dollar sign, then check if we're starting
// another interpolation. If so, then we're done.
if c == '$' {
n := x.peek()
// If it is '{', then we're starting another interpolation
if n == '{' {
x.backup()
break
}
// If it is '$', then we're escaping a dollar sign
if n == '$' {
x.next()
}
}
if _, err := b.WriteRune(c); err != nil {
x.Error(err.Error())
return lexEOF, false
}
}
yylval.token = &parserToken{Value: b.String()}
return STRING, terminated
}
// Return the next rune for the lexer.
func (x *parserLex) next() rune {
if int(x.pos) >= len(x.Input) {
x.width = 0
return lexEOF
}
r, w := utf8.DecodeRuneInString(x.Input[x.pos:])
x.width = w
x.pos += x.width
if x.line == 0 {
x.line = 1
x.col = 1
} else {
x.col += 1
}
if r == '\n' {
x.lastLine = x.col
x.line += 1
x.col = 1
}
if x.astPos == nil {
x.astPos = &ast.Pos{Column: x.col, Line: x.line}
}
return r
}
// peek returns but does not consume the next rune in the input
func (x *parserLex) peek() rune {
r := x.next()
x.backup()
return r
}
// backup steps back one rune. Can only be called once per next.
func (x *parserLex) backup() {
x.pos -= x.width
x.col -= 1
// If we are at column 0, we're backing up across a line boundary
// so we need to be careful to get the proper value.
if x.col == 0 {
x.col = x.lastLine
x.line -= 1
}
}
// The parser calls this method on a parse error.
func (x *parserLex) Error(s string) {
x.Err = fmt.Errorf("parse error: %s", s)
}

140
vendor/github.com/hashicorp/hil/lex_test.go generated vendored Normal file
View File

@ -0,0 +1,140 @@
package hil
import (
"reflect"
"testing"
)
func TestLex(t *testing.T) {
cases := []struct {
Input string
Output []int
}{
{
"foo",
[]int{STRING, lexEOF},
},
{
"foo$bar",
[]int{STRING, lexEOF},
},
{
"foo ${bar}",
[]int{STRING, PROGRAM_BRACKET_LEFT, IDENTIFIER, PROGRAM_BRACKET_RIGHT, lexEOF},
},
{
"foo $${bar}",
[]int{STRING, lexEOF},
},
{
"foo $$$${bar}",
[]int{STRING, lexEOF},
},
{
"foo ${\"bar\"}",
[]int{STRING, PROGRAM_BRACKET_LEFT, STRING, PROGRAM_BRACKET_RIGHT, lexEOF},
},
{
"${bar(baz)}",
[]int{PROGRAM_BRACKET_LEFT,
IDENTIFIER, PAREN_LEFT, IDENTIFIER, PAREN_RIGHT,
PROGRAM_BRACKET_RIGHT, lexEOF},
},
{
"${bar(baz, foo)}",
[]int{PROGRAM_BRACKET_LEFT,
IDENTIFIER, PAREN_LEFT,
IDENTIFIER, COMMA, IDENTIFIER,
PAREN_RIGHT,
PROGRAM_BRACKET_RIGHT, lexEOF},
},
{
"${bar(42)}",
[]int{PROGRAM_BRACKET_LEFT,
IDENTIFIER, PAREN_LEFT, INTEGER, PAREN_RIGHT,
PROGRAM_BRACKET_RIGHT, lexEOF},
},
{
"${bar(42+1)}",
[]int{PROGRAM_BRACKET_LEFT,
IDENTIFIER, PAREN_LEFT,
INTEGER, ARITH_OP, INTEGER,
PAREN_RIGHT,
PROGRAM_BRACKET_RIGHT, lexEOF},
},
{
"${bar(3.14159)}",
[]int{PROGRAM_BRACKET_LEFT,
IDENTIFIER, PAREN_LEFT, FLOAT, PAREN_RIGHT,
PROGRAM_BRACKET_RIGHT, lexEOF},
},
{
"${bar(inner(baz))}",
[]int{PROGRAM_BRACKET_LEFT,
IDENTIFIER, PAREN_LEFT,
IDENTIFIER, PAREN_LEFT,
IDENTIFIER,
PAREN_RIGHT, PAREN_RIGHT,
PROGRAM_BRACKET_RIGHT, lexEOF},
},
{
"foo ${foo.bar.baz}",
[]int{STRING, PROGRAM_BRACKET_LEFT, IDENTIFIER, PROGRAM_BRACKET_RIGHT, lexEOF},
},
{
"foo ${foo.bar.*.baz}",
[]int{STRING, PROGRAM_BRACKET_LEFT, IDENTIFIER, PROGRAM_BRACKET_RIGHT, lexEOF},
},
{
"foo ${foo(\"baz\")}",
[]int{STRING, PROGRAM_BRACKET_LEFT,
IDENTIFIER, PAREN_LEFT, STRING, PAREN_RIGHT,
PROGRAM_BRACKET_RIGHT, lexEOF},
},
{
`foo ${"${var.foo}"}`,
[]int{STRING, PROGRAM_BRACKET_LEFT,
PROGRAM_BRACKET_LEFT, IDENTIFIER, PROGRAM_BRACKET_RIGHT,
PROGRAM_BRACKET_RIGHT, lexEOF},
},
}
for _, tc := range cases {
l := &parserLex{Input: tc.Input}
var actual []int
for {
token := l.Lex(new(parserSymType))
actual = append(actual, token)
if token == lexEOF {
break
}
// Be careful against what are probably infinite loops
if len(actual) > 100 {
t.Fatalf("Input:%s\n\nExausted.", tc.Input)
}
}
if !reflect.DeepEqual(actual, tc.Output) {
t.Fatalf(
"Input: %s\n\nBad: %#v\n\nExpected: %#v",
tc.Input, actual, tc.Output)
}
}
}

32
vendor/github.com/hashicorp/hil/parse.go generated vendored Normal file
View File

@ -0,0 +1,32 @@
package hil
import (
"sync"
"github.com/hashicorp/hil/ast"
)
var parserErrors []error
var parserLock sync.Mutex
var parserResult ast.Node
// Parse parses the given program and returns an executable AST tree.
func Parse(v string) (ast.Node, error) {
// Unfortunately due to the way that goyacc generated parsers are
// formatted, we can only do a single parse at a time without a lot
// of extra work. In the future we can remove this limitation.
parserLock.Lock()
defer parserLock.Unlock()
// Reset our globals
parserErrors = nil
parserResult = nil
// Create the lexer
lex := &parserLex{Input: v}
// Parse!
parserParse(lex)
return parserResult, lex.Err
}

363
vendor/github.com/hashicorp/hil/parse_test.go generated vendored Normal file
View File

@ -0,0 +1,363 @@
package hil
import (
"reflect"
"testing"
"github.com/hashicorp/hil/ast"
)
func TestParse(t *testing.T) {
cases := []struct {
Input string
Error bool
Result ast.Node
}{
{
"",
false,
&ast.LiteralNode{
Value: "",
Typex: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
},
},
{
"foo",
false,
&ast.LiteralNode{
Value: "foo",
Typex: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
},
},
{
"$${var.foo}",
false,
&ast.LiteralNode{
Value: "${var.foo}",
Typex: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
},
},
{
"foo ${var.bar}",
false,
&ast.Concat{
Posx: ast.Pos{Column: 1, Line: 1},
Exprs: []ast.Node{
&ast.LiteralNode{
Value: "foo ",
Typex: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
},
&ast.VariableAccess{
Name: "var.bar",
Posx: ast.Pos{Column: 7, Line: 1},
},
},
},
},
{
"foo ${var.bar} baz",
false,
&ast.Concat{
Posx: ast.Pos{Column: 1, Line: 1},
Exprs: []ast.Node{
&ast.LiteralNode{
Value: "foo ",
Typex: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
},
&ast.VariableAccess{
Name: "var.bar",
Posx: ast.Pos{Column: 7, Line: 1},
},
&ast.LiteralNode{
Value: " baz",
Typex: ast.TypeString,
Posx: ast.Pos{Column: 15, Line: 1},
},
},
},
},
{
"foo ${\"bar\"}",
false,
&ast.Concat{
Posx: ast.Pos{Column: 1, Line: 1},
Exprs: []ast.Node{
&ast.LiteralNode{
Value: "foo ",
Typex: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
},
&ast.LiteralNode{
Value: "bar",
Typex: ast.TypeString,
Posx: ast.Pos{Column: 7, Line: 1},
},
},
},
},
{
`foo ${func('baz')}`,
true,
nil,
},
{
"foo ${42}",
false,
&ast.Concat{
Posx: ast.Pos{Column: 1, Line: 1},
Exprs: []ast.Node{
&ast.LiteralNode{
Value: "foo ",
Typex: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
},
&ast.LiteralNode{
Value: 42,
Typex: ast.TypeInt,
Posx: ast.Pos{Column: 7, Line: 1},
},
},
},
},
{
"foo ${3.14159}",
false,
&ast.Concat{
Posx: ast.Pos{Column: 1, Line: 1},
Exprs: []ast.Node{
&ast.LiteralNode{
Value: "foo ",
Typex: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
},
&ast.LiteralNode{
Value: 3.14159,
Typex: ast.TypeFloat,
Posx: ast.Pos{Column: 7, Line: 1},
},
},
},
},
{
"foo ${42+1}",
false,
&ast.Concat{
Posx: ast.Pos{Column: 1, Line: 1},
Exprs: []ast.Node{
&ast.LiteralNode{
Value: "foo ",
Typex: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
},
&ast.Arithmetic{
Op: ast.ArithmeticOpAdd,
Exprs: []ast.Node{
&ast.LiteralNode{
Value: 42,
Typex: ast.TypeInt,
Posx: ast.Pos{Column: 7, Line: 1},
},
&ast.LiteralNode{
Value: 1,
Typex: ast.TypeInt,
Posx: ast.Pos{Column: 10, Line: 1},
},
},
Posx: ast.Pos{Column: 7, Line: 1},
},
},
},
},
{
"foo ${var.bar*1} baz",
false,
&ast.Concat{
Posx: ast.Pos{Column: 1, Line: 1},
Exprs: []ast.Node{
&ast.LiteralNode{
Value: "foo ",
Typex: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
},
&ast.Arithmetic{
Op: ast.ArithmeticOpMul,
Exprs: []ast.Node{
&ast.VariableAccess{
Name: "var.bar",
Posx: ast.Pos{Column: 7, Line: 1},
},
&ast.LiteralNode{
Value: 1,
Typex: ast.TypeInt,
Posx: ast.Pos{Column: 15, Line: 1},
},
},
Posx: ast.Pos{Column: 7, Line: 1},
},
&ast.LiteralNode{
Value: " baz",
Typex: ast.TypeString,
Posx: ast.Pos{Column: 17, Line: 1},
},
},
},
},
{
"${foo()}",
false,
&ast.Concat{
Posx: ast.Pos{Column: 3, Line: 1},
Exprs: []ast.Node{
&ast.Call{
Func: "foo",
Args: nil,
Posx: ast.Pos{Column: 3, Line: 1},
},
},
},
},
{
"${foo(bar)}",
false,
&ast.Concat{
Posx: ast.Pos{Column: 3, Line: 1},
Exprs: []ast.Node{
&ast.Call{
Func: "foo",
Posx: ast.Pos{Column: 3, Line: 1},
Args: []ast.Node{
&ast.VariableAccess{
Name: "bar",
Posx: ast.Pos{Column: 7, Line: 1},
},
},
},
},
},
},
{
"${foo(bar, baz)}",
false,
&ast.Concat{
Posx: ast.Pos{Column: 3, Line: 1},
Exprs: []ast.Node{
&ast.Call{
Func: "foo",
Posx: ast.Pos{Column: 3, Line: 1},
Args: []ast.Node{
&ast.VariableAccess{
Name: "bar",
Posx: ast.Pos{Column: 7, Line: 1},
},
&ast.VariableAccess{
Name: "baz",
Posx: ast.Pos{Column: 11, Line: 1},
},
},
},
},
},
},
{
"${foo(bar(baz))}",
false,
&ast.Concat{
Posx: ast.Pos{Column: 3, Line: 1},
Exprs: []ast.Node{
&ast.Call{
Func: "foo",
Posx: ast.Pos{Column: 3, Line: 1},
Args: []ast.Node{
&ast.Call{
Func: "bar",
Posx: ast.Pos{Column: 7, Line: 1},
Args: []ast.Node{
&ast.VariableAccess{
Name: "baz",
Posx: ast.Pos{Column: 11, Line: 1},
},
},
},
},
},
},
},
},
{
`foo ${"bar ${baz}"}`,
false,
&ast.Concat{
Posx: ast.Pos{Column: 1, Line: 1},
Exprs: []ast.Node{
&ast.LiteralNode{
Value: "foo ",
Typex: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
},
&ast.Concat{
Posx: ast.Pos{Column: 7, Line: 1},
Exprs: []ast.Node{
&ast.LiteralNode{
Value: "bar ",
Typex: ast.TypeString,
Posx: ast.Pos{Column: 7, Line: 1},
},
&ast.VariableAccess{
Name: "baz",
Posx: ast.Pos{Column: 14, Line: 1},
},
},
},
},
},
},
{
`foo ${bar ${baz}}`,
true,
nil,
},
{
`foo ${${baz}}`,
true,
nil,
},
{
"${var",
true,
nil,
},
}
for _, tc := range cases {
actual, err := Parse(tc.Input)
if err != nil != tc.Error {
t.Fatalf("Error: %s\n\nInput: %s", err, tc.Input)
}
if !reflect.DeepEqual(actual, tc.Result) {
t.Fatalf("Bad: %#v\n\nInput: %s", actual, tc.Input)
}
}
}

29
vendor/github.com/hashicorp/hil/transform_fixed.go generated vendored Normal file
View File

@ -0,0 +1,29 @@
package hil
import (
"github.com/hashicorp/hil/ast"
)
// FixedValueTransform transforms an AST to return a fixed value for
// all interpolations. i.e. you can make "hi ${anything}" always
// turn into "hi foo".
//
// The primary use case for this is for config validations where you can
// verify that interpolations result in a certain type of string.
func FixedValueTransform(root ast.Node, Value *ast.LiteralNode) ast.Node {
// We visit the nodes in top-down order
result := root
switch n := result.(type) {
case *ast.Concat:
for i, v := range n.Exprs {
n.Exprs[i] = FixedValueTransform(v, Value)
}
case *ast.LiteralNode:
// We keep it as-is
default:
// Anything else we replace
result = Value
}
return result
}

View File

@ -0,0 +1,48 @@
package hil
import (
"reflect"
"testing"
"github.com/hashicorp/hil/ast"
)
func TestFixedValueTransform(t *testing.T) {
cases := []struct {
Input ast.Node
Output ast.Node
}{
{
&ast.LiteralNode{Value: 42},
&ast.LiteralNode{Value: 42},
},
{
&ast.VariableAccess{Name: "bar"},
&ast.LiteralNode{Value: "foo"},
},
{
&ast.Concat{
Exprs: []ast.Node{
&ast.VariableAccess{Name: "bar"},
&ast.LiteralNode{Value: 42},
},
},
&ast.Concat{
Exprs: []ast.Node{
&ast.LiteralNode{Value: "foo"},
&ast.LiteralNode{Value: 42},
},
},
},
}
value := &ast.LiteralNode{Value: "foo"}
for _, tc := range cases {
actual := FixedValueTransform(tc.Input, value)
if !reflect.DeepEqual(actual, tc.Output) {
t.Fatalf("bad: %#v\n\nInput: %#v", actual, tc.Input)
}
}
}

266
vendor/github.com/hashicorp/hil/walk.go generated vendored Normal file
View File

@ -0,0 +1,266 @@
package hil
import (
"fmt"
"reflect"
"strings"
"github.com/hashicorp/hil/ast"
"github.com/mitchellh/reflectwalk"
)
// WalkFn is the type of function to pass to Walk. Modify fields within
// WalkData to control whether replacement happens.
type WalkFn func(*WalkData) error
// WalkData is the structure passed to the callback of the Walk function.
//
// This structure contains data passed in as well as fields that are expected
// to be written by the caller as a result. Please see the documentation for
// each field for more information.
type WalkData struct {
// Root is the parsed root of this HIL program
Root ast.Node
// Location is the location within the structure where this
// value was found. This can be used to modify behavior within
// slices and so on.
Location reflectwalk.Location
// The below two values must be set by the callback to have any effect.
//
// Replace, if true, will replace the value in the structure with
// ReplaceValue. It is up to the caller to make sure this is a string.
Replace bool
ReplaceValue string
}
// Walk will walk an arbitrary Go structure and parse any string as an
// HIL program and call the callback cb to determine what to replace it
// with.
//
// This function is very useful for arbitrary HIL program interpolation
// across a complex configuration structure. Due to the heavy use of
// reflection in this function, it is recommend to write many unit tests
// with your typical configuration structures to hilp mitigate the risk
// of panics.
func Walk(v interface{}, cb WalkFn) error {
walker := &interpolationWalker{F: cb}
return reflectwalk.Walk(v, walker)
}
// interpolationWalker implements interfaces for the reflectwalk package
// (github.com/mitchellh/reflectwalk) that can be used to automatically
// execute a callback for an interpolation.
type interpolationWalker struct {
F WalkFn
key []string
lastValue reflect.Value
loc reflectwalk.Location
cs []reflect.Value
csKey []reflect.Value
csData interface{}
sliceIndex int
unknownKeys []string
}
func (w *interpolationWalker) Enter(loc reflectwalk.Location) error {
w.loc = loc
return nil
}
func (w *interpolationWalker) Exit(loc reflectwalk.Location) error {
w.loc = reflectwalk.None
switch loc {
case reflectwalk.Map:
w.cs = w.cs[:len(w.cs)-1]
case reflectwalk.MapValue:
w.key = w.key[:len(w.key)-1]
w.csKey = w.csKey[:len(w.csKey)-1]
case reflectwalk.Slice:
// Split any values that need to be split
w.splitSlice()
w.cs = w.cs[:len(w.cs)-1]
case reflectwalk.SliceElem:
w.csKey = w.csKey[:len(w.csKey)-1]
}
return nil
}
func (w *interpolationWalker) Map(m reflect.Value) error {
w.cs = append(w.cs, m)
return nil
}
func (w *interpolationWalker) MapElem(m, k, v reflect.Value) error {
w.csData = k
w.csKey = append(w.csKey, k)
w.key = append(w.key, k.String())
w.lastValue = v
return nil
}
func (w *interpolationWalker) Slice(s reflect.Value) error {
w.cs = append(w.cs, s)
return nil
}
func (w *interpolationWalker) SliceElem(i int, elem reflect.Value) error {
w.csKey = append(w.csKey, reflect.ValueOf(i))
w.sliceIndex = i
return nil
}
func (w *interpolationWalker) Primitive(v reflect.Value) error {
setV := v
// We only care about strings
if v.Kind() == reflect.Interface {
setV = v
v = v.Elem()
}
if v.Kind() != reflect.String {
return nil
}
astRoot, err := Parse(v.String())
if err != nil {
return err
}
// If the AST we got is just a literal string value with the same
// value then we ignore it. We have to check if its the same value
// because it is possible to input a string, get out a string, and
// have it be different. For example: "foo-$${bar}" turns into
// "foo-${bar}"
if n, ok := astRoot.(*ast.LiteralNode); ok {
if s, ok := n.Value.(string); ok && s == v.String() {
return nil
}
}
if w.F == nil {
return nil
}
data := WalkData{Root: astRoot, Location: w.loc}
if err := w.F(&data); err != nil {
return fmt.Errorf(
"%s in:\n\n%s",
err, v.String())
}
if data.Replace {
/*
if remove {
w.removeCurrent()
return nil
}
*/
resultVal := reflect.ValueOf(data.ReplaceValue)
switch w.loc {
case reflectwalk.MapKey:
m := w.cs[len(w.cs)-1]
// Delete the old value
var zero reflect.Value
m.SetMapIndex(w.csData.(reflect.Value), zero)
// Set the new key with the existing value
m.SetMapIndex(resultVal, w.lastValue)
// Set the key to be the new key
w.csData = resultVal
case reflectwalk.MapValue:
// If we're in a map, then the only way to set a map value is
// to set it directly.
m := w.cs[len(w.cs)-1]
mk := w.csData.(reflect.Value)
m.SetMapIndex(mk, resultVal)
default:
// Otherwise, we should be addressable
setV.Set(resultVal)
}
}
return nil
}
func (w *interpolationWalker) removeCurrent() {
// Append the key to the unknown keys
w.unknownKeys = append(w.unknownKeys, strings.Join(w.key, "."))
for i := 1; i <= len(w.cs); i++ {
c := w.cs[len(w.cs)-i]
switch c.Kind() {
case reflect.Map:
// Zero value so that we delete the map key
var val reflect.Value
// Get the key and delete it
k := w.csData.(reflect.Value)
c.SetMapIndex(k, val)
return
}
}
panic("No container found for removeCurrent")
}
func (w *interpolationWalker) replaceCurrent(v reflect.Value) {
c := w.cs[len(w.cs)-2]
switch c.Kind() {
case reflect.Map:
// Get the key and delete it
k := w.csKey[len(w.csKey)-1]
c.SetMapIndex(k, v)
}
}
func (w *interpolationWalker) splitSlice() {
// Get the []interface{} slice so we can do some operations on
// it without dealing with reflection. We'll document each step
// here to be clear.
var s []interface{}
raw := w.cs[len(w.cs)-1]
switch v := raw.Interface().(type) {
case []interface{}:
s = v
case []map[string]interface{}:
return
default:
panic("Unknown kind: " + raw.Kind().String())
}
// Check if we have any elements that we need to split. If not, then
// just return since we're done.
split := false
if !split {
return
}
// Make a new result slice that is twice the capacity to fit our growth.
result := make([]interface{}, 0, len(s)*2)
// Go over each element of the original slice and start building up
// the resulting slice by splitting where we have to.
for _, v := range s {
sv, ok := v.(string)
if !ok {
// Not a string, so just set it
result = append(result, v)
continue
}
// Not a string list, so just set it
result = append(result, sv)
}
// Our slice is now done, we have to replace the slice now
// with this new one that we have.
w.replaceCurrent(reflect.ValueOf(result))
}

190
vendor/github.com/hashicorp/hil/walk_test.go generated vendored Normal file
View File

@ -0,0 +1,190 @@
package hil
import (
"fmt"
"reflect"
"testing"
)
func TestInterpolationWalker_detect(t *testing.T) {
cases := []struct {
Input interface{}
Result []string
}{
{
Input: map[string]interface{}{
"foo": "$${var.foo}",
},
Result: []string{
"Literal(TypeString, ${var.foo})",
},
},
{
Input: map[string]interface{}{
"foo": "${var.foo}",
},
Result: []string{
"Variable(var.foo)",
},
},
{
Input: map[string]interface{}{
"foo": "${aws_instance.foo.*.num}",
},
Result: []string{
"Variable(aws_instance.foo.*.num)",
},
},
{
Input: map[string]interface{}{
"foo": "${lookup(var.foo)}",
},
Result: []string{
"Call(lookup, Variable(var.foo))",
},
},
{
Input: map[string]interface{}{
"foo": `${file("test.txt")}`,
},
Result: []string{
"Call(file, Literal(TypeString, test.txt))",
},
},
{
Input: map[string]interface{}{
"foo": `${file("foo/bar.txt")}`,
},
Result: []string{
"Call(file, Literal(TypeString, foo/bar.txt))",
},
},
{
Input: map[string]interface{}{
"foo": `${join(",", foo.bar.*.id)}`,
},
Result: []string{
"Call(join, Literal(TypeString, ,), Variable(foo.bar.*.id))",
},
},
{
Input: map[string]interface{}{
"foo": `${concat("localhost", ":8080")}`,
},
Result: []string{
"Call(concat, Literal(TypeString, localhost), Literal(TypeString, :8080))",
},
},
}
for i, tc := range cases {
var actual []string
detectFn := func(data *WalkData) error {
actual = append(actual, fmt.Sprintf("%s", data.Root))
return nil
}
if err := Walk(tc.Input, detectFn); err != nil {
t.Fatalf("err: %s", err)
}
if !reflect.DeepEqual(actual, tc.Result) {
t.Fatalf("%d: bad:\n\n%#v", i, actual)
}
}
}
func TestInterpolationWalker_replace(t *testing.T) {
cases := []struct {
Input interface{}
Output interface{}
Value string
}{
{
Input: map[string]interface{}{
"foo": "$${var.foo}",
},
Output: map[string]interface{}{
"foo": "bar",
},
Value: "bar",
},
{
Input: map[string]interface{}{
"foo": "hi, ${var.foo}",
},
Output: map[string]interface{}{
"foo": "bar",
},
Value: "bar",
},
{
Input: map[string]interface{}{
"foo": map[string]interface{}{
"${var.foo}": "bar",
},
},
Output: map[string]interface{}{
"foo": map[string]interface{}{
"bar": "bar",
},
},
Value: "bar",
},
/*
{
Input: map[string]interface{}{
"foo": []interface{}{
"${var.foo}",
"bing",
},
},
Output: map[string]interface{}{
"foo": []interface{}{
"bar",
"baz",
"bing",
},
},
Value: NewStringList([]string{"bar", "baz"}).String(),
},
{
Input: map[string]interface{}{
"foo": []interface{}{
"${var.foo}",
"bing",
},
},
Output: map[string]interface{}{},
Value: NewStringList([]string{UnknownVariableValue, "baz"}).String(),
},
*/
}
for i, tc := range cases {
fn := func(data *WalkData) error {
data.Replace = true
data.ReplaceValue = tc.Value
return nil
}
if err := Walk(tc.Input, fn); err != nil {
t.Fatalf("err: %s", err)
}
if !reflect.DeepEqual(tc.Input, tc.Output) {
t.Fatalf("%d: bad:\n\nexpected:%#v\ngot:%#v", i, tc.Output, tc.Input)
}
}
}

621
vendor/github.com/hashicorp/hil/y.go generated vendored Normal file
View File

@ -0,0 +1,621 @@
//line lang.y:6
package hil
import __yyfmt__ "fmt"
//line lang.y:6
import (
"github.com/hashicorp/hil/ast"
)
//line lang.y:14
type parserSymType struct {
yys int
node ast.Node
nodeList []ast.Node
str string
token *parserToken
}
const PROGRAM_BRACKET_LEFT = 57346
const PROGRAM_BRACKET_RIGHT = 57347
const PROGRAM_STRING_START = 57348
const PROGRAM_STRING_END = 57349
const PAREN_LEFT = 57350
const PAREN_RIGHT = 57351
const COMMA = 57352
const ARITH_OP = 57353
const IDENTIFIER = 57354
const INTEGER = 57355
const FLOAT = 57356
const STRING = 57357
var parserToknames = [...]string{
"$end",
"error",
"$unk",
"PROGRAM_BRACKET_LEFT",
"PROGRAM_BRACKET_RIGHT",
"PROGRAM_STRING_START",
"PROGRAM_STRING_END",
"PAREN_LEFT",
"PAREN_RIGHT",
"COMMA",
"ARITH_OP",
"IDENTIFIER",
"INTEGER",
"FLOAT",
"STRING",
}
var parserStatenames = [...]string{}
const parserEofCode = 1
const parserErrCode = 2
const parserMaxDepth = 200
//line lang.y:165
//line yacctab:1
var parserExca = [...]int{
-1, 1,
1, -1,
-2, 0,
}
const parserNprod = 19
const parserPrivate = 57344
var parserTokenNames []string
var parserStates []string
const parserLast = 30
var parserAct = [...]int{
9, 20, 16, 16, 7, 7, 3, 18, 10, 8,
1, 17, 14, 12, 13, 6, 6, 19, 8, 22,
15, 23, 24, 11, 2, 25, 16, 21, 4, 5,
}
var parserPact = [...]int{
1, -1000, 1, -1000, -1000, -1000, -1000, 0, -1000, 15,
0, 1, -1000, -1000, -1, -1000, 0, -8, 0, -1000,
-1000, 12, -9, -1000, 0, -9,
}
var parserPgo = [...]int{
0, 0, 29, 28, 23, 6, 27, 10,
}
var parserR1 = [...]int{
0, 7, 7, 4, 4, 5, 5, 2, 1, 1,
1, 1, 1, 1, 1, 6, 6, 6, 3,
}
var parserR2 = [...]int{
0, 0, 1, 1, 2, 1, 1, 3, 3, 1,
1, 1, 3, 1, 4, 0, 3, 1, 1,
}
var parserChk = [...]int{
-1000, -7, -4, -5, -3, -2, 15, 4, -5, -1,
8, -4, 13, 14, 12, 5, 11, -1, 8, -1,
9, -6, -1, 9, 10, -1,
}
var parserDef = [...]int{
1, -2, 2, 3, 5, 6, 18, 0, 4, 0,
0, 9, 10, 11, 13, 7, 0, 0, 15, 12,
8, 0, 17, 14, 0, 16,
}
var parserTok1 = [...]int{
1,
}
var parserTok2 = [...]int{
2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15,
}
var parserTok3 = [...]int{
0,
}
var parserErrorMessages = [...]struct {
state int
token int
msg string
}{}
//line yaccpar:1
/* parser for yacc output */
var (
parserDebug = 0
parserErrorVerbose = false
)
type parserLexer interface {
Lex(lval *parserSymType) int
Error(s string)
}
type parserParser interface {
Parse(parserLexer) int
Lookahead() int
}
type parserParserImpl struct {
lookahead func() int
}
func (p *parserParserImpl) Lookahead() int {
return p.lookahead()
}
func parserNewParser() parserParser {
p := &parserParserImpl{
lookahead: func() int { return -1 },
}
return p
}
const parserFlag = -1000
func parserTokname(c int) string {
if c >= 1 && c-1 < len(parserToknames) {
if parserToknames[c-1] != "" {
return parserToknames[c-1]
}
}
return __yyfmt__.Sprintf("tok-%v", c)
}
func parserStatname(s int) string {
if s >= 0 && s < len(parserStatenames) {
if parserStatenames[s] != "" {
return parserStatenames[s]
}
}
return __yyfmt__.Sprintf("state-%v", s)
}
func parserErrorMessage(state, lookAhead int) string {
const TOKSTART = 4
if !parserErrorVerbose {
return "syntax error"
}
for _, e := range parserErrorMessages {
if e.state == state && e.token == lookAhead {
return "syntax error: " + e.msg
}
}
res := "syntax error: unexpected " + parserTokname(lookAhead)
// To match Bison, suggest at most four expected tokens.
expected := make([]int, 0, 4)
// Look for shiftable tokens.
base := parserPact[state]
for tok := TOKSTART; tok-1 < len(parserToknames); tok++ {
if n := base + tok; n >= 0 && n < parserLast && parserChk[parserAct[n]] == tok {
if len(expected) == cap(expected) {
return res
}
expected = append(expected, tok)
}
}
if parserDef[state] == -2 {
i := 0
for parserExca[i] != -1 || parserExca[i+1] != state {
i += 2
}
// Look for tokens that we accept or reduce.
for i += 2; parserExca[i] >= 0; i += 2 {
tok := parserExca[i]
if tok < TOKSTART || parserExca[i+1] == 0 {
continue
}
if len(expected) == cap(expected) {
return res
}
expected = append(expected, tok)
}
// If the default action is to accept or reduce, give up.
if parserExca[i+1] != 0 {
return res
}
}
for i, tok := range expected {
if i == 0 {
res += ", expecting "
} else {
res += " or "
}
res += parserTokname(tok)
}
return res
}
func parserlex1(lex parserLexer, lval *parserSymType) (char, token int) {
token = 0
char = lex.Lex(lval)
if char <= 0 {
token = parserTok1[0]
goto out
}
if char < len(parserTok1) {
token = parserTok1[char]
goto out
}
if char >= parserPrivate {
if char < parserPrivate+len(parserTok2) {
token = parserTok2[char-parserPrivate]
goto out
}
}
for i := 0; i < len(parserTok3); i += 2 {
token = parserTok3[i+0]
if token == char {
token = parserTok3[i+1]
goto out
}
}
out:
if token == 0 {
token = parserTok2[1] /* unknown char */
}
if parserDebug >= 3 {
__yyfmt__.Printf("lex %s(%d)\n", parserTokname(token), uint(char))
}
return char, token
}
func parserParse(parserlex parserLexer) int {
return parserNewParser().Parse(parserlex)
}
func (parserrcvr *parserParserImpl) Parse(parserlex parserLexer) int {
var parsern int
var parserlval parserSymType
var parserVAL parserSymType
var parserDollar []parserSymType
_ = parserDollar // silence set and not used
parserS := make([]parserSymType, parserMaxDepth)
Nerrs := 0 /* number of errors */
Errflag := 0 /* error recovery flag */
parserstate := 0
parserchar := -1
parsertoken := -1 // parserchar translated into internal numbering
parserrcvr.lookahead = func() int { return parserchar }
defer func() {
// Make sure we report no lookahead when not parsing.
parserstate = -1
parserchar = -1
parsertoken = -1
}()
parserp := -1
goto parserstack
ret0:
return 0
ret1:
return 1
parserstack:
/* put a state and value onto the stack */
if parserDebug >= 4 {
__yyfmt__.Printf("char %v in %v\n", parserTokname(parsertoken), parserStatname(parserstate))
}
parserp++
if parserp >= len(parserS) {
nyys := make([]parserSymType, len(parserS)*2)
copy(nyys, parserS)
parserS = nyys
}
parserS[parserp] = parserVAL
parserS[parserp].yys = parserstate
parsernewstate:
parsern = parserPact[parserstate]
if parsern <= parserFlag {
goto parserdefault /* simple state */
}
if parserchar < 0 {
parserchar, parsertoken = parserlex1(parserlex, &parserlval)
}
parsern += parsertoken
if parsern < 0 || parsern >= parserLast {
goto parserdefault
}
parsern = parserAct[parsern]
if parserChk[parsern] == parsertoken { /* valid shift */
parserchar = -1
parsertoken = -1
parserVAL = parserlval
parserstate = parsern
if Errflag > 0 {
Errflag--
}
goto parserstack
}
parserdefault:
/* default state action */
parsern = parserDef[parserstate]
if parsern == -2 {
if parserchar < 0 {
parserchar, parsertoken = parserlex1(parserlex, &parserlval)
}
/* look through exception table */
xi := 0
for {
if parserExca[xi+0] == -1 && parserExca[xi+1] == parserstate {
break
}
xi += 2
}
for xi += 2; ; xi += 2 {
parsern = parserExca[xi+0]
if parsern < 0 || parsern == parsertoken {
break
}
}
parsern = parserExca[xi+1]
if parsern < 0 {
goto ret0
}
}
if parsern == 0 {
/* error ... attempt to resume parsing */
switch Errflag {
case 0: /* brand new error */
parserlex.Error(parserErrorMessage(parserstate, parsertoken))
Nerrs++
if parserDebug >= 1 {
__yyfmt__.Printf("%s", parserStatname(parserstate))
__yyfmt__.Printf(" saw %s\n", parserTokname(parsertoken))
}
fallthrough
case 1, 2: /* incompletely recovered error ... try again */
Errflag = 3
/* find a state where "error" is a legal shift action */
for parserp >= 0 {
parsern = parserPact[parserS[parserp].yys] + parserErrCode
if parsern >= 0 && parsern < parserLast {
parserstate = parserAct[parsern] /* simulate a shift of "error" */
if parserChk[parserstate] == parserErrCode {
goto parserstack
}
}
/* the current p has no shift on "error", pop stack */
if parserDebug >= 2 {
__yyfmt__.Printf("error recovery pops state %d\n", parserS[parserp].yys)
}
parserp--
}
/* there is no state on the stack with an error shift ... abort */
goto ret1
case 3: /* no shift yet; clobber input char */
if parserDebug >= 2 {
__yyfmt__.Printf("error recovery discards %s\n", parserTokname(parsertoken))
}
if parsertoken == parserEofCode {
goto ret1
}
parserchar = -1
parsertoken = -1
goto parsernewstate /* try again in the same state */
}
}
/* reduction by production parsern */
if parserDebug >= 2 {
__yyfmt__.Printf("reduce %v in:\n\t%v\n", parsern, parserStatname(parserstate))
}
parsernt := parsern
parserpt := parserp
_ = parserpt // guard against "declared and not used"
parserp -= parserR2[parsern]
// parserp is now the index of $0. Perform the default action. Iff the
// reduced production is ε, $1 is possibly out of range.
if parserp+1 >= len(parserS) {
nyys := make([]parserSymType, len(parserS)*2)
copy(nyys, parserS)
parserS = nyys
}
parserVAL = parserS[parserp+1]
/* consult goto table to find next state */
parsern = parserR1[parsern]
parserg := parserPgo[parsern]
parserj := parserg + parserS[parserp].yys + 1
if parserj >= parserLast {
parserstate = parserAct[parserg]
} else {
parserstate = parserAct[parserj]
if parserChk[parserstate] != -parsern {
parserstate = parserAct[parserg]
}
}
// dummy call; replaced with literal code
switch parsernt {
case 1:
parserDollar = parserS[parserpt-0 : parserpt+1]
//line lang.y:35
{
parserResult = &ast.LiteralNode{
Value: "",
Typex: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
}
}
case 2:
parserDollar = parserS[parserpt-1 : parserpt+1]
//line lang.y:43
{
parserResult = parserDollar[1].node
// We want to make sure that the top value is always a Concat
// so that the return value is always a string type from an
// interpolation.
//
// The logic for checking for a LiteralNode is a little annoying
// because functionally the AST is the same, but we do that because
// it makes for an easy literal check later (to check if a string
// has any interpolations).
if _, ok := parserDollar[1].node.(*ast.Concat); !ok {
if n, ok := parserDollar[1].node.(*ast.LiteralNode); !ok || n.Typex != ast.TypeString {
parserResult = &ast.Concat{
Exprs: []ast.Node{parserDollar[1].node},
Posx: parserDollar[1].node.Pos(),
}
}
}
}
case 3:
parserDollar = parserS[parserpt-1 : parserpt+1]
//line lang.y:66
{
parserVAL.node = parserDollar[1].node
}
case 4:
parserDollar = parserS[parserpt-2 : parserpt+1]
//line lang.y:70
{
var result []ast.Node
if c, ok := parserDollar[1].node.(*ast.Concat); ok {
result = append(c.Exprs, parserDollar[2].node)
} else {
result = []ast.Node{parserDollar[1].node, parserDollar[2].node}
}
parserVAL.node = &ast.Concat{
Exprs: result,
Posx: result[0].Pos(),
}
}
case 5:
parserDollar = parserS[parserpt-1 : parserpt+1]
//line lang.y:86
{
parserVAL.node = parserDollar[1].node
}
case 6:
parserDollar = parserS[parserpt-1 : parserpt+1]
//line lang.y:90
{
parserVAL.node = parserDollar[1].node
}
case 7:
parserDollar = parserS[parserpt-3 : parserpt+1]
//line lang.y:96
{
parserVAL.node = parserDollar[2].node
}
case 8:
parserDollar = parserS[parserpt-3 : parserpt+1]
//line lang.y:102
{
parserVAL.node = parserDollar[2].node
}
case 9:
parserDollar = parserS[parserpt-1 : parserpt+1]
//line lang.y:106
{
parserVAL.node = parserDollar[1].node
}
case 10:
parserDollar = parserS[parserpt-1 : parserpt+1]
//line lang.y:110
{
parserVAL.node = &ast.LiteralNode{
Value: parserDollar[1].token.Value.(int),
Typex: ast.TypeInt,
Posx: parserDollar[1].token.Pos,
}
}
case 11:
parserDollar = parserS[parserpt-1 : parserpt+1]
//line lang.y:118
{
parserVAL.node = &ast.LiteralNode{
Value: parserDollar[1].token.Value.(float64),
Typex: ast.TypeFloat,
Posx: parserDollar[1].token.Pos,
}
}
case 12:
parserDollar = parserS[parserpt-3 : parserpt+1]
//line lang.y:126
{
parserVAL.node = &ast.Arithmetic{
Op: parserDollar[2].token.Value.(ast.ArithmeticOp),
Exprs: []ast.Node{parserDollar[1].node, parserDollar[3].node},
Posx: parserDollar[1].node.Pos(),
}
}
case 13:
parserDollar = parserS[parserpt-1 : parserpt+1]
//line lang.y:134
{
parserVAL.node = &ast.VariableAccess{Name: parserDollar[1].token.Value.(string), Posx: parserDollar[1].token.Pos}
}
case 14:
parserDollar = parserS[parserpt-4 : parserpt+1]
//line lang.y:138
{
parserVAL.node = &ast.Call{Func: parserDollar[1].token.Value.(string), Args: parserDollar[3].nodeList, Posx: parserDollar[1].token.Pos}
}
case 15:
parserDollar = parserS[parserpt-0 : parserpt+1]
//line lang.y:143
{
parserVAL.nodeList = nil
}
case 16:
parserDollar = parserS[parserpt-3 : parserpt+1]
//line lang.y:147
{
parserVAL.nodeList = append(parserDollar[1].nodeList, parserDollar[3].node)
}
case 17:
parserDollar = parserS[parserpt-1 : parserpt+1]
//line lang.y:151
{
parserVAL.nodeList = append(parserVAL.nodeList, parserDollar[1].node)
}
case 18:
parserDollar = parserS[parserpt-1 : parserpt+1]
//line lang.y:157
{
parserVAL.node = &ast.LiteralNode{
Value: parserDollar[1].token.Value.(string),
Typex: ast.TypeString,
Posx: parserDollar[1].token.Pos,
}
}
}
goto parserstack /* stack new state and value */
}

263
vendor/github.com/hashicorp/hil/y.output generated vendored Normal file
View File

@ -0,0 +1,263 @@
state 0
$accept: .top $end
top: . (1)
PROGRAM_BRACKET_LEFT shift 7
STRING shift 6
. reduce 1 (src line 34)
interpolation goto 5
literal goto 4
literalModeTop goto 2
literalModeValue goto 3
top goto 1
state 1
$accept: top.$end
$end accept
. error
state 2
top: literalModeTop. (2)
literalModeTop: literalModeTop.literalModeValue
PROGRAM_BRACKET_LEFT shift 7
STRING shift 6
. reduce 2 (src line 42)
interpolation goto 5
literal goto 4
literalModeValue goto 8
state 3
literalModeTop: literalModeValue. (3)
. reduce 3 (src line 64)
state 4
literalModeValue: literal. (5)
. reduce 5 (src line 84)
state 5
literalModeValue: interpolation. (6)
. reduce 6 (src line 89)
state 6
literal: STRING. (18)
. reduce 18 (src line 155)
state 7
interpolation: PROGRAM_BRACKET_LEFT.expr PROGRAM_BRACKET_RIGHT
PROGRAM_BRACKET_LEFT shift 7
PAREN_LEFT shift 10
IDENTIFIER shift 14
INTEGER shift 12
FLOAT shift 13
STRING shift 6
. error
expr goto 9
interpolation goto 5
literal goto 4
literalModeTop goto 11
literalModeValue goto 3
state 8
literalModeTop: literalModeTop literalModeValue. (4)
. reduce 4 (src line 69)
state 9
interpolation: PROGRAM_BRACKET_LEFT expr.PROGRAM_BRACKET_RIGHT
expr: expr.ARITH_OP expr
PROGRAM_BRACKET_RIGHT shift 15
ARITH_OP shift 16
. error
state 10
expr: PAREN_LEFT.expr PAREN_RIGHT
PROGRAM_BRACKET_LEFT shift 7
PAREN_LEFT shift 10
IDENTIFIER shift 14
INTEGER shift 12
FLOAT shift 13
STRING shift 6
. error
expr goto 17
interpolation goto 5
literal goto 4
literalModeTop goto 11
literalModeValue goto 3
state 11
literalModeTop: literalModeTop.literalModeValue
expr: literalModeTop. (9)
PROGRAM_BRACKET_LEFT shift 7
STRING shift 6
. reduce 9 (src line 105)
interpolation goto 5
literal goto 4
literalModeValue goto 8
state 12
expr: INTEGER. (10)
. reduce 10 (src line 109)
state 13
expr: FLOAT. (11)
. reduce 11 (src line 117)
state 14
expr: IDENTIFIER. (13)
expr: IDENTIFIER.PAREN_LEFT args PAREN_RIGHT
PAREN_LEFT shift 18
. reduce 13 (src line 133)
state 15
interpolation: PROGRAM_BRACKET_LEFT expr PROGRAM_BRACKET_RIGHT. (7)
. reduce 7 (src line 94)
state 16
expr: expr ARITH_OP.expr
PROGRAM_BRACKET_LEFT shift 7
PAREN_LEFT shift 10
IDENTIFIER shift 14
INTEGER shift 12
FLOAT shift 13
STRING shift 6
. error
expr goto 19
interpolation goto 5
literal goto 4
literalModeTop goto 11
literalModeValue goto 3
state 17
expr: PAREN_LEFT expr.PAREN_RIGHT
expr: expr.ARITH_OP expr
PAREN_RIGHT shift 20
ARITH_OP shift 16
. error
state 18
expr: IDENTIFIER PAREN_LEFT.args PAREN_RIGHT
args: . (15)
PROGRAM_BRACKET_LEFT shift 7
PAREN_LEFT shift 10
IDENTIFIER shift 14
INTEGER shift 12
FLOAT shift 13
STRING shift 6
. reduce 15 (src line 142)
expr goto 22
interpolation goto 5
literal goto 4
literalModeTop goto 11
literalModeValue goto 3
args goto 21
state 19
expr: expr.ARITH_OP expr
expr: expr ARITH_OP expr. (12)
. reduce 12 (src line 125)
state 20
expr: PAREN_LEFT expr PAREN_RIGHT. (8)
. reduce 8 (src line 100)
state 21
expr: IDENTIFIER PAREN_LEFT args.PAREN_RIGHT
args: args.COMMA expr
PAREN_RIGHT shift 23
COMMA shift 24
. error
state 22
expr: expr.ARITH_OP expr
args: expr. (17)
ARITH_OP shift 16
. reduce 17 (src line 150)
state 23
expr: IDENTIFIER PAREN_LEFT args PAREN_RIGHT. (14)
. reduce 14 (src line 137)
state 24
args: args COMMA.expr
PROGRAM_BRACKET_LEFT shift 7
PAREN_LEFT shift 10
IDENTIFIER shift 14
INTEGER shift 12
FLOAT shift 13
STRING shift 6
. error
expr goto 25
interpolation goto 5
literal goto 4
literalModeTop goto 11
literalModeValue goto 3
state 25
expr: expr.ARITH_OP expr
args: args COMMA expr. (16)
ARITH_OP shift 16
. reduce 16 (src line 146)
15 terminals, 8 nonterminals
19 grammar rules, 26/2000 states
0 shift/reduce, 0 reduce/reduce conflicts reported
57 working sets used
memory: parser 35/30000
21 extra closures
45 shift entries, 1 exceptions
14 goto entries
23 entries saved by goto default
Optimizer space used: output 30/30000
30 table entries, 0 zero
maximum spread: 15, maximum offset: 24