lua

package module
v1.1.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 5, 2021 License: Zlib Imports: 12 Imported by: 50

README

DCLua - Go Lua Compiler and VM:

This is a Lua 5.3 VM and compiler written in Go. This is intended to allow easy embedding into Go programs, with minimal fuss and bother.

I have been using this VM/compiler as the primary script host in Rubble (a scripted templating system used to generate data files for the game Dwarf Fortress) for over a year now, so they are fairly well tested. In addition to the real-world "testing" that this has received I am slowly adding proper tests based on the official Lua test suite. These tests are far from complete, but are slowly getting more so as time passes.

Most (if not all) of the API functions may cause a panic, but only if things go REALLY wrong. If a function does not state that it can panic or "raise an error" it will only do so if a critical internal assumption proves to be wrong (AKA there is a bug in the code somewhere). These errors will have a special prefix prepended onto the error message stating that this error indicates an internal VM bug. If you ever see such an error I want to know about it ASAP.

That said, if an API function can "raise an error" it can and will panic if something goes wrong. This is not a problem inside a native function (as the VM is prepared for this), but if you need to call these functions outside of code to be run by the VM you may want to use Protect or Recover to properly catch these errors.

The VM itself does not provide any Lua functions, the standard library is provided entirely by other packages. This means that the standard library never does anything that your own code cannot do (there is no "private API" that is used by the standard library).

Anything to do with the OS or file IO is not provided. Such things do not belong in the core libraries of an embedded scripting language (do you really want scripts to be able to read and write random files without restriction?).

All functions (including most of the internal functions) are documented to one degree or another, most quite well. The API is designed to be easy to use, and everything was added because I needed it. There are no "bloat" functions added because I thought they could be useful.

Note that another version of this exists over at ofunc/lua. That version has some interesting changes/features, I suggest you give it look to see if it suits your needs better.

Loading Code:

This VM fully supports binary chunks, so if you want to precompile your script it is possible. To precompile a script for use with this VM you can either build a copy of luac (the reference Lua compiler) or use any other third party Lua complier provided that it generates code compatible with the reference compiler. There is no separate compiler binary that you can build, but it wouldn't be hard to write one. Note that the VM does not handle certain instructions in pairs like the reference Lua VM does, and I don't remember if I made the compiler take advantage of this or not. If I did then binaries generated by my compiler may not work with the reference VM.

If you want to use a third-party compiler it will need to produce binaries with the following settings:

  • 64 or 32 bit pointers (C type size_t), 64 bit preferred.
  • 32 bit integers (C type int).
  • 64 bit float numbers.
  • 64 bit integer numbers.
  • Little Endian byte order.

When building the reference compiler on most systems these settings should be the default.

The VM API has a function that wraps luac to load code, but the way it does this may or may not fit your needs. To use this wrapper you will need to have luac on your path or otherwise placed so the VM can find it. See the documentation for State.LoadTextExternal for more information. Keep in mind that due to limitations in Go and luac, this function is not reentrant! If you need concurrency support it would be better to use State.LoadBinary and write your own wrapper.

The default compiler provided by this library does not support constant folding, and some special instructions are not used at all (instead preferring simpler sequences of other instructions). Expressions use a simple "recursive" code generation style, meaning that it wastes registers like crazy in some (rare) cases.

One of the biggest code quality offenders is or and and, as they can result in sequences like this one:

[4]   LT        A:1  B:r(0)   C:k(2)  ; CK:5
[5]   JMP       A:0  SBX:1            ; to:7
[6]   LOADBOOL  A:2  B:1      C:1
[7]   LOADBOOL  A:2  B:0      C:0
[8]   TEST      A:2           C:1
[9]   JMP       A:0  SBX:7            ; to:17
[10]  EQ        A:1  B:r(1)   C:k(3)  ; CK:<nil>
... (7 more instructions to implement next part of condition)

As you can see this is terrible. That sequence would be better written as:

[4]   LT        A:1  B:r(0)   C:k(2)  ; CK:5
[5]   JMP       A:0  SBX:2            ; to:8
[6]   EQ        A:1  B:r(1)   C:k(3)  ; CK:<nil>
... (1 more instruction to implement next part of condition)

But the current expression compiler is not smart enough to do it that way. Luckily this is the worst offender, most things produce code that is very close or identical to what luac produces. Note that the reason why this code is so bad is entirely because the expression used or (and the implementation of and and or is very bad).

To my knowledge there is only one case where my compiler does a better job than luac, namely when compiling loops or conditionals with constant conditions, impossible conditions are elided (so if you say while false do x(y z) end the compiler will do nothing). AFAIK there is no way to jump into such blocks anyway, so eliding them should have no effect on the correctness of the program.

The compiler provides an implementation of a continue keyword, but the keyword definition in the lexer is commented out. If you want continue all you need to do is uncomment the indicated line (near the top of ast/lexer.go). There is also a flag in the VM that should make tables use 0 based indexing. This feature has received minimal testing, so it probably doesn't work properly. If you want to try 0 based indexing just set the variable TableIndexOffset to 0. Note that TableIndexOffset is strictly a VM setting, the standard modules do not respect this setting (for example the table module and ipairs will still insist on using 1 as the first index).

Missing Stuff:

