2016-02-20 00:21:51 +08:00
|
|
|
package eval
|
|
|
|
|
|
|
|
import (
|
2016-02-20 00:33:55 +08:00
|
|
|
"fmt"
|
2020-04-26 20:14:51 +08:00
|
|
|
"io"
|
2018-02-01 09:13:58 +08:00
|
|
|
"strings"
|
2016-02-20 00:33:55 +08:00
|
|
|
|
2019-12-24 04:00:59 +08:00
|
|
|
"github.com/elves/elvish/pkg/diag"
|
|
|
|
"github.com/elves/elvish/pkg/parse"
|
2020-04-27 04:24:30 +08:00
|
|
|
"github.com/elves/elvish/pkg/prog"
|
2016-02-20 00:21:51 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
// compiler maintains the set of states needed when compiling a single source
|
|
|
|
// file.
|
|
|
|
type compiler struct {
|
2017-12-24 07:51:32 +08:00
|
|
|
// Builtin namespace.
|
|
|
|
builtin staticNs
|
|
|
|
// Lexical namespaces.
|
|
|
|
scopes []staticNs
|
2016-02-20 00:21:51 +08:00
|
|
|
// Variables captured from outer scopes.
|
2017-12-24 07:51:32 +08:00
|
|
|
capture staticNs
|
2020-04-27 02:04:49 +08:00
|
|
|
// New variables created in a lexical scope.
|
2020-03-29 07:49:30 +08:00
|
|
|
newLocals []string
|
2020-04-26 20:14:51 +08:00
|
|
|
// Destination of warning messages. This is currently only used for
|
|
|
|
// deprecation messages.
|
|
|
|
warn io.Writer
|
|
|
|
// Deprecation registry.
|
|
|
|
deprecations deprecationRegistry
|
2016-10-11 21:37:27 +08:00
|
|
|
// Information about the source.
|
2020-04-26 02:22:38 +08:00
|
|
|
srcMeta parse.Source
|
2016-02-20 00:21:51 +08:00
|
|
|
}
|
|
|
|
|
2020-04-26 20:14:51 +08:00
|
|
|
func compile(b, g staticNs, tree parse.Tree, w io.Writer) (op Op, err error) {
|
|
|
|
cp := &compiler{
|
|
|
|
b, []staticNs{g}, make(staticNs), nil,
|
|
|
|
w, newDeprecationRegistry(), tree.Source}
|
2020-01-13 06:55:17 +08:00
|
|
|
defer func() {
|
|
|
|
r := recover()
|
|
|
|
if r == nil {
|
|
|
|
return
|
|
|
|
} else if e, ok := GetCompilationError(r); ok {
|
|
|
|
// Save the compilation error and stop the panic.
|
|
|
|
err = e
|
|
|
|
} else {
|
|
|
|
// Resume the panic; it is not supposed to be handled here.
|
|
|
|
panic(r)
|
|
|
|
}
|
|
|
|
}()
|
2020-04-27 02:04:49 +08:00
|
|
|
savedLocals := cp.pushNewLocals()
|
|
|
|
chunkOp := cp.chunkOp(tree.Root)
|
|
|
|
scopeOp := wrapScopeOp(chunkOp, cp.newLocals)
|
|
|
|
cp.newLocals = savedLocals
|
|
|
|
|
|
|
|
return Op{scopeOp, tree.Source}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cp *compiler) pushNewLocals() []string {
|
|
|
|
saved := cp.newLocals
|
|
|
|
cp.newLocals = nil
|
|
|
|
return saved
|
2016-02-20 00:21:51 +08:00
|
|
|
}
|
|
|
|
|
2020-03-29 04:34:52 +08:00
|
|
|
func (cp *compiler) errorpf(r diag.Ranger, format string, args ...interface{}) {
|
2020-01-13 06:55:17 +08:00
|
|
|
// The panic is caught by the recover in compile above.
|
|
|
|
panic(NewCompilationError(fmt.Sprintf(format, args...),
|
2020-03-29 06:11:23 +08:00
|
|
|
*diag.NewContext(cp.srcMeta.Name, cp.srcMeta.Code, r)))
|
2016-02-20 04:16:23 +08:00
|
|
|
}
|
|
|
|
|
2017-12-24 07:51:32 +08:00
|
|
|
func (cp *compiler) thisScope() staticNs {
|
2016-02-20 00:21:51 +08:00
|
|
|
return cp.scopes[len(cp.scopes)-1]
|
|
|
|
}
|
|
|
|
|
2017-12-24 07:51:32 +08:00
|
|
|
func (cp *compiler) pushScope() staticNs {
|
|
|
|
sc := make(staticNs)
|
2016-02-20 00:21:51 +08:00
|
|
|
cp.scopes = append(cp.scopes, sc)
|
|
|
|
return sc
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cp *compiler) popScope() {
|
2017-12-24 07:51:32 +08:00
|
|
|
cp.scopes[len(cp.scopes)-1] = make(staticNs)
|
2016-02-20 00:21:51 +08:00
|
|
|
cp.scopes = cp.scopes[:len(cp.scopes)-1]
|
|
|
|
}
|
|
|
|
|
2019-10-23 05:49:32 +08:00
|
|
|
func (cp *compiler) registerVariableSet(qname string) bool {
|
2020-04-26 20:14:51 +08:00
|
|
|
return cp.registerVariableAccess(qname, true, nil)
|
2017-12-24 07:51:32 +08:00
|
|
|
}
|
|
|
|
|
2020-04-26 20:14:51 +08:00
|
|
|
func (cp *compiler) registerVariableGet(qname string, r diag.Ranger) bool {
|
|
|
|
return cp.registerVariableAccess(qname, false, r)
|
2019-10-23 05:49:32 +08:00
|
|
|
}
|
|
|
|
|
2020-04-26 20:14:51 +08:00
|
|
|
func (cp *compiler) registerVariableAccess(qname string, set bool, r diag.Ranger) bool {
|
2019-10-23 05:49:32 +08:00
|
|
|
readLocal := func(name string) bool { return cp.thisScope().has(name) }
|
|
|
|
|
|
|
|
readUpvalue := func(name string) bool {
|
2016-02-28 06:56:12 +08:00
|
|
|
for i := len(cp.scopes) - 2; i >= 0; i-- {
|
2018-02-10 03:11:38 +08:00
|
|
|
if cp.scopes[i].has(name) {
|
2016-02-28 06:56:12 +08:00
|
|
|
// Existing name: record capture and return.
|
2017-12-24 07:51:32 +08:00
|
|
|
cp.capture.set(name)
|
2016-02-28 06:56:12 +08:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
2019-10-23 05:49:32 +08:00
|
|
|
return false
|
2016-02-28 06:56:12 +08:00
|
|
|
}
|
2019-10-23 05:49:32 +08:00
|
|
|
|
2020-04-26 20:14:51 +08:00
|
|
|
readBuiltin := func(name string) bool {
|
|
|
|
cp.checkDeprecatedBuiltin(name, r)
|
|
|
|
return cp.builtin.has(name)
|
|
|
|
}
|
2019-10-23 05:49:32 +08:00
|
|
|
|
|
|
|
readNonPseudo := func(name string) bool {
|
|
|
|
return readLocal(name) || readUpvalue(name) || readBuiltin(name)
|
|
|
|
}
|
|
|
|
|
|
|
|
createLocal := func(name string) bool {
|
|
|
|
if set && name != "" && !strings.ContainsRune(name[:len(name)-1], ':') {
|
|
|
|
cp.thisScope().set(name)
|
2020-03-29 07:49:30 +08:00
|
|
|
cp.newLocals = append(cp.newLocals, name)
|
2016-02-20 00:21:51 +08:00
|
|
|
return true
|
|
|
|
}
|
2019-10-23 05:49:32 +08:00
|
|
|
return false
|
2016-02-20 00:21:51 +08:00
|
|
|
}
|
|
|
|
|
2019-10-23 05:49:32 +08:00
|
|
|
ns, name := SplitQNameNsFirst(qname) // ns = "", name = "ns:"
|
|
|
|
name1 := name // name1 = "ns:"
|
|
|
|
if name != "" && strings.ContainsRune(name[:len(name)-1], ':') {
|
|
|
|
name1, _ = SplitQNameNsFirst(name)
|
|
|
|
}
|
2018-01-11 08:27:11 +08:00
|
|
|
|
2020-05-18 09:14:51 +08:00
|
|
|
// This switch mirrors the structure of that from (*Frame).ResolveVar.
|
2016-02-20 00:21:51 +08:00
|
|
|
switch ns {
|
2019-10-23 05:49:32 +08:00
|
|
|
case "E:":
|
2017-09-21 07:10:21 +08:00
|
|
|
return true
|
2019-10-23 05:49:32 +08:00
|
|
|
case "e:":
|
|
|
|
return !set && strings.HasSuffix(name, FnSuffix)
|
|
|
|
case "local:":
|
|
|
|
return readLocal(name1) || createLocal(name)
|
|
|
|
case "up:":
|
|
|
|
return readUpvalue(name1)
|
|
|
|
case "builtin:":
|
|
|
|
return readBuiltin(name1)
|
|
|
|
case "", ":":
|
|
|
|
return readNonPseudo(name1) || createLocal(name)
|
2016-02-20 00:21:51 +08:00
|
|
|
default:
|
2019-10-23 05:49:32 +08:00
|
|
|
return readNonPseudo(ns)
|
2018-02-01 09:13:58 +08:00
|
|
|
}
|
2016-02-20 00:21:51 +08:00
|
|
|
}
|
2020-04-26 20:14:51 +08:00
|
|
|
|
|
|
|
func (cp *compiler) checkDeprecatedBuiltin(name string, r diag.Ranger) {
|
|
|
|
if cp.warn == nil || r == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
msg := ""
|
|
|
|
switch name {
|
2020-08-17 06:07:30 +08:00
|
|
|
case "-source~":
|
|
|
|
msg = `the "source" command is deprecated; use "eval" instead`
|
2020-08-16 23:02:48 +08:00
|
|
|
case "ord~":
|
2020-08-14 04:56:28 +08:00
|
|
|
msg = `the "ord" command is deprecated; use "str:to-codepoints" instead`
|
2020-08-16 23:02:48 +08:00
|
|
|
case "chr~":
|
2020-08-14 04:56:28 +08:00
|
|
|
msg = `the "chr" command is deprecated; use "str:from-codepoints" instead`
|
2020-08-16 23:27:24 +08:00
|
|
|
case "has-prefix~":
|
|
|
|
msg = `the "has-prefix" command is deprecated; use "str:has-prefix" instead`
|
|
|
|
case "has-suffix~":
|
|
|
|
msg = `the "has-suffix" command is deprecated; use "str:has-suffix" instead`
|
2020-04-26 20:14:51 +08:00
|
|
|
default:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
dep := deprecation{cp.srcMeta.Name, r.Range(), msg}
|
2020-04-27 04:24:30 +08:00
|
|
|
if prog.ShowDeprecations && cp.deprecations.register(dep) {
|
2020-04-26 20:14:51 +08:00
|
|
|
err := diag.Error{
|
|
|
|
Type: "deprecation", Message: msg,
|
|
|
|
Context: diag.Context{
|
|
|
|
Name: cp.srcMeta.Name, Source: cp.srcMeta.Code, Ranging: r.Range()}}
|
|
|
|
fmt.Fprintln(cp.warn, err.Show(""))
|
|
|
|
}
|
|
|
|
}
|