terraform/config/lang/lex.go

188 lines
3.5 KiB
Go
Raw Normal View History

2015-01-11 21:38:45 +01:00
package lang
import (
"bytes"
"fmt"
"unicode"
"unicode/utf8"
)
//go:generate go tool yacc -p parser lang.y
// 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 parserLex struct {
Err error
Input string
interpolationDepth int
pos int
width int
}
// The parser calls this method to get each new token.
func (x *parserLex) Lex(yylval *parserSymType) int {
for {
c := x.next()
if c == lexEOF {
return lexEOF
}
// Are we starting an interpolation?
if c == '$' && x.peek() == '{' {
x.next()
x.interpolationDepth++
return PROGRAM_BRACKET_LEFT
}
2015-01-11 22:03:37 +01:00
if x.interpolationDepth == 0 {
// We're just a normal string that isn't part of any
// interpolation yet.
x.backup()
return x.lexString(yylval, false)
}
// Ignore all whitespace
if unicode.IsSpace(c) {
continue
}
2015-01-11 21:38:45 +01:00
// If we see a double quote and we're in an interpolation, then
// we are lexing a string.
2015-01-11 22:03:37 +01:00
if c == '"' {
2015-01-11 21:38:45 +01:00
return x.lexString(yylval, true)
}
switch c {
case '}':
x.interpolationDepth--
return PROGRAM_BRACKET_RIGHT
2015-01-11 22:03:37 +01:00
case '(':
return PAREN_LEFT
case ')':
return PAREN_RIGHT
case ',':
return COMMA
2015-01-11 21:38:45 +01:00
default:
x.backup()
2015-01-11 22:03:37 +01:00
return x.lexId(yylval)
2015-01-11 21:38:45 +01:00
}
}
}
func (x *parserLex) lexId(yylval *parserSymType) 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 {
x.Error(err.Error())
return lexEOF
}
}
yylval.str = b.String()
return IDENTIFIER
}
func (x *parserLex) lexString(yylval *parserSymType, quoted bool) int {
var b bytes.Buffer
for {
c := x.next()
if c == lexEOF {
break
}
// Behavior is a bit different if we're lexing within a quoted string.
if quoted {
// If its a double quote, we've reached the end of the string
if c == '"' {
break
}
// Let's check to see if we're escaping anything.
if c == '\\' {
switch n := x.next(); n {
case '\\':
fallthrough
case '"':
c = n
case 'n':
c = '\n'
default:
x.backup()
}
}
}
// If we hit a '}' and we're in a program, then end it.
if c == '}' && x.interpolationDepth > 0 {
x.backup()
break
}
// If we hit a dollar sign, then check if we're starting
// another interpolation. If so, then we're done.
if c == '$' && x.peek() == '{' {
x.backup()
break
}
if _, err := b.WriteRune(c); err != nil {
x.Error(err.Error())
return lexEOF
}
}
yylval.str = b.String()
return STRING
}
// Return the next rune for the lexer.
func (x *parserLex) 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 *parserLex) peek() rune {
r := x.next()
x.backup()
return r
}
// backup steps back one rune. Can only be called once per next.
func (x *parserLex) backup() {
x.pos -= x.width
}
// The parser calls this method on a parse error.
func (x *parserLex) Error(s string) {
x.Err = fmt.Errorf("parse error: %s", s)
}