diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e82fdd7..bc44233b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,8 @@ and the dot operator will cause errors in 3.0.
Example: `hilbish.editor.getLine()` should be changed to `hilbish.editor:getLine()`
before 3.0
- Added the `hilbish.editor:read` and `hilbish.editor:log(text)` functions.
+- `yarn` threading library (See the docs)
+
### Changed
- Documentation for Lunacolors has been improved, with more information added.
- Values returned by bait hooks will be passed to the `throw` caller
diff --git a/api.go b/api.go
index ccbbdfae..3d0ba262 100644
--- a/api.go
+++ b/api.go
@@ -62,7 +62,9 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
mod := rt.NewTable()
util.SetExports(rtm, mod, exports)
- hshMod = mod
+ if hshMod == nil {
+ hshMod = mod
+ }
host, _ := os.Hostname()
username := curuser.Username
@@ -129,7 +131,7 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
pluginModule := moduleLoader(rtm)
mod.Set(rt.StringValue("module"), rt.TableValue(pluginModule))
- sinkModule := util.SinkLoader(l)
+ sinkModule := util.SinkLoader(rtm)
mod.Set(rt.StringValue("sink"), rt.TableValue(sinkModule))
return rt.TableValue(mod), nil
diff --git a/cmd/docgen/docgen.go b/cmd/docgen/docgen.go
index 4961a648..202dffab 100644
--- a/cmd/docgen/docgen.go
+++ b/cmd/docgen/docgen.go
@@ -86,6 +86,7 @@ var prefix = map[string]string{
"terminal": "term",
"snail": "snail",
"readline": "rl",
+ "yarn": "yarn",
}
func getTagsAndDocs(docs string) (map[string][]tag, []string) {
diff --git a/docs/api/yarn.md b/docs/api/yarn.md
new file mode 100644
index 00000000..a9602209
--- /dev/null
+++ b/docs/api/yarn.md
@@ -0,0 +1,51 @@
+---
+title: Module yarn
+description: multi threading library
+layout: doc
+menu:
+ docs:
+ parent: "API"
+---
+
+## Introduction
+Yarn is a simple multithreading library. Threads are individual Lua states,
+so they do NOT share the same environment as the code that runs the thread.
+Bait and Commanders are shared though, so you *can* throw hooks from 1 thread to another.
+
+Example:
+
+```lua
+local yarn = require 'yarn'
+
+-- calling t will run the yarn thread.
+local t = yarn.thread(print)
+t 'printing from another lua state!'
+```
+
+## Functions
+|||
+|----|----|
+|thread(fun) -> @Thread|Creates a new, fresh Yarn thread.|
+
+
+
+
+yarn.thread(fun) -> Thread
+
+
+
+
+
+Creates a new, fresh Yarn thread.
+`fun` is the function that will run in the thread.
+
+#### Parameters
+This function has no parameters.
+
+
+## Types
+
+
+## Thread
+
+### Methods
diff --git a/emmyLuaDocs/yarn.lua b/emmyLuaDocs/yarn.lua
new file mode 100644
index 00000000..3fa4f431
--- /dev/null
+++ b/emmyLuaDocs/yarn.lua
@@ -0,0 +1,9 @@
+--- @meta
+
+local yarn = {}
+
+--- Creates a new, fresh Yarn thread.
+--- `fun` is the function that will run in the thread.
+function yarn.thread(fun) end
+
+return yarn
diff --git a/exec.go b/exec.go
index 4ed53a02..63dac576 100644
--- a/exec.go
+++ b/exec.go
@@ -29,12 +29,12 @@ func handleLua(input string) (string, uint8, error) {
chunk, err := l.CompileAndLoadLuaChunk("", []byte(cmdString), rt.TableValue(l.GlobalEnv()))
if err != nil && noexecute {
fmt.Println(err)
- /* if lerr, ok := err.(*lua.ApiError); ok {
- if perr, ok := lerr.Cause.(*parse.Error); ok {
- print(perr.Pos.Line == parse.EOF)
+ /* if lerr, ok := err.(*lua.ApiError); ok {
+ if perr, ok := lerr.Cause.(*parse.Error); ok {
+ print(perr.Pos.Line == parse.EOF)
+ }
}
- }
- */
+ */
return cmdString, 125, err
}
// And if there's no syntax errors and -n isnt provided, run
diff --git a/golibs/yarn/yarn.go b/golibs/yarn/yarn.go
new file mode 100644
index 00000000..9121e70b
--- /dev/null
+++ b/golibs/yarn/yarn.go
@@ -0,0 +1,147 @@
+// multi threading library
+// Yarn is a simple multithreading library. Threads are individual Lua states,
+// so they do NOT share the same environment as the code that runs the thread.
+// Bait and Commanders are shared though, so you *can* throw hooks from 1 thread to another.
+/*
+Example:
+
+```lua
+local yarn = require 'yarn'
+
+-- calling t will run the yarn thread.
+local t = yarn.thread(print)
+t 'printing from another lua state!'
+```
+*/
+package yarn
+
+import (
+ "fmt"
+ "hilbish/util"
+ "os"
+
+ "github.com/arnodel/golua/lib/packagelib"
+ rt "github.com/arnodel/golua/runtime"
+)
+
+var yarnMetaKey = rt.StringValue("hshyarn")
+var globalSpool *Yarn
+
+type Yarn struct {
+ initializer func(*rt.Runtime)
+ Loader packagelib.Loader
+}
+
+// #type
+type Thread struct {
+ rtm *rt.Runtime
+ f rt.Callable
+}
+
+func New(init func(*rt.Runtime)) *Yarn {
+ yrn := &Yarn{
+ initializer: init,
+ }
+ yrn.Loader = packagelib.Loader{
+ Load: yrn.loaderFunc,
+ Name: "yarn",
+ }
+
+ globalSpool = yrn
+
+ return yrn
+}
+
+func (y *Yarn) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
+ yarnMeta := rt.NewTable()
+ yarnMeta.Set(rt.StringValue("__call"), rt.FunctionValue(rt.NewGoFunction(yarnrun, "__call", 1, true)))
+ rtm.SetRegistry(yarnMetaKey, rt.TableValue(yarnMeta))
+
+ exports := map[string]util.LuaExport{
+ "thread": {
+ Function: yarnthread,
+ ArgNum: 1,
+ Variadic: false,
+ },
+ }
+
+ mod := rt.NewTable()
+ util.SetExports(rtm, mod, exports)
+
+ return rt.TableValue(mod), nil
+}
+
+func (y *Yarn) init(th *Thread) {
+ y.initializer(th.rtm)
+}
+
+// thread(fun) -> @Thread
+// Creates a new, fresh Yarn thread.
+// `fun` is the function that will run in the thread.
+func yarnthread(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
+ if err := c.Check1Arg(); err != nil {
+ return nil, err
+ }
+
+ fun, err := c.CallableArg(0)
+ if err != nil {
+ return nil, err
+ }
+
+ yrn := &Thread{
+ rtm: rt.New(os.Stdout),
+ f: fun,
+ }
+ globalSpool.init(yrn)
+
+ return c.PushingNext(t.Runtime, rt.UserDataValue(yarnUserData(t.Runtime, yrn))), nil
+}
+
+func yarnrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
+ if err := c.Check1Arg(); err != nil {
+ return nil, err
+ }
+
+ yrn, err := yarnArg(c, 0)
+ if err != nil {
+ return nil, err
+ }
+
+ yrn.Run(c.Etc())
+
+ return c.Next(), nil
+}
+
+func (y *Thread) Run(args []rt.Value) {
+ go func() {
+ term := rt.NewTerminationWith(y.rtm.MainThread().CurrentCont(), 0, true)
+ err := rt.Call(y.rtm.MainThread(), rt.FunctionValue(y.f), args, term)
+ if err != nil {
+ panic(err)
+ }
+ }()
+}
+
+func yarnArg(c *rt.GoCont, arg int) (*Thread, error) {
+ j, ok := valueToYarn(c.Arg(arg))
+ if !ok {
+ return nil, fmt.Errorf("#%d must be a yarn thread", arg+1)
+ }
+
+ return j, nil
+}
+
+func valueToYarn(val rt.Value) (*Thread, bool) {
+ u, ok := val.TryUserData()
+ if !ok {
+ return nil, false
+ }
+
+ j, ok := u.Value().(*Thread)
+ return j, ok
+}
+
+func yarnUserData(rtm *rt.Runtime, t *Thread) *rt.UserData {
+ yarnMeta := rtm.Registry(yarnMetaKey)
+ return rt.NewUserData(t, yarnMeta.AsTable())
+}
diff --git a/lua.go b/lua.go
index 1c407000..3a51eaaf 100644
--- a/lua.go
+++ b/lua.go
@@ -10,6 +10,7 @@ import (
"hilbish/golibs/fs"
"hilbish/golibs/snail"
"hilbish/golibs/terminal"
+ "hilbish/golibs/yarn"
"hilbish/util"
"github.com/arnodel/golua/lib"
@@ -21,41 +22,11 @@ var minimalconf = `hilbish.prompt '& '`
func luaInit() {
l = rt.New(os.Stdout)
- l.PushContext(rt.RuntimeContextDef{
- MessageHandler: debuglib.Traceback,
- })
- lib.LoadAll(l)
- lib.LoadLibs(l, hilbishLoader)
- // yes this is stupid, i know
- util.DoString(l, "hilbish = require 'hilbish'")
+ loadLibs(l)
- hooks = bait.New(l)
- hooks.SetRecoverer(func(event string, handler *bait.Listener, err interface{}) {
- fmt.Println("Error in `error` hook handler:", err)
- hooks.Off(event, handler)
- })
- lib.LoadLibs(l, hooks.Loader)
-
- // Add Ctrl-C handler
- hooks.On("signal.sigint", func(...interface{}) rt.Value {
- if !interactive {
- os.Exit(0)
- }
- return rt.NilValue
- })
-
- lr.rl.RawInputCallback = func(r []rune) {
- hooks.Emit("hilbish.rawInput", string(r))
- }
-
- lib.LoadLibs(l, fs.Loader)
- lib.LoadLibs(l, terminal.Loader)
- lib.LoadLibs(l, snail.Loader)
-
- cmds = commander.New(l)
- lib.LoadLibs(l, cmds.Loader)
- lib.LoadLibs(l, lr.rl.Loader)
+ yarnPool := yarn.New(yarnloadLibs)
+ lib.LoadLibs(l, yarnPool.Loader)
// Add more paths that Lua can require from
_, err := util.DoString(l, "package.path = package.path .. "+requirePaths)
@@ -74,6 +45,60 @@ func luaInit() {
}
}
+func loadLibs(r *rt.Runtime) {
+ r.PushContext(rt.RuntimeContextDef{
+ MessageHandler: debuglib.Traceback,
+ })
+ lib.LoadAll(r)
+
+ lib.LoadLibs(r, hilbishLoader)
+ // yes this is stupid, i know
+ util.DoString(r, "hilbish = require 'hilbish'")
+
+ hooks = bait.New(r)
+ hooks.SetRecoverer(func(event string, handler *bait.Listener, err interface{}) {
+ fmt.Println("Error in `error` hook handler:", err)
+ hooks.Off(event, handler)
+ })
+ lib.LoadLibs(r, hooks.Loader)
+
+ // Add Ctrl-C handler
+ hooks.On("signal.sigint", func(...interface{}) rt.Value {
+ if !interactive {
+ os.Exit(0)
+ }
+ return rt.NilValue
+ })
+
+ lr.rl.RawInputCallback = func(rn []rune) {
+ hooks.Emit("hilbish.rawInput", string(rn))
+ }
+
+ lib.LoadLibs(r, fs.Loader)
+ lib.LoadLibs(r, terminal.Loader)
+ lib.LoadLibs(r, snail.Loader)
+
+ cmds = commander.New(r)
+ lib.LoadLibs(r, cmds.Loader)
+ lib.LoadLibs(l, lr.rl.Loader)
+}
+
+func yarnloadLibs(r *rt.Runtime) {
+ r.PushContext(rt.RuntimeContextDef{
+ MessageHandler: debuglib.Traceback,
+ })
+ lib.LoadAll(r)
+
+ lib.LoadLibs(r, hilbishLoader)
+ lib.LoadLibs(r, hooks.Loader)
+ lib.LoadLibs(r, fs.Loader)
+ lib.LoadLibs(r, terminal.Loader)
+ lib.LoadLibs(r, snail.Loader)
+ lib.LoadLibs(r, cmds.Loader)
+ lib.LoadLibs(l, lr.rl.Loader)
+
+}
+
func runConfig(confpath string) {
if !interactive {
return