2016-08-13 12:49:04 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
// Grammar of the language used in the evaluator
|
|
|
|
//
|
|
|
|
// Expr = SetExpr
|
|
|
|
// | MapExpr
|
|
|
|
// | CmdExpr
|
|
|
|
// | CallExpr
|
|
|
|
// | ExecExpr
|
|
|
|
// | ListExpr
|
|
|
|
//
|
|
|
|
// SetExpr = 'set' <opt> <val> ';'
|
|
|
|
//
|
|
|
|
// MapExpr = 'map' <keys> Expr ';'
|
|
|
|
//
|
|
|
|
// CmdExpr = 'cmd' <name> Expr ';'
|
|
|
|
//
|
|
|
|
// CallExpr = <name> <args> ';'
|
|
|
|
//
|
|
|
|
// ExecExpr = Prefix <expr> '\n'
|
|
|
|
// | Prefix '{{' <expr> '}}' ';'
|
|
|
|
//
|
|
|
|
// Prefix = '$' | '!' | '&' | '/' | '?'
|
|
|
|
//
|
2016-10-15 18:46:31 +00:00
|
|
|
// ListExpr = ':' ListRest '\n'
|
2016-08-13 12:49:04 +00:00
|
|
|
// | ':' '{{' ListRest '}}' ';'
|
|
|
|
//
|
|
|
|
// ListRest = Nil
|
|
|
|
// | Expr ListRest
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
)
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
type expr interface {
|
2016-08-13 12:49:04 +00:00
|
|
|
String() string
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
eval(app *app, args []string)
|
2016-08-13 12:49:04 +00:00
|
|
|
// TODO: add a bind method to avoid passing args in eval
|
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
type setExpr struct {
|
2016-08-13 12:49:04 +00:00
|
|
|
opt string
|
|
|
|
val string
|
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func (e *setExpr) String() string { return fmt.Sprintf("set %s %s", e.opt, e.val) }
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
type mapExpr struct {
|
2016-08-13 12:49:04 +00:00
|
|
|
keys string
|
2016-12-17 21:47:37 +00:00
|
|
|
expr expr
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func (e *mapExpr) String() string { return fmt.Sprintf("map %s %s", e.keys, e.expr) }
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
type cmdExpr struct {
|
2016-08-13 12:49:04 +00:00
|
|
|
name string
|
2016-12-17 21:47:37 +00:00
|
|
|
expr expr
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func (e *cmdExpr) String() string { return fmt.Sprintf("cmd %s %s", e.name, e.expr) }
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
type callExpr struct {
|
2016-08-13 12:49:04 +00:00
|
|
|
name string
|
|
|
|
args []string
|
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func (e *callExpr) String() string { return fmt.Sprintf("%s -- %s", e.name, e.args) }
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
type execExpr struct {
|
2016-08-13 12:49:04 +00:00
|
|
|
pref string
|
|
|
|
expr string
|
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func (e *execExpr) String() string { return fmt.Sprintf("%s %s", e.pref, e.expr) }
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
type listExpr struct {
|
|
|
|
exprs []expr
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func (e *listExpr) String() string {
|
2016-08-13 12:49:04 +00:00
|
|
|
buf := []byte{':', '{', '{', ' '}
|
|
|
|
for _, expr := range e.exprs {
|
|
|
|
buf = append(buf, expr.String()...)
|
|
|
|
buf = append(buf, ';', ' ')
|
|
|
|
}
|
|
|
|
buf = append(buf, '}', '}')
|
|
|
|
return string(buf)
|
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
type parser struct {
|
|
|
|
scanner *scanner
|
|
|
|
expr expr
|
2016-08-13 12:49:04 +00:00
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func newParser(r io.Reader) *parser {
|
2016-08-13 12:49:04 +00:00
|
|
|
scanner := newScanner(r)
|
|
|
|
|
|
|
|
scanner.scan()
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
return &parser{
|
2016-08-13 12:49:04 +00:00
|
|
|
scanner: scanner,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func (p *parser) parseExpr() expr {
|
2016-08-13 12:49:04 +00:00
|
|
|
s := p.scanner
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
var result expr
|
2016-08-13 12:49:04 +00:00
|
|
|
|
|
|
|
switch s.typ {
|
2016-12-17 21:47:37 +00:00
|
|
|
case tokenEOF:
|
2016-08-13 12:49:04 +00:00
|
|
|
return nil
|
2016-12-17 21:47:37 +00:00
|
|
|
case tokenIdent:
|
2016-08-13 12:49:04 +00:00
|
|
|
switch s.tok {
|
|
|
|
case "set":
|
2016-10-16 11:19:19 +00:00
|
|
|
var val string
|
|
|
|
|
2016-08-13 12:49:04 +00:00
|
|
|
s.scan()
|
2016-12-17 21:47:37 +00:00
|
|
|
if s.typ != tokenIdent {
|
2016-10-16 11:19:19 +00:00
|
|
|
p.err = fmt.Errorf("expected identifier: %s", s.tok)
|
|
|
|
}
|
2016-08-13 12:49:04 +00:00
|
|
|
opt := s.tok
|
|
|
|
|
|
|
|
s.scan()
|
2016-12-17 21:47:37 +00:00
|
|
|
if s.typ != tokenSemicolon {
|
2016-08-13 12:49:04 +00:00
|
|
|
val = s.tok
|
|
|
|
s.scan()
|
|
|
|
}
|
|
|
|
|
|
|
|
s.scan()
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
result = &setExpr{opt, val}
|
2016-08-13 12:49:04 +00:00
|
|
|
case "map":
|
2016-12-17 21:47:37 +00:00
|
|
|
var expr expr
|
2016-10-16 18:23:24 +00:00
|
|
|
|
2016-08-13 12:49:04 +00:00
|
|
|
s.scan()
|
|
|
|
keys := s.tok
|
|
|
|
|
|
|
|
s.scan()
|
2016-12-17 21:47:37 +00:00
|
|
|
if s.typ != tokenSemicolon {
|
2016-10-16 18:23:24 +00:00
|
|
|
expr = p.parseExpr()
|
|
|
|
} else {
|
|
|
|
s.scan()
|
|
|
|
}
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
result = &mapExpr{keys, expr}
|
2016-08-13 12:49:04 +00:00
|
|
|
case "cmd":
|
2016-12-17 21:47:37 +00:00
|
|
|
var expr expr
|
2016-10-16 18:23:24 +00:00
|
|
|
|
2016-08-13 12:49:04 +00:00
|
|
|
s.scan()
|
|
|
|
name := s.tok
|
|
|
|
|
|
|
|
s.scan()
|
2016-12-17 21:47:37 +00:00
|
|
|
if s.typ != tokenSemicolon {
|
2016-10-16 18:23:24 +00:00
|
|
|
expr = p.parseExpr()
|
|
|
|
} else {
|
|
|
|
s.scan()
|
|
|
|
}
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
result = &cmdExpr{name, expr}
|
2016-08-13 12:49:04 +00:00
|
|
|
default:
|
|
|
|
name := s.tok
|
|
|
|
|
|
|
|
var args []string
|
2016-12-17 21:47:37 +00:00
|
|
|
for s.scan() && s.typ != tokenSemicolon {
|
2016-08-13 12:49:04 +00:00
|
|
|
args = append(args, s.tok)
|
|
|
|
}
|
|
|
|
|
|
|
|
s.scan()
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
result = &callExpr{name, args}
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
2016-12-17 21:47:37 +00:00
|
|
|
case tokenColon:
|
2016-08-13 12:49:04 +00:00
|
|
|
s.scan()
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
var exprs []expr
|
|
|
|
if s.typ == tokenLBraces {
|
2016-08-13 12:49:04 +00:00
|
|
|
s.scan()
|
|
|
|
for {
|
|
|
|
e := p.parseExpr()
|
|
|
|
if e == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
exprs = append(exprs, e)
|
2016-12-17 21:47:37 +00:00
|
|
|
if s.typ == tokenRBraces {
|
2016-08-13 12:49:04 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
s.scan()
|
|
|
|
} else {
|
|
|
|
for {
|
|
|
|
e := p.parseExpr()
|
|
|
|
if e == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
exprs = append(exprs, e)
|
|
|
|
if s.tok == "\n" {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
s.scan()
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
result = &listExpr{exprs}
|
|
|
|
case tokenPrefix:
|
2016-10-16 11:19:19 +00:00
|
|
|
var expr string
|
|
|
|
|
2016-08-13 12:49:04 +00:00
|
|
|
pref := s.tok
|
|
|
|
|
|
|
|
s.scan()
|
2016-12-17 21:47:37 +00:00
|
|
|
if s.typ == tokenLBraces {
|
2016-08-13 12:49:04 +00:00
|
|
|
s.scan()
|
|
|
|
expr = s.tok
|
|
|
|
s.scan()
|
|
|
|
} else {
|
2016-10-16 11:19:19 +00:00
|
|
|
expr = s.tok
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
s.scan()
|
|
|
|
s.scan()
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
result = &execExpr{pref, expr}
|
2016-08-13 12:49:04 +00:00
|
|
|
default:
|
2016-10-16 11:19:19 +00:00
|
|
|
p.err = fmt.Errorf("unexpected token: %s", s.tok)
|
2016-08-13 12:49:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2016-12-17 21:47:37 +00:00
|
|
|
func (p *parser) parse() bool {
|
2016-08-13 12:49:04 +00:00
|
|
|
if p.expr = p.parseExpr(); p.expr == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|