mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-05 03:17:50 +08:00
3ded2fb772
Rather than having specialized commands make a `file:pipe` object indexable so we can use the generic `file:close` command. This does not address existing problems; such as builtins not failing when writing to a `file:pipe` object if the read-end is closed. Related #1316
157 lines
4.4 KiB
Go
157 lines
4.4 KiB
Go
package eval
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
|
|
"src.elv.sh/pkg/diag"
|
|
"src.elv.sh/pkg/eval/vars"
|
|
"src.elv.sh/pkg/parse"
|
|
"src.elv.sh/pkg/prog"
|
|
)
|
|
|
|
// compiler maintains the set of states needed when compiling a single source
|
|
// file.
|
|
type compiler struct {
|
|
// Builtin namespace.
|
|
builtin *staticNs
|
|
// Lexical namespaces.
|
|
scopes []*staticNs
|
|
// Sources of captured variables.
|
|
captures []*staticUpNs
|
|
// Destination of warning messages. This is currently only used for
|
|
// deprecation messages.
|
|
warn io.Writer
|
|
// Deprecation registry.
|
|
deprecations deprecationRegistry
|
|
// Information about the source.
|
|
srcMeta parse.Source
|
|
}
|
|
|
|
type capture struct {
|
|
name string
|
|
// If true, the captured variable is from the immediate outer level scope,
|
|
// i.e. the local scope the lambda is evaluated in. Otherwise the captured
|
|
// variable is from a more outer level, i.e. the upvalue scope the lambda is
|
|
// evaluated in.
|
|
local bool
|
|
// Index to the captured variable.
|
|
index int
|
|
}
|
|
|
|
func compile(b, g *staticNs, tree parse.Tree, w io.Writer) (op nsOp, err error) {
|
|
g = g.clone()
|
|
cp := &compiler{
|
|
b, []*staticNs{g}, []*staticUpNs{new(staticUpNs)},
|
|
w, newDeprecationRegistry(), tree.Source}
|
|
defer func() {
|
|
r := recover()
|
|
if r == nil {
|
|
return
|
|
} else if e := GetCompilationError(r); e != nil {
|
|
// Save the compilation error and stop the panic.
|
|
err = e
|
|
} else {
|
|
// Resume the panic; it is not supposed to be handled here.
|
|
panic(r)
|
|
}
|
|
}()
|
|
chunkOp := cp.chunkOp(tree.Root)
|
|
return nsOp{chunkOp, g}, nil
|
|
}
|
|
|
|
type nsOp struct {
|
|
inner effectOp
|
|
template *staticNs
|
|
}
|
|
|
|
// Prepares the local namespace, and returns the namespace and a function for
|
|
// executing the inner effectOp. Mutates fm.local.
|
|
func (op nsOp) prepare(fm *Frame) (*Ns, func() Exception) {
|
|
if len(op.template.names) > len(fm.local.names) {
|
|
n := len(op.template.names)
|
|
newLocal := &Ns{make([]vars.Var, n), op.template.names, op.template.deleted}
|
|
copy(newLocal.slots, fm.local.slots)
|
|
for i := len(fm.local.names); i < n; i++ {
|
|
newLocal.slots[i] = MakeVarFromName(newLocal.names[i])
|
|
}
|
|
fm.local = newLocal
|
|
} else {
|
|
// If no new has been created, there might still be some existing
|
|
// variables deleted.
|
|
fm.local = &Ns{fm.local.slots, fm.local.names, op.template.deleted}
|
|
}
|
|
return fm.local, func() Exception { return op.inner.exec(fm) }
|
|
}
|
|
|
|
const compilationErrorType = "compilation error"
|
|
|
|
func (cp *compiler) errorpf(r diag.Ranger, format string, args ...interface{}) {
|
|
// The panic is caught by the recover in compile above.
|
|
panic(&diag.Error{
|
|
Type: compilationErrorType,
|
|
Message: fmt.Sprintf(format, args...),
|
|
Context: *diag.NewContext(cp.srcMeta.Name, cp.srcMeta.Code, r)})
|
|
}
|
|
|
|
// GetCompilationError returns a *diag.Error if the given value is a compilation
|
|
// error. Otherwise it returns nil.
|
|
func GetCompilationError(e interface{}) *diag.Error {
|
|
if e, ok := e.(*diag.Error); ok && e.Type == compilationErrorType {
|
|
return e
|
|
}
|
|
return nil
|
|
}
|
|
func (cp *compiler) thisScope() *staticNs {
|
|
return cp.scopes[len(cp.scopes)-1]
|
|
}
|
|
|
|
func (cp *compiler) pushScope() (*staticNs, *staticUpNs) {
|
|
sc := new(staticNs)
|
|
up := new(staticUpNs)
|
|
cp.scopes = append(cp.scopes, sc)
|
|
cp.captures = append(cp.captures, up)
|
|
return sc, up
|
|
}
|
|
|
|
func (cp *compiler) popScope() {
|
|
cp.scopes[len(cp.scopes)-1] = nil
|
|
cp.scopes = cp.scopes[:len(cp.scopes)-1]
|
|
cp.captures[len(cp.captures)-1] = nil
|
|
cp.captures = cp.captures[:len(cp.captures)-1]
|
|
}
|
|
|
|
func (cp *compiler) checkDeprecatedBuiltin(name string, r diag.Ranger) {
|
|
msg := ""
|
|
minLevel := 16
|
|
switch name {
|
|
case "fopen~":
|
|
msg = `the "fopen" command is deprecated; use "file:open" instead`
|
|
case "fclose~":
|
|
msg = `the "fclose" command is deprecated; use "file:close" instead`
|
|
case "pipe~":
|
|
msg = `the "pipe" command is deprecated; use "file:pipe" instead`
|
|
case "prclose~":
|
|
msg = `the "prclose" command is deprecated; use "file:close $p[r]" instead`
|
|
case "pwclose":
|
|
msg = `the "pwclose" command is deprecated; use "file:close $p[w]" instead`
|
|
default:
|
|
return
|
|
}
|
|
cp.deprecate(r, msg, minLevel)
|
|
}
|
|
|
|
func (cp *compiler) deprecate(r diag.Ranger, msg string, minLevel int) {
|
|
if cp.warn == nil || r == nil {
|
|
return
|
|
}
|
|
dep := deprecation{cp.srcMeta.Name, r.Range(), msg}
|
|
if prog.DeprecationLevel >= minLevel && cp.deprecations.register(dep) {
|
|
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(""))
|
|
}
|
|
}
|