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  }