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"
|
2022-12-11 22:21:09 +08:00
|
|
|
"strings"
|
2016-02-20 00:33:55 +08:00
|
|
|
|
2021-01-27 09:28:38 +08:00
|
|
|
"src.elv.sh/pkg/diag"
|
|
|
|
"src.elv.sh/pkg/eval/vars"
|
|
|
|
"src.elv.sh/pkg/parse"
|
|
|
|
"src.elv.sh/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.
|
2020-12-25 01:39:51 +08:00
|
|
|
builtin *staticNs
|
2017-12-24 07:51:32 +08:00
|
|
|
// Lexical namespaces.
|
2020-12-25 01:39:51 +08:00
|
|
|
scopes []*staticNs
|
|
|
|
// Sources of captured variables.
|
|
|
|
captures []*staticUpNs
|
2021-10-07 08:21:28 +08:00
|
|
|
// Pragmas tied to scopes.
|
|
|
|
pragmas []*scopePragma
|
2022-12-11 22:21:09 +08:00
|
|
|
// Names of internal modules.
|
|
|
|
modules []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
|
2022-11-30 09:38:50 +08:00
|
|
|
// Compilation errors.
|
2022-11-29 06:49:55 +08:00
|
|
|
errors []*diag.Error
|
2022-12-11 22:21:09 +08:00
|
|
|
// Suggested code to fix potential issues found during compilation.
|
|
|
|
autofixes []string
|
2016-02-20 00:21:51 +08:00
|
|
|
}
|
|
|
|
|
2021-10-07 08:21:28 +08:00
|
|
|
type scopePragma struct {
|
|
|
|
unknownCommandIsExternal bool
|
|
|
|
}
|
|
|
|
|
2022-12-11 22:21:09 +08:00
|
|
|
func compile(b, g *staticNs, modules []string, tree parse.Tree, w io.Writer) (nsOp, []string, error) {
|
2020-12-25 01:39:51 +08:00
|
|
|
g = g.clone()
|
2020-04-26 20:14:51 +08:00
|
|
|
cp := &compiler{
|
2020-12-25 01:39:51 +08:00
|
|
|
b, []*staticNs{g}, []*staticUpNs{new(staticUpNs)},
|
2021-10-07 08:21:28 +08:00
|
|
|
[]*scopePragma{{unknownCommandIsExternal: true}},
|
2022-12-11 22:21:09 +08:00
|
|
|
modules,
|
|
|
|
w, newDeprecationRegistry(), tree.Source, nil, nil}
|
2020-04-27 02:04:49 +08:00
|
|
|
chunkOp := cp.chunkOp(tree.Root)
|
2022-12-11 22:21:09 +08:00
|
|
|
return nsOp{chunkOp, g}, cp.autofixes, diag.PackCognateErrors(cp.errors)
|
2021-01-09 22:59:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
type nsOp struct {
|
2021-01-10 08:54:02 +08:00
|
|
|
inner effectOp
|
|
|
|
template *staticNs
|
2021-01-09 22:59:00 +08:00
|
|
|
}
|
|
|
|
|
2021-01-24 22:10:45 +08:00
|
|
|
// 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) {
|
2021-10-14 06:57:14 +08:00
|
|
|
if len(op.template.infos) > len(fm.local.infos) {
|
|
|
|
n := len(op.template.infos)
|
|
|
|
newLocal := &Ns{make([]vars.Var, n), op.template.infos}
|
2021-01-09 22:59:00 +08:00
|
|
|
copy(newLocal.slots, fm.local.slots)
|
2021-10-14 06:57:14 +08:00
|
|
|
for i := len(fm.local.infos); i < n; i++ {
|
|
|
|
// TODO: Take readOnly into account too
|
|
|
|
newLocal.slots[i] = MakeVarFromName(newLocal.infos[i].name)
|
2021-01-09 22:59:00 +08:00
|
|
|
}
|
|
|
|
fm.local = newLocal
|
2021-01-10 08:54:02 +08:00
|
|
|
} else {
|
2021-10-14 06:57:14 +08:00
|
|
|
// If no new variable has been created, there might still be some
|
|
|
|
// existing variables deleted.
|
|
|
|
fm.local = &Ns{fm.local.slots, op.template.infos}
|
2021-01-09 22:59:00 +08:00
|
|
|
}
|
2021-01-24 22:10:45 +08:00
|
|
|
return fm.local, func() Exception { return op.inner.exec(fm) }
|
2020-04-27 02:04:49 +08:00
|
|
|
}
|
|
|
|
|
2021-01-04 00:25:52 +08:00
|
|
|
const compilationErrorType = "compilation error"
|
|
|
|
|
2022-03-20 23:50:25 +08:00
|
|
|
func (cp *compiler) errorpf(r diag.Ranger, format string, args ...any) {
|
2022-11-29 06:49:55 +08:00
|
|
|
cp.errors = append(cp.errors, &diag.Error{
|
2021-01-04 00:25:52 +08:00
|
|
|
Type: compilationErrorType,
|
|
|
|
Message: fmt.Sprintf(format, args...),
|
|
|
|
Context: *diag.NewContext(cp.srcMeta.Name, cp.srcMeta.Code, r)})
|
2016-02-20 04:16:23 +08:00
|
|
|
}
|
|
|
|
|
2022-11-30 09:38:50 +08:00
|
|
|
// UnpackCompilationErrors returns the constituent compilation errors if the
|
|
|
|
// given error contains one or more compilation errors. Otherwise it returns
|
|
|
|
// nil.
|
|
|
|
func UnpackCompilationErrors(e error) []*diag.Error {
|
|
|
|
if errs := diag.UnpackCognateErrors(e); len(errs) > 0 && errs[0].Type == compilationErrorType {
|
|
|
|
return errs
|
2021-01-04 00:25:52 +08:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2021-10-07 08:21:28 +08:00
|
|
|
|
2020-12-25 01:39:51 +08:00
|
|
|
func (cp *compiler) thisScope() *staticNs {
|
2016-02-20 00:21:51 +08:00
|
|
|
return cp.scopes[len(cp.scopes)-1]
|
|
|
|
}
|
|
|
|
|
2021-10-07 08:21:28 +08:00
|
|
|
func (cp *compiler) currentPragma() *scopePragma {
|
|
|
|
return cp.pragmas[len(cp.pragmas)-1]
|
|
|
|
}
|
|
|
|
|
2020-12-25 01:39:51 +08:00
|
|
|
func (cp *compiler) pushScope() (*staticNs, *staticUpNs) {
|
|
|
|
sc := new(staticNs)
|
|
|
|
up := new(staticUpNs)
|
2016-02-20 00:21:51 +08:00
|
|
|
cp.scopes = append(cp.scopes, sc)
|
2020-12-25 01:39:51 +08:00
|
|
|
cp.captures = append(cp.captures, up)
|
2021-10-07 08:21:28 +08:00
|
|
|
currentPragmaCopy := *cp.currentPragma()
|
|
|
|
cp.pragmas = append(cp.pragmas, ¤tPragmaCopy)
|
2020-12-25 01:39:51 +08:00
|
|
|
return sc, up
|
2016-02-20 00:21:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (cp *compiler) popScope() {
|
2020-12-25 01:39:51 +08:00
|
|
|
cp.scopes[len(cp.scopes)-1] = nil
|
2016-02-20 00:21:51 +08:00
|
|
|
cp.scopes = cp.scopes[:len(cp.scopes)-1]
|
2020-12-25 01:39:51 +08:00
|
|
|
cp.captures[len(cp.captures)-1] = nil
|
|
|
|
cp.captures = cp.captures[:len(cp.captures)-1]
|
2021-10-07 08:21:28 +08:00
|
|
|
cp.pragmas[len(cp.pragmas)-1] = nil
|
|
|
|
cp.pragmas = cp.pragmas[:len(cp.pragmas)-1]
|
2020-04-26 20:14:51 +08:00
|
|
|
}
|
2021-01-20 07:17:56 +08:00
|
|
|
|
|
|
|
func (cp *compiler) checkDeprecatedBuiltin(name string, r diag.Ranger) {
|
|
|
|
msg := ""
|
2022-04-09 18:45:15 +08:00
|
|
|
minLevel := 19
|
2021-01-20 07:17:56 +08:00
|
|
|
switch name {
|
2022-04-09 18:45:15 +08:00
|
|
|
case "float64~":
|
|
|
|
msg = `the "float64" command is deprecated; use "num" or "inexact-num" instead`
|
2021-01-20 07:17:56 +08:00
|
|
|
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,
|
2022-12-09 03:00:10 +08:00
|
|
|
Context: *diag.NewContext(cp.srcMeta.Name, cp.srcMeta.Code, r.Range())}
|
2021-01-20 07:17:56 +08:00
|
|
|
fmt.Fprintln(cp.warn, err.Show(""))
|
|
|
|
}
|
|
|
|
}
|
2022-12-11 22:21:09 +08:00
|
|
|
|
|
|
|
// Given a variable that doesn't resolve, add any applicable autofixes.
|
|
|
|
func (cp *compiler) autofixUnresolvedVar(qname string) {
|
|
|
|
if len(cp.modules) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
first, _ := SplitQName(qname)
|
|
|
|
mod := strings.TrimSuffix(first, ":")
|
|
|
|
if mod != first && sliceContains(cp.modules, mod) {
|
|
|
|
cp.autofixes = append(cp.autofixes, "use "+mod)
|
|
|
|
}
|
|
|
|
}
|