kevinschoon-pomo/vendor/github.com/jawher/mow.cli/spec_parser.go

244 lines
4.5 KiB
Go

package cli
import "fmt"
func uParse(c *Cmd) (*state, error) {
tokens, err := uTokenize(c.Spec)
if err != nil {
return nil, err
}
p := &uParser{cmd: c, tokens: tokens}
return p.parse()
}
type uParser struct {
cmd *Cmd
tokens []*uToken
tkpos int
matchedToken *uToken
rejectOptions bool
}
func (p *uParser) parse() (s *state, err error) {
defer func() {
if v := recover(); v != nil {
pos := len(p.cmd.Spec)
if !p.eof() {
pos = p.token().pos
}
s = nil
switch t, ok := v.(string); ok {
case true:
err = &parseError{p.cmd.Spec, t, pos}
default:
panic(v)
}
}
}()
err = nil
var e *state
s, e = p.seq(false)
if !p.eof() {
s = nil
err = &parseError{p.cmd.Spec, "Unexpected input", p.token().pos}
return
}
e.terminal = true
s.simplify()
return
}
func (p *uParser) seq(required bool) (*state, *state) {
start := newState(p.cmd)
end := start
appendComp := func(s, e *state) {
for _, tr := range s.transitions {
end.t(tr.matcher, tr.next)
}
end = e
}
if required {
s, e := p.choice()
appendComp(s, e)
}
for p.canAtom() {
s, e := p.choice()
appendComp(s, e)
}
return start, end
}
func (p *uParser) choice() (*state, *state) {
start, end := newState(p.cmd), newState(p.cmd)
add := func(s, e *state) {
start.t(shortcut, s)
e.t(shortcut, end)
}
add(p.atom())
for p.found(utChoice) {
add(p.atom())
}
return start, end
}
func (p *uParser) atom() (*state, *state) {
start := newState(p.cmd)
var end *state
switch {
case p.eof():
panic("Unexpected end of input")
case p.found(utPos):
name := p.matchedToken.val
arg, declared := p.cmd.argsIdx[name]
if !declared {
p.back()
panic(fmt.Sprintf("Undeclared arg %s", name))
}
end = start.t(arg, newState(p.cmd))
case p.found(utOptions):
if p.rejectOptions {
p.back()
panic("No options after --")
}
end = newState(p.cmd)
start.t(optsMatcher{options: p.cmd.options, optionsIndex: p.cmd.optionsIdx}, end)
case p.found(utShortOpt):
if p.rejectOptions {
p.back()
panic("No options after --")
}
name := p.matchedToken.val
opt, declared := p.cmd.optionsIdx[name]
if !declared {
p.back()
panic(fmt.Sprintf("Undeclared option %s", name))
}
end = start.t(&optMatcher{
theOne: opt,
optionsIdx: p.cmd.optionsIdx,
}, newState(p.cmd))
p.found(utOptValue)
case p.found(utLongOpt):
if p.rejectOptions {
p.back()
panic("No options after --")
}
name := p.matchedToken.val
opt, declared := p.cmd.optionsIdx[name]
if !declared {
p.back()
panic(fmt.Sprintf("Undeclared option %s", name))
}
end = start.t(&optMatcher{
theOne: opt,
optionsIdx: p.cmd.optionsIdx,
}, newState(p.cmd))
p.found(utOptValue)
case p.found(utOptSeq):
if p.rejectOptions {
p.back()
panic("No options after --")
}
end = newState(p.cmd)
sq := p.matchedToken.val
opts := []*opt{}
for i := range sq {
sn := sq[i : i+1]
opt, declared := p.cmd.optionsIdx["-"+sn]
if !declared {
p.back()
panic(fmt.Sprintf("Undeclared option %s", sn))
}
opts = append(opts, opt)
}
start.t(optsMatcher{options: opts, optionsIndex: p.cmd.optionsIdx}, end)
case p.found(utOpenPar):
start, end = p.seq(true)
p.expect(utClosePar)
case p.found(utOpenSq):
start, end = p.seq(true)
start.t(shortcut, end)
p.expect(utCloseSq)
case p.found(utDoubleDash):
p.rejectOptions = true
end = start.t(optsEnd, newState(p.cmd))
return start, end
default:
panic("Unexpected input: was expecting a command or a positional argument or an option")
}
if p.found(utRep) {
end.t(shortcut, start)
}
return start, end
}
func (p *uParser) canAtom() bool {
switch {
case p.is(utPos):
return true
case p.is(utOptions):
return true
case p.is(utShortOpt):
return true
case p.is(utLongOpt):
return true
case p.is(utOptSeq):
return true
case p.is(utOpenPar):
return true
case p.is(utOpenSq):
return true
case p.is(utDoubleDash):
return true
default:
return false
}
}
func (p *uParser) found(t uTokenType) bool {
if p.is(t) {
p.matchedToken = p.token()
p.tkpos++
return true
}
return false
}
func (p *uParser) is(t uTokenType) bool {
if p.eof() {
return false
}
return p.token().typ == t
}
func (p *uParser) expect(t uTokenType) {
if !p.found(t) {
panic(fmt.Sprintf("Was expecting %v", t))
}
}
func (p *uParser) back() {
p.tkpos--
}
func (p *uParser) eof() bool {
return p.tkpos >= len(p.tokens)
}
func (p *uParser) token() *uToken {
if p.eof() {
return nil
}
return p.tokens[p.tkpos]
}