JSON parser

This commit is contained in:
Mitchell Hashimoto 2014-08-02 11:38:41 -07:00
parent e0d7c7b378
commit 358b43ddbc
18 changed files with 1451 additions and 33 deletions

1
.gitignore vendored
View File

@ -1,2 +1 @@
y.go
y.output

View File

@ -1,9 +1,20 @@
default: test
test: y.go
go test
fmt: y.go json/y.go
go fmt ./...
test: y.go json/y.go
go test ./...
y.go: parse.y
go tool yacc -p "hcl" parse.y
json/y.go: json/parse.y
cd json/ && \
go tool yacc -p "json" parse.y
clean:
rm -f y.go
rm -f json/y.go
.PHONY: default test

View File

@ -1,4 +1,4 @@
package hcl
package ast
// ValueType is an enum represnting the type of a value in
// a LiteralNode.
@ -8,6 +8,8 @@ const (
ValueTypeUnknown ValueType = iota
ValueTypeInt
ValueTypeString
ValueTypeBool
ValueTypeNil
)
// Node is implemented by all AST nodes for HCL.

View File

@ -1,4 +1,4 @@
package hcl
package ast
import (
"reflect"

View File

@ -1,4 +1,4 @@
package hcl
package ast
// MockVisitor is a visitor implementation that can be used for tests
// and simply records the nodes that it has visited.

4
json/json_test.go Normal file
View File

@ -0,0 +1,4 @@
package json
// This is the directory where our test fixtures are.
const fixtureDir = "./test-fixtures"

165
json/lex.go Normal file
View File

@ -0,0 +1,165 @@
package json
import (
"bytes"
"fmt"
"strconv"
"unicode"
"unicode/utf8"
)
// This marks the end of the lexer
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 jsonLex struct {
Input string
pos int
width int
col, line int
err error
}
// The parser calls this method to get each new token.
func (x *jsonLex) Lex(yylval *jsonSymType) int {
for {
c := x.next()
if c == lexEOF {
return lexEOF
}
// Ignore all whitespace except a newline which we handle
// specially later.
if unicode.IsSpace(c) {
continue
}
// If it is a number, lex the number
if c >= '0' && c <= '9' {
x.backup()
return x.lexNumber(yylval)
}
switch c {
case ':':
return COLON
case ',':
return COMMA
case '[':
return LEFTBRACKET
case ']':
return RIGHTBRACKET
case '{':
return LEFTBRACE
case '}':
return RIGHTBRACE
case '"':
return x.lexString(yylval)
default:
x.createErr(fmt.Sprintf("unexpected character: %c", c))
return lexEOF
}
}
}
// lexNumber lexes out a number
func (x *jsonLex) lexNumber(yylval *jsonSymType) int {
var b bytes.Buffer
for {
c := x.next()
if c == lexEOF {
break
}
// No more numeric characters
if c < '0' || c > '9' {
x.backup()
break
}
if _, err := b.WriteRune(c); err != nil {
x.createErr(fmt.Sprintf("Internal error: %s", err))
return lexEOF
}
}
v, err := strconv.ParseInt(b.String(), 0, 0)
if err != nil {
x.createErr(fmt.Sprintf("Expected number: %s", err))
return lexEOF
}
yylval.num = int(v)
return NUMBER
}
// lexString extracts a string from the input
func (x *jsonLex) lexString(yylval *jsonSymType) int {
var b bytes.Buffer
for {
c := x.next()
if c == lexEOF {
break
}
// String end
if c == '"' {
break
}
if _, err := b.WriteRune(c); err != nil {
return lexEOF
}
}
yylval.str = b.String()
return STRING
}
// Return the next rune for the lexer.
func (x *jsonLex) 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
x.col += 1
if x.line == 0 {
x.line = 1
}
if r == '\n' {
x.line += 1
x.col = 0
}
return r
}
// peek returns but does not consume the next rune in the input
func (x *jsonLex) peek() rune {
r := x.next()
x.backup()
return r
}
// backup steps back one rune. Can only be called once per next.
func (x *jsonLex) backup() {
x.col -= 1
x.pos -= x.width
}
// createErr records the given error
func (x *jsonLex) createErr(msg string) {
x.err = fmt.Errorf("Line %d, column %d: %s", x.line, x.col, msg)
}
// The parser calls this method on a parse error.
func (x *jsonLex) Error(s string) {
x.createErr(s)
}

78
json/lex_test.go Normal file
View File

