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 }