224 lines
4.2 KiB
Go
224 lines
4.2 KiB
Go
|
package cli
|
||
|
|
||
|
import (
|
||
|
"strings"
|
||
|
|
||
|
"fmt"
|
||
|
)
|
||
|
|
||
|
type uTokenType string
|
||
|
|
||
|
const (
|
||
|
utPos uTokenType = "Pos"
|
||
|
utOpenPar uTokenType = "OpenPar"
|
||
|
utClosePar uTokenType = "ClosePar"
|
||
|
utOpenSq uTokenType = "OpenSq"
|
||
|
utCloseSq uTokenType = "CloseSq"
|
||
|
utChoice uTokenType = "Choice"
|
||
|
utOptions uTokenType = "Options"
|
||
|
utRep uTokenType = "Rep"
|
||
|
utShortOpt uTokenType = "ShortOpt"
|
||
|
utLongOpt uTokenType = "LongOpt"
|
||
|
utOptSeq uTokenType = "OptSeq"
|
||
|
utOptValue uTokenType = "OptValue"
|
||
|
utDoubleDash uTokenType = "DblDash"
|
||
|
)
|
||
|
|
||
|
type uToken struct {
|
||
|
typ uTokenType
|
||
|
val string
|
||
|
pos int
|
||
|
}
|
||
|
|
||
|
func (t *uToken) String() string {
|
||
|
return fmt.Sprintf("%s('%s')@%d", t.typ, t.val, t.pos)
|
||
|
}
|
||
|
|
||
|
type parseError struct {
|
||
|
input string
|
||
|
msg string
|
||
|
pos int
|
||
|
}
|
||
|
|
||
|
func (t *parseError) ident() string {
|
||
|
return strings.Map(func(c rune) rune {
|
||
|
switch c {
|
||
|
case '\t':
|
||
|
return c
|
||
|
default:
|
||
|
return ' '
|
||
|
}
|
||
|
}, t.input[:t.pos])
|
||
|
}
|
||
|
func (t *parseError) Error() string {
|
||
|
return fmt.Sprintf("Parse error at position %d:\n%s\n%s^ %s",
|
||
|
t.pos, t.input, t.ident(), t.msg)
|
||
|
}
|
||
|
|
||
|
func uTokenize(usage string) ([]*uToken, *parseError) {
|
||
|
pos := 0
|
||
|
res := []*uToken{}
|
||
|
var (
|
||
|
tk = func(t uTokenType, v string) {
|
||
|
res = append(res, &uToken{t, v, pos})
|
||
|
}
|
||
|
|
||
|
tkp = func(t uTokenType, v string, p int) {
|
||
|
res = append(res, &uToken{t, v, p})
|
||
|
}
|
||
|
|
||
|
err = func(msg string) *parseError {
|
||
|
return &parseError{usage, msg, pos}
|
||
|
}
|
||
|
)
|
||
|
eof := len(usage)
|
||
|
for pos < eof {
|
||
|
switch c := usage[pos]; c {
|
||
|
case ' ':
|
||
|
pos++
|
||
|
case '\t':
|
||
|
pos++
|
||
|
case '[':
|
||
|
tk(utOpenSq, "[")
|
||
|
pos++
|
||
|
case ']':
|
||
|
tk(utCloseSq, "]")
|
||
|
pos++
|
||
|
case '(':
|
||
|
tk(utOpenPar, "(")
|
||
|
pos++
|
||
|
case ')':
|
||
|
tk(utClosePar, ")")
|
||
|
pos++
|
||
|
case '|':
|
||
|
tk(utChoice, "|")
|
||
|
pos++
|
||
|
case '.':
|
||
|
start := pos
|
||
|
pos++
|
||
|
if pos >= eof || usage[pos] != '.' {
|
||
|
return nil, err("Unexpected end of usage, was expecting '..'")
|
||
|
}
|
||
|
pos++
|
||
|
if pos >= eof || usage[pos] != '.' {
|
||
|
return nil, err("Unexpected end of usage, was expecting '.'")
|
||
|
}
|
||
|
tkp(utRep, "...", start)
|
||
|
pos++
|
||
|
case '-':
|
||
|
start := pos
|
||
|
pos++
|
||
|
if pos >= eof {
|
||
|
return nil, err("Unexpected end of usage, was expecting an option name")
|
||
|
}
|
||
|
|
||
|
switch o := usage[pos]; {
|
||
|
case isLetter(o):
|
||
|
pos++
|
||
|
for ; pos < eof; pos++ {
|
||
|
ok := isLetter(usage[pos])
|
||
|
if !ok {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
typ := utShortOpt
|
||
|
if pos-start > 2 {
|
||
|
typ = utOptSeq
|
||
|
start++
|
||
|
}
|
||
|
opt := usage[start:pos]
|
||
|
tkp(typ, opt, start)
|
||
|
if pos < eof && usage[pos] == '-' {
|
||
|
return nil, err("Invalid syntax")
|
||
|
}
|
||
|
case o == '-':
|
||
|
pos++
|
||
|
if pos == eof || usage[pos] == ' ' {
|
||
|
tkp(utDoubleDash, "--", start)
|
||
|
continue
|
||
|
}
|
||
|
for pos0 := pos; pos < eof; pos++ {
|
||
|
ok := isOkLongOpt(usage[pos], pos == pos0)
|
||
|
if !ok {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
opt := usage[start:pos]
|
||
|
if len(opt) == 2 {
|
||
|
return nil, err("Was expecting a long option name")
|
||
|
}
|
||
|
tkp(utLongOpt, opt, start)
|
||
|
}
|
||
|
|
||
|
case '=':
|
||
|
start := pos
|
||
|
pos++
|
||
|
if pos >= eof || usage[pos] != '<' {
|
||
|
return nil, err("Unexpected end of usage, was expecting '=<'")
|
||
|
}
|
||
|
closed := false
|
||
|
for ; pos < eof; pos++ {
|
||
|
closed = usage[pos] == '>'
|
||
|
if closed {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
if !closed {
|
||
|
return nil, err("Unclosed option value")
|
||
|
}
|
||
|
if pos-start == 2 {
|
||
|
return nil, err("Was expecting an option value")
|
||
|
}
|
||
|
pos++
|
||
|
value := usage[start:pos]
|
||
|
|
||
|
tkp(utOptValue, value, start)
|
||
|
|
||
|
default:
|
||
|
switch {
|
||
|
case isUppercase(c):
|
||
|
start := pos
|
||
|
for pos = pos + 1; pos < eof; pos++ {
|
||
|
if !isOkPos(usage[pos]) {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
s := usage[start:pos]
|
||
|
typ := utPos
|
||
|
if s == "OPTIONS" {
|
||
|
typ = utOptions
|
||
|
}
|
||
|
tkp(typ, s, start)
|
||
|
default:
|
||
|
return nil, err("Unexpected input")
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return res, nil
|
||
|
}
|
||
|
|
||
|
func isLowercase(c uint8) bool {
|
||
|
return c >= 'a' && c <= 'z'
|
||
|
}
|
||
|
|
||
|
func isUppercase(c uint8) bool {
|
||
|
return c >= 'A' && c <= 'Z'
|
||
|
}
|
||
|
|
||
|
func isOkPos(c uint8) bool {
|
||
|
return isUppercase(c) || isDigit(c) || c == '_'
|
||
|
}
|
||
|
|
||
|
func isLetter(c uint8) bool {
|
||
|
return isLowercase(c) || isUppercase(c)
|
||
|
}
|
||
|
|
||
|
func isDigit(c uint8) bool {
|
||
|
return c >= '0' && c <= '9'
|
||
|
}
|
||
|
func isOkLongOpt(c uint8, first bool) bool {
|
||
|
return isLetter(c) || isDigit(c) || c == '_' || (!first && c == '-')
|
||
|
}
|