delve/vendor/github.com/cosiner/argv/parser.go
2020-06-24 10:00:37 -07:00

247 lines
5.0 KiB
Go

package argv
import (
"errors"
"fmt"
"os"
)
type (
Expander func(string) (string, error)
// Parser take tokens from Scanner, and do syntax checking, and generate the splitted arguments array.
Parser struct {
s *Scanner
tokbuf []Token
backQuoteExpander Expander
stringExpander Expander
sections [][]string
currSection []string
currStrValid bool
currStr []rune
}
)
// NewParser create a cmdline string parser.
func NewParser(s *Scanner, backQuoteExpander, stringExpander Expander) *Parser {
if backQuoteExpander == nil {
backQuoteExpander = func(r string) (string, error) {
return r, fmt.Errorf("back quote doesn't allowed")
}
}
if stringExpander == nil {
stringExpander = func(runes string) (string, error) {
s := os.ExpandEnv(string(runes))
return s, nil
}
}
return &Parser{
s: s,
backQuoteExpander: backQuoteExpander,
stringExpander: stringExpander,
}
}
func (p *Parser) nextToken() (Token, error) {
if l := len(p.tokbuf); l > 0 {
tok := p.tokbuf[l-1]
p.tokbuf = p.tokbuf[:l-1]
return tok, nil
}
return p.s.Next()
}
var (
// ErrInvalidSyntax was reported if there is a syntax error in command line string.
ErrInvalidSyntax = errors.New("invalid syntax")
)
func (p *Parser) unreadToken(tok Token) {
p.tokbuf = append(p.tokbuf, tok)
}
// Parse split command line string into arguments array.
//
// EBNF:
// Cmdline = Section [ Pipe Cmdline ]
// Section = [Space] SpacedSection { SpacedSection }
// SpacedSection = MultipleUnit [Space]
// MultipleUnit = Unit {Unit}
// Unit = String | BackQuote | SingleQuote | DoubleQuote
func (p *Parser) Parse() ([][]string, error) {
err := p.cmdline()
if err != nil {
return nil, err
}
return p.sections, nil
}
func (p *Parser) cmdline() error {
err := p.section()
if err != nil {
return err
}
p.endSection()
tok, err := p.nextToken()
if err != nil {
return err
}
if tok.Type == TokEOF {
return nil
}
if !p.accept(tok.Type, TokPipe) {
return ErrInvalidSyntax
}
return p.cmdline()
}
func (p *Parser) section() error {
leftSpace, err := p.optional(TokSpace)
if err != nil {
return err
}
var hasUnit bool
for {
unit, ok, err := p.spacedSection()
if err != nil {
return err
}
if !ok {
break
}
hasUnit = true
err = p.appendUnit(leftSpace, unit)
if err != nil {
return err
}
leftSpace = unit.rightSpace
}
if !hasUnit {
return ErrInvalidSyntax
}
return nil
}
type unit struct {
rightSpace bool
toks []Token
}
func (p *Parser) spacedSection() (u unit, ok bool, err error) {
u.toks, ok, err = p.multipleUnit()
if err != nil {
return
}
if !ok {
return u, false, nil
}
u.rightSpace, err = p.optional(TokSpace)
return
}
func (p *Parser) multipleUnit() ([]Token, bool, error) {
var (
toks []Token
)
for {
tok, ok, err := p.unit()
if err != nil {
return nil, false, err
}
if !ok {
break
}
toks = append(toks, tok)
}
return toks, len(toks) > 0, nil
}
func (p *Parser) unit() (Token, bool, error) {
tok, err := p.nextToken()
if err != nil {
return tok, false, err
}
if p.accept(tok.Type, TokString, TokStringSingleQuote, TokStringDoubleQuote, TokBackQuote) {
return tok, true, nil
}
p.unreadToken(tok)
return tok, false, nil
}
func (p *Parser) optional(typ TokenType) (bool, error) {
tok, err := p.nextToken()
if err != nil {
return false, err
}
var ok bool
if ok = p.accept(tok.Type, typ); !ok {
p.unreadToken(tok)
}
return ok, nil
}
func (p *Parser) accept(t TokenType, types ...TokenType) bool {
for _, typ := range types {
if t == typ {
return true
}
}
return false
}
func (p *Parser) appendUnit(leftSpace bool, u unit) error {
if leftSpace {
p.currStr = p.currStr[:0]
}
for _, tok := range u.toks {
switch tok.Type {
case TokBackQuote:
expanded, err := p.backQuoteExpander(string(tok.Value))
if err != nil {
return fmt.Errorf("expand string back quoted failed: %s, %w", string(tok.Value), err)
}
p.currStr = append(p.currStr, []rune(expanded)...)
case TokString:
expanded, err := p.stringExpander(string(tok.Value))
if err != nil {
return fmt.Errorf("expand string failed: %s, %w", string(tok.Value), err)
}
p.currStr = append(p.currStr, []rune(expanded)...)
case TokStringSingleQuote:
p.currStr = append(p.currStr, tok.Value...)
case TokStringDoubleQuote:
expanded, err := p.stringExpander(string(tok.Value))
if err != nil {
return fmt.Errorf("expand string double quoted failed: %s, %w", string(tok.Value), err)
}
p.currStr = append(p.currStr, []rune(expanded)...)
}
}
p.currStrValid = true
if u.rightSpace {
p.currSection = append(p.currSection, string(p.currStr))
p.currStr = p.currStr[:0]
p.currStrValid = false
}
return nil
}
func (p *Parser) endSection() {
if p.currStrValid {
p.currSection = append(p.currSection, string(p.currStr))
}
p.currStr = p.currStr[:0]
p.currStrValid = false
if len(p.currSection) > 0 {
p.sections = append(p.sections, p.currSection)
p.currSection = nil
}
}