2014-03-03 13:41:44 +08:00
|
|
|
package eval
|
|
|
|
|
2014-04-02 10:51:09 +08:00
|
|
|
// Builtin special forms.
|
2014-03-03 13:41:44 +08:00
|
|
|
|
2014-10-30 03:50:10 +08:00
|
|
|
import "github.com/elves/elvish/parse"
|
2014-03-03 13:41:44 +08:00
|
|
|
|
2014-04-30 10:45:54 +08:00
|
|
|
type strOp func(*Evaluator) string
|
|
|
|
type builtinSpecialCompile func(*Compiler, *parse.FormNode) strOp
|
2014-04-02 10:51:09 +08:00
|
|
|
|
|
|
|
type builtinSpecial struct {
|
2014-04-30 10:45:54 +08:00
|
|
|
compile builtinSpecialCompile
|
2014-04-02 10:51:09 +08:00
|
|
|
streamTypes [2]StreamType
|
|
|
|
}
|
|
|
|
|
|
|
|
var builtinSpecials map[string]builtinSpecial
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
// Needed to avoid initialization loop
|
|
|
|
builtinSpecials = map[string]builtinSpecial{
|
2014-04-30 10:45:54 +08:00
|
|
|
"var": builtinSpecial{compileVar, [2]StreamType{}},
|
|
|
|
"set": builtinSpecial{compileSet, [2]StreamType{}},
|
|
|
|
"del": builtinSpecial{compileDel, [2]StreamType{}},
|
2014-04-02 10:51:09 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-26 05:26:56 +08:00
|
|
|
func checkSetType(cp *Compiler, names []string, values []*parse.CompoundNode, vop valuesOp, p parse.Pos) {
|
|
|
|
if !vop.tr.mayCountTo(len(names)) {
|
|
|
|
cp.errorf(p, "number of variables doesn't match that of values")
|
2014-07-19 16:53:31 +08:00
|
|
|
}
|
2014-09-17 23:45:32 +08:00
|
|
|
_, more := vop.tr.count()
|
|
|
|
if more {
|
|
|
|
// TODO Try to check soundness to some extent
|
|
|
|
return
|
|
|
|
}
|
2014-09-26 05:26:56 +08:00
|
|
|
for i, name := range names {
|
2014-09-17 23:45:32 +08:00
|
|
|
t := vop.tr[i].t
|
|
|
|
if _, ok := t.(AnyType); ok {
|
2014-07-19 16:53:31 +08:00
|
|
|
// TODO Check type soundness at runtime
|
|
|
|
continue
|
|
|
|
}
|
2014-09-20 07:23:29 +08:00
|
|
|
if cp.ResolveVar(name) != t {
|
2014-09-26 05:26:56 +08:00
|
|
|
cp.errorf(values[i].Pos, "type mismatch")
|
2014-07-19 16:53:31 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-19 23:46:40 +08:00
|
|
|
// ensure that a CompoundNode contains exactly one PrimaryNode.
|
|
|
|
func ensurePrimary(cp *Compiler, cn *parse.CompoundNode, msg string) *parse.PrimaryNode {
|
2014-09-26 05:26:56 +08:00
|
|
|
if len(cn.Nodes) != 1 || cn.Nodes[0].Right != nil {
|
|
|
|
cp.errorf(cn.Pos, msg)
|
|
|
|
}
|
|
|
|
return cn.Nodes[0].Left
|
|
|
|
}
|
|
|
|
|
2015-01-19 23:46:40 +08:00
|
|
|
// ensure that a CompoundNode contains exactly one PrimaryNode of type
|
|
|
|
// VariablePrimary or StringPrimary
|
|
|
|
func ensureVariableOrStringPrimary(cp *Compiler, cn *parse.CompoundNode, msg string) (*parse.PrimaryNode, string) {
|
|
|
|
pn := ensurePrimary(cp, cn, msg)
|
|
|
|
switch pn.Typ {
|
|
|
|
case parse.VariablePrimary, parse.StringPrimary:
|
|
|
|
return pn, pn.Node.(*parse.StringNode).Text
|
|
|
|
default:
|
|
|
|
cp.errorf(cn.Pos, msg)
|
|
|
|
return nil, ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ensure the first compound of the form is a VariablePrimary. This is merely
|
|
|
|
// for better error messages; No actual processing is done.
|
|
|
|
func ensureStartWithVariable(cp *Compiler, fn *parse.FormNode, form string) {
|
|
|
|
if len(fn.Args.Nodes) == 0 {
|
|
|
|
cp.errorf(fn.Pos, "expect variable after %s", form)
|
|
|
|
}
|
|
|
|
expect := "expect variable"
|
|
|
|
pn := ensurePrimary(cp, fn.Args.Nodes[0], expect)
|
|
|
|
if pn.Typ != parse.VariablePrimary {
|
|
|
|
cp.errorf(pn.Pos, expect)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-26 05:26:56 +08:00
|
|
|
const (
|
|
|
|
varArg0Req = "must be either a variable or a table"
|
|
|
|
varArg0ReqMultiElem = "must be either a variable or a string referring to a type"
|
|
|
|
varArg1ReqMulti = "must be a table with no dict part"
|
|
|
|
varArg1ReqSingle = "must be a string referring to a type"
|
|
|
|
|
|
|
|
setArg0Req = varArg0Req
|
|
|
|
setArg0ReqMultiElem = "must be a variable"
|
|
|
|
setArg1ReqMulti = varArg1ReqMulti
|
|
|
|
)
|
|
|
|
|
2015-01-19 23:46:40 +08:00
|
|
|
// An invocation of the var special form looks like:
|
|
|
|
//
|
|
|
|
// VarForm = 'var' { VarGroup } [ { VariablePrimary } ] [ Assignment ]
|
|
|
|
// VarGroup = { VariablePrimary } StringPrimary
|
|
|
|
// Assignment = '=' { Compound }
|
|
|
|
//
|
|
|
|
// Variables in the same VarGroup has the type specified by the StringPrimary.
|
|
|
|
// Trailing variables have type Any. For instance,
|
|
|
|
//
|
|
|
|
// var $u $v Type1 $x $y Type2 $z = a b c d e
|
|
|
|
//
|
|
|
|
// gives $u and $v type Type1, $x $y type Type2 and $z type Any and
|
|
|
|
// assigns them the values a, b, c, d, e respectively.
|
2014-09-26 05:26:56 +08:00
|
|
|
func compileVar(cp *Compiler, fn *parse.FormNode) strOp {
|
|
|
|
var (
|
|
|
|
names []string
|
|
|
|
types []Type
|
|
|
|
values []*parse.CompoundNode
|
|
|
|
)
|
|
|
|
|
2015-01-19 23:46:40 +08:00
|
|
|
ensureStartWithVariable(cp, fn, "var")
|
2014-09-26 05:26:56 +08:00
|
|
|
|
2015-01-19 23:46:40 +08:00
|
|
|
for i, cn := range fn.Args.Nodes {
|
|
|
|
expect := "expect variable, type or equal sign"
|
|
|
|
pn, text := ensureVariableOrStringPrimary(cp, cn, expect)
|
|
|
|
if pn.Typ == parse.VariablePrimary {
|
|
|
|
names = append(names, text)
|
2014-03-03 13:41:44 +08:00
|
|
|
} else {
|
2015-01-19 23:46:40 +08:00
|
|
|
if text == "=" {
|
|
|
|
values = fn.Args.Nodes[i+1:]
|
|
|
|
break
|
|
|
|
} else {
|
|
|
|
if t, ok := typenames[text]; !ok {
|
|
|
|
cp.errorf(pn.Pos, "%v is not a valid type name", text)
|
2014-09-26 05:26:56 +08:00
|
|
|
} else {
|
2015-01-19 23:46:40 +08:00
|
|
|
if len(names) == len(types) {
|
|
|
|
cp.errorf(pn.Pos, "duplicate type")
|
2014-09-26 05:26:56 +08:00
|
|
|
}
|
|
|
|
for i := len(types); i < len(names); i++ {
|
|
|
|
types = append(types, t)
|
|
|
|
}
|
2014-03-30 16:02:56 +08:00
|
|
|
}
|
|
|
|
}
|
2014-03-03 13:41:44 +08:00
|
|
|
}
|
2015-01-19 23:46:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
for i := len(types); i < len(names); i++ {
|
|
|
|
types = append(types, AnyType{})
|
2014-09-26 05:26:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
for i, name := range names {
|
|
|
|
cp.pushVar(name, types[i])
|
2014-03-03 13:41:44 +08:00
|
|
|
}
|
2014-09-26 05:26:56 +08:00
|
|
|
|
|
|
|
var vop valuesOp
|
|
|
|
if values != nil {
|
|
|
|
vop = cp.compileCompounds(values)
|
|
|
|
checkSetType(cp, names, values, vop, fn.Pos)
|
|
|
|
}
|
|
|
|
return func(ev *Evaluator) string {
|
|
|
|
for i, name := range names {
|
|
|
|
ev.scope[name] = valuePtr(types[i].Default())
|
2014-04-30 10:45:54 +08:00
|
|
|
}
|
2014-09-26 05:26:56 +08:00
|
|
|
if vop.f != nil {
|
|
|
|
return doSet(ev, names, vop.f(ev))
|
2014-04-30 10:45:54 +08:00
|
|
|
}
|
2014-09-26 05:26:56 +08:00
|
|
|
return ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-19 23:46:40 +08:00
|
|
|
// An invocation of the set special form looks like:
|
|
|
|
//
|
|
|
|
// SetForm = 'set' { VariablePrimary } '=' { Compound }
|
2014-09-26 05:26:56 +08:00
|
|
|
func compileSet(cp *Compiler, fn *parse.FormNode) strOp {
|
|
|
|
var (
|
|
|
|
names []string
|
|
|
|
values []*parse.CompoundNode
|
|
|
|
)
|
|
|
|
|
2015-01-19 23:46:40 +08:00
|
|
|
ensureStartWithVariable(cp, fn, "set")
|
2014-09-26 05:26:56 +08:00
|
|
|
|
2015-01-19 23:46:40 +08:00
|
|
|
for i, cn := range fn.Args.Nodes {
|
|
|
|
expect := "expect variable or equal sign"
|
|
|
|
pn, text := ensureVariableOrStringPrimary(cp, cn, expect)
|
|
|
|
if pn.Typ == parse.VariablePrimary {
|
|
|
|
names = append(names, text)
|
|
|
|
} else {
|
|
|
|
if text != "=" {
|
|
|
|
cp.errorf(pn.Pos, expect)
|
2014-04-30 10:45:54 +08:00
|
|
|
}
|
2015-01-19 23:46:40 +08:00
|
|
|
values = fn.Args.Nodes[i+1:]
|
|
|
|
break
|
2014-03-30 16:02:56 +08:00
|
|
|
}
|
2014-03-30 10:10:06 +08:00
|
|
|
}
|
2014-03-03 13:41:44 +08:00
|
|
|
|
2014-09-26 05:26:56 +08:00
|
|
|
var vop valuesOp
|
|
|
|
vop = cp.compileCompounds(values)
|
|
|
|
checkSetType(cp, names, values, vop, fn.Pos)
|
2014-03-20 13:19:30 +08:00
|
|
|
|
2014-09-26 05:26:56 +08:00
|
|
|
return func(ev *Evaluator) string {
|
|
|
|
return doSet(ev, names, vop.f(ev))
|
|
|
|
}
|
2014-03-20 13:19:30 +08:00
|
|
|
}
|
|
|
|
|
2014-03-20 13:50:13 +08:00
|
|
|
func doSet(ev *Evaluator, names []string, values []Value) string {
|
|
|
|
// TODO Support assignment of mismatched arity in some restricted way -
|
|
|
|
// "optional" and "rest" arguments and the like
|
|
|
|
if len(names) != len(values) {
|
|
|
|
return "arity mismatch"
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, name := range names {
|
|
|
|
// TODO Prevent overriding builtin variables e.g. $pid $env
|
2014-04-30 10:45:54 +08:00
|
|
|
*ev.scope[name] = values[i]
|
2014-03-20 13:50:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2014-04-30 10:45:54 +08:00
|
|
|
func compileDel(cp *Compiler, fn *parse.FormNode) strOp {
|
2014-09-16 23:57:33 +08:00
|
|
|
// Do conventional compiling of all compound expressions, including
|
|
|
|
// ensuring that variables can be resolved
|
2014-09-26 05:26:56 +08:00
|
|
|
var names []string
|
2014-04-02 11:13:43 +08:00
|
|
|
for _, n := range fn.Args.Nodes {
|
2014-09-16 23:57:33 +08:00
|
|
|
compoundReq := "must be a varible"
|
2014-09-17 03:12:46 +08:00
|
|
|
if len(n.Nodes) != 1 || n.Nodes[0].Right != nil {
|
2014-09-17 04:33:22 +08:00
|
|
|
cp.errorf(n.Pos, "%s", compoundReq)
|
2014-04-02 11:13:43 +08:00
|
|
|
}
|
2014-09-17 03:12:46 +08:00
|
|
|
nf := n.Nodes[0].Left
|
2014-09-04 23:16:52 +08:00
|
|
|
if nf.Typ != parse.VariablePrimary {
|
2014-09-17 04:33:22 +08:00
|
|
|
cp.errorf(n.Pos, "%s", compoundReq)
|
2014-04-02 11:13:43 +08:00
|
|
|
}
|
|
|
|
name := nf.Node.(*parse.StringNode).Text
|
2014-09-20 07:23:29 +08:00
|
|
|
cp.mustResolveVar(name, nf.Pos)
|
2014-04-30 10:45:54 +08:00
|
|
|
if !cp.hasVarOnThisScope(name) {
|
2014-09-17 04:33:22 +08:00
|
|
|
cp.errorf(n.Pos, "can only delete variable on current scope")
|
2014-04-02 11:13:43 +08:00
|
|
|
}
|
2014-04-30 10:45:54 +08:00
|
|
|
cp.popVar(name)
|
2014-09-26 05:26:56 +08:00
|
|
|
names = append(names, name)
|
2014-04-02 11:13:43 +08:00
|
|
|
}
|
2014-04-30 10:45:54 +08:00
|
|
|
return func(ev *Evaluator) string {
|
2014-09-26 05:26:56 +08:00
|
|
|
for _, name := range names {
|
2014-04-30 10:45:54 +08:00
|
|
|
delete(ev.scope, name)
|
|
|
|
}
|
|
|
|
return ""
|
2014-04-02 11:13:43 +08:00
|
|
|
}
|
|
|
|
}
|