Skip to content

Instantly share code, notes, and snippets.

@clsource
Created April 28, 2021 05:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save clsource/3ea744e28e04c5a5d317b37cda89dbdd to your computer and use it in GitHub Desktop.
Save clsource/3ea744e28e04c5a5d317b37cda89dbdd to your computer and use it in GitHub Desktop.
How to Extend Wren CLI with Go functions

Wren CLI is the official project for a small command line application that embeds Wren. Serves as an example implementation.

If you want to use the exact version of this tutorial. See this commit.

In this simple exercise we will export a go function and use it inside the CLI as a new class.

The function will be a simple Http server that returns a message if we go to localhost:8080

116343900 5c08d780 a7b3 11eb 85d8 ec9f88597934

Golang

If you need to install go you can download it from here. (go version go1.16.3 darwin/amd64)

Our go code is really simple. Based on https://golangr.com/golang-http-server/

package main

import "C"

import (
	"io"
	"log"
	"net/http"
)

//export Http
func Http() {
	// Set routing rules
	http.HandleFunc("/", Tmp)

	//Use the default DefaultServeMux.
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
			log.Fatal(err)
	}
}

func Tmp(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "Calling Go functions from Wren in static libs")
}

func main() {}

The main requirements for our go code are:

  • import "C" : Imports the cgo runtime

  • //export Http : Tells the compiler to export a function named Http

  • func main() {}: Is required to export the lib.

If you need more examples you can look here.

Now lets create a new directory and files inside the cli project:

116344740 cb32fb80 a7b4 11eb 9b63 fff5d9f4bf3e

Create a new directory named go inside src and inside create two files http.go and Makefile.

Fill http.go with the code above. Then Makefile with:

.PHONY: build
b build:
	go build -buildmode=c-archive -o libhttp.a http.go

Now if we go to the src/go directory and run make build we will have two new files libhttp.a and libhttp.h.

116345764 cbcc9180 a7b6 11eb 853c 5ea2fe715a58

Explendid!

Now we have to configure our C code files and add a new Wren class.

Go to src/module and create server.h, server.c, server.wren and server.wren.inc

116346053 6e851000 a7b7 11eb 8407 78b86f269bad

server.h

#ifndef server_h
#define server_h

#include "wren.h"

void httpServer(WrenVM* vm);

#endif

server.c

#include "wren.h"

// We import our generated h file from go
#include "libhttp.h"

// And create a simple wrapper to Bind the exported function to the Wren VM
void httpServer(WrenVM* vm) {
  Http();
}

server.wren

class Http {
// foreign is used to tell Wren this will be implemented in C
    foreign static serve()
}

server.wren.inc

// This file can be auto generated too! using
// python3 util/wren_to_c_string.py src/module/server.wren.inc src/module/server.wren
// the convention is <filename>ModuleSource

static const char* serverModuleSource =
"class Http {\n"
"  foreign static serve()\n"
"}\n"
"\n";

Ok let’s modify our src/cli/modules.c file to include our new class.

// near line 4
#include "modules.h"

#include "io.wren.inc"
#include "os.wren.inc"
#include "repl.wren.inc"
#include "scheduler.wren.inc"
#include "timer.wren.inc"

// We add our generated server.wren.inc file
#include "server.wren.inc"
// near line 51
extern void stdoutFlush(WrenVM* vm);
extern void schedulerCaptureMethods(WrenVM* vm);
extern void timerStartTimer(WrenVM* vm);

// Add our new function as a extern (this will tell the compiler that this function
// is implemented elsewhere (in our server.c file)
extern void httpServer(WrenVM* vm);
  // near line 180
  MODULE(timer)
    CLASS(Timer)
      STATIC_METHOD("startTimer_(_,_)", timerStartTimer)
    END_CLASS
  END_MODULE

  // We add our module mapping
  // import "server" for Http
  // Http.serve()
  MODULE(server)
    CLASS(Http)
      STATIC_METHOD("serve()", httpServer)
    END_CLASS
  END_MODULE

Finally we have to include the new paths in the Makefile so the libs and objects are included in the compilation.

Go to projects/make.mac/wren_cli.make

116347424 3206e380 a7ba 11eb 8694 01beae4b4f23

# Near line 30 prepend -I../../src/go
INCLUDES += -I../../src/go -I../../src/cli
# Near line 34
LIBS += -L../../src/go -lhttp -framework CoreFoundation -framework Security
# Note that we use -lhttp to refer to libhttp.a
# also we include the required frameworks from MacOS that Go http module needs to work
# Near line 160
OBJECTS += $(OBJDIR)/wren_value.o
OBJECTS += $(OBJDIR)/wren_vm.o

OBJECTS += $(OBJDIR)/server.o
# We include the generated object
# Near line 382
$(OBJDIR)/timer1.o: ../../src/module/timer.c
	@echo $(notdir $<)
	$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"

$(OBJDIR)/server.o: ../../src/module/server.c
	@echo $(notdir $<)
	$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"

# We include the server.c source file for the object.

Now we are almost ready. Just go to the make.mac directory and execute make command.

116348291 d63d5a00 a7bb 11eb 968d 334f37c04c3f

If all went well you will have a shiny wren_cli binary inside bin/. And if you execute the REPL you can use the new module and go to localhost:8080 for testing it

bin/wren_cli
\\/"-
 \_/   wren v0.4.0
> import "server" for Http
> Http.serve()
import "server" for Http
Http.serve()

Considerations

  • The generated static library contains lots of functions, even if you just exported one. So it will add weight to the wren_cli. In this case up to 5Mb more.

  • You can automate generating wren.inc files with python3 util/wren_to_c_string.py src/module/server.wren.inc src/module/server.wren

  • You can automate generating projects/make.mac/wren_cli.make files by using Premake.

  • The same procedure can be followed by other languages like Rust and bring all its power to Wren.

Using Premake

The minimum version required is:

  • premake5 (Premake Build Script Generator) 5.0.0-alpha14

Configure projects/premake/premake5.lua

-- near line 58
includedirs {
    "../../src/cli",
    "../../src/module",
    "../../src/go",
  }
-- near line 118
  filter "system:macosx"
    systemversion "10.12"
    links { "http", "/Library/Frameworks/CoreFoundation.framework", "/Library/Frameworks/Security.framework" }
    linkoptions {"-L../../src/go"}

And then execute python3 utils/generate_projects.py

Conclusion

Wren is wonderful and it’s CLI is ease to hack and extend. If you need Wren to have industry level extensions you can rely on Go or Rust extensive libraries and create something wonderful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment