github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/compiler.go (about) 1 package eval 2 3 import ( 4 "fmt" 5 "io" 6 7 "github.com/markusbkk/elvish/pkg/diag" 8 "github.com/markusbkk/elvish/pkg/eval/vars" 9 "github.com/markusbkk/elvish/pkg/parse" 10 "github.com/markusbkk/elvish/pkg/prog" 11 ) 12 13 // compiler maintains the set of states needed when compiling a single source 14 // file. 15 type compiler struct { 16 // Builtin namespace. 17 builtin *staticNs 18 // Lexical namespaces. 19 scopes []*staticNs 20 // Sources of captured variables. 21 captures []*staticUpNs 22 // Pragmas tied to scopes. 23 pragmas []*scopePragma 24 // Destination of warning messages. This is currently only used for 25 // deprecation messages. 26 warn io.Writer 27 // Deprecation registry. 28 deprecations deprecationRegistry 29 // Information about the source. 30 srcMeta parse.Source 31 } 32 33 type scopePragma struct { 34 unknownCommandIsExternal bool 35 } 36 37 func compile(b, g *staticNs, tree parse.Tree, w io.Writer) (op nsOp, err error) { 38 g = g.clone() 39 cp := &compiler{ 40 b, []*staticNs{g}, []*staticUpNs{new(staticUpNs)}, 41 []*scopePragma{{unknownCommandIsExternal: true}}, 42 w, newDeprecationRegistry(), tree.Source} 43 defer func() { 44 r := recover() 45 if r == nil { 46 return 47 } else if e := GetCompilationError(r); e != nil { 48 // Save the compilation error and stop the panic. 49 err = e 50 } else { 51 // Resume the panic; it is not supposed to be handled here. 52 panic(r) 53 } 54 }() 55 chunkOp := cp.chunkOp(tree.Root) 56 return nsOp{chunkOp, g}, nil 57 } 58 59 type nsOp struct { 60 inner effectOp 61 template *staticNs 62 } 63 64 // Prepares the local namespace, and returns the namespace and a function for 65 // executing the inner effectOp. Mutates fm.local. 66 func (op nsOp) prepare(fm *Frame) (*Ns, func() Exception) { 67 if len(op.template.infos) > len(fm.local.infos) { 68 n := len(op.template.infos) 69 newLocal := &Ns{make([]vars.Var, n), op.template.infos} 70 copy(newLocal.slots, fm.local.slots) 71 for i := len(fm.local.infos); i < n; i++ { 72 // TODO: Take readOnly into account too 73 newLocal.slots[i] = MakeVarFromName(newLocal.infos[i].name) 74 } 75 fm.local = newLocal 76 } else { 77 // If no new variable has been created, there might still be some 78 // existing variables deleted. 79 fm.local = &Ns{fm.local.slots, op.template.infos} 80 } 81 return fm.local, func() Exception { return op.inner.exec(fm) } 82 } 83 84 const compilationErrorType = "compilation error" 85 86 func (cp *compiler) errorpf(r diag.Ranger, format string, args ...interface{}) { 87 // The panic is caught by the recover in compile above. 88 panic(&diag.Error{ 89 Type: compilationErrorType, 90 Message: fmt.Sprintf(format, args...), 91 Context: *diag.NewContext(cp.srcMeta.Name, cp.srcMeta.Code, r)}) 92 } 93 94 // GetCompilationError returns a *diag.Error if the given value is a compilation 95 // error. Otherwise it returns nil. 96 func GetCompilationError(e interface{}) *diag.Error { 97 if e, ok := e.(*diag.Error); ok && e.Type == compilationErrorType { 98 return e 99 } 100 return nil 101 } 102 103 func (cp *compiler) thisScope() *staticNs { 104 return cp.scopes[len(cp.scopes)-1] 105 } 106 107 func (cp *compiler) currentPragma() *scopePragma { 108 return cp.pragmas[len(cp.pragmas)-1] 109 } 110 111 func (cp *compiler) pushScope() (*staticNs, *staticUpNs) { 112 sc := new(staticNs) 113 up := new(staticUpNs) 114 cp.scopes = append(cp.scopes, sc) 115 cp.captures = append(cp.captures, up) 116 currentPragmaCopy := *cp.currentPragma() 117 cp.pragmas = append(cp.pragmas, ¤tPragmaCopy) 118 return sc, up 119 } 120 121 func (cp *compiler) popScope() { 122 cp.scopes[len(cp.scopes)-1] = nil 123 cp.scopes = cp.scopes[:len(cp.scopes)-1] 124 cp.captures[len(cp.captures)-1] = nil 125 cp.captures = cp.captures[:len(cp.captures)-1] 126 cp.pragmas[len(cp.pragmas)-1] = nil 127 cp.pragmas = cp.pragmas[:len(cp.pragmas)-1] 128 } 129 130 func (cp *compiler) checkDeprecatedBuiltin(name string, r diag.Ranger) { 131 msg := "" 132 minLevel := 18 133 // There is no builtin deprecated for 0.18.x yet. But this function is 134 // only called for builtins that actually exist, so no harm checking for a 135 // non-existent command here. 136 switch name { 137 case "deprecated-builtin~": 138 msg = `the "deprecated-builtin" command is deprecated; use "new-builtin" instead` 139 default: 140 return 141 } 142 cp.deprecate(r, msg, minLevel) 143 } 144 145 func (cp *compiler) deprecate(r diag.Ranger, msg string, minLevel int) { 146 if cp.warn == nil || r == nil { 147 return 148 } 149 dep := deprecation{cp.srcMeta.Name, r.Range(), msg} 150 if prog.DeprecationLevel >= minLevel && cp.deprecations.register(dep) { 151 err := diag.Error{ 152 Type: "deprecation", Message: msg, 153 Context: diag.Context{ 154 Name: cp.srcMeta.Name, Source: cp.srcMeta.Code, Ranging: r.Range()}} 155 fmt.Fprintln(cp.warn, err.Show("")) 156 } 157 }