github.com/arnodel/golua@v0.0.0-20230215163904-e0b5347eaaa1/runtime/lib.go (about)

     1  package runtime
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"strings"
     8  
     9  	"github.com/arnodel/golua/ast"
    10  	"github.com/arnodel/golua/astcomp"
    11  	"github.com/arnodel/golua/code"
    12  	"github.com/arnodel/golua/ir"
    13  	"github.com/arnodel/golua/ircomp"
    14  	"github.com/arnodel/golua/parsing"
    15  	"github.com/arnodel/golua/scanner"
    16  )
    17  
    18  // RawGet returns the item in a table for the given key, or nil if t is nil.  It
    19  // doesn't check the metatable of t.
    20  func RawGet(t *Table, k Value) Value {
    21  	if t == nil {
    22  		return NilValue
    23  	}
    24  	return t.Get(k)
    25  }
    26  
    27  const maxIndexChainLength = 100
    28  
    29  // Index returns the item in a collection for the given key k, using the
    30  // '__index' metamethod if appropriate.
    31  // Index always consumes CPU.
    32  func Index(t *Thread, coll Value, k Value) (Value, error) {
    33  	for i := 0; i < maxIndexChainLength; i++ {
    34  		t.RequireCPU(1)
    35  		tbl, ok := coll.TryTable()
    36  		if ok {
    37  			if val := RawGet(tbl, k); !val.IsNil() {
    38  				return val, nil
    39  			}
    40  		}
    41  		metaIdx := t.metaGetS(coll, "__index")
    42  		if metaIdx.IsNil() {
    43  			if ok {
    44  				return NilValue, nil
    45  			}
    46  			return NilValue, indexError(coll)
    47  		}
    48  		if _, ok := metaIdx.TryTable(); ok {
    49  			coll = metaIdx
    50  		} else {
    51  			res := NewTerminationWith(t.CurrentCont(), 1, false)
    52  			if err := Call(t, metaIdx, []Value{coll, k}, res); err != nil {
    53  				return NilValue, err
    54  			}
    55  			return res.Get(0), nil
    56  		}
    57  	}
    58  	return NilValue, fmt.Errorf("'__index' chain too long; possible loop")
    59  }
    60  
    61  func indexError(coll Value) error {
    62  	return fmt.Errorf("attempt to index a %s value", coll.CustomTypeName())
    63  }
    64  
    65  // SetIndex sets the item in a collection for the given key, using the
    66  // '__newindex' metamethod if appropriate.  SetIndex always consumes CPU if it
    67  // doesn't return an error.
    68  func SetIndex(t *Thread, coll Value, idx Value, val Value) error {
    69  	if idx.IsNil() {
    70  		return errors.New("index is nil")
    71  	}
    72  	for i := 0; i < maxIndexChainLength; i++ {
    73  		t.RequireCPU(1)
    74  		tbl, isTable := coll.TryTable()
    75  		if isTable && idx.IsNaN() {
    76  			return errTableIndexIsNaN
    77  		}
    78  		if isTable && tbl.Reset(idx, val) {
    79  			return nil
    80  		}
    81  		metaNewIndex := t.metaGetS(coll, "__newindex")
    82  		if metaNewIndex.IsNil() {
    83  			if isTable {
    84  				// No need to call SetTableCheck
    85  				t.SetTable(tbl, idx, val)
    86  				return nil
    87  			}
    88  			return fmt.Errorf(
    89  				"attempt to index %s value without __newindex", coll.TypeName())
    90  		}
    91  		if _, ok := metaNewIndex.TryTable(); ok {
    92  			coll = metaNewIndex
    93  		} else {
    94  			return Call(t, metaNewIndex, []Value{coll, idx, val}, NewTermination(t.CurrentCont(), nil, nil))
    95  		}
    96  	}
    97  	return fmt.Errorf("'__newindex' chain too long; possible loop")
    98  }
    99  
   100  // Truth returns true if v is neither nil nor a false boolean.
   101  func Truth(v Value) bool {
   102  	if v.IsNil() {
   103  		return false
   104  	}
   105  	b, ok := v.TryBool()
   106  	return !ok || b
   107  }
   108  
   109  // Metacall calls the metamethod called method on obj with the given arguments
   110  // args, pushing the result to the continuation next.
   111  func Metacall(t *Thread, obj Value, method string, args []Value, next Cont) (error, bool) {
   112  	if f := t.metaGetS(obj, method); !f.IsNil() {
   113  		return Call(t, f, args, next), true
   114  	}
   115  	return nil, false
   116  }
   117  
   118  // Continue tries to continue the value f or else use its '__call'
   119  // metamethod and returns the continuations that needs to be run to get the
   120  // results.
   121  func Continue(t *Thread, f Value, next Cont) (Cont, error) {
   122  	callable, ok := f.TryCallable()
   123  	if ok {
   124  		return callable.Continuation(t, next), nil
   125  	}
   126  	cont, err, ok := metacont(t, f, "__call", next)
   127  	if !ok {
   128  		return nil, fmt.Errorf("attempt to call a %s value", f.CustomTypeName())
   129  	}
   130  	if cont != nil {
   131  		t.Push1(cont, f)
   132  	}
   133  	return cont, err
   134  }
   135  
   136  // Call calls f with arguments args, pushing the results on next.  It may use
   137  // the metamethod '__call' if f is not callable.
   138  func Call(t *Thread, f Value, args []Value, next Cont) error {
   139  	if f.IsNil() {
   140  		return errors.New("attempt to call a nil value")
   141  	}
   142  	callable, ok := f.TryCallable()
   143  	if ok {
   144  		return t.call(callable, args, next)
   145  	}
   146  	err, ok := Metacall(t, f, "__call", append([]Value{f}, args...), next)
   147  	if ok {
   148  		return err
   149  	}
   150  	return fmt.Errorf("attempt to call a %s value", f.CustomTypeName())
   151  }
   152  
   153  // Call1 is a convenience method that calls f with arguments args and returns
   154  // exactly one value.
   155  func Call1(t *Thread, f Value, args ...Value) (Value, error) {
   156  	term := NewTerminationWith(t.CurrentCont(), 1, false)
   157  	if err := Call(t, f, args, term); err != nil {
   158  		return NilValue, err
   159  	}
   160  	return term.Get(0), nil
   161  }
   162  
   163  // Concat returns x .. y, possibly calling the '__concat' metamethod.
   164  func Concat(t *Thread, x, y Value) (Value, error) {
   165  	var sx, sy string
   166  	var okx, oky bool
   167  	if sx, okx = x.ToString(); okx {
   168  		if sy, oky = y.ToString(); oky {
   169  			t.RequireBytes(len(sx) + len(sy))
   170  			return StringValue(sx + sy), nil
   171  		}
   172  	}
   173  	res, err, ok := metabin(t, "__concat", x, y)
   174  	if ok {
   175  		return res, err
   176  	}
   177  	return NilValue, concatError(x, y, okx, oky)
   178  }
   179  
   180  func concatError(x, y Value, okx, oky bool) error {
   181  	var wrongVal Value
   182  	switch {
   183  	case oky:
   184  		wrongVal = x
   185  	case okx:
   186  		wrongVal = y
   187  	default:
   188  		return fmt.Errorf("attempt to concatenate a %s value with a %s value", x.CustomTypeName(), y.CustomTypeName())
   189  	}
   190  	return fmt.Errorf("attempt to concatenate a %s value", wrongVal.CustomTypeName())
   191  }
   192  
   193  // IntLen returns the length of v as an int64, possibly calling the '__len'
   194  // metamethod.  This is an optimization of Len for an integer output.
   195  func IntLen(t *Thread, v Value) (int64, error) {
   196  	if s, ok := v.TryString(); ok {
   197  		return int64(len(s)), nil
   198  	}
   199  	res := NewTerminationWith(t.CurrentCont(), 1, false)
   200  	err, ok := Metacall(t, v, "__len", []Value{v}, res)
   201  	if ok {
   202  		if err != nil {
   203  			return 0, err
   204  		}
   205  		l, ok := ToInt(res.Get(0))
   206  		if !ok {
   207  			err = errors.New("len should return an integer")
   208  		}
   209  		return l, err
   210  	}
   211  	if tbl, ok := v.TryTable(); ok {
   212  		return tbl.Len(), nil
   213  	}
   214  	return 0, lenError(v)
   215  }
   216  
   217  // Len returns the length of v, possibly calling the '__len' metamethod.
   218  func Len(t *Thread, v Value) (Value, error) {
   219  	if s, ok := v.TryString(); ok {
   220  		return IntValue(int64(len(s))), nil
   221  	}
   222  	res := NewTerminationWith(t.CurrentCont(), 1, false)
   223  	err, ok := Metacall(t, v, "__len", []Value{v}, res)
   224  	if ok {
   225  		if err != nil {
   226  			return NilValue, err
   227  		}
   228  		return res.Get(0), nil
   229  	}
   230  	if tbl, ok := v.TryTable(); ok {
   231  		return IntValue(tbl.Len()), nil
   232  	}
   233  	return NilValue, lenError(v)
   234  }
   235  
   236  func lenError(x Value) error {
   237  	return fmt.Errorf("attempt to get length of a %s value", x.CustomTypeName())
   238  }
   239  
   240  // SetEnv sets the item in the table t for a string key.  Useful when writing
   241  // libraries
   242  func (r *Runtime) SetEnv(t *Table, name string, v Value) {
   243  	r.SetTable(t, StringValue(name), v)
   244  }
   245  
   246  // SetEnvGoFunc sets the item in the table t for a string key to be a GoFunction
   247  // defined by f.  Useful when writing libraries
   248  func (r *Runtime) SetEnvGoFunc(t *Table, name string, f GoFunctionFunc, nArgs int, hasEtc bool) *GoFunction {
   249  	gof := &GoFunction{
   250  		f:      f,
   251  		name:   name,
   252  		nArgs:  nArgs,
   253  		hasEtc: hasEtc,
   254  	}
   255  	r.SetTable(t, StringValue(name), FunctionValue(gof))
   256  	return gof
   257  }
   258  
   259  // ParseLuaChunk parses a string as a Lua statement and returns the AST.
   260  func (r *Runtime) ParseLuaChunk(name string, source []byte, scannerOptions ...scanner.Option) (stat *ast.BlockStat, statSize uint64, err error) {
   261  	s := scanner.New(name, source, scannerOptions...)
   262  
   263  	// Account for CPU and memory used to make the AST.  This is an estimate,
   264  	// but statSize is proportional to the size of the source.
   265  	statSize = uint64(len(source))
   266  	r.LinearRequire(4, uint64(len(source))) // 4 is a factor pulled out of thin air
   267  
   268  	stat = new(ast.BlockStat)
   269  	*stat, err = parsing.ParseChunk(s)
   270  	if err != nil {
   271  		r.ReleaseMem(statSize)
   272  		var parseErr parsing.Error
   273  		if !errors.As(err, &parseErr) {
   274  			return nil, 0, err
   275  		}
   276  		return nil, 0, NewSyntaxError(name, parseErr)
   277  	}
   278  	return
   279  }
   280  
   281  // ParseLuaExp parses a string as a Lua expression and returns the AST.
   282  func (r *Runtime) ParseLuaExp(name string, source []byte, scannerOptions ...scanner.Option) (stat *ast.BlockStat, statSize uint64, err error) {
   283  	s := scanner.New(name, source, scannerOptions...)
   284  
   285  	// Account for CPU and memory used to make the AST.  This is an estimate,
   286  	// but statSize is proportional to the size of the source.
   287  	statSize = uint64(len(source))
   288  	r.LinearRequire(4, uint64(len(source))) // 4 is a factor pulled out of thin air
   289  
   290  	exp, err := parsing.ParseExp(s)
   291  	if err != nil {
   292  		r.ReleaseMem(statSize)
   293  		var parseErr parsing.Error
   294  		if !errors.As(err, &parseErr) {
   295  			return nil, 0, err
   296  		}
   297  		return nil, 0, NewSyntaxError(name, parseErr)
   298  	}
   299  	stat = new(ast.BlockStat)
   300  	*stat = ast.NewBlockStat(nil, []ast.ExpNode{exp})
   301  	return
   302  }
   303  
   304  func (r *Runtime) compileLuaStat(name string, stat *ast.BlockStat, statSize uint64) (*code.Unit, uint64, error) {
   305  	// In any event the AST goes out of scope when leaving this function
   306  	defer func() { r.ReleaseMem(statSize) }()
   307  
   308  	// Account for CPU and memory needed to compile the AST to IR.  This is an
   309  	// estimate, but constsSize is proportional to the size of the AST.
   310  	constsSize := statSize
   311  	r.LinearRequire(4, constsSize) // 4 is a factor pulled out of thin air
   312  
   313  	// The IR consts go out of scope when we leave the function
   314  	defer r.ReleaseMem(constsSize)
   315  
   316  	// Compile ast to ir
   317  	kidx, constants, err := astcomp.CompileLuaChunk(name, *stat)
   318  
   319  	// We no longer need the AST (whether that succeeded or not)
   320  	r.ReleaseMem(statSize)
   321  
   322  	if err != nil {
   323  		return nil, 0, fmt.Errorf("%s:%s", name, err)
   324  	}
   325  
   326  	statSize = 0 // So that the deferred function above doesn't release the memory again.
   327  
   328  	// "Optimise" the ir code
   329  	constants = ir.FoldConstants(constants, ir.DefaultFold)
   330  
   331  	// Set up the IR to code compiler
   332  	kc := ircomp.NewConstantCompiler(constants, code.NewBuilder(name))
   333  	kc.QueueConstant(kidx)
   334  
   335  	// Account for CPU and memory needed to compile IR to a code unit.  This is
   336  	// an estimate, but unitSize is proportional to the size of the IR consts.
   337  	unitSize := constsSize
   338  	r.LinearRequire(4, unitSize) // 4 is a factor pulled out of thin air
   339  
   340  	// Compile IR to code
   341  	unit, err := kc.CompileQueue()
   342  	if err != nil {
   343  		return nil, 0, err
   344  	}
   345  
   346  	// We no longer need the constants
   347  	return unit, unitSize, nil
   348  }
   349  
   350  func (r *Runtime) CompileLuaChunkOrExp(name string, source []byte, scannerOptions ...scanner.Option) (unit *code.Unit, sz uint64, err error) {
   351  	var statErr error
   352  	stat, statSize, expErr := r.ParseLuaExp(name, source, scannerOptions...)
   353  	if expErr != nil {
   354  		stat, statSize, statErr = r.ParseLuaChunk(name, source, scannerOptions...)
   355  	}
   356  	if expErr != nil && statErr != nil {
   357  		// If parsing as expression or chunk failed, then try to return the error that showed the most parsing
   358  		expSyntaxErr, okExp := AsSyntaxError(expErr)
   359  		statSyntaxErr, okStat := AsSyntaxError(statErr)
   360  		switch {
   361  		case !okStat:
   362  			err = expErr
   363  		case !okExp:
   364  			err = statErr
   365  		case expSyntaxErr.Err.Got.Pos.Offset >= statSyntaxErr.Err.Got.Offset:
   366  			err = expErr
   367  		default:
   368  			err = statErr
   369  		}
   370  		return
   371  	}
   372  	return r.compileLuaStat(name, stat, statSize)
   373  }
   374  
   375  // CompileLuaChunk parses and compiles the source as a Lua Chunk and returns the
   376  // compile code Unit.
   377  func (r *Runtime) CompileLuaChunk(name string, source []byte, scannerOptions ...scanner.Option) (*code.Unit, uint64, error) {
   378  	stat, statSize, err := r.ParseLuaChunk(name, source, scannerOptions...)
   379  	if err != nil {
   380  		return nil, 0, err
   381  	}
   382  	return r.compileLuaStat(name, stat, statSize)
   383  }
   384  
   385  // CompileAndLoadLuaChunk parses, compiles and loads a Lua chunk from source and
   386  // returns the closure that runs the chunk in the given global environment.
   387  func (r *Runtime) CompileAndLoadLuaChunkOrExp(name string, source []byte, env Value, scannerOptions ...scanner.Option) (*Closure, error) {
   388  	unit, unitSize, err := r.CompileLuaChunkOrExp(name, source, scannerOptions...)
   389  	defer r.ReleaseMem(unitSize)
   390  	if err != nil {
   391  		return nil, err
   392  	}
   393  	return r.LoadLuaUnit(unit, env), nil
   394  }
   395  
   396  // CompileAndLoadLuaChunk parses, compiles and loads a Lua chunk from source and
   397  // returns the closure that runs the chunk in the given global environment.
   398  func (r *Runtime) CompileAndLoadLuaChunk(name string, source []byte, env Value, scannerOptions ...scanner.Option) (*Closure, error) {
   399  	unit, unitSize, err := r.CompileLuaChunk(name, source, scannerOptions...)
   400  	defer r.ReleaseMem(unitSize)
   401  	if err != nil {
   402  		return nil, err
   403  	}
   404  	return r.LoadLuaUnit(unit, env), nil
   405  }
   406  
   407  // LoadFromSourceOrCode loads the given source, compiling it if it is source
   408  // code or unmarshaling it if it is dumped code.  It returns the closure that
   409  // runs the chunk in the given global environment.
   410  func (r *Runtime) LoadFromSourceOrCode(name string, source []byte, mode string, env Value, stripComment bool) (*Closure, error) {
   411  	var (
   412  		canBeBinary      = strings.IndexByte(mode, 'b') >= 0
   413  		canBeText        = strings.IndexByte(mode, 't') >= 0
   414  		firstLineSkipped = false
   415  	)
   416  	if stripComment {
   417  		source, firstLineSkipped = stripFirstLineComment(source)
   418  	}
   419  
   420  	switch {
   421  	case canBeBinary && HasMarshalPrefix(source):
   422  		buf := bytes.NewBuffer(source)
   423  		k, used, err := UnmarshalConst(buf, r.LinearUnused(10))
   424  		r.LinearRequire(10, used)
   425  		if err != nil {
   426  			return nil, err
   427  		}
   428  		code, ok := k.TryCode()
   429  		if !ok {
   430  			return nil, errors.New("Expected function to load")
   431  		}
   432  		clos := NewClosure(r, code)
   433  		if code.UpvalueCount > 0 {
   434  			clos.AddUpvalue(newCell(env))
   435  			r.RequireCPU(uint64(code.UpvalueCount))
   436  			for i := int16(1); i < code.UpvalueCount; i++ {
   437  				clos.AddUpvalue(newCell(NilValue))
   438  			}
   439  		}
   440  		return clos, nil
   441  	case HasMarshalPrefix(source):
   442  		return nil, errors.New("attempt to load a binary chunk")
   443  	case !canBeText:
   444  		return nil, errors.New("attempt to load a text chunk")
   445  	default:
   446  		var opts []scanner.Option
   447  		if firstLineSkipped {
   448  			opts = append(opts, scanner.WithStartLine(2))
   449  		}
   450  		return r.CompileAndLoadLuaChunk(name, source, env, opts...)
   451  	}
   452  }
   453  
   454  func stripFirstLineComment(chunk []byte) ([]byte, bool) {
   455  	// Skip BOM
   456  	if bytes.HasPrefix(chunk, []byte{0xEF, 0xBB, 0xBF}) {
   457  		chunk = chunk[3:]
   458  	}
   459  	if len(chunk) == 0 || chunk[0] != '#' {
   460  		return chunk, false
   461  	}
   462  	for i, b := range chunk {
   463  		if b == '\n' || b == '\r' {
   464  			return chunk[i+1:], true
   465  		}
   466  	}
   467  	return nil, true
   468  }
   469  
   470  func metacont(t *Thread, obj Value, method string, next Cont) (Cont, error, bool) {
   471  	f := t.metaGetS(obj, method)
   472  	if f.IsNil() {
   473  		return nil, nil, false
   474  	}
   475  	cont, err := Continue(t, f, next)
   476  	if err != nil {
   477  		return nil, err, true
   478  	}
   479  	return cont, nil, true
   480  }
   481  
   482  func metabin(t *Thread, f string, x Value, y Value) (Value, error, bool) {
   483  	xy := []Value{x, y}
   484  	res := NewTerminationWith(t.CurrentCont(), 1, false)
   485  	err, ok := Metacall(t, x, f, xy, res)
   486  	if !ok {
   487  		err, ok = Metacall(t, y, f, xy, res)
   488  	}
   489  	if ok {
   490  		return res.Get(0), err, true
   491  	}
   492  	return NilValue, nil, false
   493  }
   494  
   495  func metaun(t *Thread, f string, x Value) (Value, error, bool) {
   496  	res := NewTerminationWith(t.CurrentCont(), 1, false)
   497  	err, ok := Metacall(t, x, f, []Value{x}, res)
   498  	if ok {
   499  		return res.Get(0), err, true
   500  	}
   501  	return NilValue, nil, false
   502  }