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
|
||||
}
|
||||
|
||||
// Ignore all whitespace
|
||||
// Ignore all whitespace except a newline which we handle
|
||||
// specially later.
|
||||
if unicode.IsSpace(c) {
|
||||
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 c >= '0' && c <= '9' {
|
||||
x.backup()
|
||||
@ -43,21 +56,18 @@ func (x *hclLex) Lex(yylval *hclSymType) int {
|
||||
}
|
||||
|
||||
switch c {
|
||||
case ',':
|
||||
return COMMA
|
||||
case '=':
|
||||
return EQUAL
|
||||
case '[':
|
||||
return LEFTBRACKET
|
||||
case ']':
|
||||
return RIGHTBRACKET
|
||||
case '{':
|
||||
return LEFTBRACE
|
||||
case '}':
|
||||
return RIGHTBRACE
|
||||
case ';':
|
||||
return SEMICOLON
|
||||
case '#':
|
||||
fallthrough
|
||||
case '/':
|
||||
// Starting comment
|
||||
if !x.consumeComment(c) {
|
||||
return lexEOF
|
||||
}
|
||||
case '"':
|
||||
return x.lexString(yylval)
|
||||
default:
|
||||
@ -220,6 +230,16 @@ func (x *hclLex) next() rune {
|
||||
r, w := utf8.DecodeRuneInString(x.Input[x.pos:])
|
||||
x.width = w
|
||||
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
|
||||
}
|
||||
|
||||
@ -232,15 +252,17 @@ func (x *hclLex) peek() rune {
|
||||
|
||||
// backup steps back one rune. Can only be called once per next.
|
||||
func (x *hclLex) backup() {
|
||||
x.col -= 1
|
||||
x.pos -= x.width
|
||||
}
|
||||
|
||||
// createErr records the given error
|
||||
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.
|
||||
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",
|
||||
[]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",
|
||||
[]int{
|
||||
@ -28,7 +44,8 @@ func TestLex(t *testing.T) {
|
||||
"structure.hcl",
|
||||
[]int{
|
||||
IDENTIFIER, IDENTIFIER, STRING, LEFTBRACE,
|
||||
IDENTIFIER, EQUAL, NUMBER, SEMICOLON,
|
||||
IDENTIFIER, EQUAL, NUMBER,
|
||||
IDENTIFIER, EQUAL, STRING,
|
||||
RIGHTBRACE, lexEOF,
|
||||
},
|
||||
},
|
||||
@ -56,7 +73,9 @@ func TestLex(t *testing.T) {
|
||||
}
|
||||
|
||||
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.
|
||||
var hclErrors []error
|
||||
var hclLock sync.Mutex
|
||||
var hclResult map[string]interface{}
|
||||
var hclResult *ObjectNode
|
||||
|
||||
// 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()
|
||||
defer hclLock.Unlock()
|
||||
hclErrors = nil
|
||||
|
105
parse.y
105
parse.y
@ -6,38 +6,83 @@ package hcl
|
||||
%}
|
||||
|
||||
%union {
|
||||
list []Node
|
||||
listitem Node
|
||||
num int
|
||||
obj map[string]interface{}
|
||||
obj ObjectNode
|
||||
str string
|
||||
}
|
||||
|
||||
%type <obj> block object
|
||||
%type <list> list
|
||||
%type <listitem> listitem
|
||||
%type <obj> block object objectlist
|
||||
%type <str> blockId
|
||||
|
||||
%token <num> NUMBER
|
||||
%token <str> IDENTIFIER EQUAL SEMICOLON STRING
|
||||
%token <str> LEFTBRACE RIGHTBRACE
|
||||
%token <str> COMMA IDENTIFIER EQUAL NEWLINE STRING
|
||||
%token <str> LEFTBRACE RIGHTBRACE LEFTBRACKET RIGHTBRACKET
|
||||
|
||||
%%
|
||||
|
||||
top:
|
||||
object
|
||||
objectlist
|
||||
{
|
||||
hclResult = $1
|
||||
hclResult = &ObjectNode{
|
||||
Elem: $1.Elem,
|
||||
}
|
||||
}
|
||||
|
||||
object:
|
||||
object SEMICOLON
|
||||
objectlist:
|
||||
object
|
||||
{
|
||||
$$ = $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
|
||||
{
|
||||
$$ = 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
|
||||
{
|
||||
@ -45,13 +90,21 @@ object:
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
$$ = map[string]interface{}{$1: []interface{}{$2}}
|
||||
$$ = ObjectNode{
|
||||
Elem: map[string][]Node{
|
||||
$1: []Node{$2},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
blockId:
|
||||
@ -64,4 +117,28 @@ blockId:
|
||||
$$ = $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) {
|
||||
cases := []struct {
|
||||
Input string
|
||||
Output map[string]interface{}
|
||||
Output *ObjectNode
|
||||
}{
|
||||
{
|
||||
"comment.hcl",
|
||||
map[string]interface{}{
|
||||
"foo": []interface{}{"bar"},
|
||||
},
|
||||
},
|
||||
{
|
||||
"structure_basic.hcl",
|
||||
map[string]interface{}{
|
||||
"foo": []interface{}{
|
||||
map[string]interface{}{
|
||||
"value": []interface{}{7},
|
||||
&ObjectNode{
|
||||
Elem: map[string][]Node{
|
||||
"foo": []Node{
|
||||
ValueNode{
|
||||
Type: ValueTypeString,
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"structure.hcl",
|
||||
map[string]interface{}{
|
||||
"foo": []interface{}{
|
||||
map[string]interface{}{
|
||||
"bar": []interface{}{
|
||||
map[string]interface{}{
|
||||
"baz": []interface{}{
|
||||
map[string]interface{}{
|
||||
"key": []interface{}{7},
|
||||
"multiple.hcl",
|
||||
&ObjectNode{
|
||||
Elem: map[string][]Node{
|
||||
"foo": []Node{
|
||||
ValueNode{
|
||||
Type: ValueTypeString,
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
"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 {
|
||||
|
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
|
||||
foo bar "baz" {
|
||||
key = 7;
|
||||
key = 7
|
||||
foo = "bar"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user