sgf2gopher/lex.go

228 lines
3.5 KiB
Go

package main
import (
"fmt"
"strings"
)
const (
TEXT int = iota
SPC
NL
NDSTRT
OPAREN
CPAREN
OVAL
CVAL
INVALID
)
type tok struct {
kind int
val rune
}
type lexed struct {
ts []tok
n []node
curs int
depth int
}
func lex(r rune) tok {
switch r {
case ' ':
return tok{SPC, r}
case '\n':
return tok{NL, r}
case ';':
return tok{NDSTRT, r}
case '(':
return tok{OPAREN, r}
case ')':
return tok{CPAREN, r}
case '[':
return tok{OVAL, r}
case ']':
return tok{CVAL, r}
}
if r >= '!' && r <= '~' {
return tok{TEXT, r}
}
return tok{INVALID, r}
}
func generateTree(s []rune) []node {
l := lexed{make([]tok,0,10),make([]node,0,10),0, -1}
for _, x := range s {
l.ts = append(l.ts, lex(x))
}
l.parse()
return l.n
}
func (l *lexed) parse() {
parseloop:
for {
switch l.ts[l.curs].kind {
case OPAREN:
if l.depth < 0 {
l.depth++
} else {
l.eatSubTree()
}
case CPAREN:
l.depth--
if l.depth < 0 {
break parseloop
}
case NDSTRT:
err := l.next()
if err != nil {
break parseloop
}
no, err := l.parseNode()
if err != nil {
break parseloop
}
l.n = append(l.n, no)
}
err := l.next()
if err != nil {
break parseloop
}
}
}
func (l *lexed) eatSubTree() {
for l.depth > 0 {
switch l.ts[l.curs].kind {
case OPAREN:
l.depth++
case CPAREN:
l.depth--
}
err := l.next()
if err != nil {
break
}
}
}
func (l *lexed) eatVal() {
eatvalLoop:
for {
switch l.ts[l.curs].kind {
case CVAL:
break eatvalLoop
default:
err := l.next()
if err != nil {
break eatvalLoop
}
}
}
}
func (l *lexed) parseNode() (node, error) {
n := make(node, 0, 5)
nodeloop:
for {
switch l.ts[l.curs].kind {
case NDSTRT:
l.prev()
break nodeloop
case OPAREN, CPAREN:
l.prev()
break nodeloop
case TEXT:
p, err := l.parseProp()
if err != nil {
break nodeloop
}
n = append(n, p)
case OVAL:
l.eatVal()
}
err := l.next()
if err != nil {
break nodeloop
}
}
return n, nil
}
func (l *lexed) parseProp() (property, error) {
p := property{}
var k, v = false, false
var b strings.Builder
proploop:
for {
switch l.ts[l.curs].kind {
case SPC, NL:
if k {
b.WriteRune(l.ts[l.curs].val)
}
case OVAL:
if k {
return p, fmt.Errorf("Tried to add two properties")
}
k = true
p.kind = strings.ToUpper(b.String())
b.Reset()
case CVAL:
if !k {
return p, fmt.Errorf("Tried to close a property without a kind")
}
v = true
p.value = b.String()
break proploop
case OPAREN, CPAREN:
if k {
b.WriteRune(l.ts[l.curs].val)
} else {
l.prev()
return p, fmt.Errorf("Unexpected paren, tossing up the chain")
}
case TEXT:
b.WriteRune(l.ts[l.curs].val)
}
err := l.next()
if err != nil {
break proploop
}
if v {
break proploop
}
}
return p, nil
}
func (l *lexed) next() error {
l.curs++
if l.curs > len(l.ts) - 1 {
l.curs--
return fmt.Errorf("Reached end")
}
return nil
}
func (l *lexed) prev() error {
if l.curs > 0 {
l.curs--
return nil
}
return fmt.Errorf("Reached beginning")
}
func (l *lexed) findStart() {
for i, x := range l.ts {
if x.kind == OPAREN {
l.curs = i
break
}
}
}