The following standard functions/variables are not available:

  • collectgarbage (not possible, VM uses the Go collector)
  • dofile (violates my security policy)
  • loadfile (violates my security policy)
  • xpcall (VM has no concept of a message handler)
  • package.config (violates my security policy)
  • package.cpath (VM has no support for native modules)
  • package.loadlib (VM has no support for native modules)
  • package.path (violates my security policy)
  • package.searchpath (violates my security policy)
  • string.gmatch (No pattern matching support)
  • string.gsub (No pattern matching support)
  • string.match (No pattern matching support)
  • string.pack (too lazy to implement, ask if you need it)
  • string.packsize (too lazy to implement, ask if you need it)
  • string.unpack (too lazy to implement, ask if you need it)

The following standard modules are not available:

  • coroutine (no coroutine support yet, ask if you need it)
  • io (violates my security policy)
  • os (violates my security policy)
  • debug (violates my security policy, if you really need something from here ask)

Coroutine support is not available. I can implement something based on goroutines fairly easily, but I will only do so if someone actually needs it and/or if I get really bored...


In addition to the stuff that is not available at all the following functions are not implemented exactly as the Lua 5.3 specification requires:

  • string.find does not allow pattern matching yet (the fourth option is effectively always set to true).
  • Only one searcher is added to package.searchers, the one for finding modules in package.preloaded.
  • next is not reentrant for a single table, as it needs to store state information about each table it is used to iterate. Starting a new iteration for a particular table invalidates the state information for the previous iteration of that table. Never use this function for iterating a table unless you absolutely have to, use the non-standard getiter function instead. getiter works the way next should have, namely it uses a single iterator value that stores all required iteration state internally (the way the default next works is only possible if your hash table is implemented a certain way).

Finally there are a few things that are implemented exactly as the Lua 5.3 specification requires, where the reference Lua implementation does not follow the specification exactly:

  • The # (length) operator always returns the exact length of a (table) sequence, not the total length of the array portion of the table. See the comment in table.go (about halfway down) for more details (including quotes from the spec and examples).
  • My modulo operator (%) is implemented the same way most languages implement it, not the way Lua does. This does not matter unless you are using negative operands, in which case it may not provide the results a Lua programmer may expect (although C or Go programmers will be fine :P).

The following core language features are not supported:

  • Hexadecimal floating point literals are not supported at this time. This "feature" is not supported for two reasons: I hate floating point in general (so trying to write a converter is pure torture), and when have you ever used hexadecimal floating point literals? Lua is the only language I have ever used that supports them, so they are not exactly popular...

  • Weak references of any kind are not supported. This is because I use Go's garbage collector, and it does not support weak references.

  • I do not currently support finalizers. It would probably be possible to support them, but it would be a lot of work for a feature that is of limited use (I have only ever needed to use a finalizer once, ironically in this library). If you have a compelling reason why you need finalizers I could probably add them...

  • The reference compiler allows you to use goto to jump to a label at the end of a block ignoring any variables in said block. For example:

      do
      	goto x
      	local a
      	::x::
      end
    

    My compiler does not currently allow this, treating it as a jump into the scope of a local variable. I consider this a bug, and will probably fix it sooner or later...

    Note that AFAIK there is nothing in the Lua spec that implies this is allowed, but it seems like a logical thing to permit so I suppose I'll have to fix it, sigh.

TODO:

Stuff that should be done sometime. Feel free to help out :)

The list is (roughly) in priority order.

  • Write more tests for the compiler and VM.
  • (supermeta) Allow using byte slices as strings and vice-versa. Maybe attach a method to byte slices that allows conversion back and forth? (this would probably be fairly easy to do)
    • Do the same with rune slices?
  • Write better stack traces for errors.
  • Improve compilation of and and or.
  • Fix jumping to a label at the end of a block.
  • Fix CONCAT so it performs better when there is a value with a __concat metamethod.
  • (supermeta) Look into allowing scripts to call functions/methods. It's certainly possible, but possibly difficult (possible not as difficult as I think).
  • Marshaling the AST as XML works poorly at best. The main problem is that some items retain their type info, and others have it stripped in favor of their parent field name.

Changes:

A note on versions:

For this project I more-or-less follow semantic versioning, so I try to maintain backwards compatibility across point releases. That said I feel free to break minor things in the name of bugfixes. Read the changelog before upgrading!


