147

Writing Emacs modules with Go

 6 years ago
source link: https://mrosset.github.io/emacs-module/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Writing Emacs modules with Go

org-mode-unicorn-logo_2017-09-05_10-16-22.pngThis page is generated using (org-html-export-to-html) if you prefer using org-mode you can get the org file from GitHub.

1 Introduction

This page is currently complete, but should still be considered a draft. If you find any grammar, spelling or technical errors, please create an issue on GitHub

As of Emacs version 25.1 Emacs has support for loading dynamic modules. And as of version 1.5 Go supports building packages as dynamic shared libraries. Allowing us to write Emacs functions in Go. This guide will walk you through creating first a simple module in C and then a simple module in Go. There is not a huge difference between the two. But understanding the C module well help when we write our Go module.

2 C Emacs module

First create a top level directory to house our project . I like to create my projects in $HOME/src pick whichever location suits you. Once we create the directory we'll cd into it. And any commands we run from now on. Will be from that directory.

mkdir -pv ~/src/emacs-module
cd ~/src/emacs-module

To compile a Emacs modules we need the emacs-module.h header file. The header file is found in the src directory of the Emacs source tarball. It's possible your OS provides this already. However this guide is OS agnostic and we'll assume that the header file has not been installed. Since we just need the header and not all of the emacs tarball we'll download it from the git repository.

Create the src directory

mkdir src

Download the emacs header file

wget "http://git.savannah.gnu.org/cgit/emacs.git/plain/src/emacs-module.h?h=emacs-25.2" -O src/emacs-module.h

2.1 src/cmodule.c

If you are using org-mode you can generate this file automatically with C-c C-v t or M-x org-babel-tangle .

Here is the complete C file. In the next section we'll do a line by line breakdown of what it does.

#include <emacs-module.h>
#include <stdio.h>

#define CMOD_VERSION  "0.1"

int plugin_is_GPL_compatible;

static emacs_value
Fcmodule_version (emacs_env * env, ptrdiff_t nargs, emacs_value args[],
      void *data)
{
  return env->make_string (env, CMOD_VERSION, 3);
}

extern
int emacs_module_init (struct emacs_runtime *ert)

{
emacs_env *env = ert->get_environment (ert);

emacs_value Qfeat = env->intern (env, "cmodule");
emacs_value Qprovide = env->intern (env, "provide");

emacs_value pargs[] = { Qfeat };
env->funcall (env, Qprovide, 1, pargs);

emacs_value fn = env->make_function (env, 0, 0, Fcmodule_version,
                 "Returns cmodule version", NULL);

emacs_value Qfset = env->intern (env, "fset");
emacs_value Qsym = env->intern (env, "cmodule-version");

emacs_value fargs[] = { Qsym, fn };
env->funcall(env, Qfset, 2, fargs);
return 0;
}

2.2 C Breakdown

First we include emacs-module.h header file. We'll need this for the Emacs declaration types.

#include <emacs-module.h>
#include <stdio.h>

#define CMOD_VERSION  "0.1"

Emacs also requires a plugin_is_GPL_compatible symbol, if not declared then Emacs will not load the module.

int plugin_is_GPL_compatible;

Next is the C function that we plan to call from Emacs. Our function is simple, all it does is take a C string literal called CMOD_VERSION. Creates an Emacs string and then returns it.

static emacs_value
Fcmodule_version (emacs_env * env, ptrdiff_t nargs, emacs_value args[],
      void *data)
{
  return env->make_string (env, CMOD_VERSION, 3);
}

Next is the entry point of our module and is run when the module is first loaded by Emacs.

extern
int emacs_module_init (struct emacs_runtime *ert)

First we get the Emacs environment from the Emacs run-time.

