Intro
This is such a niche topic that I'm not going to write it like my normal posts. Just going to make it more of a note taking post. I have very crude working knowledge of both languages. Swift has proven to be very annoying to work with when it comes to string manipulation. The packages available in swift are somewhat lacking. Additionally most docs come up for mobile apps and not MacOS.
Golang on the other hand is very pleasant and has plenty of packages. Threading is also very nice in golang. So I wondered if I could get the 2 to interop.
Both have quasi support for C. So let's see what it is like to use that as the bridge. My inspiration was mostly taken from here.
[insert picture here]
This is library I'd like to call in Swift: Syntax highlighting in golang
Golang bridge
package main
import (
"C"
"bytes"
"github.com/alecthomas/chroma"
"github.com/alecthomas/chroma/formatters"
"github.com/alecthomas/chroma/lexers"
"github.com/alecthomas/chroma/styles"
)
//export highlight
func highlight(c_source, c_lexer, c_formatter, c_style *C.char) (*C.char, *C.char) {
source := C.GoString(c_source)
lexer := C.GoString(c_lexer)
formatter := C.GoString(c_formatter)
style := C.GoString(c_style)
// Determine lexer.
l := lexers.Get(lexer)
if l == nil {
l = lexers.Analyse(source)
}
if l == nil {
l = lexers.Fallback
}
l = chroma.Coalesce(l)
// Determine formatter.
f := formatters.Get(formatter)
if f == nil {
f = formatters.Fallback
}
// Determine style.
s := styles.Get(style)
if s == nil {
s = styles.Fallback
}
it, err := l.Tokenise(nil, source)
if err != nil {
return C.CString(""), C.CString(err.Error())
}
var buf bytes.Buffer
err = f.Format(&buf, s, it)
if err != nil {
return C.CString(""), C.CString(err.Error())
}
return C.CString(buf.String()), C.CString("")
}
// We need an entry point; it's ok for this to be empty
func main() {}
To generate a c-archive with header file run:
go build -buildmode=c-archive -o nautilus.a main.go
In this case I'm calling the lib nautilus. Take a look at the generated header file:
/* Code generated by cmd/cgo; DO NOT EDIT. */
/* package command-line-arguments */
#line 1 "cgo-builtin-export-prolog"
#include <stddef.h> /* for ptrdiff_t below */
#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
#endif
#endif
/* Start of preamble from import "C" comments. */
/* End of preamble from import "C" comments. */
/* Start of boilerplate cgo prologue. */
#line 1 "cgo-gcc-export-header-prolog"
#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H
typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef __SIZE_TYPE__ GoUintptr;
typedef float GoFloat32;
typedef double GoFloat64;
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;
/*
static assertion to make sure the file is being used on architecture
at least with matching size of GoInt.
*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
#endif
/* End of boilerplate cgo prologue. */
#ifdef __cplusplus
extern "C" {
#endif
/* Return type for highlight */
struct highlight_return {
char* r0;
char* r1;
};
extern struct highlight_return highlight(char* p0, char* p1, char* p2, char* p3);
#ifdef __cplusplus
}
#endif
Lots of useful info, but for this example it can be simplified to:
/* Return type for highlight */
struct highlight_return {
char* r0;
char* r1;
};
extern struct highlight_return highlight(const char* p0,const char* p1,const char* p2,const char* p3);
Swift
So with a standard swift macos app:
func applicationDidFinishLaunching(_ aNotification: Notification) {
for _ in 0...100000 {
let ptr = highlight("", "go", "html", "monokai")
print(ptr)
let test = String(cString: ptr.r0)
// free(ptr.r0)
// free(ptr.r1)
print(test)
}
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
// Create the window and set the content view.
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
}
So what you'll see here is a test to get html output of syntax highlighting in the console. There is no code in this sample. ptr is a pointer to a C struct seen in the header file. Here's what happen when when you don't free the pointers in swift:
So free your memory:
The other interesting note is that the most recent version of swift has a pleasant auto conversion to C char type. Explicitly highlight("", "go", "html", "monokai") each of those strings should be like "go".cString(using: String.Encoding.utf8).