Introducing The Embeddable API

One of the goals of Ghost was to make an embeddable scripting language. The ability to embed Ghost in other applications is a powerful tool and means to build and expand on the core offerings of the language to truly create something unique.

We've made the first step towards that realization the other day by introducing the beginnings of the embeddable API. We really want the API to be simple and easy to use, taking cues from Lua and Wren's embeddable APIs. For the time being, the API is limited in what it offers. We can register and execute scripts through Go, and we can extend Ghost with new native functions. Let's take a look at how we can do these two things below.

Embedding and Executing Ghost

package main

import "ghostlang.org/x/ghost/ghost"

func main() {
    script := `print('hello world!')`

    ghost.NewScript(script)
    ghost.Evaluate()
}

With just two lines of code and a single import, we're able to load in Ghost source code and execute it. Couldn't be simpler! But that's not why we'd want to embed Ghost in our application. Not only do we want to execute code, but we want to extend it. So let's look how we can add our own native functions to Ghost next.

Registering Functions

package main

import (
    "fmt"

    "ghostlang.org/x/ghost/ghost"
    "ghostlang.org/x/ghost/object"
)

func main() {
    script := `quote('hello world!')`

    ghost.NewScript(script)

    ghost.RegisterFunction("quote", quoteFunction)

    ghost.Evaluate()
}

func quoteFunction(args ...object.Object) object.Object {
    fmt.Println(`"%s"`, args[0].Inspect())

    return ghost.NULL
}

Here we've introduced ghost.RegisterFunction(), it accepts two parameters: the name of our new function and the actual implementation of it. In our implmentation we'll receive a list of arguments in the form of Ghost objects. These are the arguments passed though when calling our function through our script. Finally, the returned result is another Ghost object. All functions produce a result, so if you don't have a return value you may simply return ghost.NULL.

We'll be getting the documentation up to date with the latest versions of the interpreter, where you will find information on the provided objects and how they can be utilized.

Conclusion

Even with these two "limited" abilities, we have a powerful arsenal at our fingertips. We can embed Ghost in other applications and extend it with new native functions not provided with the language. In future updates we'll be exploring the ability to call and pass values to and from our Ghost instance. This will be especially useful in cases such as game engines, where we need to call a specific function at key moments in our application (e.g. calling a draw function inside our game loop).

Stay tuned for more posts and be sure to keep an eye on GitHub for development updates ✌️