Tip
Suppose you want to create a webserver and make it scriptable via a minimal, fast and go interoperability supporting language - Sophia is here for you and makes it as easy as possible.Installing the runtime
Install sophia as a project dependency:
1$ go get github.com/xnacly/sophia
Initial embedding
The skeleton imports the embed package - this package provides abstractions over configuring, instantiating and starting the sophia runtime.
1package main
2
3import (
4 "os"
5
6 "github.com/xnacly/sophia/embed"
7)
8
9func main() {
10 embed.Embed(embed.Configuration{})
11 file, err := os.Open("config.phia")
12 if err != nil {
13 panic(err)
14 }
15 embed.Execute(file, nil)
16}
Tip
The embed.Configuration
structure allows for a more in depth configuration,
such as enabling the linkage of the go standard library into the sophia
language via embed.Configuration.EnableGoStd
or toggling the debug mode via
embed.Configuration.Debug
.
On the other hand the embed.Execute
function takes *os.File
and
io.Writer
, is the writer nil the runtime prints error message to os.Stdout
,
otherwise the writer is used. The embed.Execute
function also returns an
error if the execution was faulty.
Configuration script
Lets add a structure we want to configure with a sophia script:
1package main
2
3import (
4 "fmt"
5 "os"
6
7 "github.com/xnacly/sophia/embed"
8)
9
10type Configuration struct {
11 Port int
12}
13
14var config = &Configuration{}
15
16func main() {
17 embed.Embed(embed.Configuration{})
18 file, err := os.Open("config.phia")
19 if err != nil {
20 panic(err)
21 }
22 embed.Execute(file, nil)
23}
And write the corresponding sophia script, named config.phia
:
1(let port 8080)
2(set-port port)
Interfacing with go
Sophia supports interoperability with go by allowing the declaration and
injection of functions written in go into its runtime, this can be done by
adding a function of type type.KnownFunctionInterface
to the
embed.Configuration.Functions
map:
1package main
2
3import (
4 "fmt"
5 "os"
6
7 "github.com/xnacly/sophia/core/token"
8 "github.com/xnacly/sophia/core/types"
9 "github.com/xnacly/sophia/embed"
10)
11
12type Configuration struct {
13 Port int
14}
15
16var config = &Configuration{}
17
18func main() {
19 embed.Embed(embed.Configuration{
20 Functions: map[string]types.KnownFunctionInterface{
21 "set-port": func(t *token.Token, n ...types.Node) any {
22 return nil
23 },
24 },
25 })
26 file, err := os.Open("config.phia")
27 if err != nil {
28 panic(err)
29 }
30 embed.Execute(file, nil)
31}
Input validation
We only want exactly one parameter, thus we check for the length and use the
serror
package (short for sophia error) for creating an error with the given
token (the first element after our desired argument length).
1package main
2
3import (
4 "fmt"
5 "os"
6
7 "github.com/xnacly/sophia/core/serror"
8 "github.com/xnacly/sophia/core/token"
9 "github.com/xnacly/sophia/core/types"
10 "github.com/xnacly/sophia/embed"
11)
12
13type Configuration struct {
14 Port int
15}
16
17var config = &Configuration{}
18
19func main() {
20 embed.Embed(embed.Configuration{
21 Functions: map[string]types.KnownFunctionInterface{
22 "set-port": func(t *token.Token, n ...types.Node) any {
23 if len(n) > 1 {
24 serror.Add(n[1].GetToken(), "Too many arguments", "Expected 1 argument for set-port, got %d", len(n))
25 serror.Panic()
26 }
27 return nil
28 },
29 },
30 })
31 file, err := os.Open("config.phia")
32 if err != nil {
33 panic(err)
34 }
35 embed.Execute(file, nil)
36 fmt.Println("port:", config.Port)
37}
If we pass two ports to our set-port
function we will get the following error message:
1$ cat config.phia
2(let port 8080)
3(set-port port port)
4$ go run .
5error: Too many arguments
6
7 at: /home/teo/programming/embedding_sophia/config.phia:3:16:
8
9 1| ;; vim: syntax=lisp
10 2| (let port 8080)
11 3| (set-port port port)
12 | ^^^^
13
14Expected 1 argument for set-port, got 2
Type validation
Lets evaluate the result of the argument passed to our function, cast it to a float64 and assign it to config.Port
:
1package main
2
3import (
4 "fmt"
5 "os"
6
7 "github.com/xnacly/sophia/core/serror"
8 "github.com/xnacly/sophia/core/token"
9 "github.com/xnacly/sophia/core/types"
10 "github.com/xnacly/sophia/embed"
11)
12
13type Configuration struct {
14 Port int
15}
16
17var config = &Configuration{}
18
19func main() {
20 embed.Embed(embed.Configuration{
21 Functions: map[string]types.KnownFunctionInterface{
22 "set-port": func(t *token.Token, n ...types.Node) any {
23 if len(n) > 1 {
24 serror.Add(n[1].GetToken(), "Too many arguments", "Expected 1 argument for set-port, got %d", len(n))
25 serror.Panic()
26 }
27 res := n[0].Eval()
28 port, ok := res.(float64)
29 if !ok {
30 serror.Add(n[0].GetToken(), "Type error", "Expected float64 for port, got %T", res)
31 serror.Panic()
32 }
33
34 config.Port = int(port)
35
36 return nil
37 },
38 },
39 })
40 file, err := os.Open("config.phia")
41 if err != nil {
42 panic(err)
43 }
44 embed.Execute(file, nil)
45 fmt.Println("port:", config.Port)
46}
Again, lets check the error handling:
1$ cat config.phia
2(let port "8080")
3(set-port port)
4$ go run .
5error: Type error
6
7 at: /home/teo/programming/embedding_sophia/config.phia:3:11:
8
9 1| ;; vim: syntax=lisp
10 2| (let port "8080")
11 3| (set-port port)
12 | ^^^^
13
14Expected float64 for port, got string
Resulting embedding of Sophia
Simply running our script with valid inputs according to our previous checks will result in the following output:
1$ cat config.phia
2(let port 8080)
3(set-port port)
4$ go run .
5port: 8080
Thats it, a simple configuration structure and two method calls, can’t really get shorter than that - I am now going to migrate my package manager mehr to be configured with sophia.