config/lang: have position in AST

This commit is contained in:
Mitchell Hashimoto 2015-01-12 00:28:47 -08:00
parent d3b1010444
commit 662760da11
11 changed files with 206 additions and 75 deletions

View File

@ -1,9 +1,25 @@
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.
Accept(Visitor)
// Pos returns the position of this node in some source.
Pos() Pos
}
// 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.

View File

@ -4,6 +4,7 @@ package ast
type Call struct {
Func string
Args []Node
Posx Pos
}
func (n *Call) Accept(v Visitor) {
@ -13,3 +14,7 @@ func (n *Call) Accept(v Visitor) {
v(n)
}
func (n *Call) Pos() Pos {
return n.Posx
}

View File

@ -8,6 +8,7 @@ import (
// concatenated. The result of all expressions must be a string.
type Concat struct {
Exprs []Node
Posx Pos
}
func (n *Concat) Accept(v Visitor) {
@ -18,6 +19,10 @@ func (n *Concat) Accept(v Visitor) {
v(n)
}
func (n *Concat) Pos() Pos {
return n.Posx
}
func (n *Concat) GoString() string {
return fmt.Sprintf("*%#v", *n)
}

View File

@ -9,12 +9,17 @@ import (
type LiteralNode struct {
Value interface{}
Type Type
Posx Pos
}
func (n *LiteralNode) Accept(v Visitor) {
v(n)
}
func (n *LiteralNode) Pos() Pos {
return n.Posx
}
func (n *LiteralNode) GoString() string {
return fmt.Sprintf("*%#v", *n)
}

View File

@ -7,12 +7,17 @@ import (
// VariableAccess represents a variable access.
type VariableAccess struct {
Name string
Posx Pos
}
func (n *VariableAccess) Accept(v Visitor) {
v(n)
}
func (n *VariableAccess) Pos() Pos {
return n.Posx
}
func (n *VariableAccess) GoString() string {
return fmt.Sprintf("*%#v", *n)
}

View File

@ -15,12 +15,15 @@ import (
node ast.Node
nodeList []ast.Node
str string
token *parserToken
}
%token <str> STRING IDENTIFIER PROGRAM_BRACKET_LEFT PROGRAM_BRACKET_RIGHT
%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> IDENTIFIER STRING
%type <node> expr interpolation literal literalModeTop literalModeValue
%type <nodeList> args
@ -48,6 +51,7 @@ literalModeTop:
$$ = &ast.Concat{
Exprs: result,
Posx: result[0].Pos(),
}
}
@ -74,11 +78,11 @@ expr:
}
| IDENTIFIER
{
$$ = &ast.VariableAccess{Name: $1}
$$ = &ast.VariableAccess{Name: $1.Value.(string), Posx: $1.Pos}
}
| IDENTIFIER PAREN_LEFT args PAREN_RIGHT
{
$$ = &ast.Call{Func: $1, Args: $3}
$$ = &ast.Call{Func: $1.Value.(string), Args: $3, Posx: $1.Pos}
}
args:
@ -97,7 +101,11 @@ args:
literal:
STRING
{
$$ = &ast.LiteralNode{Value: $1, Type: ast.TypeString}
$$ = &ast.LiteralNode{
Value: $1.Value.(string),
Type: ast.TypeString,
Posx: $1.Pos,
}
}
%%

View File

@ -5,6 +5,8 @@ import (
"fmt"
"unicode"
"unicode/utf8"
"github.com/hashicorp/terraform/config/lang/ast"
)
//go:generate go tool yacc -p parser lang.y
@ -22,6 +24,17 @@ type parserLex struct {
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
@ -43,6 +56,17 @@ func (x *parserLex) Lex(yylval *parserSymType) int {
x.mode = parserModeLiteral
}
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)
@ -81,8 +105,8 @@ func (x *parserLex) lexModeLiteral(yylval *parserSymType) int {
// If the string is empty, just skip it. We're still in
// an interpolation so we do this to avoid empty nodes.
if yylval.str == "" {
return x.Lex(yylval)
if yylval.token.Value.(string) == "" {
return x.lex(yylval)
}
}
@ -113,8 +137,8 @@ func (x *parserLex) lexModeInterpolation(yylval *parserSymType) int {
// If the string is empty and we're starting an interpolation,
// then just skip it to avoid empty string AST nodes
if yylval.str == "" {
return x.Lex(yylval)
if yylval.token.Value.(string) == "" {
return x.lex(yylval)
}
}
@ -165,7 +189,7 @@ func (x *parserLex) lexId(yylval *parserSymType) int {
}
}
yylval.str = b.String()
yylval.token = &parserToken{Value: b.String()}
return IDENTIFIER
}
@ -224,7 +248,7 @@ func (x *parserLex) lexString(yylval *parserSymType, quoted bool) (int, bool) {
}
}
yylval.str = b.String()
yylval.token = &parserToken{Value: b.String()}
return STRING, terminated
}
@ -238,6 +262,24 @@ func (x *parserLex) next() rune {
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
}
@ -251,6 +293,14 @@ func (x *parserLex) peek() rune {
// 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.

View File

@ -19,6 +19,7 @@ func TestParse(t *testing.T) {
&ast.LiteralNode{
Value: "foo",
Type: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
},
},
@ -26,13 +27,16 @@ func TestParse(t *testing.T) {
"foo ${var.bar}",
false,
&ast.Concat{
Posx: ast.Pos{Column: 1, Line: 1},
Exprs: []ast.Node{
&ast.LiteralNode{
Value: "foo ",
Type: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
},
&ast.VariableAccess{
Name: "var.bar",
Posx: ast.Pos{Column: 7, Line: 1},
},
},
},
@ -42,17 +46,21 @@ func TestParse(t *testing.T) {
"foo ${var.bar} baz",
false,
&ast.Concat{
Posx: ast.Pos{Column: 1, Line: 1},
Exprs: []ast.Node{
&ast.LiteralNode{
Value: "foo ",
Type: 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",
Type: ast.TypeString,
Posx: ast.Pos{Column: 15, Line: 1},
},
},
},
@ -62,14 +70,17 @@ func TestParse(t *testing.T) {
"foo ${\"bar\"}",
false,
&ast.Concat{
Posx: ast.Pos{Column: 1, Line: 1},
Exprs: []ast.Node{
&ast.LiteralNode{
Value: "foo ",
Type: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
},
&ast.LiteralNode{
Value: "bar",
Type: ast.TypeString,
Posx: ast.Pos{Column: 7, Line: 1},
},
},
},
@ -81,6 +92,7 @@ func TestParse(t *testing.T) {
&ast.Call{
Func: "foo",
Args: nil,
Posx: ast.Pos{Column: 3, Line: 1},
},
},
@ -89,9 +101,11 @@ func TestParse(t *testing.T) {
false,
&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},
},
},
},
@ -102,12 +116,15 @@ func TestParse(t *testing.T) {
false,
&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},
},
},
},
@ -118,12 +135,15 @@ func TestParse(t *testing.T) {
false,
&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},
},
},
},
@ -135,19 +155,24 @@ func TestParse(t *testing.T) {
`foo ${"bar ${baz}"}`,
false,
&ast.Concat{
Posx: ast.Pos{Column: 1, Line: 1},
Exprs: []ast.Node{
&ast.LiteralNode{
Value: "foo ",
Type: 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 ",
Type: ast.TypeString,
Posx: ast.Pos{Column: 7, Line: 1},
},
&ast.VariableAccess{
Name: "baz",
Posx: ast.Pos{Column: 14, Line: 1},
},
},
},

View File

@ -41,7 +41,7 @@ func (v *TypeVisitor) visit(raw ast.Node) {
case *ast.VariableAccess:
v.visitVariableAccess(n)
default:
v.err = fmt.Errorf("unknown node: %#v", raw)
v.createErr(n, fmt.Sprintf("unknown node: %#v", raw))
}
}
@ -59,7 +59,8 @@ func (v *TypeVisitor) visitConcat(n *ast.Concat) {
// All concat args must be strings, so validate that
for i, t := range types {
if t != ast.TypeString {
v.err = fmt.Errorf("%s: argument %d must be a sting", n, i+1)
v.createErr(n, fmt.Sprintf(
"argument %d must be a sting", n, i+1))
return
}
}
@ -76,7 +77,8 @@ func (v *TypeVisitor) visitVariableAccess(n *ast.VariableAccess) {
// Look up the variable in the map
variable, ok := v.VarMap[n.Name]
if !ok {
v.err = fmt.Errorf("unknown variable accessed: %s", n.Name)
v.createErr(n, fmt.Sprintf(
"unknown variable accessed: %s", n.Name))
return
}
@ -84,6 +86,10 @@ func (v *TypeVisitor) visitVariableAccess(n *ast.VariableAccess) {
v.stackPush(variable.Type)
}
func (v *TypeVisitor) createErr(n ast.Node, str string) {
v.err = fmt.Errorf("%s: %s", n.Pos(), str)
}
func (v *TypeVisitor) reset() {
v.stack = nil
v.err = nil

View File

@ -14,21 +14,20 @@ type parserSymType struct {
node ast.Node
nodeList []ast.Node
str string
token *parserToken
}
const STRING = 57346
const IDENTIFIER = 57347
const PROGRAM_BRACKET_LEFT = 57348
const PROGRAM_BRACKET_RIGHT = 57349
const PROGRAM_STRING_START = 57350
const PROGRAM_STRING_END = 57351
const PAREN_LEFT = 57352
const PAREN_RIGHT = 57353
const COMMA = 57354
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 IDENTIFIER = 57353
const STRING = 57354
var parserToknames = []string{
"STRING",
"IDENTIFIER",
"PROGRAM_BRACKET_LEFT",
"PROGRAM_BRACKET_RIGHT",
"PROGRAM_STRING_START",
@ -36,6 +35,8 @@ var parserToknames = []string{
"PAREN_LEFT",
"PAREN_RIGHT",
"COMMA",
"IDENTIFIER",
"STRING",
}
var parserStatenames = []string{}
@ -43,7 +44,7 @@ const parserEofCode = 1
const parserErrCode = 2
const parserMaxDepth = 200
//line lang.y:103
//line lang.y:111
//line yacctab:1
var parserExca = []int{
@ -62,18 +63,18 @@ const parserLast = 21
var parserAct = []int{
9, 16, 17, 13, 3, 12, 1, 8, 6, 11,
7, 6, 14, 7, 15, 8, 10, 2, 18, 4,
9, 7, 7, 13, 3, 16, 17, 8, 11, 6,
6, 12, 10, 2, 15, 8, 1, 14, 18, 4,
5,
}
var parserPact = []int{
7, -1000, 7, -1000, -1000, -1000, -1000, 4, -1000, -2,
7, -7, -1000, 4, -10, -1000, -1000, 4, -1000,
-2, -1000, -2, -1000, -1000, -1000, -1000, -3, -1000, 6,
-2, -5, -1000, -3, -4, -1000, -1000, -3, -1000,
}
var parserPgo = []int{
0, 0, 20, 19, 16, 4, 12, 6,
0, 0, 20, 19, 12, 4, 17, 16,
}
var parserR1 = []int{
@ -87,8 +88,8 @@ var parserR2 = []int{
}
var parserChk = []int{
-1000, -7, -4, -5, -3, -2, 4, 6, -5, -1,
-4, 5, 7, 10, -6, -1, 11, 12, -1,
-1000, -7, -4, -5, -3, -2, 12, 4, -5, -1,
-4, 11, 5, 8, -6, -1, 9, 10, -1,
}
var parserDef = []int{
@ -334,17 +335,17 @@ parserdefault:
switch parsernt {
case 1:
//line lang.y:31
//line lang.y:34
{
parserResult = parserS[parserpt-0].node
}
case 2:
//line lang.y:37
//line lang.y:40
{
parserVAL.node = parserS[parserpt-0].node
}
case 3:
//line lang.y:41
//line lang.y:44
{
var result []ast.Node
if c, ok := parserS[parserpt-1].node.(*ast.Concat); ok {
@ -355,57 +356,62 @@ parserdefault:
parserVAL.node = &ast.Concat{
Exprs: result,
Posx: result[0].Pos(),
}
}
case 4:
//line lang.y:56
{
parserVAL.node = parserS[parserpt-0].node
}
case 5:
//line lang.y:60
{
parserVAL.node = parserS[parserpt-0].node
}
case 5:
//line lang.y:64
{
parserVAL.node = parserS[parserpt-0].node
}
case 6:
//line lang.y:66
//line lang.y:70
{
parserVAL.node = parserS[parserpt-1].node
}
case 7:
//line lang.y:72
//line lang.y:76
{
parserVAL.node = parserS[parserpt-0].node
}
case 8:
//line lang.y:76
{
parserVAL.node = &ast.VariableAccess{Name: parserS[parserpt-0].str}
}
case 9:
//line lang.y:80
{
parserVAL.node = &ast.Call{Func: parserS[parserpt-3].str, Args: parserS[parserpt-1].nodeList}
parserVAL.node = &ast.VariableAccess{Name: parserS[parserpt-0].token.Value.(string), Posx: parserS[parserpt-0].token.Pos}
}
case 9:
//line lang.y:84
{
parserVAL.node = &ast.Call{Func: parserS[parserpt-3].token.Value.(string), Args: parserS[parserpt-1].nodeList, Posx: parserS[parserpt-3].token.Pos}
}
case 10:
//line lang.y:85
//line lang.y:89
{
parserVAL.nodeList = nil
}
case 11:
//line lang.y:89
//line lang.y:93
{
parserVAL.nodeList = append(parserS[parserpt-2].nodeList, parserS[parserpt-0].node)
}
case 12:
//line lang.y:93
//line lang.y:97
{
parserVAL.nodeList = append(parserVAL.nodeList, parserS[parserpt-0].node)
}
case 13:
//line lang.y:99
//line lang.y:103
{
parserVAL.node = &ast.LiteralNode{Value: parserS[parserpt-0].str, Type: ast.TypeString}
parserVAL.node = &ast.LiteralNode{
Value: parserS[parserpt-0].token.Value.(string),
Type: ast.TypeString,
Posx: parserS[parserpt-0].token.Pos,
}
}
}
goto parserstack /* stack new state and value */

View File

@ -2,8 +2,8 @@
state 0
$accept: .top $end
STRING shift 6
PROGRAM_BRACKET_LEFT shift 7
STRING shift 6
. error
interpolation goto 5
@ -23,9 +23,9 @@ state 2
top: literalModeTop. (1)
literalModeTop: literalModeTop.literalModeValue
STRING shift 6
PROGRAM_BRACKET_LEFT shift 7
. reduce 1 (src line 29)
STRING shift 6
. reduce 1 (src line 32)
interpolation goto 5
literal goto 4
@ -34,33 +34,33 @@ state 2
state 3
literalModeTop: literalModeValue. (2)
. reduce 2 (src line 35)
. reduce 2 (src line 38)
state 4
literalModeValue: literal. (4)
. reduce 4 (src line 54)
. reduce 4 (src line 58)
state 5
literalModeValue: interpolation. (5)
. reduce 5 (src line 59)
. reduce 5 (src line 63)
state 6
literal: STRING. (13)
. reduce 13 (src line 97)
. reduce 13 (src line 101)
state 7
interpolation: PROGRAM_BRACKET_LEFT.expr PROGRAM_BRACKET_RIGHT
STRING shift 6
IDENTIFIER shift 11
PROGRAM_BRACKET_LEFT shift 7
IDENTIFIER shift 11
STRING shift 6
. error
expr goto 9
@ -72,7 +72,7 @@ state 7
state 8
literalModeTop: literalModeTop literalModeValue. (3)
. reduce 3 (src line 40)
. reduce 3 (src line 43)
state 9
@ -86,9 +86,9 @@ state 10
literalModeTop: literalModeTop.literalModeValue
expr: literalModeTop. (7)
STRING shift 6
PROGRAM_BRACKET_LEFT shift 7
. reduce 7 (src line 70)
STRING shift 6
. reduce 7 (src line 74)
interpolation goto 5
literal goto 4
@ -99,23 +99,23 @@ state 11
expr: IDENTIFIER.PAREN_LEFT args PAREN_RIGHT
PAREN_LEFT shift 13
. reduce 8 (src line 75)
. reduce 8 (src line 79)
state 12
interpolation: PROGRAM_BRACKET_LEFT expr PROGRAM_BRACKET_RIGHT. (6)
. reduce 6 (src line 64)
. reduce 6 (src line 68)
state 13
expr: IDENTIFIER PAREN_LEFT.args PAREN_RIGHT
args: . (10)
STRING shift 6
IDENTIFIER shift 11
PROGRAM_BRACKET_LEFT shift 7
. reduce 10 (src line 84)
IDENTIFIER shift 11
STRING shift 6
. reduce 10 (src line 88)
expr goto 15
interpolation goto 5
@ -136,21 +136,21 @@ state 14
state 15
args: expr. (12)
. reduce 12 (src line 92)
. reduce 12 (src line 96)
state 16
expr: IDENTIFIER PAREN_LEFT args PAREN_RIGHT. (9)
. reduce 9 (src line 79)
. reduce 9 (src line 83)
state 17
args: args COMMA.expr
STRING shift 6
IDENTIFIER shift 11
PROGRAM_BRACKET_LEFT shift 7
IDENTIFIER shift 11
STRING shift 6
. error
expr goto 18
@ -162,7 +162,7 @@ state 17
state 18
args: args COMMA expr. (11)
. reduce 11 (src line 88)
. reduce 11 (src line 92)
12 terminals, 8 nonterminals