github.com/google/skylark@v0.0.0-20181101142754-a5f7082aabed/internal/compile/serial.go (about)

     1  package compile
     2  
     3  // This files defines functions to read and write a compile.Program to a file.
     4  // Currently we use gob encoding because it is convenient.
     5  //
     6  // It is the client's responsibility to manage version skew between the
     7  // compiler used to produce a file and the interpreter that consumes it.
     8  // The version number is provided as a constant. Incompatible protocol
     9  // changes should also increment the version number.
    10  
    11  import (
    12  	"encoding/gob"
    13  	"fmt"
    14  	"io"
    15  
    16  	"github.com/google/skylark/syntax"
    17  )
    18  
    19  const magic = "!sky"
    20  
    21  type gobProgram struct {
    22  	Version   int
    23  	Filename  string
    24  	Loads     []gobIdent
    25  	Names     []string
    26  	Constants []interface{}
    27  	Functions []gobFunction
    28  	Globals   []gobIdent
    29  	Toplevel  gobFunction
    30  }
    31  
    32  type gobFunction struct {
    33  	Id                    gobIdent // hack: name and pos
    34  	Code                  []byte
    35  	Pclinetab             []uint16
    36  	Locals                []gobIdent
    37  	Freevars              []gobIdent
    38  	MaxStack              int
    39  	NumParams             int
    40  	HasVarargs, HasKwargs bool
    41  }
    42  
    43  type gobIdent struct {
    44  	Name      string
    45  	Line, Col int32 // the filename is gobProgram.Filename
    46  }
    47  
    48  // Write writes a compiled Skylark program to out.
    49  func (prog *Program) Write(out io.Writer) error {
    50  	out.Write([]byte(magic))
    51  
    52  	gobIdents := func(idents []Ident) []gobIdent {
    53  		res := make([]gobIdent, len(idents))
    54  		for i, id := range idents {
    55  			res[i].Name = id.Name
    56  			res[i].Line = id.Pos.Line
    57  			res[i].Col = id.Pos.Col
    58  		}
    59  		return res
    60  	}
    61  
    62  	gobFunc := func(fn *Funcode) gobFunction {
    63  		return gobFunction{
    64  			Id: gobIdent{
    65  				Name: fn.Name,
    66  				Line: fn.Pos.Line,
    67  				Col:  fn.Pos.Col,
    68  			},
    69  			Code:       fn.Code,
    70  			Pclinetab:  fn.pclinetab,
    71  			Locals:     gobIdents(fn.Locals),
    72  			Freevars:   gobIdents(fn.Freevars),
    73  			MaxStack:   fn.MaxStack,
    74  			NumParams:  fn.NumParams,
    75  			HasVarargs: fn.HasVarargs,
    76  			HasKwargs:  fn.HasKwargs,
    77  		}
    78  	}
    79  
    80  	gp := &gobProgram{
    81  		Version:   Version,
    82  		Filename:  prog.Toplevel.Pos.Filename(),
    83  		Loads:     gobIdents(prog.Loads),
    84  		Names:     prog.Names,
    85  		Constants: prog.Constants,
    86  		Functions: make([]gobFunction, len(prog.Functions)),
    87  		Globals:   gobIdents(prog.Globals),
    88  		Toplevel:  gobFunc(prog.Toplevel),
    89  	}
    90  	for i, f := range prog.Functions {
    91  		gp.Functions[i] = gobFunc(f)
    92  	}
    93  
    94  	return gob.NewEncoder(out).Encode(gp)
    95  }
    96  
    97  // ReadProgram reads a compiled Skylark program from in.
    98  func ReadProgram(in io.Reader) (*Program, error) {
    99  	magicBuf := []byte(magic)
   100  	n, err := in.Read(magicBuf)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  	if n != len(magic) {
   105  		return nil, fmt.Errorf("not a compiled module: no magic number")
   106  	}
   107  	if string(magicBuf) != magic {
   108  		return nil, fmt.Errorf("not a compiled module: got magic number %q, want %q",
   109  			magicBuf, magic)
   110  	}
   111  
   112  	dec := gob.NewDecoder(in)
   113  	var gp gobProgram
   114  	if err := dec.Decode(&gp); err != nil {
   115  		return nil, fmt.Errorf("decoding program: %v", err)
   116  	}
   117  
   118  	if gp.Version != Version {
   119  		return nil, fmt.Errorf("version mismatch: read %d, want %d",
   120  			gp.Version, Version)
   121  	}
   122  
   123  	file := gp.Filename // copy, to avoid keeping gp live
   124  
   125  	ungobIdents := func(idents []gobIdent) []Ident {
   126  		res := make([]Ident, len(idents))
   127  		for i, id := range idents {
   128  			res[i].Name = id.Name
   129  			res[i].Pos = syntax.MakePosition(&file, id.Line, id.Col)
   130  		}
   131  		return res
   132  	}
   133  
   134  	prog := &Program{
   135  		Loads:     ungobIdents(gp.Loads),
   136  		Names:     gp.Names,
   137  		Constants: gp.Constants,
   138  		Globals:   ungobIdents(gp.Globals),
   139  		Functions: make([]*Funcode, len(gp.Functions)),
   140  	}
   141  
   142  	ungobFunc := func(gf *gobFunction) *Funcode {
   143  		pos := syntax.MakePosition(&file, gf.Id.Line, gf.Id.Col)
   144  		return &Funcode{
   145  			Prog:       prog,
   146  			Pos:        pos,
   147  			Name:       gf.Id.Name,
   148  			Code:       gf.Code,
   149  			pclinetab:  gf.Pclinetab,
   150  			Locals:     ungobIdents(gf.Locals),
   151  			Freevars:   ungobIdents(gf.Freevars),
   152  			MaxStack:   gf.MaxStack,
   153  			NumParams:  gf.NumParams,
   154  			HasVarargs: gf.HasVarargs,
   155  			HasKwargs:  gf.HasKwargs,
   156  		}
   157  	}
   158  
   159  	for i := range gp.Functions {
   160  		prog.Functions[i] = ungobFunc(&gp.Functions[i])
   161  	}
   162  	prog.Toplevel = ungobFunc(&gp.Toplevel)
   163  	return prog, nil
   164  }