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/sophiaInitial 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 2Type 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 stringResulting 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: 8080Thats 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.