@ -0,0 +1,78 @@
package json
import (
"io/ioutil"
"path/filepath"
"reflect"
"testing"
)
func TestLexJson(t *testing.T) {
cases := []struct {
Input string
Output []int
}{
{
"basic.json",
[]int{
LEFTBRACE,
STRING, COLON, STRING,
RIGHTBRACE,
lexEOF,
},
},
{
"array.json",
[]int{
LEFTBRACE,
STRING, COLON, LEFTBRACKET,
NUMBER, COMMA, NUMBER, COMMA, STRING,
RIGHTBRACKET, COMMA,
STRING, COLON, STRING,
RIGHTBRACE,
lexEOF,
},
},
{
"object.json",
[]int{
LEFTBRACE,
STRING, COLON, LEFTBRACE,
STRING, COLON, LEFTBRACKET,
NUMBER, COMMA, NUMBER,
RIGHTBRACKET,
RIGHTBRACE,
RIGHTBRACE,
lexEOF,
},
},
}
for _, tc := range cases {
d, err := ioutil.ReadFile(filepath.Join(fixtureDir, tc.Input))
if err != nil {
t.Fatalf("err: %s", err)
}
l := &jsonLex{Input: string(d)}
var actual []int
for {
token := l.Lex(new(jsonSymType))
actual = append(actual, token)
if token == lexEOF {
break
}
if len(actual) > 500 {
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)
}
}
}

40
json/parse.go Normal file
View File

@ -0,0 +1,40 @@
package json
import (
"sync"
"github.com/hashicorp/hcl/ast"
"github.com/hashicorp/terraform/helper/multierror"
)
// jsonErrors are the errors built up from parsing. These should not
// be accessed directly.
var jsonErrors []error
var jsonLock sync.Mutex
var jsonResult *ast.ObjectNode
// Parse parses the given string and returns the result.
func Parse(v string) (*ast.ObjectNode, error) {
jsonLock.Lock()
defer jsonLock.Unlock()
jsonErrors = nil
jsonResult = nil
// Parse
lex := &jsonLex{Input: v}
jsonParse(lex)
// If we have an error in the lexer itself, return it
if lex.err != nil {
return nil, lex.err
}
// Build up the errors
var err error
if len(jsonErrors) > 0 {
err = &multierror.Error{Errors: jsonErrors}
jsonResult = nil
}
return jsonResult, err
}

136
json/parse.y Normal file
View File

@ -0,0 +1,136 @@
// This is the yacc input for creating the parser for HCL JSON.
%{
package json
import (
"github.com/hashicorp/hcl/ast"
)
%}
%union {
array ast.ListNode
assign ast.AssignmentNode
item ast.Node
list []ast.Node
num int
str string
obj ast.ObjectNode
}
%type <array> array
%type <assign> pair
%type <item> value
%type <list> elements members
%type <obj> object
%token <num> NUMBER
%token <str> COLON COMMA IDENTIFIER EQUAL NEWLINE STRING
%token <str> LEFTBRACE RIGHTBRACE LEFTBRACKET RIGHTBRACKET
%token <str> TRUE FALSE NULL
%%
top:
object
{
obj := $1
jsonResult = &obj
}
object:
LEFTBRACE members RIGHTBRACE
{
$$ = ast.ObjectNode{Elem: $2}
}
| LEFTBRACE RIGHTBRACE
{
$$ = ast.ObjectNode{}
}
members:
pair
{
$$ = []ast.Node{$1}
}
| pair COMMA members
{
$$ = append($3, $1)
}
pair:
STRING COLON value
{
$$ = ast.AssignmentNode{
Key: $1,
Value: $3,
}
}
value:
STRING
{
$$ = ast.LiteralNode{
Type: ast.ValueTypeString,
Value: $1,
}
}
| NUMBER
{
$$ = ast.LiteralNode{
Type: ast.ValueTypeInt,
Value: $1,
}
}
| object
{
$$ = $1
}
| array
{
$$ = $1
}
| TRUE
{
$$ = ast.LiteralNode{
Type: ast.ValueTypeBool,
Value: true,
}
}
| FALSE
{
$$ = ast.LiteralNode{
Type: ast.ValueTypeBool,
Value: false,
}
}
| NULL
{
$$ = ast.LiteralNode{
Type: ast.ValueTypeNil,
Value: nil,
}
}
array:
LEFTBRACKET RIGHTBRACKET
{
$$ = ast.ListNode{}
}
| LEFTBRACKET elements RIGHTBRACKET
{
$$ = ast.ListNode{Elem: $2}
}
elements:
value
{
$$ = []ast.Node{$1}
}
| value COMMA elements
{
$$ = append($3, $1)
}
%%

39
json/parse_test.go Normal file
View File

