Skip to content
This repository has been archived by the owner on Jul 18, 2021. It is now read-only.

Terisback/wrengo

Repository files navigation

Wren
go.dev github actions build

Wrengo is binding for Wren scripting language.

The package has not been updated for a long time, based on 0.2.0 Wren, and not compiles on Go version bigger than 1.14
If anyone really needs updated package, open issue ^_^

⚡️ Quickstart

package main

import (
    "fmt"

    "github.com/Terisback/wrengo"
)

func main() {
    config := wrengo.NewConfiguration()
    config.WriteFunc = wrengo.CallbackWrite
    vm := wrengo.NewVM(config)
    defer vm.FreeVM()

    vm.Interpret("main", `System.print("Hello world!")`)
}

⚙️ Installation

First of all, download and install Go. The project is written within 1.14, but you can try your luck.

Installation is done using the go get command:

go get -u github.com/Terisback/wrengo

Also you need installed gcc:

On Windows you need Mingw-w64 or TDM-GCC. You can also build binary in MSYS2 shell.

On MacOS you need Xcode or Command Line Tools for Xcode.

🎯 Features

  • Wren is small. The VM implementation is under 4,000 semicolons. You can skim the whole thing in an afternoon. It’s small, but not dense. It is readable and lovingly-commented.
  • Wren is fast. A fast single-pass compiler to tight bytecode, and a compact object representation help Wren compete with other dynamic languages.
  • Wren is class-based. There are lots of scripting languages out there, but many have unusual or non-existent object models. Wren places classes front and center.
  • Wren is concurrent. Lightweight fibers are core to the execution model and let you organize your program into an army of communicating coroutines.

🏇🏻 Benchmark

fib(35) fibt(35) Type
Go 96ms 25ms Go (native)
Wrengo 34ms 29ms Wren VM
Tengo 34ms 30ms VM on Go
Lua 2ms 25ms Lua (native)
go-lua 7ms 26ms Lua VM on Go
GopherLua 7ms 29ms Lua VM on Go
Python 5ms 58ms Python (native)
starlark-go 16ms 26ms Python-like Interpreter on Go
gpython 49ms 42ms Python Interpreter on Go
goja 8ms 28ms JS VM on Go
otto 131ms 35ms JS Interpreter on Go
Anko 126ms 27ms Interpreter on Go

* fib(35): Fibonacci(35)
* fibt(35): tail-call version of Fibonacci(35)
* Go does not read the source code from file, while all other cases do
* Results were rounded up
* Tested on my Microsoft Sufrace 6 Pro
* See here for commands/codes used

👨‍🦽 Compromises

Foreign Function Limits Due to Go's inability to generate C-exported functions at runtime, the number of foreign methods able to be registered with the Wren VM through this package is limited to 256 for functions and 256 for classes. This number is completely arbitrary, though, and can be changed by modifying the directive at the bottom of wrengo.go and running "go generate". If you feel like this number is a terrible default, pull requests will be happily accepted.
Cast types when receiving foreign class. I think there is a more “right” way to do this. Feel free to pull request.

👨🏻‍💻 Examples

Simplified examples is listed below. (Without error checking and import)

Full examples are stored in the cmd folder

Interpret

func main() {
    // New configuration for VM
    config := wrengo.NewConfiguration()

    // Adding callbacks
    config.WriteFunc = wrengo.CallbackWrite
    config.ErrorFunc = wrengo.CallbackError

    // Creating new VM
    vm := wrengo.NewVM(config)
    defer vm.FreeVM()

    // New console read buffer
    reader := bufio.NewReader(os.Stdin)

    fmt.Println("Ready! Press Ctrl+C for exit.")

    // Interpret loop
    for {
        fmt.Print("> ")
        text, _ := reader.ReadString('\n')
        vm.Interpret("main", text+"\n")
    }
}

Handles

func main() {
    program := `
        class WrenMath {
            static do_add(a, b) {
                return a + b
            }
        }
    `

    config := wrengo.NewConfiguration()
    config.WriteFunc = wrengo.CallbackWrite
    config.ErrorFunc = wrengo.CallbackError
    vm := wrengo.NewVM(config)
    defer vm.FreeVM()

    vm.Interpret(wrengo.DefaultModule, program)

    // Make sure enough slots are allocated
    vm.EnsureSlots(3)

    // Getting class to slot 0
    vm.GetVariable(wrengo.DefaultModule, "WrenMath", 0)

    // Creating handle to do_add function
    h := vm.NewCallHandle("do_add(_,_)")

    // Setting call arguments
    vm.SetSlotDouble(1, 9)
    vm.SetSlotDouble(2, 3)

    // Calling
    h.Call()

    // Print the result
    fmt.Println(vm.GetSlotDouble(0))
}

Foreign

type God struct { msg string }

// The constructor for a foreign type takes no arguments and returns
// an interface{} value representing the new object.
func NewGod() interface{} {
    return &God{msg: "What are you doing? %s"}
}

// Function to bind into Wren
func GetGodsMessage(vm *wrengo.VM) {
    // Getting foreign class
    god := vm.GetSlotForeign(0, God{}).(God)

    // Getting argument
    name := vm.GetSlotString(1)

    // Return result
    vm.SetSlotString(0, fmt.Sprintf(god.msg, name))
}

func main() {
    // Wren code
    program := `
        foreign class God {
            construct new() {}
            foreign getMessage(name)
		}

        var god = God.new()
        System.print(god.getMessage("Silly boy"))
	`

    config := wrengo.NewConfiguration()
    config.WriteFunc = wrengo.CallbackWrite
    config.ErrorFunc = wrengo.CallbackError
    vm := wrengo.NewVM(config)
    defer vm.FreeVM()

    // Bind God class
    vm.BindForeignClass("God", NewGod)

    // Bind God.getMessage()
    vm.BindForeignMethod("God", false, "getMessage(_)", GetGodsMessage)

    vm.Interpret(wrengo.DefaultModule, program)
}

📚 Similar/nearby projects

  • go-wren - it's based on nearly 0.1.0 Wren sources, when Wrengo based on 0.2.0 Wren release. It's doing same work as this package, but working with older version of Wren and it has more simplifications.
  • tengo - Tengo is fast and secure because it's compiled/executed as bytecode on stack-based VM that's written in native Go.
  • go-lua - Port of the Lua 5.2 VM to pure Go.

More in awesome-go repository.