2022-05-12 02:11:05 +00:00
|
|
|
package argh
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
errSyntax = errors.New("syntax error")
|
2022-05-14 00:58:55 +00:00
|
|
|
|
|
|
|
DefaultParserConfig = &ParserConfig{
|
|
|
|
Commands: []string{},
|
|
|
|
ValueFlags: []string{},
|
|
|
|
ScannerConfig: DefaultScannerConfig,
|
|
|
|
}
|
2022-05-12 02:11:05 +00:00
|
|
|
)
|
|
|
|
|
2022-05-14 00:58:55 +00:00
|
|
|
func ParseArgs(args []string, pCfg *ParserConfig) (*Argh, error) {
|
2022-05-12 02:11:05 +00:00
|
|
|
reEncoded := strings.Join(args, string(nul))
|
|
|
|
|
|
|
|
return NewParser(
|
|
|
|
strings.NewReader(reEncoded),
|
2022-05-14 00:58:55 +00:00
|
|
|
pCfg,
|
2022-05-12 02:11:05 +00:00
|
|
|
).Parse()
|
|
|
|
}
|
|
|
|
|
|
|
|
type Parser struct {
|
|
|
|
s *Scanner
|
|
|
|
buf ParserBuffer
|
2022-05-14 00:58:55 +00:00
|
|
|
|
|
|
|
commands map[string]struct{}
|
|
|
|
valueFlags map[string]struct{}
|
|
|
|
|
|
|
|
nodes []Node
|
|
|
|
stopSeen bool
|
2022-05-12 02:11:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type ParserBuffer struct {
|
|
|
|
tok Token
|
|
|
|
lit string
|
2022-05-14 00:58:55 +00:00
|
|
|
pos int
|
2022-05-12 02:11:05 +00:00
|
|
|
n int
|
|
|
|
}
|
|
|
|
|
2022-05-14 00:58:55 +00:00
|
|
|
type ParserConfig struct {
|
|
|
|
Commands []string
|
|
|
|
ValueFlags []string
|
|
|
|
ScannerConfig *ScannerConfig
|
2022-05-12 02:11:05 +00:00
|
|
|
}
|
|
|
|
|
2022-05-14 00:58:55 +00:00
|
|
|
type parseDirective struct {
|
|
|
|
Break bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewParser(r io.Reader, pCfg *ParserConfig) *Parser {
|
|
|
|
if pCfg == nil {
|
|
|
|
pCfg = DefaultParserConfig
|
2022-05-12 02:11:05 +00:00
|
|
|
}
|
|
|
|
|
2022-05-14 00:58:55 +00:00
|
|
|
parser := &Parser{
|
|
|
|
s: NewScanner(r, pCfg.ScannerConfig),
|
|
|
|
commands: map[string]struct{}{},
|
|
|
|
valueFlags: map[string]struct{}{},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, command := range pCfg.Commands {
|
|
|
|
parser.commands[command] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, valueFlag := range pCfg.ValueFlags {
|
|
|
|
parser.valueFlags[valueFlag] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
tracef("NewParser parser=%+#v", parser)
|
|
|
|
tracef("NewParser pCfg=%+#v", pCfg)
|
|
|
|
|
|
|
|
return parser
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Parser) Parse() (*Argh, error) {
|
|
|
|
p.nodes = []Node{}
|
|
|
|
|
2022-05-12 02:11:05 +00:00
|
|
|
for {
|
2022-05-14 00:58:55 +00:00
|
|
|
pd, err := p.parseArg()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2022-05-12 02:11:05 +00:00
|
|
|
}
|
|
|
|
|
2022-05-14 00:58:55 +00:00
|
|
|
if pd != nil && pd.Break {
|
2022-05-12 02:11:05 +00:00
|
|
|
break
|
|
|
|
}
|
2022-05-14 00:58:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return &Argh{ParseTree: &ParseTree{Nodes: p.nodes}}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Parser) parseArg() (*parseDirective, error) {
|
|
|
|
tok, lit, pos := p.scan()
|
|
|
|
if tok == ILLEGAL {
|
|
|
|
return nil, errors.Wrapf(errSyntax, "illegal value %q at pos=%v", lit, pos)
|
|
|
|
}
|
|
|
|
|
|
|
|
if tok == EOL {
|
|
|
|
return &parseDirective{Break: true}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
p.unscan()
|
|
|
|
|
|
|
|
node, err := p.nodify()
|
|
|
|
|
|
|
|
tracef("parseArg node=%+#v err=%+#v", node, err)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "value %q at pos=%v", lit, pos)
|
|
|
|
}
|
|
|
|
|
|
|
|
if node != nil {
|
|
|
|
p.nodes = append(p.nodes, node)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Parser) nodify() (Node, error) {
|
|
|
|
tok, lit, pos := p.scan()
|
|
|
|
|
|
|
|
tracef("nodify tok=%s lit=%q pos=%v", tok, lit, pos)
|
|
|
|
|
|
|
|
switch tok {
|
|
|
|
case IDENT:
|
|
|
|
if len(p.nodes) == 0 {
|
2022-05-15 00:58:09 +00:00
|
|
|
return Program{Name: lit}, nil
|
2022-05-14 00:58:55 +00:00
|
|
|
}
|
2022-05-15 00:58:09 +00:00
|
|
|
|
|
|
|
if _, ok := p.commands[lit]; ok {
|
|
|
|
return Command{Name: lit}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return Ident{Literal: lit}, nil
|
2022-05-14 00:58:55 +00:00
|
|
|
case ARG_DELIMITER:
|
2022-05-15 00:58:09 +00:00
|
|
|
return ArgDelimiter{}, nil
|
2022-05-14 00:58:55 +00:00
|
|
|
case COMPOUND_SHORT_FLAG:
|
|
|
|
flagNodes := []Node{}
|
|
|
|
|
2022-05-15 00:58:09 +00:00
|
|
|
for _, r := range lit[1:] {
|
2022-05-14 00:58:55 +00:00
|
|
|
flagNodes = append(
|
|
|
|
flagNodes,
|
|
|
|
Flag{
|
|
|
|
Name: string(r),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-05-15 00:58:09 +00:00
|
|
|
return Statement{Nodes: flagNodes}, nil
|
2022-05-14 00:58:55 +00:00
|
|
|
case SHORT_FLAG:
|
|
|
|
flagName := string(lit[1:])
|
|
|
|
if _, ok := p.valueFlags[flagName]; ok {
|
|
|
|
return p.scanValueFlag(flagName, pos)
|
|
|
|
}
|
|
|
|
|
2022-05-15 00:58:09 +00:00
|
|
|
return Flag{Name: flagName}, nil
|
2022-05-14 00:58:55 +00:00
|
|
|
case LONG_FLAG:
|
|
|
|
flagName := string(lit[2:])
|
|
|
|
if _, ok := p.valueFlags[flagName]; ok {
|
|
|
|
return p.scanValueFlag(flagName, pos)
|
|
|
|
}
|
2022-05-12 02:11:05 +00:00
|
|
|
|
2022-05-15 00:58:09 +00:00
|
|
|
return Flag{Name: flagName}, nil
|
2022-05-14 00:58:55 +00:00
|
|
|
default:
|
2022-05-12 02:11:05 +00:00
|
|
|
}
|
|
|
|
|
2022-05-15 00:58:09 +00:00
|
|
|
return Ident{Literal: lit}, nil
|
2022-05-12 02:11:05 +00:00
|
|
|
}
|
|
|
|
|
2022-05-14 00:58:55 +00:00
|
|
|
func (p *Parser) scanValueFlag(flagName string, pos int) (Node, error) {
|
|
|
|
tracef("scanValueFlag flagName=%q pos=%v", flagName, pos)
|
|
|
|
|
|
|
|
lit, err := p.scanIdent()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-05-15 00:58:09 +00:00
|
|
|
return Flag{Name: flagName, Value: ptr(lit)}, nil
|
2022-05-14 00:58:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Parser) scanIdent() (string, error) {
|
|
|
|
tok, lit, pos := p.scan()
|
|
|
|
|
|
|
|
nUnscan := 0
|
|
|
|
|
|
|
|
if tok == ASSIGN || tok == ARG_DELIMITER {
|
|
|
|
nUnscan++
|
|
|
|
tok, lit, pos = p.scan()
|
|
|
|
}
|
|
|
|
|
|
|
|
if tok == IDENT {
|
|
|
|
return lit, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < nUnscan; i++ {
|
|
|
|
p.unscan()
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", errors.Wrapf(errSyntax, "expected ident at pos=%v but got %s (%q)", pos, tok, lit)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Parser) scan() (Token, string, int) {
|
2022-05-12 02:11:05 +00:00
|
|
|
if p.buf.n != 0 {
|
|
|
|
p.buf.n = 0
|
2022-05-14 00:58:55 +00:00
|
|
|
return p.buf.tok, p.buf.lit, p.buf.pos
|
2022-05-12 02:11:05 +00:00
|
|
|
}
|
|
|
|
|
2022-05-14 00:58:55 +00:00
|
|
|
tok, lit, pos := p.s.Scan()
|
2022-05-12 02:11:05 +00:00
|
|
|
|
2022-05-14 00:58:55 +00:00
|
|
|
p.buf.tok, p.buf.lit, p.buf.pos = tok, lit, pos
|
2022-05-12 02:11:05 +00:00
|
|
|
|
2022-05-14 00:58:55 +00:00
|
|
|
return tok, lit, pos
|
2022-05-12 02:11:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Parser) unscan() {
|
|
|
|
p.buf.n = 1
|
|
|
|
}
|
2022-05-14 00:58:55 +00:00
|
|
|
|
|
|
|
func ptr[T any](v T) *T {
|
|
|
|
return &v
|
|
|
|
}
|