{
emacs_env *env = ert->get_environment (ert);

We need to provision the module. We'll call the elisp (provide) function through the C interface. If not Emacs will error with feature not provided.

We convert our feature string into a qouted lisp symbol. The nameing is important our module will be compiled to cmodule.so so the feature symbol must be named cmodule, anything else will not work. Then we get a quoted symbol for the elisp provide function.

emacs_value Qfeat = env->intern (env, "cmodule");
emacs_value Qprovide = env->intern (env, "provide");

emacs_value pargs[] = { Qfeat };
env->funcall (env, Qprovide, 1, pargs);

Next we declare our function and then use Emacs fset to define it. In short we are telling Emacs that whenever we call (cmodule-verion) to execute the Fcmodule_version C function.

emacs_value fn = env->make_function (env, 0, 0, Fcmodule_version,
                 "Returns cmodule version", NULL);

emacs_value Qfset = env->intern (env, "fset");
emacs_value Qsym = env->intern (env, "cmodule-version");

emacs_value fargs[] = { Qsym, fn };
env->funcall(env, Qfset, 2, fargs);
return 0;
}

2.3 Compiling the module

Create a lib directory to hold our shared libraries.

mkdir -p lib

Now we compile src/cmodule.c as a shared C library.

gcc -I src -fPIC -shared src/cmodule.c -o lib/cmodule.so

2.4 Testing C module with Emacs.

To test our module we'll start Emacs in batch mode then call our custom function. We'll use Emacs message function to print the value to stdout.

emacs -Q -L ./lib -batch -l cmodule --eval "(message (cmodule-version))"
0.1

2.5 C summary

Writing a Emacs module in C is straight forward. These are the basic things you need to create a Emacs module.

  1. emacs-module.h
  2. pluginisGPLcompatibe symbol
  3. emacsmoduleinit entry point
  4. Provision module feature within Emacs
  5. fset function within Emacs

This C example was put together from diobla.info blog . He does a good job of breaking things down, and has some helper functions for binding and provisioning.

Here is some additional links if your looking to create some more advanced C module.

3 Go Emacs Module

We'll now make a similar module, but this time we'll access the Go runtime and get the version of go we are using. We'll have to do some boiler plating and some C type conversions but we can for the most part stick to go.

3.1 src/main.go

Create and edit file src/main.go. We'll do a breakdown of this file in the next section.

package main

/*
#include <emacs-module.h>
#include <stdlib.h>
int plugin_is_GPL_compatible;

static void
provide (struct emacs_runtime *ert, const char *feature)
{
  emacs_env *env = ert->get_environment (ert);

  emacs_value Qfeat = env->intern (env, feature);
  emacs_value Qprovide = env->intern (env, "provide");
  emacs_value args[] = { Qfeat };

  env->funcall (env, Qprovide, 1, args);
}

extern emacs_value Fgo_version(emacs_env* p0);

static void
fset (struct emacs_runtime *ert, const char *name, void *fn)
{
  emacs_env *env = ert->get_environment (ert);

  emacs_value Qfn = env->make_function (env, 0, 0, fn,
          "Return string describing the go runtime version.", NULL);

  emacs_value Qfset = env->intern (env, "fset");
  emacs_value Qsym = env->intern (env, name);

  emacs_value fargs[] = { Qsym, Qfn };
  env->funcall (env, Qfset, 2, fargs);
}

static emacs_value
make_string (emacs_env * env, const char *s)
{
  return env->make_string (env, s, strlen(s));
}

*/
import "C"
import (
       "runtime"
       "unsafe"
)

func freeString(cstring *C.char) {
  C.free(unsafe.Pointer(cstring))
}

//export Fgo_version
func Fgo_version(env *C.emacs_env) C.emacs_value {
  version := runtime.Version()
  cversion := C.CString(version)
  defer freeString(cversion)
  return C.make_string(env, cversion)
}

//export emacs_module_init
func emacs_module_init(ert *C.struct_emacs_runtime) C.int {
  cfeat := C.CString("gmodule")
  fname := C.CString("go-version")

  defer freeString(fname)
  defer freeString(cfeat)

  C.provide(ert, cfeat)
  C.fset(ert, fname, C.Fgo_version);
  return 0
}

func main() {}

3.2 Go Breakdown

We'll name our package main, even though we are not creating a command module it's important the package name is main, otherwise when we build a c-shared library it will build an ar archive, and not a dynamic library.

package main

And in the comments section we include emacs-module.h . We also declare int plugin_is_GPL_compatible.

