123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250 |
- 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 */
- }
|