mirror of
https://github.com/dragonheim/gagent.git
synced 2025-04-05 02:42:39 -07:00
250 lines
3.9 KiB
Go
250 lines
3.9 KiB
Go
package picol
|
|
|
|
import (
|
|
"unicode"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
const (
|
|
PT_ESC = iota
|
|
PT_STR
|
|
PT_CMD
|
|
PT_VAR
|
|
PT_SEP
|
|
PT_EOL
|
|
PT_EOF
|
|
)
|
|
|
|
type Parser struct {
|
|
text string
|
|
p, start, end, ln int
|
|
insidequote int
|
|
Type int
|
|
}
|
|
|
|
func InitParser(text string) *Parser {
|
|
return &Parser{text, 0, 0, 0, len(text), 0, PT_EOL}
|
|
}
|
|
|
|
func (p *Parser) next() {
|
|
_, w := utf8.DecodeRuneInString(p.text[p.p:])
|
|
p.p += w
|
|
p.ln -= w
|
|
}
|
|
|
|
func (p *Parser) current() rune {
|
|
r, _ := utf8.DecodeRuneInString(p.text[p.p:])
|
|
return r
|
|
}
|
|
|
|
func (p *Parser) token() (t string) {
|
|
defer recover()
|
|
return p.text[p.start:p.end]
|
|
}
|
|
|
|
func (p *Parser) parseSep() string {
|
|
p.start = p.p
|
|
for ; p.p < len(p.text); p.next() {
|
|
if !unicode.IsSpace(p.current()) {
|
|
break
|
|
}
|
|
}
|
|
p.end = p.p
|
|
p.Type = PT_SEP
|
|
return p.token()
|
|
}
|
|
|
|
func (p *Parser) parseEol() string {
|
|
p.start = p.p
|
|
|
|
for ; p.p < len(p.text); p.next() {
|
|
if p.current() == ';' || unicode.IsSpace(p.current()) {
|
|
// pass
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
p.end = p.p
|
|
p.Type = PT_EOL
|
|
return p.token()
|
|
}
|
|
|
|
func (p *Parser) parseCommand() string {
|
|
level, blevel := 1, 0
|
|
p.next() // skip
|
|
p.start = p.p
|
|
Loop:
|
|
for {
|
|
switch {
|
|
case p.ln == 0:
|
|
break Loop
|
|
case p.current() == '[' && blevel == 0:
|
|
level++
|
|
case p.current() == ']' && blevel == 0:
|
|
level--
|
|
if level == 0 {
|
|
break Loop
|
|
}
|
|
case p.current() == '\\':
|
|
p.next()
|
|
case p.current() == '{':
|
|
blevel++
|
|
case p.current() == '}' && blevel != 0:
|
|
blevel--
|
|
}
|
|
p.next()
|
|
}
|
|
p.end = p.p
|
|
p.Type = PT_CMD
|
|
if p.p < len(p.text) && p.current() == ']' {
|
|
p.next()
|
|
}
|
|
return p.token()
|
|
}
|
|
|
|
func (p *Parser) parseVar() string {
|
|
p.next() // skip the $
|
|
p.start = p.p
|
|
|
|
if p.current() == '{' {
|
|
p.Type = PT_VAR
|
|
return p.parseBrace()
|
|
}
|
|
|
|
for p.p < len(p.text) {
|
|
c := p.current()
|
|
if unicode.IsLetter(c) || ('0' <= c && c <= '9') || c == '_' {
|
|
p.next()
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
|
|
if p.start == p.p { // It's just a single char string "$"
|
|
p.start = p.p - 1
|
|
p.end = p.p
|
|
p.Type = PT_STR
|
|
} else {
|
|
p.end = p.p
|
|
p.Type = PT_VAR
|
|
}
|
|
return p.token()
|
|
}
|
|
|
|
func (p *Parser) parseBrace() string {
|
|
level := 1
|
|
p.next() // skip
|
|
p.start = p.p
|
|
|
|
Loop:
|
|
for p.p < len(p.text) {
|
|
c := p.current()
|
|
switch {
|
|
case p.ln >= 2 && c == '\\':
|
|
p.next()
|
|
case p.ln == 0 || c == '}':
|
|
level--
|
|
if level == 0 || p.ln == 0 {
|
|
break Loop
|
|
}
|
|
case c == '{':
|
|
level++
|
|
}
|
|
p.next()
|
|
}
|
|
p.end = p.p
|
|
if p.ln != 0 { // Skip final closed brace
|
|
p.next()
|
|
}
|
|
return p.token()
|
|
}
|
|
|
|
func (p *Parser) parseString() string {
|
|
newword := p.Type == PT_SEP || p.Type == PT_EOL || p.Type == PT_STR
|
|
|
|
if c := p.current(); newword && c == '{' {
|
|
p.Type = PT_STR
|
|
return p.parseBrace()
|
|
} else if newword && c == '"' {
|
|
p.insidequote = 1
|
|
p.next() // skip
|
|
}
|
|
|
|
p.start = p.p
|
|
|
|
Loop:
|
|
for ; p.ln != 0; p.next() {
|
|
switch p.current() {
|
|
case '\\':
|
|
if p.ln >= 2 {
|
|
p.next()
|
|
}
|
|
case '$', '[':
|
|
break Loop
|
|
case '"':
|
|
if p.insidequote != 0 {
|
|
p.end = p.p
|
|
p.Type = PT_ESC
|
|
p.next()
|
|
p.insidequote = 0
|
|
return p.token()
|
|
}
|
|
}
|
|
if p.current() == ';' || unicode.IsSpace(p.current()) {
|
|
if p.insidequote == 0 {
|
|
break Loop
|
|
}
|
|
}
|
|
}
|
|
|
|
p.end = p.p
|
|
p.Type = PT_ESC
|
|
return p.token()
|
|
}
|
|
|
|
func (p *Parser) parseComment() string {
|
|
for p.ln != 0 && p.current() != '\n' {
|
|
p.next()
|
|
}
|
|
return p.token()
|
|
}
|
|
|
|
func (p *Parser) GetToken() string {
|
|
for {
|
|
if p.ln == 0 {
|
|
if p.Type != PT_EOL && p.Type != PT_EOF {
|
|
p.Type = PT_EOL
|
|
} else {
|
|
p.Type = PT_EOF
|
|
}
|
|
return p.token()
|
|
}
|
|
|
|
switch p.current() {
|
|
case ' ', '\t', '\r':
|
|
if p.insidequote != 0 {
|
|
return p.parseString()
|
|
}
|
|
return p.parseSep()
|
|
case '\n', ';':
|
|
if p.insidequote != 0 {
|
|
return p.parseString()
|
|
}
|
|
return p.parseEol()
|
|
case '[':
|
|
return p.parseCommand()
|
|
case '$':
|
|
return p.parseVar()
|
|
case '#':
|
|
if p.Type == PT_EOL {
|
|
p.parseComment()
|
|
continue
|
|
}
|
|
return p.parseString()
|
|
default:
|
|
return p.parseString()
|
|
}
|
|
}
|
|
return p.token() /* unreached */
|
|
}
|