228 lines
3.5 KiB
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
|
|
}
|
|
}
|
|
}
|