Configuration file syntax and parsing for golang
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

193 lines
3.4 KiB

package forge
import (
"bufio"
"io"
"strings"
"github.com/brettlangdon/forge/token"
)
var eof = rune(0)
func isLetter(ch rune) bool {
return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z')
}
func isDigit(ch rune) bool {
return ('0' <= ch && ch <= '9')
}
func isWhitespace(ch rune) bool {
return (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r')
}
func isBoolean(str string) bool {
lower := strings.ToLower(str)
return lower == "true" || lower == "false"
}
func isNull(str string) bool {
return strings.ToLower(str) == "null"
}
func isInclude(str string) bool {
return strings.ToLower(str) == "include"
}
type Scanner struct {
curLine int
curCol int
curTok token.Token
curCh rune
newline bool
reader *bufio.Reader
}
func NewScanner(reader io.Reader) *Scanner {
scanner := &Scanner{
reader: bufio.NewReader(reader),
curLine: 0,
curCol: 0,
newline: false,
}
scanner.readRune()
return scanner
}
func (this *Scanner) readRune() {
if this.newline {
this.curLine += 1
this.curCol = 0
this.newline = false
} else {
this.curCol += 1
}
nextCh, _, err := this.reader.ReadRune()
if err != nil {
this.curCh = eof
return
}
this.curCh = nextCh
if this.curCh == '\n' {
this.newline = true
}
}
func (this *Scanner) parseIdentifier() {
this.curTok.ID = token.IDENTIFIER
this.curTok.Literal = string(this.curCh)
for {
this.readRune()
if !isLetter(this.curCh) && this.curCh != '_' {
break
}
this.curTok.Literal += string(this.curCh)
}
if isBoolean(this.curTok.Literal) {
this.curTok.ID = token.BOOLEAN
} else if isNull(this.curTok.Literal) {
this.curTok.ID = token.NULL
} else if isInclude(this.curTok.Literal) {
this.curTok.ID = token.INCLUDE
}
}
func (this *Scanner) parseNumber() {
this.curTok.ID = token.INTEGER
this.curTok.Literal = string(this.curCh)
digit := false
for {
this.readRune()
if this.curCh == '.' && digit == false {
this.curTok.ID = token.FLOAT
digit = true
} else if !isDigit(this.curCh) {
break
}
this.curTok.Literal += string(this.curCh)
}
}
func (this *Scanner) parseString() {
this.curTok.ID = token.STRING
this.curTok.Literal = string(this.curCh)
for {
this.readRune()
if this.curCh == '"' {
break
}
this.curTok.Literal += string(this.curCh)
}
this.readRune()
}
func (this *Scanner) parseComment() {
this.curTok.ID = token.COMMENT
this.curTok.Literal = ""
for {
this.readRune()
if this.curCh == '\n' {
break
}
this.curTok.Literal += string(this.curCh)
}
this.readRune()
}
func (this *Scanner) skipWhitespace() {
for {
this.readRune()
if !isWhitespace(this.curCh) {
break
}
}
}
func (this *Scanner) NextToken() token.Token {
if isWhitespace(this.curCh) {
this.skipWhitespace()
}
this.curTok = token.Token{
ID: token.ILLEGAL,
Literal: string(this.curCh),
Line: this.curLine,
Column: this.curCol,
}
switch ch := this.curCh; {
case isLetter(ch) || ch == '_':
this.parseIdentifier()
case isDigit(ch):
this.parseNumber()
case ch == '#':
this.parseComment()
case ch == eof:
this.curTok.ID = token.EOF
this.curTok.Literal = "EOF"
default:
this.readRune()
this.curTok.Literal = string(ch)
switch ch {
case '=':
this.curTok.ID = token.EQUAL
case '"':
this.parseString()
case '{':
this.curTok.ID = token.LBRACKET
case '}':
this.curTok.ID = token.RBRACKET
case ';':
this.curTok.ID = token.SEMICOLON
case '.':
this.curTok.ID = token.PERIOD
}
}
return this.curTok
}