src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/eval/compiler.go (about) 1 package eval 2 3 import ( 4 "fmt" 5 "io" 6 "strings" 7 8 "src.elv.sh/pkg/diag" 9 "src.elv.sh/pkg/eval/vars" 10 "src.elv.sh/pkg/parse" 11 "src.elv.sh/pkg/prog" 12 ) 13 14 // compiler maintains the set of states needed when compiling a single source 15 // file. 16 type compiler struct { 17 // Builtin namespace. 18 builtin *staticNs 19 // Lexical namespaces. 20 scopes []*staticNs 21 // Sources of captured variables. 22 captures []*staticUpNs 23 // Pragmas tied to scopes. 24 pragmas []*scopePragma 25 // Names of internal modules. 26 modules []string 27 // Destination of warning messages. This is currently only used for 28 // deprecation messages. 29 warn io.Writer 30 // Deprecation registry. 31 deprecations deprecationRegistry 32 // Information about the source. 33 srcMeta parse.Source 34 // Compilation errors. 35 errors []*CompilationError 36 // Suggested code to fix potential issues found during compilation. 37 autofixes []string 38 } 39 40 type scopePragma struct { 41 unknownCommandIsExternal bool 42 } 43 44 func compile(b, g *staticNs, modules []string, tree parse.Tree, w io.Writer) (nsOp, []string, error) { 45 g = g.clone() 46 cp := &compiler{ 47 b, []*staticNs{g}, []*staticUpNs{new(staticUpNs)}, 48 []*scopePragma{{unknownCommandIsExternal: true}}, 49 modules, 50 w, newDeprecationRegistry(), tree.Source, nil, nil} 51 chunkOp := cp.chunkOp(tree.Root) 52 return nsOp{chunkOp, g}, cp.autofixes, diag.PackErrors(cp.errors) 53 } 54 55 type nsOp struct { 56 inner effectOp 57 template *staticNs 58 } 59 60 // Prepares the local namespace, and returns the namespace and a function for 61 // executing the inner effectOp. Mutates fm.local. 62 func (op nsOp) prepare(fm *Frame) (*Ns, func() Exception) { 63 if len(op.template.infos) > len(fm.local.infos) { 64 n := len(op.template.infos) 65 newLocal := &Ns{make([]vars.Var, n), op.template.infos} 66 copy(newLocal.slots, fm.local.slots) 67 for i := len(fm.local.infos); i < n; i++ { 68 // TODO: Take readOnly into account too 69 newLocal.slots[i] = MakeVarFromName(newLocal.infos[i].name) 70 } 71 fm.local = newLocal 72 } else { 73 // If no new variable has been created, there might still be some 74 // existing variables deleted. 75 fm.local = &Ns{fm.local.slots, op.template.infos} 76 } 77 return fm.local, func() Exception { return op.inner.exec(fm) } 78 } 79 80 type CompilationError = diag.Error[CompilationErrorTag] 81 82 // CompilationErrorTag parameterizes [diag.Error] to define [CompilationError]. 83 type CompilationErrorTag struct{} 84 85 func (CompilationErrorTag) ErrorTag() string { return "compilation error" } 86 87 func (cp *compiler) errorpf(r diag.Ranger, format string, args ...any) { 88 cp.errors = append(cp.errors, &CompilationError{ 89 Message: fmt.Sprintf(format, args...), 90 Context: *diag.NewContext(cp.srcMeta.Name, cp.srcMeta.Code, r)}) 91 } 92 93 // UnpackCompilationErrors returns the constituent compilation errors if the 94 // given error contains one or more compilation errors. Otherwise it returns 95 // nil. 96 func UnpackCompilationErrors(e error) []*CompilationError { 97 if errs := diag.UnpackErrors[CompilationErrorTag](e); len(errs) > 0 { 98 return errs 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 := 20 133 switch name { 134 case "eawk~": 135 msg = `the "eawk" command is deprecated; use "re:awk" instead` 136 default: 137 return 138 } 139 cp.deprecate(r, msg, minLevel) 140 } 141 142 type deprecationTag struct{} 143 144 func (deprecationTag) ErrorTag() string { return "deprecation" } 145 146 func (cp *compiler) deprecate(r diag.Ranger, msg string, minLevel int) { 147 if cp.warn == nil || r == nil { 148 return 149 } 150 dep := deprecation{cp.srcMeta.Name, r.Range(), msg} 151 if prog.DeprecationLevel >= minLevel && cp.deprecations.register(dep) { 152 err := diag.Error[deprecationTag]{ 153 Message: msg, 154 Context: *diag.NewContext(cp.srcMeta.Name, cp.srcMeta.Code, r.Range())} 155 fmt.Fprintln(cp.warn, err.Show("")) 156 } 157 } 158 159 // Given a variable that doesn't resolve, add any applicable autofixes. 160 func (cp *compiler) autofixUnresolvedVar(qname string) { 161 if len(cp.modules) == 0 { 162 return 163 } 164 first, _ := SplitQName(qname) 165 mod := strings.TrimSuffix(first, ":") 166 if mod != first && sliceContains(cp.modules, mod) { 167 cp.autofixes = append(cp.autofixes, "use "+mod) 168 } 169 }