Last active
January 13, 2017 07:26
A lua repl in go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"encoding/json" | |
"fmt" | |
"log" | |
"os" | |
"regexp" | |
"strings" | |
"time" | |
"github.com/aarzilli/golua/lua" | |
) | |
type Message struct { | |
Command string `json:"command"` | |
Data string `json:"data"` | |
Error string `json:"error"` | |
} | |
func newMessage(cmd, data, error string) *Message { | |
return &Message{cmd, data, error} | |
} | |
// TODO: io.stdout, io.stderr are currently raw (print is redefined) | |
func main() { | |
encoder := json.NewEncoder(os.Stdout) | |
decoder := json.NewDecoder(os.Stdin) | |
var L *lua.State | |
defer L.Close() | |
init := func() { | |
L = lua.NewState() | |
L.OpenLibs() | |
// Golua renames those globals unsafe because "they can't call back | |
// to go" but we don't care about that. This works | |
L.GetGlobal("unsafe_pcall") | |
L.SetGlobal("pcall") | |
L.GetGlobal("unsafe_xpcall") | |
L.SetGlobal("xpcall") | |
print := func(L *lua.State) int { | |
nargs := L.GetTop() | |
formatted := make([]string, nargs) | |
for i := 0; i < nargs; i++ { | |
formatted = append([]string{format(L)}, formatted...) | |
} | |
encoder.Encode(newMessage("output", strings.Join(formatted, "\t")+"\n", "")) | |
return 0 | |
} | |
L.Register("print", print) | |
} | |
init() | |
// Wait a bit to make sure the caller is reading our output | |
time.Sleep(200 * time.Millisecond) | |
encoder.Encode(newMessage("ready", "", "")) | |
for { | |
var msg Message | |
if err := decoder.Decode(&msg); err != nil { | |
log.Fatal("error while decoding message: ", err) | |
} | |
if msg.Command == "reset" { | |
L.Close() | |
init() | |
encoder.Encode(newMessage("ready", "", "")) | |
continue | |
} | |
if msg.Command != "eval" { | |
log.Fatal("unknown command ", msg.Command) | |
} | |
shouldReturn := regexp.MustCompile(`^=[^=]`) | |
code := msg.Data | |
if shouldReturn.MatchString(code) { | |
code = "return " + code[1:] | |
} else { | |
// Try to make it into a returnable expression | |
expr := "return " + code | |
if r := L.LoadString(expr); r == 0 { | |
code = expr | |
} | |
L.SetTop(-2) | |
} | |
if err := L.DoString(code); err != nil { | |
encoder.Encode(newMessage("result", "", err.Error())) | |
continue | |
} | |
ret := "" | |
if L.GetTop() > 0 { | |
ret = format(L) | |
} | |
encoder.Encode(newMessage("result", ret, "")) | |
} | |
} | |
func format(L *lua.State) string { | |
ret := "" | |
t := L.Type(-1) | |
switch t { | |
case -1: | |
fallthrough | |
case 0: | |
ret = "nil" | |
case 1: | |
r := L.ToBoolean(-1) | |
if r { | |
ret = "true" | |
} else { | |
ret = "false" | |
} | |
case 3: | |
ret = fmt.Sprintf("%g", L.ToNumber(-1)) | |
case 4: | |
ret = L.ToString(-1) | |
default: | |
name := L.Typename(int(t)) | |
address := L.ToPointer(-1) | |
hexAddress := fmt.Sprintf("%x", address) | |
ret = name + ": 0x" + hexAddress | |
} | |
L.SetTop(-2) | |
return ret | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment