2014-03-03 13:41:44 +08:00
|
|
|
package eval
|
|
|
|
|
2017-06-19 18:50:52 +08:00
|
|
|
// Builtin special forms. Special forms behave mostly like ordinary commands -
|
|
|
|
// they are valid commands syntactically, and can take part in pipelines - but
|
|
|
|
// they have special rules for the evaluation of their arguments and can affect
|
|
|
|
// the compilation phase (whereas ordinary commands can only affect the
|
|
|
|
// evaluation phase).
|
|
|
|
//
|
|
|
|
// For instance, the "and" special form evaluates its arguments from left to
|
|
|
|
// right, and stops as soon as one booleanly false value is obtained: the
|
|
|
|
// command "and $false (fail haha)" does not produce an exception.
|
|
|
|
//
|
|
|
|
// As another instance, the "del" special form removes a variable, affecting the
|
|
|
|
// compiler.
|
|
|
|
//
|
|
|
|
// Flow control structures are also implemented as special forms in elvish, with
|
|
|
|
// closures functioning as code blocks.
|
2014-03-03 13:41:44 +08:00
|
|
|
|
2015-01-25 06:45:34 +08:00
|
|
|
import (
|
2015-01-25 07:31:52 +08:00
|
|
|
"os"
|
2017-09-21 07:37:17 +08:00
|
|
|
"path/filepath"
|
2016-10-11 00:07:57 +08:00
|
|
|
"strings"
|
2015-01-25 06:45:34 +08:00
|
|
|
|
2020-03-29 04:34:52 +08:00
|
|
|
"github.com/elves/elvish/pkg/diag"
|
2019-12-24 04:00:59 +08:00
|
|
|
"github.com/elves/elvish/pkg/eval/vals"
|
|
|
|
"github.com/elves/elvish/pkg/eval/vars"
|
|
|
|
"github.com/elves/elvish/pkg/parse"
|
2015-01-25 06:45:34 +08:00
|
|
|
)
|
2014-03-03 13:41:44 +08:00
|
|
|
|
2019-03-10 23:23:43 +08:00
|
|
|
type compileBuiltin func(*compiler, *parse.Form) effectOpBody
|
2014-04-02 10:51:09 +08:00
|
|
|
|
2016-01-23 01:05:15 +08:00
|
|
|
var builtinSpecials map[string]compileBuiltin
|
2016-02-08 06:23:16 +08:00
|
|
|
|
2017-05-30 06:39:40 +08:00
|
|
|
// IsBuiltinSpecial is the set of all names of builtin special forms. It is
|
|
|
|
// intended for external consumption, e.g. the syntax highlighter.
|
|
|
|
var IsBuiltinSpecial = map[string]bool{}
|
2014-04-02 10:51:09 +08:00
|
|
|
|
2020-01-11 10:27:43 +08:00
|
|
|
type noSuchModule struct{ spec string }
|
|
|
|
|
|
|
|
func (err noSuchModule) Error() string { return "no such module: " + err.spec }
|
|
|
|
|
2014-04-02 10:51:09 +08:00
|
|
|
func init() {
|
|
|
|
// Needed to avoid initialization loop
|
2016-01-23 01:05:15 +08:00
|
|
|
builtinSpecials = map[string]compileBuiltin{
|
2017-02-17 08:28:58 +08:00
|
|
|
"del": compileDel,
|
|
|
|
"fn": compileFn,
|
|
|
|
"use": compileUse,
|
2017-03-03 08:21:59 +08:00
|
|
|
"and": compileAnd,
|
|
|
|
"or": compileOr,
|
2017-02-18 12:34:11 +08:00
|
|
|
"if": compileIf,
|
2017-02-17 08:28:58 +08:00
|
|
|
"while": compileWhile,
|
2017-02-18 12:34:11 +08:00
|
|
|
"for": compileFor,
|
2017-02-18 12:16:21 +08:00
|
|
|
"try": compileTry,
|
2014-04-02 10:51:09 +08:00
|
|
|
}
|
2017-05-30 06:39:40 +08:00
|
|
|
for name := range builtinSpecials {
|
|
|
|
IsBuiltinSpecial[name] = true
|
2015-07-06 23:58:51 +08:00
|
|
|
}
|
2014-04-02 10:51:09 +08:00
|
|
|
}
|
|
|
|
|
2018-01-11 09:25:26 +08:00
|
|
|
const delArgMsg = "arguments to del must be variable or variable elements"
|
|
|
|
|
2015-01-22 00:22:55 +08:00
|
|
|
// DelForm = 'del' { VariablePrimary }
|
2019-03-10 23:23:43 +08:00
|
|
|
func compileDel(cp *compiler, fn *parse.Form) effectOpBody {
|
2018-09-29 03:45:11 +08:00
|
|
|
var ops []effectOp
|
2016-01-23 07:56:09 +08:00
|
|
|
for _, cn := range fn.Args {
|
2018-01-11 09:25:26 +08:00
|
|
|
if len(cn.Indexings) != 1 {
|
2020-03-29 04:50:41 +08:00
|
|
|
cp.errorpf(cn, delArgMsg)
|
2018-01-11 09:25:26 +08:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
head, indicies := cn.Indexings[0].Head, cn.Indexings[0].Indicies
|
|
|
|
if head.Type != parse.Bareword {
|
|
|
|
if head.Type == parse.Variable {
|
2020-03-29 04:50:41 +08:00
|
|
|
cp.errorpf(cn, "arguments to del must drop $")
|
2018-01-11 09:25:26 +08:00
|
|
|
} else {
|
2020-03-29 04:50:41 +08:00
|
|
|
cp.errorpf(cn, delArgMsg)
|
2018-01-11 09:25:26 +08:00
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2019-10-23 05:49:32 +08:00
|
|
|
sigil, qname := SplitVariableRef(head.Value)
|
|
|
|
if sigil != "" {
|
2020-03-29 04:50:41 +08:00
|
|
|
cp.errorpf(cn, "arguments to del may not have a sigils, got %q", sigil)
|
2018-01-11 09:25:26 +08:00
|
|
|
continue
|
2016-02-07 10:12:54 +08:00
|
|
|
}
|
2019-03-10 23:23:43 +08:00
|
|
|
var f effectOpBody
|
2018-01-11 09:25:26 +08:00
|
|
|
if len(indicies) == 0 {
|
2019-10-23 05:49:32 +08:00
|
|
|
ns, name := SplitQNameNsFirst(qname)
|
2018-01-11 09:25:26 +08:00
|
|
|
switch ns {
|
2019-10-23 05:49:32 +08:00
|
|
|
case "", ":", "local:":
|
2018-01-11 09:25:26 +08:00
|
|
|
if !cp.thisScope().has(name) {
|
2020-03-29 04:50:41 +08:00
|
|
|
cp.errorpf(cn, "no variable $%s in local scope", name)
|
2018-01-11 09:25:26 +08:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
cp.thisScope().del(name)
|
2018-01-21 20:49:25 +08:00
|
|
|
f = delLocalVarOp{name}
|
2019-10-23 05:49:32 +08:00
|
|
|
case "E:":
|
2018-01-21 20:49:25 +08:00
|
|
|
f = delEnvVarOp{name}
|
2018-01-11 09:25:26 +08:00
|
|
|
default:
|
2020-03-29 04:50:41 +08:00
|
|
|
cp.errorpf(cn, "only variables in local: or E: can be deleted")
|
2018-01-11 09:25:26 +08:00
|
|
|
continue
|
2015-01-25 07:05:47 +08:00
|
|
|
}
|
2018-01-11 09:25:26 +08:00
|
|
|
} else {
|
2019-10-23 05:49:32 +08:00
|
|
|
if !cp.registerVariableGet(qname) {
|
2020-03-29 04:50:41 +08:00
|
|
|
cp.errorpf(cn, "no variable $%s", head.Value)
|
2018-01-11 09:25:26 +08:00
|
|
|
continue
|
|
|
|
}
|
2019-10-23 05:49:32 +08:00
|
|
|
f = newDelElementOp(qname, head.Range().From, head.Range().To, cp.arrayOps(indicies))
|
2018-01-11 09:25:26 +08:00
|
|
|
}
|
2020-03-29 04:14:14 +08:00
|
|
|
ops = append(ops, effectOp{f, cn.Range()})
|
2018-01-11 09:25:26 +08:00
|
|
|
}
|
2018-01-21 20:49:25 +08:00
|
|
|
return seqOp{ops}
|
2018-01-11 09:25:26 +08:00
|
|
|
}
|
|
|
|
|
2018-01-21 20:49:25 +08:00
|
|
|
type delLocalVarOp struct{ name string }
|
|
|
|
|
2018-09-29 03:45:11 +08:00
|
|
|
func (op delLocalVarOp) invoke(fm *Frame) error {
|
2018-01-21 20:49:25 +08:00
|
|
|
delete(fm.local, op.name)
|
|
|
|
return nil
|
2018-01-11 09:25:26 +08:00
|
|
|
}
|
2015-01-22 00:22:55 +08:00
|
|
|
|
2018-01-21 20:49:25 +08:00
|
|
|
type delEnvVarOp struct{ name string }
|
|
|
|
|
2018-09-29 03:45:11 +08:00
|
|
|
func (op delEnvVarOp) invoke(*Frame) error {
|
2018-01-21 20:49:25 +08:00
|
|
|
return os.Unsetenv(op.name)
|
|
|
|
}
|
|
|
|
|
2019-10-23 05:49:32 +08:00
|
|
|
func newDelElementOp(qname string, begin, headEnd int, indexOps []valuesOp) effectOpBody {
|
2018-01-11 09:25:26 +08:00
|
|
|
ends := make([]int, len(indexOps)+1)
|
|
|
|
ends[0] = headEnd
|
|
|
|
for i, op := range indexOps {
|
2020-03-29 04:14:14 +08:00
|
|
|
ends[i+1] = op.To
|
2014-04-02 11:13:43 +08:00
|
|
|
}
|
2019-10-23 05:49:32 +08:00
|
|
|
return &delElemOp{qname, indexOps, begin, ends}
|
2014-04-02 11:13:43 +08:00
|
|
|
}
|
2015-01-20 05:11:44 +08:00
|
|
|
|
2018-01-21 20:49:25 +08:00
|
|
|
type delElemOp struct {
|
2019-10-23 05:49:32 +08:00
|
|
|
qname string
|
2018-09-29 03:45:11 +08:00
|
|
|
indexOps []valuesOp
|
2018-01-21 20:49:25 +08:00
|
|
|
begin int
|
|
|
|
ends []int
|
2018-01-11 09:25:26 +08:00
|
|
|
}
|
|
|
|
|
2018-09-29 03:45:11 +08:00
|
|
|
func (op *delElemOp) invoke(fm *Frame) error {
|
2018-01-30 01:39:41 +08:00
|
|
|
var indicies []interface{}
|
2018-01-21 20:49:25 +08:00
|
|
|
for _, indexOp := range op.indexOps {
|
2018-09-29 03:45:11 +08:00
|
|
|
indexValues, err := indexOp.exec(fm)
|
2018-01-21 20:49:25 +08:00
|
|
|
if err != nil {
|
2018-01-21 08:25:53 +08:00
|
|
|
return err
|
2015-07-08 18:31:40 +08:00
|
|
|
}
|
2018-01-21 20:49:25 +08:00
|
|
|
if len(indexValues) != 1 {
|
2020-04-09 06:33:22 +08:00
|
|
|
return fm.errorpf(indexOp, "index must evaluate to a single value in argument to del")
|
2018-01-21 20:49:25 +08:00
|
|
|
}
|
|
|
|
indicies = append(indicies, indexValues[0])
|
|
|
|
}
|
2019-10-23 05:49:32 +08:00
|
|
|
err := vars.DelElement(fm.ResolveVar(op.qname), indicies)
|
2018-01-21 20:49:25 +08:00
|
|
|
if err != nil {
|
2018-03-08 21:24:18 +08:00
|
|
|
if level := vars.ElementErrorLevel(err); level >= 0 {
|
2020-04-09 06:33:22 +08:00
|
|
|
return fm.errorp(diag.Ranging{From: op.begin, To: op.ends[level]}, err)
|
2018-01-21 20:49:25 +08:00
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
2015-07-08 18:31:40 +08:00
|
|
|
}
|
|
|
|
|
2016-01-27 00:42:37 +08:00
|
|
|
// FnForm = 'fn' StringPrimary LambdaPrimary
|
2015-01-22 01:06:14 +08:00
|
|
|
//
|
2016-01-28 05:24:17 +08:00
|
|
|
// fn f []{foobar} is a shorthand for set '&'f = []{foobar}.
|
2019-03-10 23:23:43 +08:00
|
|
|
func compileFn(cp *compiler, fn *parse.Form) effectOpBody {
|
2017-02-18 13:30:37 +08:00
|
|
|
args := cp.walkArgs(fn)
|
|
|
|
nameNode := args.next()
|
2017-12-19 05:24:37 +08:00
|
|
|
varName := mustString(cp, nameNode, "must be a literal string") + FnSuffix
|
2017-02-18 13:30:37 +08:00
|
|
|
bodyNode := args.nextMustLambda()
|
|
|
|
args.mustEnd()
|
2015-01-22 01:06:14 +08:00
|
|
|
|
2019-10-23 05:49:32 +08:00
|
|
|
cp.registerVariableSet(":" + varName)
|
2017-02-18 13:30:37 +08:00
|
|
|
op := cp.lambda(bodyNode)
|
2015-01-22 01:06:14 +08:00
|
|
|
|
2018-01-21 20:49:25 +08:00
|
|
|
return fnOp{varName, op}
|
|
|
|
}
|
|
|
|
|
|
|
|
type fnOp struct {
|
|
|
|
varName string
|
2018-09-29 03:45:11 +08:00
|
|
|
lambdaOp valuesOpBody
|
2018-01-21 20:49:25 +08:00
|
|
|
}
|
|
|
|
|
2018-09-29 03:45:11 +08:00
|
|
|
func (op fnOp) invoke(fm *Frame) error {
|
2018-01-21 20:49:25 +08:00
|
|
|
// Initialize the function variable with the builtin nop function. This step
|
|
|
|
// allows the definition of recursive functions; the actual function will
|
|
|
|
// never be called.
|
2019-04-19 05:57:14 +08:00
|
|
|
fm.local[op.varName] = vars.FromInit(NewGoFn("<shouldn't be called>", nop))
|
2018-09-29 03:45:11 +08:00
|
|
|
values, err := op.lambdaOp.invoke(fm)
|
2018-01-21 20:49:25 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
closure := values[0].(*Closure)
|
|
|
|
closure.Op = wrapFn(closure.Op)
|
|
|
|
return fm.local[op.varName].Set(closure)
|
|
|
|
}
|
|
|
|
|
2018-09-29 03:45:11 +08:00
|
|
|
func wrapFn(op effectOp) effectOp {
|
2020-03-29 04:14:14 +08:00
|
|
|
return effectOp{fnWrap{op}, op.Ranging}
|
2018-01-21 20:49:25 +08:00
|
|
|
}
|
|
|
|
|
2018-09-29 03:45:11 +08:00
|
|
|
type fnWrap struct{ wrapped effectOp }
|
2018-01-21 20:49:25 +08:00
|
|
|
|
2018-09-29 03:45:11 +08:00
|
|
|
func (op fnWrap) invoke(fm *Frame) error {
|
2020-04-10 04:01:14 +08:00
|
|
|
err := op.wrapped.exec(fm)
|
2018-10-09 21:21:26 +08:00
|
|
|
if err != nil && Cause(err) != Return {
|
2018-01-21 20:49:25 +08:00
|
|
|
// rethrow
|
|
|
|
return err
|
2015-01-22 01:06:14 +08:00
|
|
|
}
|
2018-01-21 20:49:25 +08:00
|
|
|
return nil
|
2015-01-22 01:06:14 +08:00
|
|
|
}
|
2016-02-17 06:14:53 +08:00
|
|
|
|
2017-09-20 18:46:35 +08:00
|
|
|
// UseForm = 'use' StringPrimary
|
2019-03-10 23:23:43 +08:00
|
|
|
func compileUse(cp *compiler, fn *parse.Form) effectOpBody {
|
2020-01-11 10:27:43 +08:00
|
|
|
var name, spec string
|
2018-09-23 00:33:11 +08:00
|
|
|
|
|
|
|
switch len(fn.Args) {
|
|
|
|
case 0:
|
2018-10-13 21:07:54 +08:00
|
|
|
end := fn.Head.Range().To
|
2020-03-29 04:34:52 +08:00
|
|
|
cp.errorpf(diag.PointRanging(end), "lack module name")
|
2018-09-23 00:33:11 +08:00
|
|
|
case 1:
|
2020-01-11 10:27:43 +08:00
|
|
|
spec = mustString(cp, fn.Args[0],
|
|
|
|
"module spec should be a literal string")
|
2018-09-23 00:33:11 +08:00
|
|
|
// Use the last path component as the name; for instance, if path =
|
|
|
|
// "a/b/c/d", name is "d". If path doesn't have slashes, name = path.
|
2020-01-11 10:27:43 +08:00
|
|
|
name = spec[strings.LastIndexByte(spec, '/')+1:]
|
2018-09-23 00:33:11 +08:00
|
|
|
case 2:
|
|
|
|
// TODO(xiaq): Allow using variable as module path
|
2020-01-11 10:27:43 +08:00
|
|
|
spec = mustString(cp, fn.Args[0],
|
|
|
|
"module spec should be a literal string")
|
2018-09-23 00:33:11 +08:00
|
|
|
name = mustString(cp, fn.Args[1],
|
|
|
|
"module name should be a literal string")
|
|
|
|
default: // > 2
|
2020-03-29 04:34:52 +08:00
|
|
|
cp.errorpf(diag.MixedRanging(fn.Args[2], fn.Args[len(fn.Args)-1]),
|
2018-09-23 00:33:11 +08:00
|
|
|
"superfluous argument(s)")
|
2016-02-17 06:14:53 +08:00
|
|
|
}
|
|
|
|
|
2018-09-23 00:33:11 +08:00
|
|
|
cp.thisScope().set(name + NsSuffix)
|
2017-09-20 18:46:35 +08:00
|
|
|
|
2020-04-09 06:33:22 +08:00
|
|
|
return useOp{fn.Range(), name, spec}
|
2018-01-21 20:49:25 +08:00
|
|
|
}
|
|
|
|
|
2020-04-09 06:33:22 +08:00
|
|
|
type useOp struct {
|
|
|
|
diag.Ranging
|
|
|
|
name, spec string
|
|
|
|
}
|
2018-01-21 20:49:25 +08:00
|
|
|
|
2018-09-29 03:45:11 +08:00
|
|
|
func (op useOp) invoke(fm *Frame) error {
|
2020-04-09 06:33:22 +08:00
|
|
|
ns, err := loadModule(fm, op, op.spec)
|
2018-01-21 08:25:53 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-01-11 10:27:43 +08:00
|
|
|
fm.local.AddNs(op.name, ns)
|
2018-01-21 08:25:53 +08:00
|
|
|
return nil
|
2017-09-21 08:52:16 +08:00
|
|
|
}
|
|
|
|
|
2020-04-09 06:33:22 +08:00
|
|
|
func loadModule(fm *Frame, r diag.Ranger, spec string) (Ns, error) {
|
2020-01-11 10:27:43 +08:00
|
|
|
if strings.HasPrefix(spec, "./") || strings.HasPrefix(spec, "../") {
|
2020-01-11 11:01:30 +08:00
|
|
|
var dir string
|
|
|
|
if fm.srcMeta.Type == FileSource {
|
|
|
|
dir = filepath.Dir(fm.srcMeta.Name)
|
|
|
|
} else {
|
|
|
|
var err error
|
|
|
|
dir, err = os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
path := filepath.Clean(dir + "/" + spec + ".elv")
|
2020-04-09 06:33:22 +08:00
|
|
|
return loadModuleFile(fm, r, spec, path)
|
2020-01-11 10:27:43 +08:00
|
|
|
}
|
|
|
|
if ns, ok := fm.Evaler.modules[spec]; ok {
|
2018-01-21 08:25:53 +08:00
|
|
|
return ns, nil
|
2016-02-22 03:10:33 +08:00
|
|
|
}
|
2020-01-11 10:27:43 +08:00
|
|
|
if code, ok := fm.bundled[spec]; ok {
|
2020-04-09 06:33:22 +08:00
|
|
|
return evalModule(fm, r, spec, NewInternalElvishSource(false, spec, code))
|
2020-01-11 10:27:43 +08:00
|
|
|
}
|
|
|
|
if fm.libDir == "" {
|
|
|
|
return nil, noSuchModule{spec}
|
|
|
|
}
|
2020-04-09 06:33:22 +08:00
|
|
|
return loadModuleFile(fm, r, spec, fm.libDir+"/"+spec+".elv")
|
2020-01-11 10:27:43 +08:00
|
|
|
}
|
2016-02-22 03:10:33 +08:00
|
|
|
|
2020-04-09 06:33:22 +08:00
|
|
|
func loadModuleFile(fm *Frame, r diag.Ranger, spec, path string) (Ns, error) {
|
2020-01-11 10:27:43 +08:00
|
|
|
if ns, ok := fm.modules[path]; ok {
|
|
|
|
return ns, nil
|
|
|
|
}
|
|
|
|
code, err := readFileUTF8(path)
|
2018-02-14 15:45:49 +08:00
|
|
|
if err != nil {
|
2020-01-11 10:27:43 +08:00
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return nil, noSuchModule{spec}
|
|
|
|
}
|
2018-02-14 15:45:49 +08:00
|
|
|
return nil, err
|
2016-02-22 03:10:33 +08:00
|
|
|
}
|
2020-04-09 06:33:22 +08:00
|
|
|
return evalModule(fm, r, path, NewModuleSource(path, code))
|
2020-01-11 10:27:43 +08:00
|
|
|
}
|
2016-02-17 06:14:53 +08:00
|
|
|
|
2020-04-09 06:33:22 +08:00
|
|
|
func evalModule(fm *Frame, r diag.Ranger, key string, src *Source) (Ns, error) {
|
2020-01-11 11:01:30 +08:00
|
|
|
n, err := parse.AsChunk(src.Name, src.Code)
|
2018-01-21 08:25:53 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-10-09 22:52:02 +08:00
|
|
|
|
2017-09-20 20:43:28 +08:00
|
|
|
// Make an empty scope to evaluate the module in.
|
2018-01-05 12:07:11 +08:00
|
|
|
modGlobal := Ns{}
|
2016-10-09 22:52:02 +08:00
|
|
|
|
2018-03-01 10:17:56 +08:00
|
|
|
newFm := &Frame{
|
|
|
|
fm.Evaler, src,
|
2018-01-05 12:07:11 +08:00
|
|
|
modGlobal, make(Ns),
|
2020-03-29 07:56:47 +08:00
|
|
|
fm.intCh, fm.ports,
|
2020-04-09 06:33:22 +08:00
|
|
|
fm.addTraceback(r), false,
|
2016-02-22 03:10:33 +08:00
|
|
|
}
|
2016-02-17 06:14:53 +08:00
|
|
|
|
2018-03-01 11:14:04 +08:00
|
|
|
op, err := compile(newFm.Builtin.static(), modGlobal.static(), n, src)
|
2018-01-21 08:25:53 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-02-17 06:14:53 +08:00
|
|
|
|
2018-02-14 15:45:49 +08:00
|
|
|
// Load the namespace before executing. This prevent circular "use"es from
|
|
|
|
// resulting in an infinite recursion.
|
2020-01-11 10:27:43 +08:00
|
|
|
fm.Evaler.modules[key] = modGlobal
|
2018-10-12 19:57:41 +08:00
|
|
|
err = newFm.Eval(op)
|
2016-10-09 22:52:02 +08:00
|
|
|
if err != nil {
|
|
|
|
// Unload the namespace.
|
2020-01-11 10:27:43 +08:00
|
|
|
delete(fm.modules, key)
|
2018-01-21 08:25:53 +08:00
|
|
|
return nil, err
|
2016-10-09 22:52:02 +08:00
|
|
|
}
|
2018-01-21 08:25:53 +08:00
|
|
|
return modGlobal, nil
|
2016-02-17 06:14:53 +08:00
|
|
|
}
|
2017-02-17 08:20:02 +08:00
|
|
|
|
2017-03-03 08:21:59 +08:00
|
|
|
// compileAnd compiles the "and" special form.
|
2018-01-21 20:49:25 +08:00
|
|
|
//
|
2017-03-03 08:21:59 +08:00
|
|
|
// The and special form evaluates arguments until a false-ish values is found
|
|
|
|
// and outputs it; the remaining arguments are not evaluated. If there are no
|
|
|
|
// false-ish values, the last value is output. If there are no arguments, it
|
|
|
|
// outputs $true, as if there is a hidden $true before actual arguments.
|
2019-03-10 23:23:43 +08:00
|
|
|
func compileAnd(cp *compiler, fn *parse.Form) effectOpBody {
|
2018-01-21 20:49:25 +08:00
|
|
|
return &andOrOp{cp.compoundOps(fn.Args), true, false}
|
2017-03-03 08:21:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// compileOr compiles the "or" special form.
|
2018-01-21 20:49:25 +08:00
|
|
|
//
|
2017-03-03 08:21:59 +08:00
|
|
|
// The or special form evaluates arguments until a true-ish values is found and
|
|
|
|
// outputs it; the remaining arguments are not evaluated. If there are no
|
|
|
|
// true-ish values, the last value is output. If there are no arguments, it
|
|
|
|
// outputs $false, as if there is a hidden $false before actual arguments.
|
2019-03-10 23:23:43 +08:00
|
|
|
func compileOr(cp *compiler, fn *parse.Form) effectOpBody {
|
2018-01-21 20:49:25 +08:00
|
|
|
return &andOrOp{cp.compoundOps(fn.Args), false, true}
|
|
|
|
}
|
|
|
|
|
|
|
|
type andOrOp struct {
|
2018-09-29 03:45:11 +08:00
|
|
|
argOps []valuesOp
|
2018-01-21 20:49:25 +08:00
|
|
|
init bool
|
|
|
|
stopAt bool
|
|
|
|
}
|
|
|
|
|
2018-09-29 03:45:11 +08:00
|
|
|
func (op *andOrOp) invoke(fm *Frame) error {
|
2018-02-15 17:14:05 +08:00
|
|
|
var lastValue interface{} = vals.Bool(op.init)
|
2018-01-21 20:49:25 +08:00
|
|
|
for _, argOp := range op.argOps {
|
2018-09-29 03:45:11 +08:00
|
|
|
values, err := argOp.exec(fm)
|
2018-01-21 20:49:25 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, value := range values {
|
2018-02-15 17:14:05 +08:00
|
|
|
if vals.Bool(value) == op.stopAt {
|
2018-01-21 20:49:25 +08:00
|
|
|
fm.OutputChan() <- value
|
|
|
|
return nil
|
2017-03-03 08:21:59 +08:00
|
|
|
}
|
2018-01-21 20:49:25 +08:00
|
|
|
lastValue = value
|
2017-03-03 08:21:59 +08:00
|
|
|
}
|
|
|
|
}
|
2018-01-21 20:49:25 +08:00
|
|
|
fm.OutputChan() <- lastValue
|
|
|
|
return nil
|
2017-03-03 08:21:59 +08:00
|
|
|
}
|
|
|
|
|
2019-03-10 23:23:43 +08:00
|
|
|
func compileIf(cp *compiler, fn *parse.Form) effectOpBody {
|
2017-02-18 12:34:11 +08:00
|
|
|
args := cp.walkArgs(fn)
|
2017-02-18 13:25:42 +08:00
|
|
|
var condNodes []*parse.Compound
|
|
|
|
var bodyNodes []*parse.Primary
|
2017-02-18 12:34:11 +08:00
|
|
|
for {
|
|
|
|
condNodes = append(condNodes, args.next())
|
2017-02-18 13:25:42 +08:00
|
|
|
bodyNodes = append(bodyNodes, args.nextMustLambda())
|
2017-02-18 12:34:11 +08:00
|
|
|
if !args.nextIs("elif") {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2017-02-18 13:25:42 +08:00
|
|
|
elseNode := args.nextMustLambdaIfAfter("else")
|
2017-02-18 12:34:11 +08:00
|
|
|
args.mustEnd()
|
|
|
|
|
|
|
|
condOps := cp.compoundOps(condNodes)
|
2017-02-18 13:25:42 +08:00
|
|
|
bodyOps := cp.primaryOps(bodyNodes)
|
2018-09-29 03:45:11 +08:00
|
|
|
var elseOp valuesOp
|
2017-02-18 12:34:11 +08:00
|
|
|
if elseNode != nil {
|
2017-02-18 13:25:42 +08:00
|
|
|
elseOp = cp.primaryOp(elseNode)
|
2017-02-18 12:34:11 +08:00
|
|
|
}
|
|
|
|
|
2018-01-21 20:49:25 +08:00
|
|
|
return &ifOp{condOps, bodyOps, elseOp}
|
|
|
|
}
|
|
|
|
|
|
|
|
type ifOp struct {
|
2018-09-29 03:45:11 +08:00
|
|
|
condOps []valuesOp
|
|
|
|
bodyOps []valuesOp
|
|
|
|
elseOp valuesOp
|
2018-01-21 20:49:25 +08:00
|
|
|
}
|
|
|
|
|
2018-09-29 03:45:11 +08:00
|
|
|
func (op *ifOp) invoke(fm *Frame) error {
|
2018-01-21 20:49:25 +08:00
|
|
|
bodies := make([]Callable, len(op.bodyOps))
|
|
|
|
for i, bodyOp := range op.bodyOps {
|
|
|
|
bodies[i] = bodyOp.execlambdaOp(fm)
|
|
|
|
}
|
2019-04-19 05:15:34 +08:00
|
|
|
elseFn := op.elseOp.execlambdaOp(fm)
|
2018-01-21 20:49:25 +08:00
|
|
|
for i, condOp := range op.condOps {
|
2018-09-29 03:45:11 +08:00
|
|
|
condValues, err := condOp.exec(fm.fork("if cond"))
|
2018-01-21 20:49:25 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2017-02-18 12:34:11 +08:00
|
|
|
}
|
2018-01-21 20:49:25 +08:00
|
|
|
if allTrue(condValues) {
|
|
|
|
return bodies[i].Call(fm.fork("if body"), NoArgs, NoOpts)
|
2017-02-18 12:34:11 +08:00
|
|
|
}
|
|
|
|
}
|
2018-09-29 03:45:11 +08:00
|
|
|
if op.elseOp.body != nil {
|
2019-04-19 05:15:34 +08:00
|
|
|
return elseFn.Call(fm.fork("if else"), NoArgs, NoOpts)
|
2018-01-21 20:49:25 +08:00
|
|
|
}
|
|
|
|
return nil
|
2017-02-18 12:34:11 +08:00
|
|
|
}
|
|
|
|
|
2019-03-10 23:23:43 +08:00
|
|
|
func compileWhile(cp *compiler, fn *parse.Form) effectOpBody {
|
2017-02-17 08:28:58 +08:00
|
|
|
args := cp.walkArgs(fn)
|
|
|
|
condNode := args.next()
|
2017-02-18 13:25:42 +08:00
|
|
|
bodyNode := args.nextMustLambda()
|
2019-10-27 07:29:25 +08:00
|
|
|
elseNode := args.nextMustLambdaIfAfter("else")
|
2019-10-27 16:25:07 +08:00
|
|
|
args.mustEnd()
|
2019-10-27 07:29:25 +08:00
|
|
|
|
|
|
|
condOp := cp.compoundOp(condNode)
|
|
|
|
bodyOp := cp.primaryOp(bodyNode)
|
|
|
|
var elseOp valuesOp
|
|
|
|
if elseNode != nil {
|
|
|
|
elseOp = cp.primaryOp(elseNode)
|
|
|
|
}
|
2017-02-17 08:28:58 +08:00
|
|
|
|
2019-10-27 07:29:25 +08:00
|
|
|
return &whileOp{condOp, bodyOp, elseOp}
|
2018-01-21 20:49:25 +08:00
|
|
|
}
|
2017-02-17 08:28:58 +08:00
|
|
|
|
2018-01-21 20:49:25 +08:00
|
|
|
type whileOp struct {
|
2019-10-27 07:29:25 +08:00
|
|
|
condOp, bodyOp, elseOp valuesOp
|
2018-01-21 20:49:25 +08:00
|
|
|
}
|
2017-02-17 08:28:58 +08:00
|
|
|
|
2018-09-29 03:45:11 +08:00
|
|
|
func (op *whileOp) invoke(fm *Frame) error {
|
2018-01-21 20:49:25 +08:00
|
|
|
body := op.bodyOp.execlambdaOp(fm)
|
2019-10-28 10:09:53 +08:00
|
|
|
elseBody := op.elseOp.execlambdaOp(fm)
|
2018-01-21 20:49:25 +08:00
|
|
|
|
2019-10-28 10:09:53 +08:00
|
|
|
iterated := false
|
2018-01-21 20:49:25 +08:00
|
|
|
for {
|
2018-09-29 03:45:11 +08:00
|
|
|
condValues, err := op.condOp.exec(fm.fork("while cond"))
|
2018-01-21 20:49:25 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !allTrue(condValues) {
|
|
|
|
break
|
|
|
|
}
|
2019-10-28 10:09:53 +08:00
|
|
|
iterated = true
|
2020-04-10 04:01:14 +08:00
|
|
|
err = body.Call(fm.fork("while"), NoArgs, NoOpts)
|
2018-01-21 20:49:25 +08:00
|
|
|
if err != nil {
|
|
|
|
exc := err.(*Exception)
|
|
|
|
if exc.Cause == Continue {
|
|
|
|
// do nothing
|
|
|
|
} else if exc.Cause == Break {
|
2018-03-03 03:30:46 +08:00
|
|
|
break
|
2018-01-21 20:49:25 +08:00
|
|
|
} else {
|
2018-03-03 03:30:46 +08:00
|
|
|
return err
|
2017-02-17 08:28:58 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-10-28 10:09:53 +08:00
|
|
|
|
|
|
|
if op.elseOp.body != nil && !iterated {
|
|
|
|
return elseBody.Call(fm.fork("while else"), NoArgs, NoOpts)
|
|
|
|
}
|
2018-01-21 20:49:25 +08:00
|
|
|
return nil
|
2017-02-17 08:28:58 +08:00
|
|
|
}
|
|
|
|
|
2019-03-10 23:23:43 +08:00
|
|
|
func compileFor(cp *compiler, fn *parse.Form) effectOpBody {
|
2017-02-17 08:20:02 +08:00
|
|
|
args := cp.walkArgs(fn)
|
|
|
|
varNode := args.next()
|
|
|
|
iterNode := args.next()
|
2017-02-18 13:25:42 +08:00
|
|
|
bodyNode := args.nextMustLambda()
|
|
|
|
elseNode := args.nextMustLambdaIfAfter("else")
|
2017-02-17 08:20:02 +08:00
|
|
|
args.mustEnd()
|
|
|
|
|
|
|
|
varOp, restOp := cp.lvaluesOp(varNode.Indexings[0])
|
2018-09-29 03:45:11 +08:00
|
|
|
if restOp.body != nil {
|
2020-03-29 04:34:52 +08:00
|
|
|
cp.errorpf(restOp, "rest not allowed")
|
2017-02-17 08:20:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
iterOp := cp.compoundOp(iterNode)
|
2017-02-18 13:25:42 +08:00
|
|
|
bodyOp := cp.primaryOp(bodyNode)
|
2018-09-29 03:45:11 +08:00
|
|
|
var elseOp valuesOp
|
2017-02-17 08:20:02 +08:00
|
|
|
if elseNode != nil {
|
2017-02-18 13:25:42 +08:00
|
|
|
elseOp = cp.primaryOp(elseNode)
|
2017-02-17 08:20:02 +08:00
|
|
|
}
|
|
|
|
|
2018-01-21 20:49:25 +08:00
|
|
|
return &forOp{varOp, iterOp, bodyOp, elseOp}
|
|
|
|
}
|
|
|
|
|
|
|
|
type forOp struct {
|
2018-09-29 03:45:11 +08:00
|
|
|
varOp lvaluesOp
|
|
|
|
iterOp valuesOp
|
|
|
|
bodyOp valuesOp
|
|
|
|
elseOp valuesOp
|
2018-01-21 20:49:25 +08:00
|
|
|
}
|
|
|
|
|
2018-09-29 03:45:11 +08:00
|
|
|
func (op *forOp) invoke(fm *Frame) error {
|
2020-04-13 20:24:49 +08:00
|
|
|
variable, err := evalForVar(fm, op.varOp, "iterator")
|
2018-01-21 20:49:25 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-04-13 20:24:49 +08:00
|
|
|
iterable, err := evalForValue(fm, op.iterOp, "value being iterated")
|
2018-09-28 03:44:46 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-02-17 08:20:02 +08:00
|
|
|
|
2018-03-01 10:17:56 +08:00
|
|
|
body := op.bodyOp.execlambdaOp(fm)
|
|
|
|
elseBody := op.elseOp.execlambdaOp(fm)
|
2017-02-17 08:20:02 +08:00
|
|
|
|
2018-01-21 20:49:25 +08:00
|
|
|
iterated := false
|
2018-01-25 08:48:31 +08:00
|
|
|
var errElement error
|
2018-02-15 17:14:05 +08:00
|
|
|
errIterate := vals.Iterate(iterable, func(v interface{}) bool {
|
2018-01-21 20:49:25 +08:00
|
|
|
iterated = true
|
|
|
|
err := variable.Set(v)
|
|
|
|
if err != nil {
|
2018-01-25 08:48:31 +08:00
|
|
|
errElement = err
|
2018-01-21 20:49:25 +08:00
|
|
|
return false
|
|
|
|
}
|
2020-04-10 04:01:14 +08:00
|
|
|
err = body.Call(fm.fork("for"), NoArgs, NoOpts)
|
2018-01-21 20:49:25 +08:00
|
|
|
if err != nil {
|
|
|
|
exc := err.(*Exception)
|
|
|
|
if exc.Cause == Continue {
|
|
|
|
// do nothing
|
|
|
|
} else if exc.Cause == Break {
|
|
|
|
return false
|
|
|
|
} else {
|
2018-01-25 08:48:31 +08:00
|
|
|
errElement = err
|
2018-01-21 08:25:53 +08:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
2018-01-21 20:49:25 +08:00
|
|
|
return true
|
|
|
|
})
|
|
|
|
if errIterate != nil {
|
|
|
|
return errIterate
|
|
|
|
}
|
2018-01-25 08:48:31 +08:00
|
|
|
if errElement != nil {
|
|
|
|
return errElement
|
|
|
|
}
|
2017-02-17 08:20:02 +08:00
|
|
|
|
2018-01-21 20:49:25 +08:00
|
|
|
if !iterated && elseBody != nil {
|
2018-03-01 10:17:56 +08:00
|
|
|
return elseBody.Call(fm.fork("for else"), NoArgs, NoOpts)
|
2017-02-17 08:20:02 +08:00
|
|
|
}
|
2018-01-21 20:49:25 +08:00
|
|
|
return nil
|
2017-02-17 08:20:02 +08:00
|
|
|
}
|
2017-02-18 08:26:44 +08:00
|
|
|
|
2019-03-10 23:23:43 +08:00
|
|
|
func compileTry(cp *compiler, fn *parse.Form) effectOpBody {
|
2017-05-22 06:54:04 +08:00
|
|
|
logger.Println("compiling try")
|
2017-02-18 12:16:21 +08:00
|
|
|
args := cp.walkArgs(fn)
|
2017-02-18 13:25:42 +08:00
|
|
|
bodyNode := args.nextMustLambda()
|
2017-05-22 06:54:04 +08:00
|
|
|
logger.Printf("body is %q", bodyNode.SourceText())
|
2017-02-18 12:16:21 +08:00
|
|
|
var exceptVarNode *parse.Indexing
|
2017-02-18 13:25:42 +08:00
|
|
|
var exceptNode *parse.Primary
|
2017-02-18 12:16:21 +08:00
|
|
|
if args.nextIs("except") {
|
2017-05-22 06:54:04 +08:00
|
|
|
logger.Println("except-ing")
|
2017-02-18 13:25:42 +08:00
|
|
|
n := args.peek()
|
|
|
|
// Is this a variable?
|
|
|
|
if len(n.Indexings) == 1 && n.Indexings[0].Head.Type == parse.Bareword {
|
|
|
|
exceptVarNode = n.Indexings[0]
|
|
|
|
args.next()
|
2017-02-18 12:16:21 +08:00
|
|
|
}
|
2017-02-18 13:25:42 +08:00
|
|
|
exceptNode = args.nextMustLambda()
|
2017-02-18 12:16:21 +08:00
|
|
|
}
|
2017-02-18 13:25:42 +08:00
|
|
|
elseNode := args.nextMustLambdaIfAfter("else")
|
|
|
|
finallyNode := args.nextMustLambdaIfAfter("finally")
|
2017-02-18 12:16:21 +08:00
|
|
|
args.mustEnd()
|
|
|
|
|
2018-09-29 03:45:11 +08:00
|
|
|
var exceptVarOp lvaluesOp
|
|
|
|
var bodyOp, exceptOp, elseOp, finallyOp valuesOp
|
2017-02-18 13:25:42 +08:00
|
|
|
bodyOp = cp.primaryOp(bodyNode)
|
2017-02-18 12:16:21 +08:00
|
|
|
if exceptVarNode != nil {
|
2018-09-29 03:45:11 +08:00
|
|
|
var restOp lvaluesOp
|
2017-02-18 12:16:21 +08:00
|
|
|
exceptVarOp, restOp = cp.lvaluesOp(exceptVarNode)
|
2018-09-29 03:45:11 +08:00
|
|
|
if restOp.body != nil {
|
2020-03-29 04:34:52 +08:00
|
|
|
cp.errorpf(restOp, "may not use @rest in except variable")
|
2017-02-18 12:16:21 +08:00
|
|
|
}
|
2017-03-03 09:05:46 +08:00
|
|
|
}
|
|
|
|
if exceptNode != nil {
|
2017-02-18 13:25:42 +08:00
|
|
|
exceptOp = cp.primaryOp(exceptNode)
|
2017-02-18 12:16:21 +08:00
|
|
|
}
|
|
|
|
if elseNode != nil {
|
2017-02-18 13:25:42 +08:00
|
|
|
elseOp = cp.primaryOp(elseNode)
|
2017-02-18 12:16:21 +08:00
|
|
|
}
|
|
|
|
if finallyNode != nil {
|
2017-02-18 13:25:42 +08:00
|
|
|
finallyOp = cp.primaryOp(finallyNode)
|
2017-02-18 12:16:21 +08:00
|
|
|
}
|
|
|
|
|
2018-01-21 20:49:25 +08:00
|
|
|
return &tryOp{bodyOp, exceptVarOp, exceptOp, elseOp, finallyOp}
|
|
|
|
}
|
2017-02-18 12:16:21 +08:00
|
|
|
|
2018-01-21 20:49:25 +08:00
|
|
|
type tryOp struct {
|
2018-09-29 03:45:11 +08:00
|
|
|
bodyOp valuesOp
|
|
|
|
exceptVarOp lvaluesOp
|
|
|
|
exceptOp valuesOp
|
|
|
|
elseOp valuesOp
|
|
|
|
finallyOp valuesOp
|
2018-01-21 20:49:25 +08:00
|
|
|
}
|
|
|
|
|
2018-09-29 03:45:11 +08:00
|
|
|
func (op *tryOp) invoke(fm *Frame) error {
|
2018-03-01 10:17:56 +08:00
|
|
|
body := op.bodyOp.execlambdaOp(fm)
|
2018-09-27 08:48:44 +08:00
|
|
|
exceptVar, err := op.exceptVarOp.execMustOne(fm)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-03-01 10:17:56 +08:00
|
|
|
except := op.exceptOp.execlambdaOp(fm)
|
2019-04-19 05:15:34 +08:00
|
|
|
elseFn := op.elseOp.execlambdaOp(fm)
|
2018-03-01 10:17:56 +08:00
|
|
|
finally := op.finallyOp.execlambdaOp(fm)
|
2018-01-21 20:49:25 +08:00
|
|
|
|
2020-04-10 04:01:14 +08:00
|
|
|
err = body.Call(fm.fork("try body"), NoArgs, NoOpts)
|
2018-01-21 20:49:25 +08:00
|
|
|
if err != nil {
|
|
|
|
if except != nil {
|
|
|
|
if exceptVar != nil {
|
|
|
|
err := exceptVar.Set(err.(*Exception))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2017-03-03 09:05:46 +08:00
|
|
|
}
|
2017-02-18 12:16:21 +08:00
|
|
|
}
|
2020-04-10 04:01:14 +08:00
|
|
|
err = except.Call(fm.fork("try except"), NoArgs, NoOpts)
|
2017-02-18 12:16:21 +08:00
|
|
|
}
|
2018-01-21 20:49:25 +08:00
|
|
|
} else {
|
2019-04-19 05:15:34 +08:00
|
|
|
if elseFn != nil {
|
2020-04-10 04:01:14 +08:00
|
|
|
err = elseFn.Call(fm.fork("try else"), NoArgs, NoOpts)
|
2017-02-18 12:16:21 +08:00
|
|
|
}
|
|
|
|
}
|
2018-01-21 20:49:25 +08:00
|
|
|
if finally != nil {
|
2018-03-03 07:29:33 +08:00
|
|
|
errFinally := finally.Call(fm.fork("try finally"), NoArgs, NoOpts)
|
|
|
|
if errFinally != nil {
|
|
|
|
// TODO: If err is not nil, this discards err. Use something similar
|
|
|
|
// to pipeline exception to expose both.
|
|
|
|
return errFinally
|
|
|
|
}
|
2018-01-21 20:49:25 +08:00
|
|
|
}
|
|
|
|
return err
|
2017-02-18 12:16:21 +08:00
|
|
|
}
|
|
|
|
|
2017-02-18 13:25:42 +08:00
|
|
|
// execLambdaOp executes a ValuesOp that is known to yield a lambda and returns
|
|
|
|
// the lambda. If the ValuesOp is empty, it returns a nil.
|
2018-09-29 03:45:11 +08:00
|
|
|
func (op valuesOp) execlambdaOp(fm *Frame) Callable {
|
|
|
|
if op.body == nil {
|
2017-02-18 08:26:44 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-09-29 03:45:11 +08:00
|
|
|
values, err := op.exec(fm)
|
2018-01-21 09:14:39 +08:00
|
|
|
if err != nil {
|
|
|
|
panic("must not be erroneous")
|
|
|
|
}
|
|
|
|
return values[0].(Callable)
|
2017-02-18 08:26:44 +08:00
|
|
|
}
|
2017-02-18 12:16:21 +08:00
|
|
|
|
2018-09-27 08:48:44 +08:00
|
|
|
// execMustOne executes the LValuesOp and returns an error if it does not
|
2017-02-18 12:16:21 +08:00
|
|
|
// evaluate to exactly one Variable. If the given LValuesOp is empty, it returns
|
|
|
|
// nil.
|
2018-09-29 03:45:11 +08:00
|
|
|
func (op lvaluesOp) execMustOne(fm *Frame) (vars.Var, error) {
|
|
|
|
if op.body == nil {
|
2018-09-27 08:48:44 +08:00
|
|
|
return nil, nil
|
2017-02-18 12:16:21 +08:00
|
|
|
}
|
2018-09-29 03:45:11 +08:00
|
|
|
variables, err := op.exec(fm)
|
2018-09-27 08:48:44 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-02-18 12:16:21 +08:00
|
|
|
if len(variables) != 1 {
|
2020-04-09 06:33:22 +08:00
|
|
|
return nil, fm.errorpf(op, "should be one variable")
|
2017-02-18 12:16:21 +08:00
|
|
|
}
|
2018-09-27 08:48:44 +08:00
|
|
|
return variables[0], nil
|
2017-02-18 12:16:21 +08:00
|
|
|
}
|