box-o-sand/argh/parser.go

366 lines
6.7 KiB
Go
Raw Normal View History

2022-05-15 18:22:56 +00:00
//go:generate stringer -type NValue
package argh
import (
2022-05-19 00:15:31 +00:00
"fmt"
"io"
"strings"
"github.com/pkg/errors"
)
2022-05-15 18:22:56 +00:00
const (
OneOrMoreValue NValue = -2
ZeroOrMoreValue NValue = -1
ZeroValue NValue = 0
2022-05-15 18:22:56 +00:00
)
var (
ErrSyntax = errors.New("syntax error")
2022-05-14 00:58:55 +00:00
DefaultParserConfig = &ParserConfig{
2022-05-19 00:15:31 +00:00
Commands: map[string]CommandConfig{},
Flags: map[string]FlagConfig{},
2022-05-14 00:58:55 +00:00
ScannerConfig: DefaultScannerConfig,
}
)
2022-05-15 18:22:56 +00:00
type NValue int
2022-05-19 00:15:31 +00:00
func ParseArgs(args []string, pCfg *ParserConfig) (*ParseTree, error) {
reEncoded := strings.Join(args, string(nul))
return NewParser(
strings.NewReader(reEncoded),
2022-05-14 00:58:55 +00:00
pCfg,
).Parse()
}
type Parser struct {
2022-05-15 18:22:56 +00:00
s *Scanner
2022-05-14 00:58:55 +00:00
2022-05-19 00:15:31 +00:00
buf []scanEntry
2022-05-15 18:22:56 +00:00
cfg *ParserConfig
2022-05-14 00:58:55 +00:00
2022-05-19 00:15:31 +00:00
nodes []Node
node Node
}
2022-05-19 00:15:31 +00:00
type ParseTree struct {
Nodes []Node `json:"nodes"`
}
type scanEntry struct {
tok Token
lit string
2022-05-14 00:58:55 +00:00
pos int
}
2022-05-14 00:58:55 +00:00
type ParserConfig struct {
2022-05-19 00:15:31 +00:00
Prog CommandConfig
Commands map[string]CommandConfig
Flags map[string]FlagConfig
2022-05-14 00:58:55 +00:00
ScannerConfig *ScannerConfig
}
2022-05-19 00:15:31 +00:00
type CommandConfig struct {
NValue NValue
ValueNames []string
Flags map[string]FlagConfig
}
type FlagConfig struct {
NValue NValue
ValueNames []string
}
2022-05-14 00:58:55 +00:00
func NewParser(r io.Reader, pCfg *ParserConfig) *Parser {
if pCfg == nil {
pCfg = DefaultParserConfig
}
2022-05-14 00:58:55 +00:00
parser := &Parser{
2022-05-19 00:15:31 +00:00
buf: []scanEntry{},
s: NewScanner(r, pCfg.ScannerConfig),
cfg: pCfg,
2022-05-14 00:58:55 +00:00
}
tracef("NewParser parser=%+#v", parser)
tracef("NewParser pCfg=%+#v", pCfg)
return parser
}
2022-05-19 00:15:31 +00:00
func (p *Parser) Parse() (*ParseTree, error) {
2022-05-14 00:58:55 +00:00
p.nodes = []Node{}
for {
br, err := p.parseArg()
2022-05-14 00:58:55 +00:00
if err != nil {
return nil, err
}
if br {
break
}
2022-05-14 00:58:55 +00:00
}
2022-05-19 00:15:31 +00:00
return &ParseTree{Nodes: p.nodes}, nil
2022-05-14 00:58:55 +00:00
}
func (p *Parser) parseArg() (bool, error) {
2022-05-14 00:58:55 +00:00
tok, lit, pos := p.scan()
if tok == ILLEGAL {
return false, errors.Wrapf(ErrSyntax, "illegal value %q at pos=%v", lit, pos)
2022-05-14 00:58:55 +00:00
}
if tok == EOL {
return true, nil
2022-05-14 00:58:55 +00:00
}
2022-05-15 18:22:56 +00:00
p.unscan(tok, lit, pos)
2022-05-14 00:58:55 +00:00
2022-05-19 00:15:31 +00:00
node, err := p.scanNode()
2022-05-14 00:58:55 +00:00
tracef("parseArg node=%+#v err=%+#v", node, err)
if err != nil {
return false, errors.Wrapf(err, "value %q at pos=%v", lit, pos)
2022-05-14 00:58:55 +00:00
}
if node != nil {
p.nodes = append(p.nodes, node)
}
return false, nil
2022-05-14 00:58:55 +00:00
}
2022-05-19 00:15:31 +00:00
func (p *Parser) scanNode() (Node, error) {
2022-05-14 00:58:55 +00:00
tok, lit, pos := p.scan()
2022-05-19 00:15:31 +00:00
tracef("scanNode tok=%s lit=%q pos=%v", tok, lit, pos)
2022-05-14 00:58:55 +00:00
switch tok {
case ARG_DELIMITER:
return ArgDelimiter{}, nil
case ASSIGN:
return nil, errors.Wrapf(ErrSyntax, "bare assignment operator at pos=%v", pos)
2022-05-14 00:58:55 +00:00
case IDENT:
2022-05-19 00:15:31 +00:00
p.unscan(tok, lit, pos)
return p.scanCommandOrIdent()
case COMPOUND_SHORT_FLAG:
p.unscan(tok, lit, pos)
return p.scanCompoundShortFlag()
case SHORT_FLAG, LONG_FLAG:
p.unscan(tok, lit, pos)
return p.scanFlag()
default:
}
2022-05-19 00:15:31 +00:00
return Ident{Literal: lit}, nil
}
2022-05-19 00:15:31 +00:00
func (p *Parser) scanCommandOrIdent() (Node, error) {
tok, lit, pos := p.scan()
2022-05-19 00:15:31 +00:00
if len(p.nodes) == 0 {
p.unscan(tok, lit, pos)
values, err := p.scanValues(p.cfg.Prog.NValue, p.cfg.Prog.ValueNames)
if err != nil {
return nil, err
}
2022-05-19 00:15:31 +00:00
return Program{Name: lit, Values: values}, nil
}
2022-05-14 00:58:55 +00:00
2022-05-19 00:15:31 +00:00
if cfg, ok := p.cfg.Commands[lit]; ok {
p.unscan(tok, lit, pos)
values, err := p.scanValues(cfg.NValue, cfg.ValueNames)
if err != nil {
return nil, err
}
2022-05-19 00:15:31 +00:00
return Command{Name: lit, Values: values}, nil
}
2022-05-19 00:15:31 +00:00
return Ident{Literal: lit}, nil
}
2022-05-19 00:15:31 +00:00
func (p *Parser) scanFlag() (Node, error) {
tok, lit, pos := p.scan()
2022-05-19 00:15:31 +00:00
flagName := string(lit[1:])
if tok == LONG_FLAG {
flagName = string(lit[2:])
}
2022-05-19 00:15:31 +00:00
if cfg, ok := p.cfg.Flags[flagName]; ok {
p.unscan(tok, flagName, pos)
values, err := p.scanValues(cfg.NValue, cfg.ValueNames)
if err != nil {
return nil, err
2022-05-14 00:58:55 +00:00
}
2022-05-19 00:15:31 +00:00
return Flag{Name: flagName, Values: values}, nil
}
2022-05-19 00:15:31 +00:00
return Flag{Name: flagName}, nil
}
2022-05-14 00:58:55 +00:00
2022-05-19 00:15:31 +00:00
func (p *Parser) scanCompoundShortFlag() (Node, error) {
tok, lit, pos := p.scan()
flagNodes := []Node{}
withoutFlagPrefix := lit[1:]
2022-05-19 00:15:31 +00:00
for i, r := range withoutFlagPrefix {
if i == len(withoutFlagPrefix)-1 {
flagName := string(r)
if cfg, ok := p.cfg.Flags[flagName]; ok {
p.unscan(tok, flagName, pos)
values, err := p.scanValues(cfg.NValue, cfg.ValueNames)
if err != nil {
return nil, err
}
flagNodes = append(flagNodes, Flag{Name: flagName, Values: values})
continue
}
2022-05-14 00:58:55 +00:00
}
2022-05-19 00:15:31 +00:00
flagNodes = append(
flagNodes,
Flag{
Name: string(r),
},
)
}
2022-05-19 00:15:31 +00:00
return CompoundShortFlag{Nodes: flagNodes}, nil
}
func (p *Parser) scanValuesAndFlags() (map[string]string, []Node, error) {
return nil, nil, nil
}
2022-05-19 00:15:31 +00:00
func (p *Parser) scanValues(n NValue, valueNames []string) (map[string]string, error) {
_, lit, pos := p.scan()
tracef("scanValues lit=%q pos=%v n=%v valueNames=%+v", lit, pos, n, valueNames)
2022-05-15 18:22:56 +00:00
2022-05-19 00:15:31 +00:00
values, err := func() (map[string]string, error) {
2022-05-15 18:22:56 +00:00
if n == ZeroValue {
2022-05-19 00:15:31 +00:00
return map[string]string{}, nil
2022-05-15 18:22:56 +00:00
}
2022-05-19 00:15:31 +00:00
ret := map[string]string{}
i := 0
2022-05-15 18:22:56 +00:00
for {
lit, err := p.scanIdent()
if err != nil {
if n == NValue(1) {
2022-05-15 18:22:56 +00:00
return nil, err
}
if n == OneOrMoreValue {
break
}
}
2022-05-19 00:15:31 +00:00
name := fmt.Sprintf("%d", i)
if len(valueNames)-1 >= i {
name = valueNames[i]
} else if len(valueNames) > 0 && strings.HasSuffix(valueNames[len(valueNames)-1], "+") {
name = strings.TrimSuffix(valueNames[len(valueNames)-1], "+")
}
ret[name] = lit
2022-05-15 18:22:56 +00:00
if n == NValue(1) && len(ret) == 1 {
2022-05-15 18:22:56 +00:00
break
}
2022-05-19 00:15:31 +00:00
i++
2022-05-15 18:22:56 +00:00
}
return ret, nil
}()
2022-05-14 00:58:55 +00:00
if err != nil {
return nil, err
}
if len(values) == 0 {
return nil, nil
}
2022-05-15 18:22:56 +00:00
return values, nil
2022-05-14 00:58:55 +00:00
}
func (p *Parser) scanIdent() (string, error) {
tok, lit, pos := p.scan()
tracef("scanIdent scanned tok=%s lit=%q pos=%v", tok, lit, pos)
2022-05-19 00:15:31 +00:00
unscanBuf := []scanEntry{}
2022-05-14 00:58:55 +00:00
if tok == ASSIGN || tok == ARG_DELIMITER {
2022-05-19 00:15:31 +00:00
entry := scanEntry{tok: tok, lit: lit, pos: pos}
tracef("scanIdent tok=%s; scanning next and pushing to unscan buffer entry=%+#v", tok, entry)
2022-05-19 00:15:31 +00:00
unscanBuf = append([]scanEntry{entry}, unscanBuf...)
2022-05-15 18:22:56 +00:00
2022-05-14 00:58:55 +00:00
tok, lit, pos = p.scan()
}
if tok == IDENT {
return lit, nil
}
2022-05-19 00:15:31 +00:00
entry := scanEntry{tok: tok, lit: lit, pos: pos}
tracef("scanIdent tok=%s; unscanning entry=%+#v", tok, entry)
2022-05-19 00:15:31 +00:00
unscanBuf = append([]scanEntry{entry}, unscanBuf...)
2022-05-15 18:22:56 +00:00
for _, entry := range unscanBuf {
p.unscan(entry.tok, entry.lit, entry.pos)
2022-05-14 00:58:55 +00:00
}
return "", errors.Wrapf(ErrSyntax, "expected ident at pos=%v but got %s (%q)", pos, tok, lit)
2022-05-14 00:58:55 +00:00
}
func (p *Parser) scan() (Token, string, int) {
2022-05-15 18:22:56 +00:00
if len(p.buf) != 0 {
entry, buf := p.buf[len(p.buf)-1], p.buf[:len(p.buf)-1]
p.buf = buf
tracef("scan returning buffer entry=%s %+#v", entry.tok, entry)
return entry.tok, entry.lit, entry.pos
}
2022-05-14 00:58:55 +00:00
tok, lit, pos := p.s.Scan()
2022-05-19 00:15:31 +00:00
tracef("scan returning next=%s %+#v", tok, scanEntry{tok: tok, lit: lit, pos: pos})
2022-05-14 00:58:55 +00:00
return tok, lit, pos
}
2022-05-15 18:22:56 +00:00
func (p *Parser) unscan(tok Token, lit string, pos int) {
2022-05-19 00:15:31 +00:00
entry := scanEntry{tok: tok, lit: lit, pos: pos}
2022-05-15 18:22:56 +00:00
tracef("unscan entry=%s %+#v", tok, entry)
2022-05-14 00:58:55 +00:00
2022-05-15 18:22:56 +00:00
p.buf = append(p.buf, entry)
2022-05-14 00:58:55 +00:00
}