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
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 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 thecgo
runtime -
//export Http
: Tells the compiler to export a function namedHttp
-
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:
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
.
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
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
# 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.
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 withpython3 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
Rust
Rust would have similar steps. The only difference would be generating the static library. For that you can follow this tutorial or this other one and the cbindgen tool A sample http server can be found here
Conclusion
Wren is marvelous and it's CLI is easy 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.
Top comments (2)
Hi, can you add some example of how this can be done in Rust as well? It seems no point just saying it can be done, but not showing how...
Thanks. Added some links for Rust :)