Embedding
Ghost was not only designed to be a standalone general-purpose programming language, but also to be a scripting language that can be embedded into other applications.
Installing Ghost
To get started, simply go get the latest version of Ghost. Make sure your project has already been initialized with go mod init.
go get ghostlang.org/x/ghost
Creating an Interpreter
The first step to embedding Ghost is to create a new interpreter instance. This is what will be used to execute Ghost code.
vm := ghost.New()
Each instance carries its own scope, so variables defined by one script are visible to the next script the same instance runs, and invisible to any other instance.
From here we need to configure and set a couple of things before we can execute any Ghost code.
Setting the Root Directory
The root directory is the directory that Ghost will use to resolve relative imports from your code. For example, if you have a file called foo.ghost in the root directory, you can import it like this anywhere in your code:
import Foo from 'foo'
To set the root directory, simply call the SetDirectory method on the Ghost VM.
vm.SetDirectory("/path/to/root/directory")
If you are embedding Ghost into an application, you can use the os.Executable function to get the path to the executable file, and then use the filepath.Dir function to get the directory that the executable is in.
executable, err := os.Executable()
if err != nil {
panic(err)
}
vm.SetDirectory(filepath.Dir(executable))
Setting the Source Code
The next step is to set the source code that you want to execute. This can be done by calling the SetSource method on the Ghost VM.
vm.SetSource(`print('Hello, universe!')`)
Setting the File Name
SetFile names the file the source came from. It has no effect on execution — it is what error messages report, so setting it turns 1:7: runtime error into 1:7:game.ghost: runtime error.
vm.SetFile("game.ghost")
Executing Ghost Code
Once you have set the source code, you can execute it by calling the Execute method on the Ghost VM. This will return a Ghost object that you can use to get the result of the execution.
result := vm.Execute()
The result will be a Ghost object. If the execution was successful, the result will be the value of the last expression in the source code. If the execution failed, the result will be an error object.
// Check if the result is an error
if _, ok := result.(*object.Error); ok {
// Handle the error
os.Exit(1)
}
Execute can be called more than once on the same instance. Set a new source and call it again, and the second script picks up where the first left off.
Calling Back Into Ghost
Once a script has run, the functions it defined are still there. Call invokes one by name with a list of Ghost objects as arguments, and hands back the result:
vm.SetSource(`function update(dt) { return dt * 2 }`)
vm.Execute()
result := vm.Call("update", []object.Object{object.NewFloat(0.016)})
This is how a host program drives a script — a game loop calling update and draw every frame, for example. Calling a name the script never defined returns an error object rather than panicking.
Extending Ghost From Go
Your program can add its own functions and modules to the language before running any code. Both registration functions live on the ghost package rather than on an instance, so what you register is available to every instance.
// A single global function, callable as `greet("world")`
ghost.RegisterFunction("greet", func(scope *object.Scope, tok token.Token, args ...object.Object) object.Object {
return &object.String{Value: "Hello, " + args[0].String()}
})
// A module of methods and properties, callable as `example.ping()`
ghost.RegisterModule("example", ExampleMethods, ExampleProperties)
A module's methods and properties are map[string]*object.LibraryFunction and map[string]*object.LibraryProperty, built with the RegisterMethod and RegisterProperty helpers in ghostlang.org/x/ghost/library/modules. This is exactly how Ghost's own standard library is written, and how Lumen adds canvas, image, audio, and the rest — the standard library's source is the best reference to work from.
Register everything before calling Execute: Ghost's optimizer resolves module and function names ahead of evaluation, so a name registered afterwards will not be found.
A Complete Example
Below is a complete example of what we have covered so far. It's a simple program that creates a Ghost VM, loads a script, and executes it.
package main
import (
"os"
"path/filepath"
"ghostlang.org/x/ghost/ghost"
"ghostlang.org/x/ghost/object"
)
func main() {
// Create a new Ghost VM
vm := ghost.New()
// Set the root directory
// Ghost will use this to resolve imports from your code
executable, err := os.Executable()
if err != nil {
panic(err)
}
vm.SetDirectory(filepath.Dir(executable))
// Set the source code to execute, and the name errors should report
vm.SetSource(`print('Hello, universe!')`)
vm.SetFile("main.ghost")
// Execute the source code
// The result will be a ghost object
result := vm.Execute()
// Check if the result is an error
if _, ok := result.(*object.Error); ok {
os.Exit(1)
}
}