Commit things
This commit is contained in:
parent
8e04dbf597
commit
9a98eac129
20
ast.go
Normal file
20
ast.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package hcl
|
||||||
|
|
||||||
|
type ValueType byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
ValueTypeUnknown ValueType = iota
|
||||||
|
ValueTypeInt
|
||||||
|
ValueTypeString
|
||||||
|
)
|
||||||
|
|
||||||
|
type Node interface{}
|
||||||
|
|
||||||
|
type ObjectNode struct {
|
||||||
|
Elem map[string][]Node
|
||||||
|
}
|
||||||
|
|
||||||
|
type ValueNode struct {
|
||||||
|
Type ValueType
|
||||||
|
Value interface{}
|
||||||
|
}
|
46
lex.go
46
lex.go
@ -31,11 +31,24 @@ func (x *hclLex) Lex(yylval *hclSymType) int {
|
|||||||
return lexEOF
|
return lexEOF
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore all whitespace
|
// Ignore all whitespace except a newline which we handle
|
||||||
|
// specially later.
|
||||||
if unicode.IsSpace(c) {
|
if unicode.IsSpace(c) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Consume all comments
|
||||||
|
switch c {
|
||||||
|
case '#':
|
||||||
|
fallthrough
|
||||||
|
case '/':
|
||||||
|
// Starting comment
|
||||||
|
if !x.consumeComment(c) {
|
||||||
|
return lexEOF
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// If it is a number, lex the number
|
// If it is a number, lex the number
|
||||||
if c >= '0' && c <= '9' {
|
if c >= '0' && c <= '9' {
|
||||||
x.backup()
|
x.backup()
|
||||||
@ -43,21 +56,18 @@ func (x *hclLex) Lex(yylval *hclSymType) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch c {
|
switch c {
|
||||||
|
case ',':
|
||||||
|
return COMMA
|
||||||
case '=':
|
case '=':
|
||||||
return EQUAL
|
return EQUAL
|
||||||
|
case '[':
|
||||||
|
return LEFTBRACKET
|
||||||
|
case ']':
|
||||||
|
return RIGHTBRACKET
|
||||||
case '{':
|
case '{':
|
||||||
return LEFTBRACE
|
return LEFTBRACE
|
||||||
case '}':
|
case '}':
|
||||||
return RIGHTBRACE
|
return RIGHTBRACE
|
||||||
case ';':
|
|
||||||
return SEMICOLON
|
|
||||||
case '#':
|
|
||||||
fallthrough
|
|
||||||
case '/':
|
|
||||||
// Starting comment
|
|
||||||
if !x.consumeComment(c) {
|
|
||||||
return lexEOF
|
|
||||||
}
|
|
||||||
case '"':
|
case '"':
|
||||||
return x.lexString(yylval)
|
return x.lexString(yylval)
|
||||||
default:
|
default:
|
||||||
@ -220,6 +230,16 @@ func (x *hclLex) next() rune {
|
|||||||
r, w := utf8.DecodeRuneInString(x.Input[x.pos:])
|
r, w := utf8.DecodeRuneInString(x.Input[x.pos:])
|
||||||
x.width = w
|
x.width = w
|
||||||
x.pos += x.width
|
x.pos += x.width
|
||||||
|
|
||||||
|
x.col += 1
|
||||||
|
if x.line == 0 {
|
||||||
|
x.line = 1
|
||||||
|
}
|
||||||
|
if r == '\n' {
|
||||||
|
x.line += 1
|
||||||
|
x.col = 0
|
||||||
|
}
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,15 +252,17 @@ func (x *hclLex) peek() rune {
|
|||||||
|
|
||||||
// backup steps back one rune. Can only be called once per next.
|
// backup steps back one rune. Can only be called once per next.
|
||||||
func (x *hclLex) backup() {
|
func (x *hclLex) backup() {
|
||||||
|
x.col -= 1
|
||||||
x.pos -= x.width
|
x.pos -= x.width
|
||||||
}
|
}
|
||||||
|
|
||||||
// createErr records the given error
|
// createErr records the given error
|
||||||
func (x *hclLex) createErr(msg string) {
|
func (x *hclLex) createErr(msg string) {
|
||||||
x.err = fmt.Errorf("Line %d, column %d: %s", x.col, x.line, msg)
|
x.err = fmt.Errorf("Line %d, column %d: %s", x.line, x.col, msg)
|
||||||
|
log.Printf("parse error: %s", x.err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The parser calls this method on a parse error.
|
// The parser calls this method on a parse error.
|
||||||
func (x *hclLex) Error(s string) {
|
func (x *hclLex) Error(s string) {
|
||||||
log.Printf("parse error: %s", s)
|
x.createErr(s)
|
||||||
}
|
}
|
||||||
|
23
lex_test.go
23
lex_test.go
@ -16,6 +16,22 @@ func TestLex(t *testing.T) {
|
|||||||
"comment.hcl",
|
"comment.hcl",
|
||||||
[]int{IDENTIFIER, EQUAL, STRING, lexEOF},
|
[]int{IDENTIFIER, EQUAL, STRING, lexEOF},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"multiple.hcl",
|
||||||
|
[]int{
|
||||||
|
IDENTIFIER, EQUAL, STRING,
|
||||||
|
IDENTIFIER, EQUAL, NUMBER,
|
||||||
|
lexEOF,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"list.hcl",
|
||||||
|
[]int{
|
||||||
|
IDENTIFIER, EQUAL, LEFTBRACKET,
|
||||||
|
NUMBER, COMMA, NUMBER, COMMA, STRING,
|
||||||
|
RIGHTBRACKET, lexEOF,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"structure_basic.hcl",
|
"structure_basic.hcl",
|
||||||
[]int{
|
[]int{
|
||||||
@ -28,7 +44,8 @@ func TestLex(t *testing.T) {
|
|||||||
"structure.hcl",
|
"structure.hcl",
|
||||||
[]int{
|
[]int{
|
||||||
IDENTIFIER, IDENTIFIER, STRING, LEFTBRACE,
|
IDENTIFIER, IDENTIFIER, STRING, LEFTBRACE,
|
||||||
IDENTIFIER, EQUAL, NUMBER, SEMICOLON,
|
IDENTIFIER, EQUAL, NUMBER,
|
||||||
|
IDENTIFIER, EQUAL, STRING,
|
||||||
RIGHTBRACE, lexEOF,
|
RIGHTBRACE, lexEOF,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -56,7 +73,9 @@ func TestLex(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(actual, tc.Output) {
|
if !reflect.DeepEqual(actual, tc.Output) {
|
||||||
t.Fatalf("Input: %s\n\nBad: %#v", tc.Input, actual)
|
t.Fatalf(
|
||||||
|
"Input: %s\n\nBad: %#v\n\nExpected: %#v",
|
||||||
|
tc.Input, actual, tc.Output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
4
parse.go
4
parse.go
@ -10,10 +10,10 @@ import (
|
|||||||
// be accessed directly.
|
// be accessed directly.
|
||||||
var hclErrors []error
|
var hclErrors []error
|
||||||
var hclLock sync.Mutex
|
var hclLock sync.Mutex
|
||||||
var hclResult map[string]interface{}
|
var hclResult *ObjectNode
|
||||||
|
|
||||||
// Parse parses the given string and returns the result.
|
// Parse parses the given string and returns the result.
|
||||||
func Parse(v string) (map[string]interface{}, error) {
|
func Parse(v string) (*ObjectNode, error) {
|
||||||
hclLock.Lock()
|
hclLock.Lock()
|
||||||
defer hclLock.Unlock()
|
defer hclLock.Unlock()
|
||||||
hclErrors = nil
|
hclErrors = nil
|
||||||
|
105
parse.y
105
parse.y
@ -6,38 +6,83 @@ package hcl
|
|||||||
%}
|
%}
|
||||||
|
|
||||||
%union {
|
%union {
|
||||||
|
list []Node
|
||||||
|
listitem Node
|
||||||
num int
|
num int
|
||||||
obj map[string]interface{}
|
obj ObjectNode
|
||||||
str string
|
str string
|
||||||
}
|
}
|
||||||
|
|
||||||
%type <obj> block object
|
%type <list> list
|
||||||
|
%type <listitem> listitem
|
||||||
|
%type <obj> block object objectlist
|
||||||
%type <str> blockId
|
%type <str> blockId
|
||||||
|
|
||||||
%token <num> NUMBER
|
%token <num> NUMBER
|
||||||
%token <str> IDENTIFIER EQUAL SEMICOLON STRING
|
%token <str> COMMA IDENTIFIER EQUAL NEWLINE STRING
|
||||||
%token <str> LEFTBRACE RIGHTBRACE
|
%token <str> LEFTBRACE RIGHTBRACE LEFTBRACKET RIGHTBRACKET
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
|
||||||
top:
|
top:
|
||||||
object
|
objectlist
|
||||||
{
|
{
|
||||||
hclResult = $1
|
hclResult = &ObjectNode{
|
||||||
|
Elem: $1.Elem,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object:
|
objectlist:
|
||||||
object SEMICOLON
|
object
|
||||||
{
|
{
|
||||||
$$ = $1
|
$$ = $1
|
||||||
}
|
}
|
||||||
| IDENTIFIER EQUAL NUMBER
|
| object objectlist
|
||||||
{
|
{
|
||||||
$$ = map[string]interface{}{$1: []interface{}{$3}}
|
$$ = $1
|
||||||
|
for k, v := range $2.Elem {
|
||||||
|
if _, ok := $$.Elem[k]; ok {
|
||||||
|
$$.Elem[k] = append($$.Elem[k], v...)
|
||||||
|
} else {
|
||||||
|
$$.Elem[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object:
|
||||||
|
IDENTIFIER EQUAL NUMBER
|
||||||
|
{
|
||||||
|
$$ = ObjectNode{
|
||||||
|
Elem: map[string][]Node{
|
||||||
|
$1: []Node{
|
||||||
|
ValueNode{
|
||||||
|
Type: ValueTypeInt,
|
||||||
|
Value: $3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
| IDENTIFIER EQUAL STRING
|
| IDENTIFIER EQUAL STRING
|
||||||
{
|
{
|
||||||
$$ = map[string]interface{}{$1: []interface{}{$3}}
|
$$ = ObjectNode{
|
||||||
|
Elem: map[string][]Node{
|
||||||
|
$1: []Node{
|
||||||
|
ValueNode{
|
||||||
|
Type: ValueTypeString,
|
||||||
|
Value: $3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
| IDENTIFIER EQUAL LEFTBRACKET list RIGHTBRACKET
|
||||||
|
{
|
||||||
|
$$ = ObjectNode{
|
||||||
|
Elem: map[string][]Node{
|
||||||
|
$1: $4,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
| block
|
| block
|
||||||
{
|
{
|
||||||
@ -45,13 +90,21 @@ object:
|
|||||||
}
|
}
|
||||||
|
|
||||||
block:
|
block:
|
||||||
blockId LEFTBRACE object RIGHTBRACE
|
blockId LEFTBRACE objectlist RIGHTBRACE
|
||||||
{
|
{
|
||||||
$$ = map[string]interface{}{$1: []interface{}{$3}}
|
$$ = ObjectNode{
|
||||||
|
Elem: map[string][]Node{
|
||||||
|
$1: []Node{$3},
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
| blockId block
|
| blockId block
|
||||||
{
|
{
|
||||||
$$ = map[string]interface{}{$1: []interface{}{$2}}
|
$$ = ObjectNode{
|
||||||
|
Elem: map[string][]Node{
|
||||||
|
$1: []Node{$2},
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blockId:
|
blockId:
|
||||||
@ -64,4 +117,28 @@ blockId:
|
|||||||
$$ = $1
|
$$ = $1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
list:
|
||||||
|
listitem
|
||||||
|
{
|
||||||
|
$$ = []Node{$1}
|
||||||
|
}
|
||||||
|
| list COMMA listitem
|
||||||
|
{
|
||||||
|
$$ = append($1, $3)
|
||||||
|
}
|
||||||
|
|
||||||
|
listitem:
|
||||||
|
object
|
||||||
|
{
|
||||||
|
$$ = $1
|
||||||
|
}
|
||||||
|
| NUMBER
|
||||||
|
{
|
||||||
|
$$ = $1
|
||||||
|
}
|
||||||
|
| STRING
|
||||||
|
{
|
||||||
|
$$ = $1
|
||||||
|
}
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
@ -10,34 +10,51 @@ import (
|
|||||||
func TestParse(t *testing.T) {
|
func TestParse(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
Input string
|
Input string
|
||||||
Output map[string]interface{}
|
Output *ObjectNode
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"comment.hcl",
|
"comment.hcl",
|
||||||
map[string]interface{}{
|
&ObjectNode{
|
||||||
"foo": []interface{}{"bar"},
|
Elem: map[string][]Node{
|
||||||
},
|
"foo": []Node{
|
||||||
},
|
ValueNode{
|
||||||
{
|
Type: ValueTypeString,
|
||||||
"structure_basic.hcl",
|
Value: "bar",
|
||||||
map[string]interface{}{
|
},
|
||||||
"foo": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"value": []interface{}{7},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"structure.hcl",
|
"multiple.hcl",
|
||||||
map[string]interface{}{
|
&ObjectNode{
|
||||||
"foo": []interface{}{
|
Elem: map[string][]Node{
|
||||||
map[string]interface{}{
|
"foo": []Node{
|
||||||
"bar": []interface{}{
|
ValueNode{
|
||||||
map[string]interface{}{
|
Type: ValueTypeString,
|
||||||
"baz": []interface{}{
|
Value: "bar",
|
||||||
map[string]interface{}{
|
},
|
||||||
"key": []interface{}{7},
|
},
|
||||||
|
"key": []Node{
|
||||||
|
ValueNode{
|
||||||
|
Type: ValueTypeInt,
|
||||||
|
Value: 7,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"structure_basic.hcl",
|
||||||
|
&ObjectNode{
|
||||||
|
Elem: map[string][]Node{
|
||||||
|
"foo": []Node{
|
||||||
|
ObjectNode{
|
||||||
|
Elem: map[string][]Node{
|
||||||
|
"value": []Node{
|
||||||
|
ValueNode{
|
||||||
|
Type: ValueTypeInt,
|
||||||
|
Value: 7,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -46,6 +63,48 @@ func TestParse(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"structure.hcl",
|
||||||
|
&ObjectNode{
|
||||||
|
Elem: map[string][]Node{
|
||||||
|
"foo": []Node{
|
||||||
|
ObjectNode{
|
||||||
|
Elem: map[string][]Node{
|
||||||
|
"bar": []Node{
|
||||||
|
ObjectNode{
|
||||||
|
Elem: map[string][]Node{
|
||||||
|
"baz": []Node{
|
||||||
|
ObjectNode{
|
||||||
|
Elem: map[string][]Node{
|
||||||
|
"key": []Node{
|
||||||
|
ValueNode{
|
||||||
|
Type: ValueTypeInt,
|
||||||
|
Value: 7,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"foo": []Node{
|
||||||
|
ValueNode{
|
||||||
|
Type: ValueTypeString,
|
||||||
|
Value: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"complex.hcl",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
42
test-fixtures/complex.hcl
Normal file
42
test-fixtures/complex.hcl
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// This comes from Terraform, as a test
|
||||||
|
variable "foo" {
|
||||||
|
default = "bar"
|
||||||
|
description = "bar"
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "aws" {
|
||||||
|
access_key = "foo"
|
||||||
|
secret_key = "bar"
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "do" {
|
||||||
|
api_key = "${var.foo}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_security_group" "firewall" {
|
||||||
|
count = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
resource aws_instance "web" {
|
||||||
|
ami = "${var.foo}"
|
||||||
|
security_groups = [
|
||||||
|
"foo",
|
||||||
|
"${aws_security_group.firewall.foo}"
|
||||||
|
]
|
||||||
|
|
||||||
|
network_interface {
|
||||||
|
device_index = 0
|
||||||
|
description = "Main network interface"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "db" {
|
||||||
|
security_groups = "${aws_security_group.firewall.*.id}"
|
||||||
|
VPC = "foo"
|
||||||
|
|
||||||
|
depends_on = ["aws_instance.web"]
|
||||||
|
}
|
||||||
|
|
||||||
|
output "web_ip" {
|
||||||
|
value = "${aws_instance.web.private_ip}"
|
||||||
|
}
|
1
test-fixtures/list.hcl
Normal file
1
test-fixtures/list.hcl
Normal file
@ -0,0 +1 @@
|
|||||||
|
foo = [1, 2, "foo"]
|
2
test-fixtures/multiple.hcl
Normal file
2
test-fixtures/multiple.hcl
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
foo = "bar"
|
||||||
|
key = 7
|
@ -1,4 +1,5 @@
|
|||||||
// This is a test structure for the lexer
|
// This is a test structure for the lexer
|
||||||
foo bar "baz" {
|
foo bar "baz" {
|
||||||
key = 7;
|
key = 7
|
||||||
|
foo = "bar"
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user