/*
#include <emacs-module.h>
#include <stdlib.h>
int plugin_is_GPL_compatible;

We need to provision our feature within emacs. To do this we need to call the Emacs (provide) function just like we did with the C module. Unfortunately we can not call C pointer functions directly from Go. But we can call them from C. So we'll create a helper C function that can access the struct function pointer we need. We'll do this in the special comment section just before import "C".

static void
provide (struct emacs_runtime *ert, const char *feature)
{
  emacs_env *env = ert->get_environment (ert);

  emacs_value Qfeat = env->intern (env, feature);
  emacs_value Qprovide = env->intern (env, "provide");
  emacs_value args[] = { Qfeat };

  env->funcall (env, Qprovide, 1, args);
}

We declare our function in C even though cgo will generate a gmodule.h file. If we don't declare it now we won't be able to reference it from this file.

extern emacs_value Fgo_version(emacs_env* p0);

We create a helper function to fset our function within emacs, this function assumes our Emacs function will not be taking any arguements it's not ideal but it will work for the function we want to create. We also hardcode our doc

static void
fset (struct emacs_runtime *ert, const char *name, void *fn)
{
  emacs_env *env = ert->get_environment (ert);

  emacs_value Qfn = env->make_function (env, 0, 0, fn,
          "Return string describing the go runtime version.", NULL);

  emacs_value Qfset = env->intern (env, "fset");
  emacs_value Qsym = env->intern (env, name);

  emacs_value fargs[] = { Qsym, Qfn };
  env->funcall (env, Qfset, 2, fargs);
}

Later on we'll need to convert the runtime version Go string into a Emacs string value. Since we can't call function pointers directly we'll create a helper function to handle this.

static emacs_value
make_string (emacs_env * env, const char *s)
{
  return env->make_string (env, s, strlen(s));
}

We need to import the C package. The C package is special since it tells go to build with cgo. It's important that this package import comes first and it on it's own line. We need to close our C comment section just before import "C" there should be no new lines after closing the comment.

*/
import "C"
import (
       "runtime"
       "unsafe"
)

We'll be using some C strings. Go will not be able to memory manage our C types. so we'll have to use some unsafe functions and free the strings ourselves. We'll create a freeString function to make this easier. C.free calls the free function we included in stdlib.h. We also need to use the unsafe package to get the pointer.

func freeString(cstring *C.char) {
  C.free(unsafe.Pointer(cstring))
}

We declare the Go function. This is the Go code that will be exectuted when we call our Emacs function using (go-version) . Our function will return a type that emacs can evaluate. In this case the go runtime version as a string.

//export Fgo_version
func Fgo_version(env *C.emacs_env) C.emacs_value {
  version := runtime.Version()
  cversion := C.CString(version)
  defer freeString(cversion)
  return C.make_string(env, cversion)
}

Next we create our Emacs entry point. and we'll use //export directive to tell cgo we want to export this as a dynamic function. This is the same as if we wrote extern int emacs_module_init(struct emacs_runtime* p0); in C, in fact Go will produce this declaration when it creates a C header file for our dynamic library.

If we were to return the Go type int instead of C.int it would create a compiler error since the signature would no longer match the Emacs one.

//export emacs_module_init
func emacs_module_init(ert *C.struct_emacs_runtime) C.int {
  cfeat := C.CString("gmodule")
  fname := C.CString("go-version")

  defer freeString(fname)
  defer freeString(cfeat)

  C.provide(ert, cfeat)
  C.fset(ert, fname, C.Fgo_version);
  return 0
}

Even though this is not a command package. We need to include a main function. Due to the fact we are exporting go functions using //export . We can simply stub one out like so.

func main() {}

3.3 Building the Go module

We export our include flags and then manually build our so file.

go build -buildmode=c-shared -o lib/gmodule.so src/main.go

3.4 Test the Go module

emacs -Q -L ./lib -batch -l "gmodule" --eval '(message (go-version))'
go1.9

4 Automating this guide.

This whole guide can be run without any manual input using.

(org-babel-tangle)
(org-babel-execute-buffer)

5 Todo's

5.1 DONE emacs-module.h

The emacs tarball is pretty large. figure out how we can download the header file and still be GPL compliant.

5.2 TODO GPL compliance

Find out what GPL implications might arise from writing packages in Go. Go uses a claused BSD license not sure of hosting the go runtime within emacs breaks GPL or not.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK