forked from hashicorp/hcl
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
403 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
y.go | ||
y.output |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
default: test | ||
|
||
test: y.go | ||
go test | ||
|
||
y.go: parse.y | ||
go tool yacc -p "hcl" parse.y | ||
|
||
.PHONY: default test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package hcl | ||
|
||
// This is the directory where our test fixtures are. | ||
const fixtureDir = "./test-fixtures" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,246 @@ | ||
package hcl | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"log" | ||
"strconv" | ||
"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 hclLex struct { | ||
Input string | ||
|
||
pos int | ||
width int | ||
col, line int | ||
err error | ||
} | ||
|
||
// The parser calls this method to get each new token. | ||
func (x *hclLex) Lex(yylval *hclSymType) int { | ||
for { | ||
c := x.next() | ||
if c == lexEOF { | ||
return lexEOF | ||
} | ||
|
||
// Ignore all whitespace | ||
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 EQUAL | ||
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: | ||
x.backup() | ||
return x.lexId(yylval) | ||
} | ||
} | ||
} | ||
|
||
func (x *hclLex) consumeComment(c rune) bool { | ||
single := c == '#' | ||
if !single { | ||
c = x.next() | ||
if c != '/' && c != '*' { | ||
x.backup() | ||
x.createErr(fmt.Sprintf("comment expected, got '%c'", c)) | ||
return false | ||
} | ||
|
||
single = c == '/' | ||
} | ||
|
||
nested := 1 | ||
for { | ||
c = x.next() | ||
if c == lexEOF { | ||
x.backup() | ||
return true | ||
} | ||
|
||
// Single line comments continue until a '\n' | ||
if single { | ||
if c == '\n' { | ||
return true | ||
} | ||
|
||
continue | ||
} | ||
|
||
// Multi-line comments continue until a '*/' | ||
switch c { | ||
case '/': | ||
c = x.next() | ||
if c == '*' { | ||
nested++ | ||
} else { | ||
x.backup() | ||
} | ||
case '*': | ||
c = x.next() | ||
if c == '/' { | ||
nested-- | ||
} else { | ||
x.backup() | ||
} | ||
default: | ||
// Continue | ||
} | ||
|
||
// If we're done with the comment, return! | ||
if nested == 0 { | ||
return true | ||
} | ||
} | ||
} | ||
|
||
// lexId lexes an identifier | ||
func (x *hclLex) lexId(yylval *hclSymType) 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 != '.' && | ||
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 | ||
} | ||
|
||
// lexNumber lexes out a number | ||
func (x *hclLex) lexNumber(yylval *hclSymType) 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 *hclLex) lexString(yylval *hclSymType) 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 *hclLex) 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 *hclLex) peek() rune { | ||
r := x.next() | ||
x.backup() | ||
return r | ||
} | ||
|
||
// backup steps back one rune. Can only be called once per next. | ||
func (x *hclLex) backup() { | ||
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) | ||
} | ||
|
||
// The parser calls this method on a parse error. | ||
func (x *hclLex) Error(s string) { | ||
log.Printf("parse error: %s", s) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package hcl | ||
|
||
import ( | ||
"io/ioutil" | ||
"path/filepath" | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
func TestLex(t *testing.T) { | ||
cases := []struct { | ||
Input string | ||
Output []int | ||
}{ | ||
{ | ||
"comment.hcl", | ||
[]int{IDENTIFIER, EQUAL, STRING, lexEOF}, | ||
}, | ||
{ | ||
"structure.hcl", | ||
[]int{ | ||
IDENTIFIER, IDENTIFIER, STRING, LEFTBRACE, | ||
IDENTIFIER, EQUAL, NUMBER, SEMICOLON, | ||
RIGHTBRACE, lexEOF, | ||
}, | ||
}, | ||
} | ||
|
||
for _, tc := range cases { | ||
d, err := ioutil.ReadFile(filepath.Join(fixtureDir, tc.Input)) | ||
if err != nil { | ||
t.Fatalf("err: %s", err) | ||
} | ||
|
||
l := &hclLex{Input: string(d)} | ||
var actual []int | ||
for { | ||
token := l.Lex(new(hclSymType)) | ||
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", tc.Input, actual) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package hcl | ||
|
||
import ( | ||
"sync" | ||
) | ||
|
||
// exprErrors are the errors built up from parsing. These should not | ||
// be accessed directly. | ||
var exprErrors []error | ||
var exprLock sync.Mutex | ||
var exprResult []map[string]interface{} | ||
|
||
/* | ||
// ExprParse parses the given expression and returns an executable | ||
// Interpolation. | ||
func ExprParse(v string) (Interpolation, error) { | ||
exprLock.Lock() | ||
defer exprLock.Unlock() | ||
exprErrors = nil | ||
exprResult = nil | ||
// Parse | ||
exprParse(&exprLex{input: v}) | ||
// Build up the errors | ||
var err error | ||
if len(exprErrors) > 0 { | ||
err = &multierror.Error{Errors: exprErrors} | ||
exprResult = nil | ||
} | ||
return exprResult, err | ||
} | ||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// This is the yacc input for creating the parser for HCL. | ||
|
||
%{ | ||
package hcl | ||
|
||
%} | ||
|
||
%union { | ||
num int | ||
obj map[string]interface{} | ||
str string | ||
} | ||
|
||
%type <obj> object | ||
|
||
%token <num> NUMBER | ||
%token <str> IDENTIFIER EQUAL SEMICOLON STRING | ||
%token <str> LEFTBRACE RIGHTBRACE | ||
|
||
%% | ||
|
||
top: | ||
object | ||
{ | ||
exprResult = []map[string]interface{}{$1} | ||
} | ||
|
||
object: | ||
IDENTIFIER EQUAL STRING | ||
{ | ||
$$ = map[string]interface{}{$1: $3} | ||
} | ||
|
||
%% |
Oops, something went wrong.