Bombabillo is a non-web client for the terminal, supporting Gopher, Gemini and much more. https://bombadillo.colorfield.space
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
bombadillo/cmdparse/parser.go

157 lines
3.1 KiB

/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package cmdparse
import (
"fmt"
"io"
)
//------------------------------------------------\\
// + + + T Y P E S + + + \\
//--------------------------------------------------\\
type Parser struct {
s *scanner
buffer struct {
token Token
size int
}
}
type Command struct {
Action string
Target string
Value []string
Type Comtype
}
type Comtype int
const (
GOURL Comtype = iota
GOLINK
SIMPLE
DOLINK
DOLINKAS
DOAS
DO
)
//------------------------------------------------\\
// + + + R E C E I V E R S + + + \\
//--------------------------------------------------\\
func (p *Parser) scan() (current Token) {
if p.buffer.size != 0 {
p.buffer.size = 0
return p.buffer.token
}
current = p.s.scan()
for {
if current.kind != Whitespace {
break
}
current = p.s.scan()
}
p.buffer.token = current
return
}
func (p *Parser) unscan() { p.buffer.size = 1 }
func (p *Parser) parseNonAction() (*Command, error) {
p.unscan()
t := p.scan()
cm := &Command{}
if t.kind == Value {
cm.Target = t.val
cm.Type = GOLINK
} else if t.kind == Word {
cm.Target = t.val
cm.Type = GOURL
} else {
return nil, fmt.Errorf("Found %q, expected action, url, or link number", t.val)
}
if u := p.scan(); u.kind != End {
return nil, fmt.Errorf("Found %q, expected EOF", u.val)
}
return cm, nil
}
func (p *Parser) parseAction() (*Command, error) {
p.unscan()
t := p.scan()
cm := &Command{}
cm.Action = t.val
t = p.scan()
switch t.kind {
case End:
cm.Type = SIMPLE
return cm, nil
case Value:
cm.Target = t.val
cm.Type = DOLINK
case Word, Action:
cm.Value = append(cm.Value, t.val)
cm.Type = DO
case Whitespace:
return nil, fmt.Errorf("Found %q (%d), expected value", t.val, t.kind)
}
t = p.scan()
if t.kind == End {
return cm, nil
} else {
if cm.Type == DOLINK {
cm.Type = DOLINKAS
} else {
cm.Type = DOAS
}
cm.Value = append(cm.Value, t.val)
for {
token := p.scan()
if token.kind == End {
break
} else if token.kind == Whitespace {
continue
}
cm.Value = append(cm.Value, token.val)
}
}
return cm, nil
}
func (p *Parser) Parse() (*Command, error) {
if t := p.scan(); t.kind != Action {
return p.parseNonAction()
} else {
return p.parseAction()
}
}
//------------------------------------------------\\
// + + + F U N C T I O N S + + + \\
//--------------------------------------------------\\
func NewParser(r io.Reader) *Parser {
return &Parser{s: NewScanner(r)}
}