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, &currentPragmaCopy)
   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  }