config: parser

This commit is contained in:
Mitchell Hashimoto 2014-07-22 15:23:01 -07:00
parent db160a0249
commit 1dcefba5c4
5 changed files with 227 additions and 2 deletions

2
.gitignore vendored
View File

@ -4,4 +4,6 @@ example.tf
terraform.tfplan
terraform.tfstate
bin/
config/y.go
config/y.output
vendor/

84
config/expr.y Normal file
View File

@ -0,0 +1,84 @@
// This is the yacc input for creating the parser for interpolation
// expressions in Go.
// To build it:
//
// go tool yacc -p "expr" expr.y (produces y.go)
//
%{
package config
import (
"fmt"
)
%}
%union {
expr Interpolation
str string
variable InterpolatedVariable
args []Interpolation
}
%type <args> args
%type <expr> expr
%type <str> string
%type <variable> variable
%token <str> STRING IDENTIFIER
%token <str> COMMA LEFTPAREN RIGHTPAREN
%%
top:
expr
{
fmt.Printf("%#v", $1)
}
expr:
string
{
$$ = &LiteralInterpolation{Literal: $1}
}
| variable
{
$$ = &VariableInterpolation{Variable: $1}
}
| IDENTIFIER LEFTPAREN args RIGHTPAREN
{
$$ = &FunctionInterpolation{Func: $1, Args: $3}
}
args:
{
$$ = nil
}
| expr COMMA expr
{
$$ = append($$, $1, $3)
}
| expr
{
$$ = append($$, $1)
}
string:
STRING
{
$$ = $1
}
variable:
IDENTIFIER
{
var err error
$$, err = NewInterpolatedVariable($1)
if err != nil {
panic(err)
}
}
%%

130
config/expr_lex.go Normal file
View File

@ -0,0 +1,130 @@
package config
import (
"bytes"
"log"
"unicode"
"unicode/utf8"
)
// 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 exprLex struct {
input string
pos int
width int
}
// The parser calls this method to get each new token.
func (x *exprLex) Lex(yylval *exprSymType) int {
for {
c := x.next()
if c == lexEOF {
return lexEOF
}
// Ignore all whitespace
if unicode.IsSpace(c) {
continue
}
switch c {
case '"':
return x.lexString(yylval)
case ',':
return COMMA
case '(':
return LEFTPAREN
case ')':
return RIGHTPAREN
default:
x.backup()
return x.lexId(yylval)
}
}
}
func (x *exprLex) lexId(yylval *exprSymType) int {
var b bytes.Buffer
for {
c := x.next()
if c == lexEOF {
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 != '*' &&
!unicode.IsLetter(c) &&
!unicode.IsNumber(c) {
x.backup()
break
}
if _, err := b.WriteRune(c); err != nil {
log.Printf("ERR: %s", err)
return lexEOF
}
}
yylval.str = b.String()
return IDENTIFIER
}
func (x *exprLex) lexString(yylval *exprSymType) 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 {
log.Printf("ERR: %s", err)
return lexEOF
}
}
yylval.str = b.String()
return STRING
}
// Return the next rune for the lexer.
func (x *exprLex) 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
return r
}
// peek returns but does not consume the next rune in the input
func (x *exprLex) peek() rune {
r := x.next()
x.backup()
return r
}
// backup steps back one rune. Can only be called once per next.
func (x *exprLex) backup() {
x.pos -= x.width
}
// The parser calls this method on a parse error.
func (x *exprLex) Error(s string) {
log.Printf("parse error: %s", s)
}

9
config/expr_test.go Normal file
View File

@ -0,0 +1,9 @@
package config
import (
"testing"
)
func TestExprParse(t *testing.T) {
exprParse(&exprLex{input: `lookup(var.foo)`})
}

View File

@ -37,8 +37,8 @@ type InterpolatedVariable interface {
// FunctionInterpolation is an Interpolation that executes a function
// with some variable number of arguments to generate a value.
type FunctionInterpolation struct {
Func InterpolationFunc
Args []InterpolatedVariable
Func string
Args []Interpolation
key string
}