Giter VIP home page Giter VIP logo

golua's Introduction

Build Status Go Report Card Coverage

GoLua

Implementation of Lua 5.4 in Go with no third party dependencies. The compiler and runtime are complete (including coroutines), the standard Lua library is mostly implemented.

Quick start: running golua

To install, run:

$ go get github.com/arnodel/golua

To run interactively (in a repl):

$ golua
> function fac(n)
|   if n == 0 then
|     return 1
|   else
|     return n * fac(n - 1)
|   end
| end
> -- For convenience the repl also evaluates expressions
> -- and prints their value
> fac(10)
3628800
> for i = 0, 5 do
|   print(i, fac(i))
| end
0	1
1	1
2	2
3	6
4	24
5	120
>

Safe execution environment (alpha)

A unique feature of Golua is that you can run code in a safe execution environment where cpu and memory are restricted. E.g.

$ golua -cpulimit=10000000
> while true do end
!!! CPU limit of 10000000 exceeded
Reset limits and continue? [yN] 

You can even do this within Lua itself:

$ golua
> a = "a"
> runtime.callcontext({kill={memory=1000000}}, function() while true do a = a..a end end)
killed
> #a
262144

For more details read more here.

Importing and using Go packages

You can dynamically import Go packages very easily as long as they are already downloaded - this include the standard library. Here is an example of running an http server in the repl:

$ golua
> go = require('golib')
> http = go.import('net/http')
> http.HandleFunc('/hello/', function(w, r)
|   w.Write('hi there from Lua! You requested ' .. r.URL.Path)
| end)
> http.ListenAndServe(':7777')

In another terminal you can do:

$ curl http://localhost:7777/hello/golua
hi there from Lua! You requested /hello/golua

To run a lua file:

$ golua myfile.lua

Or

cat myfile.lua | golua

E.g. if the file myfile.lua contains:

local function counter(start, step)
    return function()
        local val = start
        start = start + step
        return val
    end
end

local nxt = counter(5, 3)
print(nxt(), nxt(), nxt(), nxt())

Then:

$ golua myfile.lua
5	8	11	14

Errors produce useful tracebacks, e.g. if the file err.lua contains:

function foo(x)
    print(x)
    error("do not do this")
end

function bar(x)
    print(x)
    foo(x*x)
end

bar(2)

Then:

$ golua err.lua
2
4
!!! error: do not do this
in function foo (file err.lua:3)
in function bar (file err.lua:8)
in function <main chunk> (file err.lua:11)

Quick start: embedding golua

It's very easy to embed the golua compiler / runtime in a Go program. The example below compiles a lua function, runs it and displays the result.

	// First we obtain a new Lua runtime which outputs to stdout
	r := rt.New(os.Stdout)

	// This is the chunk we want to run.  It returns an adding function.
	source := []byte(`return function(x, y) return x + y end`)

	// Compile the chunk. Note that compiling doesn't require a runtime.
	chunk, _ := r.CompileAndLoadLuaChunk("test", source, rt.TableValue(r.GlobalEnv()))

	// Run the chunk in the runtime's main thread.  Its output is the Lua adding
	// function.
	add, _ := rt.Call1(r.MainThread(), rt.FunctionValue(chunk))

	// Now, run the Lua function in the main thread.
	sum, _ := rt.Call1(r.MainThread(), add, rt.IntValue(40), rt.IntValue(2))

	// --> 42
	fmt.Println(sum.AsInt())

Quick start: extending golua

It's also very easy to add write Go functions that can be called from Lua code. The example below shows how to.

This is the Go function that we are going to call from Lua. Its inputs are:

  • t: the thread the function is running in.
  • c: the go continuation that represents the context the function is called in. It contains the arguments to the function and the next continuation (the one which receives the values computed by this function).

It returns the next continuation on success, else an error.