@ -0,0 +1,39 @@
package json
import (
"io/ioutil"
"path/filepath"
"testing"
)
func TestParse(t *testing.T) {
cases := []struct {
Name string
Err bool
}{
{
"basic.json",
false,
},
{
"object.json",
false,
},
{
"array.json",
false,
},
}
for _, tc := range cases {
d, err := ioutil.ReadFile(filepath.Join(fixtureDir, tc.Name))
if err != nil {
t.Fatalf("err: %s", err)
}
_, err = Parse(string(d))
if (err != nil) != tc.Err {
t.Fatalf("Input: %s\n\nError: %s", tc.Name, err)
}
}
}

View File

@ -0,0 +1,4 @@
{
"foo": [1, 2, "bar"],
"bar": "baz"
}

View File

@ -0,0 +1,3 @@
{
"foo": "bar"
}

View File

@ -0,0 +1,5 @@
{
"foo": {
"bar": [1,2]
}
}

460
json/y.go Normal file
View File

@ -0,0 +1,460 @@
//line parse.y:3
package json
import __yyfmt__ "fmt"
//line parse.y:5
import (
"github.com/hashicorp/hcl/ast"
)
//line parse.y:12
type jsonSymType struct {
yys int
array ast.ListNode
assign ast.AssignmentNode
item ast.Node
list []ast.Node
num int
str string
obj ast.ObjectNode
}
const NUMBER = 57346
const COLON = 57347
const COMMA = 57348
const IDENTIFIER = 57349
const EQUAL = 57350
const NEWLINE = 57351
const STRING = 57352
const LEFTBRACE = 57353
const RIGHTBRACE = 57354
const LEFTBRACKET = 57355
const RIGHTBRACKET = 57356
const TRUE = 57357
const FALSE = 57358
const NULL = 57359
var jsonToknames = []string{
"NUMBER",
"COLON",
"COMMA",
"IDENTIFIER",
"EQUAL",
"NEWLINE",
"STRING",
"LEFTBRACE",
"RIGHTBRACE",
"LEFTBRACKET",
"RIGHTBRACKET",
"TRUE",
"FALSE",
"NULL",
}
var jsonStatenames = []string{}
const jsonEofCode = 1
const jsonErrCode = 2
const jsonMaxDepth = 200
//line parse.y:136
//line yacctab:1
var jsonExca = []int{
-1, 1,
1, -1,
-2, 0,
}
const jsonNprod = 18
const jsonPrivate = 57344
var jsonTokenNames []string
var jsonStates []string
const jsonLast = 35
var jsonAct = []int{
22, 14, 24, 7, 8, 5, 3, 13, 3, 14,
20, 21, 17, 18, 19, 13, 3, 23, 20, 7,
17, 18, 19, 4, 25, 9, 26, 10, 12, 15,
2, 1, 6, 11, 16,
}
var jsonPact = []int{
-5, -1000, -1000, -7, -8, -1000, 19, 22, -1000, 9,
5, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000,
-3, -1000, -12, 18, -1000, 5, -1000,
}
var jsonPgo = []int{
0, 34, 32, 17, 0, 23, 29, 31,
}
var jsonR1 = []int{
0, 7, 6, 6, 5, 5, 2, 3, 3, 3,
3, 3, 3, 3, 1, 1, 4, 4,
}
var jsonR2 = []int{
0, 1, 3, 2, 1, 3, 3, 1, 1, 1,
1, 1, 1, 1, 2, 3, 1, 3,
}
var jsonChk = []int{
-1000, -7, -6, 11, -5, 12, -2, 10, 12, 6,
5, -5, -3, 10, 4, -6, -1, 15, 16, 17,
13, 14, -4, -3, 14, 6, -4,
}
var jsonDef = []int{
0, -2, 1, 0, 0, 3, 4, 0, 2, 0,
0, 5, 6, 7, 8, 9, 10, 11, 12, 13,
0, 14, 0, 16, 15, 0, 17,
}
var jsonTok1 = []int{
1,
}
var jsonTok2 = []int{
2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17,
}
var jsonTok3 = []int{
0,
}
//line yaccpar:1
/* parser for yacc output */
var jsonDebug = 0
type jsonLexer interface {
Lex(lval *jsonSymType) int
Error(s string)
}
const jsonFlag = -1000
func jsonTokname(c int) string {
// 4 is TOKSTART above
if c >= 4 && c-4 < len(jsonToknames) {
if jsonToknames[c-4] != "" {
return jsonToknames[c-4]
}
}
return __yyfmt__.Sprintf("tok-%v", c)
}
func jsonStatname(s int) string {
if s >= 0 && s < len(jsonStatenames) {
if jsonStatenames[s] != "" {
return jsonStatenames[s]
}
}
return __yyfmt__.Sprintf("state-%v", s)
}
func jsonlex1(lex jsonLexer, lval *jsonSymType) int {
c := 0
char := lex.Lex(lval)
if char <= 0 {
c = jsonTok1[0]
goto out
}
if char < len(jsonTok1) {
c = jsonTok1[char]
goto out
}
if char >= jsonPrivate {
if char < jsonPrivate+len(jsonTok2) {
c = jsonTok2[char-jsonPrivate]
goto out
}
}
for i := 0; i < len(jsonTok3); i += 2 {
c = jsonTok3[i+0]
if c == char {
c = jsonTok3[i+1]
goto out
}
}
out:
if c == 0 {
c = jsonTok2[1] /* unknown char */
}
if jsonDebug >= 3 {
__yyfmt__.Printf("lex %s(%d)\n", jsonTokname(c), uint(char))
}
return c
}
func jsonParse(jsonlex jsonLexer) int {
var jsonn int
var jsonlval jsonSymType
var jsonVAL jsonSymType
jsonS := make([]jsonSymType, jsonMaxDepth)
Nerrs := 0 /* number of errors */
Errflag := 0 /* error recovery flag */
jsonstate := 0
jsonchar := -1
jsonp := -1
goto jsonstack
ret0:
return 0
ret1:
return 1
jsonstack:
/* put a state and value onto the stack */
if jsonDebug >= 4 {
__yyfmt__.Printf("char %v in %v\n", jsonTokname(jsonchar), jsonStatname(jsonstate))
}
jsonp++
if jsonp >= len(jsonS) {
nyys := make([]jsonSymType, len(jsonS)*2)
copy(nyys, jsonS)
jsonS = nyys
}
jsonS[jsonp] = jsonVAL
jsonS[jsonp].yys = jsonstate
jsonnewstate:
jsonn = jsonPact[jsonstate]
if jsonn <= jsonFlag {
goto jsondefault /* simple state */
}
if jsonchar < 0 {
jsonchar = jsonlex1(jsonlex, &jsonlval)
}
jsonn += jsonchar
if jsonn < 0 || jsonn >= jsonLast {
goto jsondefault
}
jsonn = jsonAct[jsonn]
if jsonChk[jsonn] == jsonchar { /* valid shift */
jsonchar = -1
jsonVAL = jsonlval
jsonstate = jsonn
if Errflag > 0 {
Errflag--
}
goto jsonstack
}
jsondefault:
/* default state action */
jsonn = jsonDef[jsonstate]
if jsonn == -2 {
if jsonchar < 0 {
jsonchar = jsonlex1(jsonlex, &jsonlval)
}
/* look through exception table */
xi := 0
for {
if jsonExca[xi+0] == -1 && jsonExca[xi+1] == jsonstate {
break
}
xi += 2
}
for xi += 2; ; xi += 2 {
jsonn = jsonExca[xi+0]
if jsonn < 0 || jsonn == jsonchar {
break
}
}
jsonn = jsonExca[xi+1]
if jsonn < 0 {
goto ret0
}
}
if jsonn == 0 {
/* error ... attempt to resume parsing */
switch Errflag {
case 0: /* brand new error */
jsonlex.Error("syntax error")
Nerrs++
if jsonDebug >= 1 {
__yyfmt__.Printf("%s", jsonStatname(jsonstate))
__yyfmt__.Printf(" saw %s\n", jsonTokname(jsonchar))
}
fallthrough
case 1, 2: /* incompletely recovered error ... try again */
Errflag = 3
/* find a state where "error" is a legal shift action */
for jsonp >= 0 {
jsonn = jsonPact[jsonS[jsonp].yys] + jsonErrCode
if jsonn >= 0 && jsonn < jsonLast {
jsonstate = jsonAct[jsonn] /* simulate a shift of "error" */
if jsonChk[jsonstate] == jsonErrCode {
goto jsonstack
}
}
/* the current p has no shift on "error", pop stack */
if jsonDebug >= 2 {
__yyfmt__.Printf("error recovery pops state %d\n", jsonS[jsonp].yys)
}
jsonp--
}
/* there is no state on the stack with an error shift ... abort */
goto ret1
case 3: /* no shift yet; clobber input char */
if jsonDebug >= 2 {
__yyfmt__.Printf("error recovery discards %s\n", jsonTokname(jsonchar))
}
if jsonchar == jsonEofCode {
goto ret1
}
jsonchar = -1
goto jsonnewstate /* try again in the same state */
}
}
/* reduction by production jsonn */
if jsonDebug >= 2 {
__yyfmt__.Printf("reduce %v in:\n\t%v\n", jsonn, jsonStatname(jsonstate))
}
jsonnt := jsonn
jsonpt := jsonp
_ = jsonpt // guard against "declared and not used"
jsonp -= jsonR2[jsonn]
jsonVAL = jsonS[jsonp+1]
/* consult goto table to find next state */
jsonn = jsonR1[jsonn]
jsong := jsonPgo[jsonn]
jsonj := jsong + jsonS[jsonp].yys + 1
if jsonj >= jsonLast {
jsonstate = jsonAct[jsong]
} else {
jsonstate = jsonAct[jsonj]
if jsonChk[jsonstate] != -jsonn {
jsonstate = jsonAct[jsong]
}
}
// dummy call; replaced with literal code
switch jsonnt {
case 1:
//line parse.y:37
{
obj := jsonS[jsonpt-0].obj
jsonResult = &obj
}
case 2:
//line parse.y:44
{
jsonVAL.obj = ast.ObjectNode{Elem: jsonS[jsonpt-1].list}
}
case 3:
//line parse.y:48
{
jsonVAL.obj = ast.ObjectNode{}
}
case 4:
//line parse.y:54
{
jsonVAL.list = []ast.Node{jsonS[jsonpt-0].assign}
}
case 5:
//line parse.y:58
{
jsonVAL.list = append(jsonS[jsonpt-0].list, jsonS[jsonpt-2].assign)
}
case 6:
//line parse.y:64
{
jsonVAL.assign = ast.AssignmentNode{
Key: jsonS[jsonpt-2].str,
Value: jsonS[jsonpt-0].item,
}
}
case 7:
//line parse.y:73
{
jsonVAL.item = ast.LiteralNode{
Type: ast.ValueTypeString,
Value: jsonS[jsonpt-0].str,
}
}
case 8:
//line parse.y:80
{
jsonVAL.item = ast.LiteralNode{
Type: ast.ValueTypeInt,
Value: jsonS[jsonpt-0].num,
}
}
case 9:
//line parse.y:87
{
jsonVAL.item = jsonS[jsonpt-0].obj
}
case 10:
//line parse.y:91
{
jsonVAL.item = jsonS[jsonpt-0].array
}
case 11:
//line parse.y:95
{
jsonVAL.item = ast.LiteralNode{
Type: ast.ValueTypeBool,
Value: true,
}
}
case 12:
//line parse.y:102
{
jsonVAL.item = ast.LiteralNode{
Type: ast.ValueTypeBool,
Value: false,
}
}
case 13:
//line parse.y:109
{
jsonVAL.item = ast.LiteralNode{
Type: ast.ValueTypeNil,
Value: nil,
}
}
case 14:
//line parse.y:118
{
jsonVAL.array = ast.ListNode{}
}
case 15:
//line parse.y:122
{
jsonVAL.array = ast.ListNode{Elem: jsonS[jsonpt-1].list}
}
case 16:
//line parse.y:128
{
jsonVAL.list = []ast.Node{jsonS[jsonpt-0].item}
}
case 17:
//line parse.y:132
{
jsonVAL.list = append(jsonS[jsonpt-0].list, jsonS[jsonpt-2].item)
}
}
goto jsonstack /* stack new state and value */
}

