DEV Community

Camilo
Camilo

Posted on • Edited on

Using Go inside Wren CLI

Original in Wren Wiki

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() {}
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

server.wren


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

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";
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode
// 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);
Enter fullscreen mode Exit fullscreen mode
  // 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
# 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
Enter fullscreen mode Exit fullscreen mode
# Near line 160
OBJECTS += $(OBJDIR)/wren_value.o
OBJECTS += $(OBJDIR)/wren_vm.o

OBJECTS += $(OBJDIR)/server.o
# We include the generated object
Enter fullscreen mode Exit fullscreen mode
# 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.
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode
import "server" for Http
Http.serve()
Enter fullscreen mode Exit fullscreen mode

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",
  }
Enter fullscreen mode Exit fullscreen mode
-- near line 118
  filter "system:macosx"
    systemversion "10.12"
    links { "http", "/Library/Frameworks/CoreFoundation.framework", "/Library/Frameworks/Security.framework" }
    linkoptions {"-L../../src/go"}
Enter fullscreen mode Exit fullscreen mode

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.

If you need a complete project you can go here

Top comments (2)

Collapse
 
davidedelpapa profile image
Davide Del Papa

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...

Collapse
 
clsource profile image
Camilo

Thanks. Added some links for Rust :)