github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/eval/compiler.go (about) 1 package eval 2 3 import ( 4 "fmt" 5 "io" 6 7 "src.elv.sh/pkg/diag" 8 "src.elv.sh/pkg/eval/vars" 9 "src.elv.sh/pkg/parse" 10 "src.elv.sh/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 // Destination of warning messages. This is currently only used for 23 // deprecation messages. 24 warn io.Writer 25 // Deprecation registry. 26 deprecations deprecationRegistry 27 // Information about the source. 28 srcMeta parse.Source 29 } 30 31 type capture struct { 32 name string 33 // If true, the captured variable is from the immediate outer level scope, 34 // i.e. the local scope the lambda is evaluated in. Otherwise the captured 35 // variable is from a more outer level, i.e. the upvalue scope the lambda is 36 // evaluated in. 37 local bool 38 // Index to the captured variable. 39 index int 40 } 41 42 func compile(b, g *staticNs, tree parse.Tree, w io.Writer) (op nsOp, err error) { 43 g = g.clone() 44 cp := &compiler{ 45 b, []*staticNs{g}, []*staticUpNs{new(staticUpNs)}, 46 w, newDeprecationRegistry(), tree.Source} 47 defer func() { 48 r := recover() 49 if r == nil { 50 return 51 } else if e := GetCompilationError(r); e != nil { 52 // Save the compilation error and stop the panic. 53 err = e 54 } else { 55 // Resume the panic; it is not supposed to be handled here. 56 panic(r) 57 } 58 }() 59 chunkOp := cp.chunkOp(tree.Root) 60 return nsOp{chunkOp, g}, nil 61 } 62 63 type nsOp struct { 64 inner effectOp 65 template *staticNs 66 } 67 68 // Prepares the local namespace, and returns the namespace and a function for 69 // executing the inner effectOp. Mutates fm.local. 70 func (op nsOp) prepare(fm *Frame) (*Ns, func() Exception) { 71 if len(op.template.names) > len(fm.local.names) { 72 n := len(op.template.names) 73 newLocal := &Ns{make([]vars.Var, n), op.template.names, op.template.deleted} 74 copy(newLocal.slots, fm.local.slots) 75 for i := len(fm.local.names); i < n; i++ { 76 newLocal.slots[i] = MakeVarFromName(newLocal.names[i]) 77 } 78 fm.local = newLocal 79 } else { 80 // If no new has been created, there might still be some existing 81 // variables deleted. 82 fm.local = &Ns{fm.local.slots, fm.local.names, op.template.deleted} 83 } 84 return fm.local, func() Exception { return op.inner.exec(fm) } 85 } 86 87 const compilationErrorType = "compilation error" 88 89 func (cp *compiler) errorpf(r diag.Ranger, format string, args ...interface{}) { 90 // The panic is caught by the recover in compile above. 91 panic(&diag.Error{ 92 Type: compilationErrorType, 93 Message: fmt.Sprintf(format, args...), 94 Context: *diag.NewContext(cp.srcMeta.Name, cp.srcMeta.Code, r)}) 95 } 96 97 // GetCompilationError returns a *diag.Error if the given value is a compilation 98 // error. Otherwise it returns nil. 99 func GetCompilationError(e interface{}) *diag.Error { 100 if e, ok := e.(*diag.Error); ok && e.Type == compilationErrorType { 101 return e 102 } 103 return nil 104 } 105 func (cp *compiler) thisScope() *staticNs { 106 return cp.scopes[len(cp.scopes)-1] 107 } 108 109 func (cp *compiler) pushScope() (*staticNs, *staticUpNs) { 110 sc := new(staticNs) 111 up := new(staticUpNs) 112 cp.scopes = append(cp.scopes, sc) 113 cp.captures = append(cp.captures, up) 114 return sc, up 115 } 116 117 func (cp *compiler) popScope() { 118 cp.scopes[len(cp.scopes)-1] = nil 119 cp.scopes = cp.scopes[:len(cp.scopes)-1] 120 cp.captures[len(cp.captures)-1] = nil 121 cp.captures = cp.captures[:len(cp.captures)-1] 122 } 123 124 func (cp *compiler) checkDeprecatedBuiltin(name string, r diag.Ranger) { 125 msg := "" 126 minLevel := 16 127 switch name { 128 case "fopen~": 129 msg = `the "fopen" command is deprecated; use "file:open" instead` 130 case "fclose~": 131 msg = `the "fclose" command is deprecated; use "file:close" instead` 132 case "pipe~": 133 msg = `the "pipe" command is deprecated; use "file:pipe" instead` 134 case "prclose~": 135 msg = `the "prclose" command is deprecated; use "file:close $p[r]" instead` 136 case "pwclose": 137 msg = `the "pwclose" command is deprecated; use "file:close $p[w]" instead` 138 default: 139 return 140 } 141 cp.deprecate(r, msg, minLevel) 142 } 143 144 func (cp *compiler) deprecate(r diag.Ranger, msg string, minLevel int) { 145 if cp.warn == nil || r == nil { 146 return 147 } 148 dep := deprecation{cp.srcMeta.Name, r.Range(), msg} 149 if prog.DeprecationLevel >= minLevel && cp.deprecations.register(dep) { 150 err := diag.Error{ 151 Type: "deprecation", Message: msg, 152 Context: diag.Context{ 153 Name: cp.srcMeta.Name, Source: cp.srcMeta.Code, Ranging: r.Range()}} 154 fmt.Fprintln(cp.warn, err.Show("")) 155 } 156 }