View File

@ -3,6 +3,7 @@ package hcl
import (
"sync"
"github.com/hashicorp/hcl/ast"
"github.com/hashicorp/terraform/helper/multierror"
)
@ -10,10 +11,10 @@ import (
// be accessed directly.
var hclErrors []error
var hclLock sync.Mutex
var hclResult *ObjectNode
var hclResult *ast.ObjectNode
// Parse parses the given string and returns the result.
func Parse(v string) (*ObjectNode, error) {
func Parse(v string) (*ast.ObjectNode, error) {
hclLock.Lock()
defer hclLock.Unlock()
hclErrors = nil

54
parse.y
View File

@ -3,14 +3,18 @@
%{
package hcl
import (
"github.com/hashicorp/hcl/ast"
)
%}
%union {
list []Node
listitem Node
num int
obj ObjectNode
str string
list []ast.Node
listitem ast.Node
num int
obj ast.ObjectNode
str string
}
%type <list> list objectlist
@ -27,7 +31,7 @@ package hcl
top:
objectlist
{
hclResult = &ObjectNode{
hclResult = &ast.ObjectNode{
Key: "",
Elem: $1,
}
@ -36,7 +40,7 @@ top:
objectlist:
objectitem
{
$$ = []Node{$1}
$$ = []ast.Node{$1}
}
| objectitem objectlist
{
@ -46,46 +50,46 @@ objectlist:
object:
LEFTBRACE objectlist RIGHTBRACE
{
$$ = ObjectNode{Elem: $2}
$$ = ast.ObjectNode{Elem: $2}
}
| LEFTBRACE RIGHTBRACE
{
$$ = ObjectNode{}
$$ = ast.ObjectNode{}
}
objectitem:
IDENTIFIER EQUAL NUMBER
{
$$ = AssignmentNode{
$$ = ast.AssignmentNode{
Key: $1,
Value: LiteralNode{
Type: ValueTypeInt,
Value: ast.LiteralNode{
Type: ast.ValueTypeInt,
Value: $3,
},
}
}
| IDENTIFIER EQUAL STRING
{
$$ = AssignmentNode{
$$ = ast.AssignmentNode{
Key: $1,
Value: LiteralNode{
Type: ValueTypeString,
Value: ast.LiteralNode{
Type: ast.ValueTypeString,
Value: $3,
},
}
}
| IDENTIFIER EQUAL object
{
$$ = AssignmentNode{
$$ = ast.AssignmentNode{
Key: $1,
Value: $3,
}
}
| IDENTIFIER EQUAL LEFTBRACKET list RIGHTBRACKET
{
$$ = AssignmentNode{
$$ = ast.AssignmentNode{
Key: $1,
Value: ListNode{Elem: $4},
Value: ast.ListNode{Elem: $4},
}
}
| block
@ -101,9 +105,9 @@ block:
}
| blockId block
{
$$ = ObjectNode{
$$ = ast.ObjectNode{
Key: $1,
Elem: []Node{$2},
Elem: []ast.Node{$2},
}
}
@ -120,7 +124,7 @@ blockId:
list:
listitem
{
$$ = []Node{$1}
$$ = []ast.Node{$1}
}
| list COMMA listitem
{
@ -130,15 +134,15 @@ list:
listitem:
NUMBER
{
$$ = LiteralNode{
Type: ValueTypeInt,
$$ = ast.LiteralNode{
Type: ast.ValueTypeInt,
Value: $1,
}
}
| STRING
{
$$ = LiteralNode{
Type: ValueTypeString,
$$ = ast.LiteralNode{
Type: ast.ValueTypeString,
Value: $1,
}
}

467
y.go Normal file
View File

@ -0,0 +1,467 @@
//line parse.y:4
package hcl
import __yyfmt__ "fmt"
//line parse.y:4
import (
"github.com/hashicorp/hcl/ast"
)
//line parse.y:12
type hclSymType struct {
yys int
list []ast.Node
listitem ast.Node
num int
obj ast.ObjectNode
str string
}
const NUMBER = 57346
const COMMA = 57347
const IDENTIFIER = 57348
const EQUAL = 57349
const NEWLINE = 57350
const STRING = 57351
const LEFTBRACE = 57352
const RIGHTBRACE = 57353
const LEFTBRACKET = 57354
const RIGHTBRACKET = 57355
var hclToknames = []string{
"NUMBER",
"COMMA",
"IDENTIFIER",
"EQUAL",
"NEWLINE",
"STRING",
"LEFTBRACE",
"RIGHTBRACE",
"LEFTBRACKET",
"RIGHTBRACKET",
}
var hclStatenames = []string{}
const hclEofCode = 1
const hclErrCode = 2
const hclMaxDepth = 200
//line parse.y:150
//line yacctab:1
var hclExca = []int{
-1, 1,
1, -1,
-2, 0,
}
const hclNprod = 19
const hclPrivate = 57344
var hclTokenNames []string
var hclStates []string
const hclLast = 33
var hclAct = []int{
21, 14, 24, 26, 2, 1, 15, 12, 8, 17,
4, 25, 10, 7, 9, 19, 13, 18, 22, 7,
12, 4, 16, 23, 7, 5, 6, 27, 3, 20,
0, 0, 11,
}
var hclPact = []int{
15, -1000, -1000, 15, 7, -1000, 10, -1000, -1000, -3,
-1000, -1000, 4, -1000, -1000, -1000, -1000, 14, -9, -1000,
-2, -1000, -1000, -1000, -1000, -1000, 14, -1000,
}
var hclPgo = []int{
0, 29, 4, 0, 28, 25, 12, 26, 5,
}
var hclR1 = []int{
0, 8, 2, 2, 6, 6, 4, 4, 4, 4,
4, 5, 5, 7, 7, 1, 1, 3, 3,
}
var hclR2 = []int{
0, 1, 1, 2, 3, 2, 3, 3, 3, 5,
1, 2, 2, 1, 1, 1, 3, 1, 1,
}
var hclChk = []int{
-1000, -8, -2, -4, 6, -5, -7, 9, -2, 7,
-6, -5, 10, 6, 4, 9, -6, 12, -2, 11,
-1, -3, 4, 9, 11, 13, 5, -3,
}
var hclDef = []int{
0, -2, 1, 2, 13, 10, 0, 14, 3, 0,
11, 12, 0, 13, 6, 7, 8, 0, 0, 5,
0, 15, 17, 18, 4, 9, 0, 16,
}
var hclTok1 = []int{
1,
}
var hclTok2 = []int{
2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13,
}
var hclTok3 = []int{
0,
}
//line yaccpar:1
/* parser for yacc output */
var hclDebug = 0
type hclLexer interface {
Lex(lval *hclSymType) int
Error(s string)
}
const hclFlag = -1000
func hclTokname(c int) string {
// 4 is TOKSTART above
if c >= 4 && c-4 < len(hclToknames) {
if hclToknames[c-4] != "" {
return hclToknames[c-4]
}
}
return __yyfmt__.Sprintf("tok-%v", c)
}
func hclStatname(s int) string {
if s >= 0 && s < len(hclStatenames) {
if hclStatenames[s] != "" {
return hclStatenames[s]
}
}
return __yyfmt__.Sprintf("state-%v", s)
}
func hcllex1(lex hclLexer, lval *hclSymType) int {
c := 0
char := lex.Lex(lval)
if char <= 0 {
c = hclTok1[0]
goto out
}
if char < len(hclTok1) {
c = hclTok1[char]
goto out
}
if char >= hclPrivate {
if char < hclPrivate+len(hclTok2) {
c = hclTok2[char-hclPrivate]
goto out
}
}
for i := 0; i < len(hclTok3); i += 2 {
c = hclTok3[i+0]
if c == char {
c = hclTok3[i+1]
goto out
}
}
out:
if c == 0 {
c = hclTok2[1] /* unknown char */
}
if hclDebug >= 3 {
__yyfmt__.Printf("lex %s(%d)\n", hclTokname(c), uint(char))
}
return c
}
func hclParse(hcllex hclLexer) int {
var hcln int
var hcllval hclSymType
var hclVAL hclSymType
hclS := make([]hclSymType, hclMaxDepth)
Nerrs := 0 /* number of errors */
Errflag := 0 /* error recovery flag */
hclstate := 0
hclchar := -1
hclp := -1
goto hclstack
ret0:
return 0
ret1:
return 1
hclstack:
/* put a state and value onto the stack */
if hclDebug >= 4 {
__yyfmt__.Printf("char %v in %v\n", hclTokname(hclchar), hclStatname(hclstate))
}
hclp++
if hclp >= len(hclS) {
nyys := make([]hclSymType, len(hclS)*2)
copy(nyys, hclS)
hclS = nyys
}
hclS[hclp] = hclVAL
hclS[hclp].yys = hclstate
hclnewstate:
hcln = hclPact[hclstate]
if hcln <= hclFlag {
goto hcldefault /* simple state */
}
if hclchar < 0 {
hclchar = hcllex1(hcllex, &hcllval)
}
hcln += hclchar
if hcln < 0 || hcln >= hclLast {
goto hcldefault
}
hcln = hclAct[hcln]
if hclChk[hcln] == hclchar { /* valid shift */
hclchar = -1
hclVAL = hcllval
hclstate = hcln
if Errflag > 0 {
Errflag--
}
goto hclstack
}
hcldefault:
/* default state action */
hcln = hclDef[hclstate]
if hcln == -2 {
if hclchar < 0 {
hclchar = hcllex1(hcllex, &hcllval)
}
/* look through exception table */
xi := 0
for {
if hclExca[xi+0] == -1 && hclExca[xi+1] == hclstate {
break
}
xi += 2
}
for xi += 2; ; xi += 2 {
hcln = hclExca[xi+0]
if hcln < 0 || hcln == hclchar {
break
}
}
hcln = hclExca[xi+1]
if hcln < 0 {
goto ret0
}
}
if hcln == 0 {
/* error ... attempt to resume parsing */
switch Errflag {
case 0: /* brand new error */
hcllex.Error("syntax error")
Nerrs++
if hclDebug >= 1 {
__yyfmt__.Printf("%s", hclStatname(hclstate))
__yyfmt__.Printf(" saw %s\n", hclTokname(hclchar))
}
fallthrough
case 1, 2: /* incompletely recovered error ... try again */
Errflag = 3
/* find a state where "error" is a legal shift action */
for hclp >= 0 {
hcln = hclPact[hclS[hclp].yys] + hclErrCode
if hcln >= 0 && hcln < hclLast {
hclstate = hclAct[hcln] /* simulate a shift of "error" */
if hclChk[hclstate] == hclErrCode {
goto hclstack
}
}
/* the current p has no shift on "error", pop stack */
if hclDebug >= 2 {
__yyfmt__.Printf("error recovery pops state %d\n", hclS[hclp].yys)
}
hclp--
}
/* there is no state on the stack with an error shift ... abort */
goto ret1
case 3: /* no shift yet; clobber input char */
if hclDebug >= 2 {
__yyfmt__.Printf("error recovery discards %s\n", hclTokname(hclchar))
}
if hclchar == hclEofCode {
goto ret1
}
hclchar = -1
goto hclnewstate /* try again in the same state */
}
}
/* reduction by production hcln */
if hclDebug >= 2 {
__yyfmt__.Printf("reduce %v in:\n\t%v\n", hcln, hclStatname(hclstate))
}
hclnt := hcln
hclpt := hclp
_ = hclpt // guard against "declared and not used"
hclp -= hclR2[hcln]
hclVAL = hclS[hclp+1]
/* consult goto table to find next state */
hcln = hclR1[hcln]
hclg := hclPgo[hcln]
hclj := hclg + hclS[hclp].yys + 1
if hclj >= hclLast {
hclstate = hclAct[hclg]
} else {
hclstate = hclAct[hclj]
if hclChk[hclstate] != -hcln {
hclstate = hclAct[hclg]
}
}
// dummy call; replaced with literal code
switch hclnt {
case 1:
//line parse.y:33
{
hclResult = &ast.ObjectNode{
Key: "",
Elem: hclS[hclpt-0].list,
}
}
case 2:
//line parse.y:42
{
hclVAL.list = []ast.Node{hclS[hclpt-0].listitem}
}
case 3:
//line parse.y:46
{
hclVAL.list = append(hclS[hclpt-0].list, hclS[hclpt-1].listitem)
}
case 4:
//line parse.y:52
{
hclVAL.obj = ast.ObjectNode{Elem: hclS[hclpt-1].list}
}
case 5:
//line parse.y:56
{
hclVAL.obj = ast.ObjectNode{}
}
case 6:
//line parse.y:62
{
hclVAL.listitem = ast.AssignmentNode{
Key: hclS[hclpt-2].str,
Value: ast.LiteralNode{
Type: ast.ValueTypeInt,
Value: hclS[hclpt-0].num,
},
}
}
case 7:
//line parse.y:72
{
hclVAL.listitem = ast.AssignmentNode{
Key: hclS[hclpt-2].str,
Value: ast.LiteralNode{
Type: ast.ValueTypeString,
Value: hclS[hclpt-0].str,
},
}
}
case 8:
//line parse.y:82
{
hclVAL.listitem = ast.AssignmentNode{
Key: hclS[hclpt-2].str,
Value: hclS[hclpt-0].obj,
}
}
case 9:
//line parse.y:89
{
hclVAL.listitem = ast.AssignmentNode{
Key: hclS[hclpt-4].str,
Value: ast.ListNode{Elem: hclS[hclpt-1].list},
}
}
case 10:
//line parse.y:96
{
hclVAL.listitem = hclS[hclpt-0].obj
}
case 11:
//line parse.y:102
{
hclVAL.obj = hclS[hclpt-0].obj
hclVAL.obj.Key = hclS[hclpt-1].str
}
case 12:
//line parse.y:107
{
hclVAL.obj = ast.ObjectNode{
Key: hclS[hclpt-1].str,
Elem: []ast.Node{hclS[hclpt-0].obj},
}
}
case 13:
//line parse.y:116
{
hclVAL.str = hclS[hclpt-0].str
}
case 14:
//line parse.y:120
{
hclVAL.str = hclS[hclpt-0].str
}
case 15:
//line parse.y:126
{
hclVAL.list = []ast.Node{hclS[hclpt-0].listitem}
}
case 16:
//line parse.y:130
{
hclVAL.list = append(hclS[hclpt-2].list, hclS[hclpt-0].listitem)
}
case 17:
//line parse.y:136
{
hclVAL.listitem = ast.LiteralNode{
Type: ast.ValueTypeInt,
Value: hclS[hclpt-0].num,
}
}
case 18:
//line parse.y:143
{
hclVAL.listitem = ast.LiteralNode{
Type: ast.ValueTypeString,
Value: hclS[hclpt-0].str,
}
}
}
goto hclstack /* stack new state and value */
}