1.1.8

  • Fixed State.ConvertString so it actually works (based on PR #21 by ofunc)
  • Fixed a really weird issue where statements that started with a parenthesized expression would be assumed to be a function call and error out if they were not. (Fixed #23)
  • Fixed issue in string.byte. (PR #24 by ofunc)
  • Fixed math.huge to have the correct value. (PR #25 by ofunc)
  • Added utf8 package (github.com/milochristiansen/lua/lmodutf8) (PR #26 by ofunc)

1.1.7

  • Function calls or parenthesized expressions that are followed by table indexers are now properly compiled (Fixed #13).
  • The compiler sometimes did not always mark "used" the proper number of registers when compiling identifiers (Fixed #16).
  • Fixed the table iterator not finalizing (Fixed #17).
  • Removed my hacky slice library and just did things properly (Fixed #18).
  • Fix pcall not returning true on success (Fixed #19).
  • Fixed setting a nil index in a table not raising an error (Fixed #20).

1.1.6

Fun with tables! Ok, not so much fun.

  • Fixed scripts with lots of constants overflowing RK fields in certain instructions. The proper constant load instructions are emitted in this case now.
  • Tables with lots of empty space at the beginning of the array portion will no longer cause crashes when the array portion is resized.

1.1.5

And, another stupid little bug.

  • Constructs similar to the following [=[]==]]=] were not working properly. The lexer was not properly constructing the lexeme, and it would return the wrong number of equals signs and it would eat the last square bracket. As a bonus I greatly simplified the string lexing code. (ast/lexer.go)

1.1.4

Not sure how I missed this one... Oh well, it should work now.

  • require was not checking package.loaded properly. (lmodpackage/functions.go)

1.1.3

One of the tests was failing on 32 bit systems, now it isn't.

  • Integer table keys that fit into a script integer but not a system default int value will no longer be truncated sometimes. Such keys were always supposed to go in the hash part of the table, but before this fix the keys were being truncated first in some cases. (table.go)

1.1.2

More script tests, but no real compiler bugs this time. Instead I found several minor issues with a few of the API functions and a few other miscellaneous VM issues (mostly related to metatables).

This version also adds a minor new feature, nothing to get excited about... Basically I made it so that JSON or XML encoding an AST produces slightly more readable results for operator expression nodes. Someone else suggested the idea (actually they submitted a patch, yay them!). I never would have thought to do this myself (never needed it), but now that I have it, it seems like it could be useful for debugging the compiler among other things.

Unfortunately due to the way the AST and most encodings work, it is impossible to unmarshal the AST. I am not 100% sure if it is possible with XML or not, but it certainly will not work with JSON. This could maybe be fixed, but would be way too much work.

Anyway, these improvements are still useful if you want to examine the AST for whatever reason...

  • Added another set of script tests. (script_test.go)
  • Fixed the tostring script function and the ConvertString API function so they pass the return value from a _tostring metamethod through unchanged (instead of converting the result to a string, for example "nil"). (api.go)
  • You may now use nil as a metatable value for the SetMetaTable API function (and the setmetatable script function). (api.go)
  • Made some changes to the compiler tester so it is easier to tweak the output for specific error types (for example it is now possible to suppress the assembly listing). (test.go)
  • Fixed variadic functions that also have named parameters (this was an issue with the new stack frame code, not the compiler). (stack.go)
  • Fixed comparisons of non-matching types via metamethods, before if the types did not match the comparison would always fail (I must have been sleep deprived when I wrote that bit). (value.go)
  • Changed the way greater-than and greater-than or equals (> and >=) where implemented. The old way was 2 > 1 == !(2 <= 1) and 2 >= 1 == !(2 < 1), the new way is 2 > 1 == 1 < 2 and 2 >= 1 == 1 <= 2. The old way worked fine (and was slightly easier to implement), but it was not what the spec required (and so could cause problems with metamethods). (compile_expr.go)
  • Changed the way the CONCAT instruction is implemented so that the __concat metamethod works correctly. Warning: using __concat even once will cause string concatenation performance to nosedive for that group of concatenation operations! (vm.go)
  • Fixed the rawset and rawget script functions so they ignore extra arguments. (lmodbase/functions.go)
  • Values with a __newindex metamethod that is a table now properly use a regular set (triggers metamethods) when indexing this table. I'm not sure how I missed this, I did it properly for gets (__index). (value.go)
  • AST operator type constants now marshal and unmarshal as text rather than raw (more-or-less meaningless) integers. Also when printed via fmt functions they should use text names by default. (ast/expr.go, commit by "erizocosmico")
  • When encoded as JSON, each Node now has an extra key named after the node type that contains an object with all fields common to all nodes (namely the line number). This greatly enhances readability of a JSON encoded AST. (ast/ast.go)

1.1.1

More script tests, more compiler bugs fixed. Same song, different verse.

  • Added another set of script tests. (script_test.go)
  • Fixed unary operators after a power operator, for example 2 ^ - -2. To fix this issue I totally rewrote how operators are parsed. (ast/parse_expr.go)
  • Fixed semicolons immediately after a return statement. (ast/parse.go)
  • Fixed an improper optimization or repeat-until loops. Basically if the loop had a constant for the loop condition its sense was being reversed (so a false condition resulted in the loop being compiled as a simple block, and a true condition resulted in an infinite loop). (compile.go)
  • Fixed and in non-boolean contexts. Also and and or may produce slightly better code now. (compile_expr.go)

1.1.0

I was a little bored recently, so I threw together a generic metatable API. It was a quick little project, based on earlier work for one of my many toy languages. This new API is kinda cool, but it in no way replaces proper metatables! Basically it is intended for quick projects and temporarily exposing data to scripts. It was fun to write, and so even if no one uses it, it has served its purpose :P

I really should have been working on more script tests, but this was more fun... I have no doubt responsibility will reassert itself soon.

Anyway, I also added two new convenience methods for table iteration, as well as some minor changes to the old one (you can still use it, but it is now a thin wrapper over one of the new functions, so you shouldn't).

  • Ran all code through go fmt. I often forget to do this, but I recently switched to a new editor that formats files automatically whenever they are saved. Anyway, everything is formatted now. (almost every file in minor ways)
  • Added Protect and Recover, simple error handlers for native code. They are to be used when calling native APIs outside of code otherwise protected (such as by a call to PCall). Recover is the old handler from PCall, wrapped so it can be used by itself. Protect simply wraps Recover so it is easier to use. (api.go)
  • Added ForEachRaw, basically ForEachInTable, but the passed in function returns a boolean specifying if you want to break out of the loop early. In other news ForEachInTable is now depreciated. (api.go)
  • Added ForEach, a version of ForEachRaw that respects the __pairs metamethod. ForEachRaw uses the table iterator directly and does much less stack manipulation, so it is probably a little faster. (api.go)
  • Added a new sub-package: supermeta adds "generic" metatables for just about any Go type. For obvious reasons this makes heavy use of reflection, so it is generally much faster to write your own metatables, that said this is really nice for quickly exposing native data to scripts. From the user's perspective you just call supermeta.New(l, &object) and object is suddenly a script value on the top of l's stack. Arrays, slices, maps, structs, etc should all work just fine. Note that this is very new, and as of yet has received little real-world testing! (supermeta/supermeta.go, supermeta/tables.go)
  • Added a new sub-package: testhelp contains a few test helper functions I find useful when writing tests that interact with the VM. Better to have all this stuff in one place rather than copied and pasted all over... (testhelp/testhelp.go)
  • Modified the script tests in the base package to use the helper functions in testhelp rather than their own copies. The API tests still have their own copies of some of the functions, as they need to be in the base package so they can access internal APIs (stupid circular imports). (script_test.go)
  • Clarified what API functions may panic, I think I got them all... (api.go)

1.0.2

More tests, more (compiler) bugs fixed. Damn compiler will be the death of me yet...

In addition to the inevitable compiler bugs I also fixed the way the VM handles upvalues. Before I was giving each closure its own copy of each upvalue, so multiple closures never properly shared values. This change fixes several subtle (and several not so subtle) bugs.

Oh, and pcall works now (it didn't work at all before. Sorry, I never used it).

  • Added more script tests. I still have a lot more to do... (script_test.go)
  • Fixed incorrect compilation of method declarations (function a:x() end). Depressingly the issue was only one incorrect word, but it resulted in very wrong results (I am really starting to remember why I hated writing the compiler, the VM was fun, the compiler... not.) (ast/parse.go)
  • Parenthesized expression that would normally (without the parenthesis) return multiple values (for example: (...)) were not properly truncating the result to a single value. (compile_expr.go)
  • Fixed a semi-major VM issue with upvalues. Closures that should have a single shared upvalue were instead each using their own private copy after said upvalue was closed. This required an almost total rewrite of the way upvalues are stored internally. (all over the place, but mainly callframe.go, function.go, api.go, and vm.go)
  • JMP instructions created by break and continue statements are now properly patched by the compiler to close any upvalues there may be. (compile.go)
  • Fixed the pcall script function so it actually works. (lmodbase/functions.go)
  • On a recovered error each stack frame's upvalues are closed before the stack is stripped. This corrects incorrect behavior that arises when a function stores a closure to an unclosed upvalue then errors out (the closure may still be referenced, but it's upvalues may be invalid). (api.go, callframe.go)

1.0.1

This version adds a bunch of tests (still not nearly as many as I would like), and fixes a ton of minor compiler errors. Most of the compiler errors were simple oversights, usually syntax constructs that I never used in my own code (and hence never tested).

The VM itself seems to be mostly bug free, but the compiler is a different story. I'm fixing bugs as fast as I discover them, but sometimes it's really tempting to just use luac and call it a day :P

  • Fixed a issue with State.Pop possibly causing a panic if you pop values when the stack is empty (or if you try to pop more values than the stack contains), it now does nothing in this case. (stack.go)
  • Added some tests for the VM native API (api_test.go)
  • Added some script tests based on the official Lua 5.3 test suite. These tests are not (even close to) complete yet, (many) more are on the way. (script_test.go)
  • Added a String method to STypeID to match the one for TypeID. (value.go)
  • Made the custom string module extensions optional. (lmodstring/functions.go, lmodstring/README.md)
  • Fixed an issue with the ForEachInTable helper function, it left the table iterator object on the stack when it returned. (api.go)
  • Fixed inexplicably missing lexer entry for the semicolon (I know it was there before, it must have gotten removed by accident at some point). (ast/lexer.go)
  • Lexer errors now contain the line number where the problem resides (or at least close to it). (ast/parse.go)
  • Fixed that numeric for loops required all three arguments. I always use the full form, so I forgot that a short two argument form is legal... (ast/parse.go)
  • Fixed that you could not repeat two unary operators in a row. (ast/parse_expr.go)
  • You may now use semicolons as well as commas as field separators in table constructors (did you know that was legal? I didn't until I rechecked the BNF). (ast/parse_expr.go)
  • Fixed certain cases in expression/name parsing. Some things are less permissive, others are more. (ast/parse.go ast/parse_expr.go)
  • Fixed certain multiple assignment statements involving table assignments and direct assignments to the same variable. If the table assignment came first the direct assignment would clobber its register/upvalue and you would get an error or (even worse) unexpected behavior. This affected statements such as the following: local a = {}; a[1], a = 1, 1 (compile.go)
  • All numeric constants were always being treated as floats, leading to errors when you tried to use a hexadecimal constants (and probably other subtle issues). (ast/lexer.go)
  • You may now use the shorthand null string escape sequence ('\0'). Thank you to whoever wrote the Lua spec, not having a proper list of valid escape sequences is really helpful /s. (ast/lexer.go)
  • Both sides of a shift are now converted to an unsigned integer for the duration of the shift, then converted back to the proper signed type. This resolves some strangeness with bitwise shifts. (value.go)
  • Removed various debugging print statements that I forgot to remove earlier. The only ones still in were a few that printed just before an error triggered, so it is unlikely anyone ever saw one... (all over the place)

Documentation

Overview

DCLua - A light weight Go Lua VM designed for easy embedding.

The compiler generates correct code in every case I have tested, but the code quality is sometimes poor. If you want better code quality it is possible to compile scripts with luac and load the binaries...

Currently the compiler does not support constant folding, and some special instructions are not used at all (instead preferring simpler sequences of other instructions). For example TESTSET is never generated, TEST is used in all cases (largely because It would greatly complicate the compiler if I tried to use TESTSET where possible). Expressions use a simple "recursive" code generation style, meaning that it wastes registers like crazy in some (rare) cases.

Most (if not all) of the API functions may cause a panic, but only if things go REALLY wrong. If a function does not state that it can panic or "raise an error" it will only do so if a critical internal assumption proves to be wrong (AKA there is a bug in the code somewhere). These errors will have a special prefix prepended onto the error message stating that this error indicates an internal VM bug. If you ever see such an error I want to know about it ASAP.

That said, if an API function *can* "raise an error" it can and will panic if something goes wrong. This is not a problem inside a native function (as the VM is prepared for this), but if you need to call these functions outside of code to be run by the VM you may want to use Protect or Recover to properly catch these errors.

The VM itself does not provide any Lua functions, the standard library is provided entirely by external packages. This means that the standard library never does anything that your own code cannot do (there is no "private API" that is used by the standard library).

Anything to do with the OS or file IO is not provided. Such things do not belong in the core libraries of an embedded scripting language (do you really want scripts to be able to read and write random files without restriction?).

Example
package main

import (
	"fmt"
	"strings"

	"github.com/milochristiansen/lua"
	"github.com/milochristiansen/lua/lmodbase"
	"github.com/milochristiansen/lua/lmodmath"
	"github.com/milochristiansen/lua/lmodpackage"
	"github.com/milochristiansen/lua/lmodstring"
	"github.com/milochristiansen/lua/lmodtable"
)

func main() {
	l := lua.NewState()

	// This is the easiest way to load a core module.
	// For other modules you probably want to use Preload or Require

	// This sequence is wrapped in a call to Protect to show how that is done, not because you need to.
	// These particular functions *should* be 100% safe to call unprotected. Protect is generally used
	// when you need to do something more complicated and panicking is unacceptable. Code inside native
	// functions does not need to worry about protection, for them it is up to the caller to worry about
	// it. It is very rare to need to explicitly call Recover or Protect.
	err := l.Protect(func() {
		l.Push(lmodbase.Open)
		l.Call(0, 0)

		l.Push(lmodpackage.Open)
		l.Call(0, 0)

		l.Push(lmodstring.Open)
		l.Call(0, 0)

		l.Push(lmodtable.Open)
		l.Call(0, 0)

		l.Push(lmodmath.Open)
		l.Call(0, 0)

		// The following standard modules are not provided for one reason or another:
		//	coroutine: No coroutine support, and if I add support later it will not follow the same rules as standard Lua.
		//	utf8: Haven't gotten around to it yet...
		//	io: IO support is not something you want in an extension language.
		//	os: Even worse than IO, untrusted scripts should not have access to this stuff.
		//	debug: Also not good to expose to untrusted scripts (although there is some stuff here that should be part of the base functions).

		// l.Require("example", loader.Function, false)
		// l.Pop(1)
	})
	if err != nil {
		fmt.Println(err)
		return
	}

	err = l.LoadText(strings.NewReader(`
	print("Hello from github.com/milochristiansen/lua!")
	`), "", 0)
	if err != nil {
		fmt.Println(err)
		return
	}

	err = l.PCall(0, 0)
	if err != nil {
		fmt.Println(err)
	}

}
Output:

Hello from github.com/milochristiansen/lua!

Index

Examples

Constants

View Source
const (

	// These exported values are used in calls to Arith
	OpAdd opCode
	OpSub
	OpMul
	OpMod
	OpPow
	OpDiv
	OpIDiv
	OpBinAND
	OpBinOR
	OpBinXOR
	OpBinShiftL
	OpBinShiftR
	OpUMinus
	OpBinNot

	OpEqual
	OpLessThan
	OpLessOrEqual
)
View Source
const (
	// If you have more than 1000000 items in a single stack frame you probably should think about refactoring...
	RegistryIndex = -1000000 - iota
	GlobalsIndex
	FirstUpVal // To get a specific upvalue use "FirstUpVal-<upvalue index>"
)

Variables

View Source
var TableIndexOffset = 1

Set to 0 for zero based table indexing. This is only partly tested!

Functions

This section is empty.

Types

type NativeFunction

type NativeFunction func(l *State) int

NativeFunction is the prototype to which native API functions must conform.

type STypeID

type STypeID int
const (
	STypNone STypeID = iota
	STypInt
	STypFloat
)

func (STypeID) String

func (typ STypeID) String() string

type State

type State struct {
	// Output should be set to whatever writer you want to use for logging.
	// This is where the standard script functions like print will write their output.
	// If nil defaults to os.Stdout.
	Output io.Writer

	// Add a native stack trace to errors that have attached stack traces.
	NativeTrace bool
	// contains filtered or unexported fields
}

State is the central arbitrator of all Lua operations.

func NewState

func NewState() *State

NewState creates a new State, ready to use.

func (*State) AbsIndex

func (l *State) AbsIndex(i int) int

AbsIndex converts the given index into an absolute index. Use -1 as the index to get the number of items currently on the stack.

func (*State) Arith

func (l *State) Arith(op opCode)

Arith performs the specified the arithmetic operator with the top two items on the stack (or just the top item for OpUMinus and OpBinNot). The result is pushed onto the stack. See "lua_arith" in the Lua 5.3 Reference Manual.

This may raise an error if they values are not appropriate for the given operator.

func (*State) Call

func (l *State) Call(args, rtns int)

Call runs a function with the given number of arguments and results. The function must be on the stack just before the first argument. If this raises an error the stack is NOT unwound! Call this only from code that is below a call to PCall unless you want your State to be permanently trashed!

func (*State) Compare

func (l *State) Compare(i1, i2 int, op opCode) bool

Compare performs the specified the comparison operator with the items at the given stack indexes. See "lua_compare" in the Lua 5.3 Reference Manual.

This may raise an error if they values are not appropriate for the given operator.

func (*State) CompareRaw

func (l *State) CompareRaw(i1, i2 int, op opCode) bool

CompareRaw is exactly like Compare, but without meta-methods.

func (*State) ConvertNumber

func (l *State) ConvertNumber(i int)

ConvertNumber gets the value at the given index and converts it to a number (preferring int over float) and pushes the result. If this is impossible then it pushes nil instead.

func (*State) ConvertString

func (l *State) ConvertString(i int)

ConvertString gets the value at the given index and converts it to a string then pushes the result. This will call a __tostring metamethod if provided. If a metamethod is called the result may or may not be a string.

This is safe if no metamethods are called, but may panic if the metamethod errors out.

func (*State) DebugValue

func (l *State) DebugValue(i int)

DebugValue prints internal information about a script value.

func (*State) DumpFunction

func (l *State) DumpFunction(i int, strip bool) []byte

DumpFunction converts the Lua function at the given index to a binary chunk. The returned value may be used with LoadBinary to get a function equivalent to the dumped function (but without the original function's up values).

Currently the "strip" argument does nothing.

This (obviously) only works with Lua functions, trying to dump a native function or a non-function value will raise an error.

func (*State) Error

func (l *State) Error()

Error pops a value off the top of the stack, converts it to a string, and raises it as a (general runtime) error.

func (*State) ForEach

func (l *State) ForEach(t int, f func() bool)

ForEach is a fancy version of ForEachRaw that respects metamethods (to be specific, __pairs).

The given function is called once for every item in the table at t. For each call of the function the value is at -1 and the key at -2. You MUST keep the stack balanced inside the function! Do not pop the key and value off the stack before returning!

The value returned by the iteration function determines if ForEach should return early. Return false to break, return true to continue to the next iteration.

Little to no error checking is done, as this is a simple convenience wrapper around a common sequence of public API functions (may raise errors).

func (*State) ForEachInTable deprecated

func (l *State) ForEachInTable(t int, f func())

ForEachInTable is a simple alias/wrapper for ForEachRaw.

Deprecated: Don't use for new code! This is here strictly for legacy support!

func (*State) ForEachRaw

func (l *State) ForEachRaw(t int, f func() bool)

ForEachRaw is a simple wrapper around GetIter and is provided as a convenience.

The given function is called once for every item in the table at t. For each call of the function the value is at -1 and the key at -2. You MUST keep the stack balanced inside the function! Do not pop the key and value off the stack before returning!

The value returned by the iteration function determines if ForEach should return early. Return false to break, return true to continue to the next iteration.

Little to no error checking is done, as this is a simple convenience wrapper around a common sequence of public API functions (may raise errors).

func (*State) GetIter

func (l *State) GetIter(i int)

GetIter pushes a table iterator onto the stack.

This value is type "userdata" and has a "__call" meta method. Calling the iterator will push the next key/value pair onto the stack. The key is not required for the next iteration, so unlike Next you must pop both values.

The end of iteration is signaled by returning a single nil value.

If the given value is not a table this will raise an error.

func (*State) GetMetaField

func (l *State) GetMetaField(i int, name string) TypeID

GetMetaField pushes the meta method with the given name for the item at the given index onto the stack, then returns the type of the pushed item. If the item does not have a meta table or does not have the specified method this does nothing and returns TypNil

func (*State) GetMetaTable

func (l *State) GetMetaTable(i int) bool

GetMetaTable gets the meta table for the value at the given index and pushes it onto the stack. If the value does not have a meta table then this returns false and pushes nothing.

func (*State) GetRaw

func (l *State) GetRaw(i int) interface{}

GetRaw gets the raw data for a Lua value. Lua types use the following mapping:

nil -> nil
number -> int64 or float64
string -> string
bool -> bool
table -> string: "table <pointer as hexadecimal>"
function -> string: "function <pointer as hexadecimal>"
userdata -> The raw user data value

func (*State) GetTable

func (l *State) GetTable(i int) TypeID

GetTable reads from the table at the given index, popping the key from the stack and pushing the result. The type of the pushed object is returned. This may raise an error if the value is not a table or is lacking the __index meta method.

func (*State) GetTableRaw

func (l *State) GetTableRaw(i int) TypeID

GetTableRaw is like GetTable except it ignores meta methods. This may raise an error if the value is not a table.

func (*State) Insert

func (l *State) Insert(i int)

Insert takes the item from the TOS and inserts it at the given stack index. Existing items are shifted up as needed, this means that when called with a relative index the item does not end up at the given index, but just *under* that index.

func (*State) IsNil

func (l *State) IsNil(i int) bool

IsNil check if the value at the given index is nil. Nonexistent values are always nil. Negative indexes are relative to TOS, positive indexes are absolute.

func (*State) Length

func (l *State) Length(i int) int

Returns the "length" of the item at the given index, exactly like the "#" operator would. If this calls a meta method it may raise an error if the length is not an integer.

func (*State) LengthRaw

func (l *State) LengthRaw(i int) int

Returns the length of the table or string at the given index. This does not call meta methods. If the value is not a table or string this will raise an error.

func (*State) ListFunc

func (l *State) ListFunc(i int)

ListFunc prints an assembly listing of the given function's code.

If the value is not a script function this will raise an error.

func (*State) LoadBinary

func (l *State) LoadBinary(in io.Reader, name string, env int) error

LoadBinary loads a binary chunk into memory and pushes the result onto the stack. If there is an error it is returned and nothing is pushed. Set env to 0 to use the default environment.

func (*State) LoadText

func (l *State) LoadText(in io.Reader, name string, env int) error

LoadText loads a text chunk into memory and pushes the result onto the stack. If there is an error it is returned and nothing is pushed. Set env to 0 to use the default environment.

This version uses my own compiler. This compiler does not produce code identical to the standard Lua compiler for all syntax constructs, sometimes it is a little worse, rarely a little better.

func (*State) LoadTextExternal

func (l *State) LoadTextExternal(in io.Reader, name string, env int) error

LoadTextExternal loads a text chunk into memory and pushes the result onto the stack. If there is an error it is returned and nothing is pushed. Set env to 0 to use the default environment.

This version looks for and runs "luac" to compile the chunk. Make sure luac is on your path.

This function is not safe for concurrent use.

func (*State) NewTable

func (l *State) NewTable(as, hs int)

NewTable creates a new table with "as" preallocated array elements and "hs" preallocated hash elements.

func (*State) Next

func (l *State) Next(i int)

Next is a basic table iterator.

Pass in the index of a table, Next will pop a key from the stack and push the next key and it's value. This function is not reentrant! Iteration order changes with each iteration, so trying to do two separate iterations of a single table at the same time will result in all kinds of weirdness. If you use this iterator in production code you need your head examined, it is here strictly to power the standard library function `next` (which you also should not use).

If the given value is not a table this will raise an error.

See GetIter.

func (*State) OptFloat

func (l *State) OptFloat(i int, d float64) float64

OptFloat is the same as ToFloat, except the given default is returned if the value is nil or non-existent.

func (*State) OptInt

func (l *State) OptInt(i int, d int64) int64

OptInt is the same as ToInt, except the given default is returned if the value is nil or non-existent.

func (*State) OptString

func (l *State) OptString(i int, d string) string

OptString is the same as ToString, except the given default is returned if the value is nil or non-existent.

func (*State) PCall

func (l *State) PCall(args, rtns int) (err error)

PCall is exactly like Call, except instead of panicking when it encounters an error the error is cleanly recovered and returned.

On error the stack is reset to the way it was before the call minus the function and it's arguments, the State may then be reused.

func (*State) Pop

func (l *State) Pop(n int)

Pop removes the top n items from the stack.

func (*State) Preload

func (l *State) Preload(name string, loader NativeFunction)

Preload adds the given loader function to "package.preload" for use with "require".

func (*State) Print

func (l *State) Print(msg ...interface{})

Print writes to the designated output writer (see fmt.Print).

func (*State) Printf

func (l *State) Printf(format string, msg ...interface{})

Printf writes to the designated output writer (see fmt.Printf).

func (*State) Println

func (l *State) Println(msg ...interface{})

Println writes to the designated output writer (see fmt.Println).

func (*State) Protect

func (l *State) Protect(f func()) (err error)

Protect calls f inside an error handler. Use when you need to use API functions that may "raise errors" outside of other error handlers (such as PCall).

Protect does the same cleanup PCall does, so it is safe to run code with Call inside a Protected function.

func (*State) Push

func (l *State) Push(v interface{})

Push pushes the given value onto the stack. If the value is not one of nil, float32, float64, int, int32, int64, string, bool, or NativeFunction it is converted to a userdata value before being pushed.

func (*State) PushClosure

func (l *State) PushClosure(f NativeFunction, v ...int)

PushClosure pushes a native function as a closure. All native functions always have at least a single upval, _ENV, but this allows you to set more of them if you wish.

func (*State) PushIndex

func (l *State) PushIndex(i int)

PushIndex pushes a copy of the value at the given index onto the stack.

func (*State) Recover

func (l *State) Recover(onStk int, trace bool) func(*error)

Recover is a simple error handler. Use when you need to use API functions that may "raise errors" outside of other error handlers (such as PCall).

Usage of recover is a little hard to explain, so here is a quick example call:

defer l.Recover(0, false)(&err)

Recover is split into two parts so that it can gather stack data before you potentially mess it up (that way it knows how far to go when unwinding). You should not call Recover before you need it, as if there is an error everything that was added to the stack after it is called will be dropped.

onStk is the number of existing items on the stack that you want to have cleaned if there is an error. 99% of the time you will want to set this to 0! Only set to something other than 0 if your are absolutely sure you know what you are doing!

If trace is false generated errors will not have attached stack traces (which is generally what you want when working with native code).

Recover is the error handler and cleanup function powering PCall and Protect. Those functions simply wrap this one for easier use.

func (*State) Require

func (l *State) Require(name string, loader NativeFunction, global bool)

Require calls the given loader (with name as an argument) if there is no entry for "name" in package.loaded. The result from the call is stored in package.loaded, and if global is true, in a global variable named "name". In any case the module value is pushed onto the stack.

It is possible (albeit, unlikely) that this will raise an error. AFAIK the only way for this to happen is if the loader function errors out.

func (*State) Set

func (l *State) Set(d, s int)

Set sets the value at index d to the value at index s (d = s). Trying to set the registry or an invalid index will do nothing. Setting an absolute index will never fail, the stack will be extended as needed. Be careful not to waste stack space or you could run out of memory! This function is mostly for setting up-values and things like that.

func (*State) SetGlobal

func (l *State) SetGlobal(name string)

SetGlobal pops a value from the stack and sets it as the new value of global name.

func (*State) SetMetaTable

func (l *State) SetMetaTable(i int)

SetMetaTable pops a table from the stack and sets it as the meta table of the value at the given index. If the value is not a userdata or table then the meta table is set for ALL values of that type!

If you try to set a metatable that is not a table or try to pass an invalid type this will raise an error.

func (*State) SetTable

func (l *State) SetTable(i int)

SetTable writes to the table at the given index, popping the key and value from the stack. This may raise an error if the value is not a table or is lacking the __newindex meta method. The value must be on TOS, the key TOS-1.

func (*State) SetTableFunctions

func (l *State) SetTableFunctions(i int, funcs map[string]NativeFunction)

SetTableFunctions does a raw set for each function in the provided map, using it's map key as the table key. This a simply a loop around calls to SetTableRaw, provided for convenience.

func (*State) SetTableRaw

func (l *State) SetTableRaw(i int)

SetTableRaw is like SetTable except it ignores meta methods. This may raise an error if the value is not a table.

func (*State) SetUpVal

func (l *State) SetUpVal(f, i, v int) bool

SetUpVal sets upvalue "i" in the function at "f" to the value at "v". If the upvalue index is out of range, "f" is not a function, or the upvalue is not closed, false is returned and nothing is done, else returns true and sets the upvalue.

Any other functions that share this upvalue will also be affected!

func (*State) SubTypeOf

func (l *State) SubTypeOf(i int) STypeID

SubTypeOf returns the sub-type of the value at the given index. Negative indexes are relative to TOS, positive indexes are absolute.

func (*State) Test

func (l *State) Test()

Test prints some stack information for sanity checking during test runs.

func (*State) ToBool

func (l *State) ToBool(i int) bool

ToBool reads a value from the stack at the given index and interprets it as a boolean. Negative indexes are relative to TOS, positive indexes are absolute.

func (*State) ToFloat

func (l *State) ToFloat(i int) float64

ToFloat reads a floating point value from the stack at the given index. Negative indexes are relative to TOS, positive indexes are absolute. If the value is not an float and cannot be converted to one this may panic.

func (*State) ToInt

func (l *State) ToInt(i int) int64

ToInt reads an integer value from the stack at the given index. Negative indexes are relative to TOS, positive indexes are absolute. If the value is not an integer and cannot be converted to one this may panic.

func (*State) ToString

func (l *State) ToString(i int) string

ToString reads a value from the stack at the given index and formats it as a string. Negative indexes are relative to TOS, positive indexes are absolute. This will call a __tostring metamethod if provided.

This is safe if no metamethods are called, but may panic if the metamethod errors out.

func (*State) ToUser

func (l *State) ToUser(i int) interface{}

ToUser reads an userdata value from the stack at the given index. Negative indexes are relative to TOS, positive indexes are absolute. If the value is not an userdata value this may panic.

func (*State) TryFloat

func (l *State) TryFloat(i int) (float64, bool)

TryFloat attempts to read the value at the given index as a floating point number. Negative indexes are relative to TOS, positive indexes are absolute.

func (*State) TryInt

func (l *State) TryInt(i int) (int64, bool)

TryInt attempts to read the value at the given index as a integer number. Negative indexes are relative to TOS, positive indexes are absolute.

func (*State) TypeOf

func (l *State) TypeOf(i int) TypeID

TypeOf returns the type of the value at the given index. Negative indexes are relative to TOS, positive indexes are absolute.

type TypeID

type TypeID int
const (
	TypNil    TypeID = iota
	TypNumber        // Both int and float
	TypString
	TypBool
	TypTable
	TypFunction
	TypUserData
)

func (TypeID) String

func (typ TypeID) String() string

Directories

Path Synopsis
Generic meta-table types and helpers.
Generic meta-table types and helpers.
Helper functions for running scripts snippets in tests.
Helper functions for running scripts snippets in tests.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL