-
Notifications
You must be signed in to change notification settings - Fork 6
Embedding Pipefish in Go
By importing the appropriate part of the Pipefish distribution (github.com/tim-hardcastle/Pipefish/source/pf) as a library, you can embed Pipefish in your own applications, rather than accessing it via the TUI or a client.
The various methods etc are documented formally by godev, so here we will give a tutorial on how to use them in practice.
Many of the available features are only necessary for the most sophisticated users, and you may just want to skip down to the example at the foot of the page and copy that.
Having imported the pf library, we request a new service using a function with the signature func NewService() *Service.
At this point, optionally:
-
If you want the service to use a database, this is the point at which you should assign it, since it might be used by
initcommands in the code. You do this with a method having signaturefunc (sv *Service) SetDatabase(db *sql.DB). -
If you want the service to reference local external services (as though they were on the same hub) you can do this by passing it a map of strings to services using a method with signature
func (sv *Service) SetLocalExternalServices(svs map[string]*Service); again, if you want to do this, you should do so at this stage, before anyinitcommand can assume that these services exist. Remote external services work as usual.
Having done either of those things if necessary, we can now compile the service. You can do this either using func (sv *Service) InitializeFromCode(code string) error or func (sv *Service) InitializeFromFilepath(scriptFilepath string) error, the names of which are more or less self-explanatory.
func (sv *Service) NeedsUpdate() (bool, error) will return true if the source code files for a service have been changed since the service was first initialized.
Then either you can perform func (s *Service) CallMain() (values.Value, error), the name of which explains itself); or to evaluate an expression or perform any other command, you use the method func (sv *Service) Do(line string) (Value, error).
This will return an non-nil error if there are compile-time errors. If intead the line compiles successfully but there is a runtime error, then that will be returned as the Value together with a nil error.
As you can see, what it returns is a Pipefish value, of type pf.Value, which is a struct having two fields: T of type pf.Type, and V of type any. You have four choices of what to do with it besides leaving it as it is.
-
Use the method
func (sv *Service) ToLiteral(v Value) stringto convert it into a Pipefish literal. -
Use the method
func (sv *Service) ToString(v Value) stringto apply thestringmethod of the service to it. -
Use the method
func (sv *Service) ToGo (pfValue Value, goType reflect.Type) (any, error)to convert it to a Go value of the given type, if possible. -
Deal with it yourself. To this end we have supplied constants
UNDEFINED_TYPE,BLING,OK,TUPLE,ERROR,NULL,INT,BOOL,STRING,RUNE,TYPE,FUNC,PAIR,LIST,MAP,SET,LABEL. There are methodsfunc (sv *Service) IsClone(v Value) bool,func (sv *Service) IsEnum(v Value) bool, andfunc (sv *Service) IsStruct(v Value) bool, which do what you think they would do, and a functionfunc (sv *Service) UnderlyingType(v Value) Typewhich returns the parent type of a type if it's a clone, and its own type otherwise. Also, thepflibrary exposes the internal types used to represent PipefishValues. See Appendix C of this wiki for further details of the internal representation of Pipefish values.
To deal with error conditions, we have the following methods:
-
func (sv *Service) IsBroken() boolwill returntrueif no script has been compiled yet, or if the compilation failed. -
func (sv *Service) ErrorsExist() boolwill returntrueif the last thing the service did produced errors, whether this was a result of trying to compile the script; of trying to compile a line usingDo; or of a runtime error caused by executing the line. -
func (sv *Service) GetErrors() []*Errorwill get a list of errors. -
func (sv *Service) GetErrorReport() (string, error)gets the errors summarized in a string. -
func ExplainError(es []*Error, i int) (string, error)will get you the same explanation you'd get fromhub why. -
func GetTraceReport(e *err.Error) stringextracts the trace from a runtime error. -
func (sv *Service) GetTrackingReport() (string, error)gets the tracking data, if any. -
func PrettyString(s string, left, right int) stringconverts the strings returned by the previous four methods from markup to highlighted text.
An InHandler is an interface to a type defining what happens when the Pipefish code performs get x from Input("<prompt>"). It has one method, Get() string`.
An OutHandler is an interface to a type defining what happens when the Pipefish code performs post x to Output(). It has two methods: Out(v Value) and Write(s string), the first of which will typically serialize the Value in some way before writing it to some output, and the second of which will typically write the string to the same output.
You can set them using the methods func (sv *Service) SetInHandler(in InHandler) error and func (sv *Service) SetOutHandler(out OutHandler) error.
To help construct them, there are the following methods and functions:
-
func MakeSimpleInHandler(in io.Reader) *SimpleInHandler. Returns an InHandler that will get its string from theio.Readerand do nothing else. -
func MakeTerminalInHandler(prompt string) *TerminalInHandler. Returns anInhandlerthat will get its string from the terminal, prompting the end-user with the given prompt. -
func (sv *Service) MakeLiteralWritingOutHandler(out io.Writer) *SimpleOutHandler. Returns anOutHandlerthat will write the literal of the passed value to the givenio.Writer. -
func (sv *Service) MakeStringWritingOutHandler(out io.Writer) *SimpleOutHandler. Returns anOutHandlerthat will write the string representation of the passed value to the givenio.Writer. -
func (sv *Service) MakeLiteralTerminalOutHandler() *SimpleOutHandler. Returns anOutHandlerthat will write the literal of the passed value to the terminal. -
func (sv *Service) MakeStringTerminalOutHandler() *SimpleOutHandler. Returns anOutHandlerthat will write the string representation of the passed value to the terminal.
-
func (sv *Service) GetVariable(vname string) (values.Value, error)gets the value of a global variable. Unlike doing this using theDomethod,GetVariablegives you access to private variables. -
func (sv *Service) SetVariable(vname string, ty values.ValueType, v any) errorsets the value of a global variable. Unlike doing this using theDomethod,SetVariablegives you access to private variables. -
func (sv *Service) GetFilepath() (string, error)gets the path to the root file of the service. -
func (sv *Service) GetSources() (map[string][]string, error)gets the source code of the service as a map from filenames to lists of strings (i.e. lines of source code). -
func (sv *Service) TypeToTypeName(t Type) (string, error): does what it says: converts something of typepf.Typeto a Go string naming the type, e.g. convertingpf.BOOLto the string"bool". -
func (sv *Service) TypeNameToType(s string) (Type, error)reversed the previous process and gets apf.Typefrom a string representing the type name.
As a complete if simple example, the following Go code will tell you that the eighth Fibonacci number is 21.
package main
import (
"reflect"
"github.com/tim-hardcastle/Pipefish/source/pf"
)
const fibCode = `// Pipefish code for computing the nth Fibonnaci number.
def
fib(n int) :
first from a, b = 0, 1 for i = 0; i < n; i + 1 :
b, a + b
`
func main() {
fibService := pf.NewService()
fibService.InitializeFromCode(fibCode)
pfResult, _ := fibService.Do(`fib 8`)
goResult, _ := fibService.ToGo(pfResult, reflect.TypeFor[int]())
println("The eighth Fibonacci number is", goResult.(int))
}
🧿 Pipefish is distributed under the MIT license. Please steal my code and ideas.
- Getting started
- Language basics
- The type system and built-in functions
- Functional Pipefish
- Encapsulation
- Imperative Pipefish
-
Imports and libraries
- The files library
- The fmt library
- The html library
- The math library
- The math/big library
- The math/cmplx library
- The math/rand library
- The path library
- The path/filepath library
- The reflect library
- The regexp library
- The sql library
- The strings library
- The terminal library
- The time library
- The unicode library
- Advanced Pipefish
- Developing in Pipefish
- Deployment
- Appendices