func addints(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
	var x, y rt.Int

	// First check there are two arguments
	err := c.CheckNArgs(2)
	if err == nil {
		// Ok then try to convert the first argument to a lua integer (rt.Int).
		x, err = c.IntArg(0)
	}
	if err == nil {
		// Ok then try to convert the first argument to a lua integer (rt.Int).
		y, err = c.IntArg(1)
	}
	if err != nil {
		// Some error occured, we return it in our context
		return nil, err
	}
	// Arguments parsed!  First get the next continuation.
	next := c.Next()

	// Then compute the result and push it to the continuation.
	t.Push1(next, x + y)

	// Finally return the next continuation.
	return next, nil

	// Note: the last 3 steps could have been written as:
	// return c.PushingNext(x + y), nil
}

The code sample below shows how this function can be added to the Lua runtime environment and demonstrates calling it from Lua.

	// First we obtain a new Lua runtime which outputs to stdout
	r := rt.New(os.Stdout)

	// Load the basic library into the runtime (we need print)
	base.Load(r)

	// Then we add our addints function to the global environment of the
	// runtime.
	r.SetEnvGoFunc(r.GlobalEnv(), "addints", addints, 2, false)

	// This is the chunk we want to run.  It calls the addints function.
	source := []byte(`print("hello", addints(40, 2))`)

	// Compile the chunk.
	chunk, _ := rt.CompileAndLoadLuaChunk("test", source, r.GlobalEnv())

	// Run the chunk in the runtime's main thread.  It should output 42!
	_, _ = rt.Call1(r.MainThread(), chunk)

You can also make custom libraries and use Go values in Lua (using e.g. the runtime.UserData type). There is an example implementing a regex Lua package that uses Go regexp.Regexp in examples/userdata

Aim

To implememt the Lua programming language in Go, easily embeddable in Go applications. It should be able to run any pure Lua code

Design constraints

  • clean room implementation: do not look at existing implementations
  • self contained: no dependencies
  • small: avoid re-implementing features which are already present in the Go language or in the standard library (e.g. garbage collection)
  • register based VM
  • no call stack (continuation passing), tail call optimisation.

Components

Lexer / Parser

  • The lexer is implemented in the package scanner.
  • The parser is hand-written and implemented in the parsing package.

AST → IR Compilation

The ast package defines all the AST nodes. The astcomp package defines a Compiler type that is able to compile an AST to IR, using an instance of ir.CodeBuilder.

The ir package defines all the IR instructions and the IR compiler.

IR → Code Compilation

The runtime bytecode is defined in the code package. The ircomp package defines a ConstantCompiler type that is able to compile IR code to runtime bytecode, using an instance of code.Builder.

Runtime

The runtime is implemented in the runtime package. This defines a Runtime type which contains the global state of a runtime, a Thread type which can run a continuation, can yield and can be resumed, the various runtime data types (e.g. String, Int...). The bytecode interpreter is implemented in the RunInThread method of the LuaCont data type.

Test Suite

There is a framework for running lua tests in the package luatesting. In the various Go packages, if there is a lua directory, each .lua file is a test. Expected output is specified in the file as comments of a special form, starting with -->:

print(1 + 2)
--> =3
-- "=" means match literally the output line

print("ababab")
--> ~^(ab)*$
-- "~" means match with a regexp (syntax is go regexp)

Most of the code is covered with such Lua tests. Specific packages or functions are covered with Go tests.

The "official" Lua 5.4.3 Test Suite

Lua provides a test suites for each version (https://www.lua.org/tests/). There is an adapted version of the 5.4.3 tests here which is supposed to be passed by the latest version of Golua. It is the form of a PR so that the difference with the original test suite can be seen easily.

Assuming golua is installed on your system, those tests can be run from the root of the repository above as follows.

golua -u -e "_U=true" all.lua

For the moment db.lua is disabled (the file testing the debug module). All other "soft" tests are run, some with adaptations. The most significant differences are in error messages.

Standard Library

The lib directory contains a number of package, each implementing a lua library.

  • base: basic library. It is complete.
  • coroutine: the coroutine library, which is done.
  • packagelib: the package library. It is able to load lua modules but not "native" modules, which would be written in Go. Obviously this is not part of the official Lua specification. Perhaps using the plugin mechanism (https://golang.org/pkg/plugin/) would be a way of doing it. I have no plan to support Lua C modules!
  • stringlib: the string library. It is complete.
  • mathlib: the math library, It is complete.
  • tablelib: the table library. It is complete.
  • iolib: the io library. It is complete.
  • utf8lib: the utf8 library. It is complete.
  • debug: partially implemented (mainly to pass the lua test suite). The getupvalue, setupvalue, upvalueid, upvaluejoin, setmetatable, functions are implemented fully. The getinfo function is partially implemented. The traceback function is implemented but its output is different from the C Lua implementation. The sethook and gethook values are implemented - line hooks may not be as accurate as for C Lua.
  • os package is almost complete - exit doesn't support "closing" the Lua state (need to figure out what it means.)

golua's People

Contributors

arnodel avatar thecount avatar torchedsammy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

golua's Issues

high memory usage on loop

i'm not sure how to title this or to properly report this.

n = 0; while n < 10000000000 do n = n + 1 end
running this sample of code in hilbish (which in reality just uses golua) and I get this:



for some reason, this doesn't happen in golua-repl

print traceback by default

why don't errors print the traceback? anytime i get a lua error, it's only half useful since i don't know where it comes from

Running the Test Suite

This is nice project!

I wanted to test running the test suite to see all is OK and then also to test with the latest 5.4.6. However, I'm not sure how to do that. Documentation say to run golua -u -e "_U=true" all.lua but there is no all.lua file.

Benchmarks

would be great to see how this compares with other implementations (shopify's go-lua and gopher-lua) even if they're different lua versions

runtime.Call keeps running continuations

The doc for runtime.Call says:

func Call(t *Thread, f Value, args []Value, next Cont) error
Call calls f with arguments args, pushing the results on next. It may use the metamethod '__call' if f is not callable.

Actually, Call not only pushes the results of the call to f onto next, but proceeds running next and further continuations until nil or an error occurs.

I'm guessing this is the intended behavior, but I think that with this documentation and/or the name Call, this API is confusing, especially if seen in contrast to runtime.Call1, which has a more "expected" signature:

func Call1(t *Thread, f Value, args ...Value) (Value, error)

One possibility to make the API more intuitive would be to rename Call to something else (e. g. TailCall) and add a new Call API

func Call(t *Thread, f Value, args []Value, ret []Value) error

whose implementation would call the "old" Call with a termination for next.

What do you think?

panic: Cannot compile r2 := Op(257)(r1, r4) on go 1.18

hi, seems that using golua with go 1.18.1 causes a panic about an invalid op on valid lua code.
this doesn't happen with go 1.17.9. for reference, im trying to run this lua file

discovered this after some people using hilbish (a shell i develop) reported it. that issue includes a full stacktrace.

while i do have a fork, there's minimal changes (see at Rosettea/golua) and it still happens on normal golua

stopping a function from running

is there any way to stop the execution of a function without stopping the runtime? from what i've seen, this would be done with a thread? but i'm not sure how.

the use case would be that if i run a function that may get stuck somewhere, i can just stop it from running and continue with something else.

Embedding quickstart example does not compile

The embedding example from the README does not compile. When I try to embed that code snippet in a program like this:

package main

import (
	"fmt"
	"os"

	rt "github.com/arnodel/golua/runtime"
)

func main() {
	// First we obtain a new Lua runtime which outputs to stdout
	r := rt.New(os.Stdout)

	// This is the chunk we want to run.  It returns an adding function.
	source := []byte(`return function(x, y) return x + y end`)

	// Compile the chunk. Note that compiling doesn't require a runtime.
	chunk, _ := rt.CompileAndLoadLuaChunk("test", source, r.GlobalEnv())

	// Run the chunk in the runtime's main thread.  Its output is the Lua adding
	// function.
	f, _ := rt.Call1(r.MainThread(), chunk)

	// Now, run the Lua function in the main thread.
	sum, _ := rt.Call1(r.MainThread(), f, rt.Int(40), rt.Int(2))

	// --> 42
	fmt.Println(sum)
}

I get compiler errors:

./embed.go:18:17: undefined: runtime.CompileAndLoadLuaChunk
./embed.go:25:43: undefined: runtime.Int

With the "obvious" fixes it works again:

package main

import (
	"fmt"
	"os"

	rt "github.com/arnodel/golua/runtime"
)

func main() {
	// First we obtain a new Lua runtime which outputs to stdout
	r := rt.New(os.Stdout)

	// This is the chunk we want to run.  It returns an adding function.
	source := []byte(`return function(x, y) return x + y end`)

	// Compile the chunk. Note that compiling requires a runtime.
	chunk, _ := r.CompileAndLoadLuaChunk(
		"test", source, rt.TableValue(r.GlobalEnv()))

	// Run the chunk in the runtime's main thread.  Its output is the Lua adding
	// function.
	f, _ := rt.Call1(r.MainThread(), rt.FunctionValue(chunk))

	// Now, run the Lua function in the main thread.
	sum, _ := rt.Call1(r.MainThread(), f, rt.IntValue(40), rt.IntValue(2))

	// --> 42
	fmt.Println(sum.AsInt())
}

correctly prints 42.

Therefore I'd like to suggest to update the README accordingly.

Quotas in the repl seems to be broken

Here are two ways to reproduce what I suspect is the same bug about the loop:

  $ golua -cpulimit=10000

> while true do end
!!! CPU limit of 400 exceeded
Reset limits and continue? [yN] y
> while true do end
... [hangs]
  • Put a fmt.Fprintf(os.Stdout, "%s -- %s\n", r.HardLimits(), r.UsedResources()) line above the following line in cmd.go.

golua/cmd.go

Line 212 in 36bacc6

if !more {

  • go install github.com/arnodel/golua

  • then

  $ golua -cpulimit=200

> 1
1
{%!s(uint64=200) %!s(uint64=0) %!s(uint64=0)} -- {%!s(uint64=192) %!s(uint64=0) %!s(uint64=0)}
> 2
{%!s(uint64=200) %!s(uint64=0) %!s(uint64=0)} -- {%!s(uint64=199) %!s(uint64=0) %!s(uint64=0)}
!!! <stdin>:1:1: unexpected symbol near '2'
> 2
{%!s(uint64=200) %!s(uint64=0) %!s(uint64=0)} -- {%!s(uint64=223) %!s(uint64=0) %!s(uint64=0)}
> 2
2
{%!s(uint64=200) %!s(uint64=0) %!s(uint64=0)} -- {%!s(uint64=235) %!s(uint64=0) %!s(uint64=0)}
> 2
2
{%!s(uint64=200) %!s(uint64=0) %!s(uint64=0)} -- {%!s(uint64=247) %!s(uint64=0) %!s(uint64=0)}
> 2
2
{%!s(uint64=200) %!s(uint64=0) %!s(uint64=0)} -- {%!s(uint64=259) %!s(uint64=0) %!s(uint64=0)}
> 2
2
{%!s(uint64=200) %!s(uint64=0) %!s(uint64=0)} -- {%!s(uint64=271) %!s(uint64=0) %!s(uint64=0)}
> 2
2
{%!s(uint64=200) %!s(uint64=0) %!s(uint64=0)} -- {%!s(uint64=283) %!s(uint64=0) %!s(uint64=0)}
> 2
2
{%!s(uint64=200) %!s(uint64=0) %!s(uint64=0)} -- {%!s(uint64=295) %!s(uint64=0) %!s(uint64=0)}
...

Spurious nil table keys during iteration

When iterating over a runtime.Table using the Next() method, I occasionally get a nil key return value with ok == true.

I can try putting together a minimal example of the bug, however, looking at the source code, I see this:

return NilValue, NilValue, true

This looks like it should be return NilValue, NilValue, false instead in the first if. Could this already be the fix?

os.date requires format arg

when trying to run some code in golua i noticed that os.date requires the format to be added where as in lua it is optional

requiring luarocks modules

I am trying to install and use luarocks modules (e.g cjson) - I'm failing lol... is there a way to do this?

I created a function to install modules from my Go app and I want to install them locally (working directory) so not to pollute the OS.

I have

// Function to install Lua modules using luarocks
func installLuaModule(module string) error {
	// Get the current working directory
	cwd, err := os.Getwd()
	if err != nil {
		return fmt.Errorf("failed to get current working directory: %w", err)
	}

	// Set LUA_PATH and LUA_CPATH to include the current working directory
	luarocksArgs := []string{"--tree=" + cwd, "install", module}

	// Run the luarocks command to install the module
	cmd := exec.Command("luarocks", luarocksArgs...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run(); err != nil {
		return fmt.Errorf("failed to install Lua module: %w", err)
	}

	return nil
}

Now i want to execute a script that require's for instance cjson:

func executeLuaScript(filePath string) error {
	os.Setenv("GOVERSION", runtime.Version())
	os.Setenv("GOOS", runtime.GOOS)
	os.Setenv("GOARCH", runtime.GOARCH)
	cwd, err := os.Getwd()
	if err != nil {
		return fmt.Errorf("failed to get current working directory: %w", err)
	}
	luaPath := filepath.Join(cwd, "share", "lua", "5.4", "?.lua") + ";" + filepath.Join(cwd, "share", "lua", "5.4", "?", "init.lua")
	luaCPath := filepath.Join(cwd, "lib", "lua", "5.4", "?.so")

	//// Read the Lua script from the file
	script, err := os.ReadFile(filePath)
	if err != nil {
		return fmt.Errorf("failed to read Lua script: %w", err)
	}
	r := rt.New(os.Stdout) // Create runtime

	rawPackage := r.GlobalEnv().Get(rt.StringValue("package"))
	var pkgTable *rt.Table
	if rawPackage.Type() == rt.TableType {
		pkgTable = rawPackage.AsTable()
	} else {
		pkgTable = rt.NewTable()
		r.GlobalEnv().Set(rt.StringValue("package"), rt.TableValue(pkgTable))
	}

	// Set path and cpath in the package table
	pkgTable.Set(rt.StringValue("path"), rt.StringValue(luaPath))
	pkgTable.Set(rt.StringValue("cpath"), rt.StringValue(luaCPath))

	lib.LoadLibs(
		r,
		base.LibLoader,       // Load base lib (needed for print)
		packagelib.LibLoader, // Load package lib (needed for require)
		regexlib.LibLoader,   // Load our example lib
	)

	// Now compile and run the lua code
	chunk, err := r.CompileAndLoadLuaChunk("test", []byte(script), rt.TableValue(r.GlobalEnv()))
	if err != nil {
		fmt.Printf("error %s\n", err)
	}
	_, err = rt.Call1(r.MainThread(), rt.FunctionValue(chunk))
	if err != nil {
		fmt.Printf("error %s\n", err)
	}
	return nil
}

But I keep getting error error: test:1: could not find package 'cjson'

I tried a simpler script

print("Lua Version:", _VERSION)
print("System Information:")
print("Go Version:", os.getenv("GOVERSION"))
print("GOOS:", os.getenv("GOOS"))
print("GOARCH:", os.getenv("GOARCH"))
print("Luarocks Config:")
os.execute("luarocks config")

but now I get


Lua Version:    Golua 5.4
System Information:
error error: test:3: attempt to index a nil value

what am I doing wrong to get erorrs in both instances?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.