760 lines
14 KiB
C
760 lines
14 KiB
C
// Copyright 2009 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
/*
|
|
* Translate a .goc file into a .c file. A .goc file is a combination
|
|
* of a limited form of Go with C.
|
|
*/
|
|
|
|
/*
|
|
package PACKAGENAME
|
|
{# line}
|
|
func NAME([NAME TYPE { , NAME TYPE }]) [(NAME TYPE { , NAME TYPE })] \{
|
|
C code with proper brace nesting
|
|
\}
|
|
*/
|
|
|
|
/*
|
|
* We generate C code which implements the function such that it can
|
|
* be called from Go and executes the C code.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
/* Whether we're emitting for gcc */
|
|
static int gcc;
|
|
|
|
/* Package prefix to use; only meaningful for gcc */
|
|
static const char *prefix;
|
|
|
|
/* File and line number */
|
|
static const char *file;
|
|
static unsigned int lineno = 1;
|
|
|
|
/* List of names and types. */
|
|
struct params {
|
|
struct params *next;
|
|
char *name;
|
|
char *type;
|
|
};
|
|
|
|
/* index into type_table */
|
|
enum {
|
|
Bool,
|
|
Float,
|
|
Int,
|
|
Uint,
|
|
Uintptr,
|
|
String,
|
|
Slice,
|
|
Eface,
|
|
};
|
|
|
|
static struct {
|
|
char *name;
|
|
int size;
|
|
} type_table[] = {
|
|
/* variable sized first, for easy replacement */
|
|
/* order matches enum above */
|
|
/* default is 32-bit architecture sizes */
|
|
"bool", 1,
|
|
"float", 4,
|
|
"int", 4,
|
|
"uint", 4,
|
|
"uintptr", 4,
|
|
"String", 8,
|
|
"Slice", 12,
|
|
"Eface", 8,
|
|
|
|
/* fixed size */
|
|
"float32", 4,
|
|
"float64", 8,
|
|
"byte", 1,
|
|
"int8", 1,
|
|
"uint8", 1,
|
|
"int16", 2,
|
|
"uint16", 2,
|
|
"int32", 4,
|
|
"uint32", 4,
|
|
"int64", 8,
|
|
"uint64", 8,
|
|
|
|
NULL,
|
|
};
|
|
|
|
/* Fixed structure alignment (non-gcc only) */
|
|
int structround = 4;
|
|
|
|
char *argv0;
|
|
|
|
static void
|
|
sysfatal(char *fmt, ...)
|
|
{
|
|
char buf[256];
|
|
va_list arg;
|
|
|
|
va_start(arg, fmt);
|
|
vsnprintf(buf, sizeof buf, fmt, arg);
|
|
va_end(arg);
|
|
|
|
fprintf(stderr, "%s: %s\n", argv0 ? argv0 : "<prog>", buf);
|
|
exit(1);
|
|
}
|
|
|
|
/* Unexpected EOF. */
|
|
static void
|
|
bad_eof(void)
|
|
{
|
|
sysfatal("%s:%ud: unexpected EOF\n", file, lineno);
|
|
}
|
|
|
|
/* Out of memory. */
|
|
static void
|
|
bad_mem(void)
|
|
{
|
|
sysfatal("%s:%ud: out of memory\n", file, lineno);
|
|
}
|
|
|
|
/* Allocate memory without fail. */
|
|
static void *
|
|
xmalloc(unsigned int size)
|
|
{
|
|
void *ret = malloc(size);
|
|
if (ret == NULL)
|
|
bad_mem();
|
|
return ret;
|
|
}
|
|
|
|
/* Reallocate memory without fail. */
|
|
static void*
|
|
xrealloc(void *buf, unsigned int size)
|
|
{
|
|
void *ret = realloc(buf, size);
|
|
if (ret == NULL)
|
|
bad_mem();
|
|
return ret;
|
|
}
|
|
|
|
/* Free a list of parameters. */
|
|
static void
|
|
free_params(struct params *p)
|
|
{
|
|
while (p != NULL) {
|
|
struct params *next;
|
|
|
|
next = p->next;
|
|
free(p->name);
|
|
free(p->type);
|
|
free(p);
|
|
p = next;
|
|
}
|
|
}
|
|
|
|
/* Read a character, tracking lineno. */
|
|
static int
|
|
getchar_update_lineno(void)
|
|
{
|
|
int c;
|
|
|
|
c = getchar();
|
|
if (c == '\n')
|
|
++lineno;
|
|
return c;
|
|
}
|
|
|
|
/* Read a character, giving an error on EOF, tracking lineno. */
|
|
static int
|
|
getchar_no_eof(void)
|
|
{
|
|
int c;
|
|
|
|
c = getchar_update_lineno();
|
|
if (c == EOF)
|
|
bad_eof();
|
|
return c;
|
|
}
|
|
|
|
/* Read a character, skipping comments. */
|
|
static int
|
|
getchar_skipping_comments(void)
|
|
{
|
|
int c;
|
|
|
|
while (1) {
|
|
c = getchar_update_lineno();
|
|
if (c != '/')
|
|
return c;
|
|
|
|
c = getchar();
|
|
if (c == '/') {
|
|
do {
|
|
c = getchar_update_lineno();
|
|
} while (c != EOF && c != '\n');
|
|
return c;
|
|
} else if (c == '*') {
|
|
while (1) {
|
|
c = getchar_update_lineno();
|
|
if (c == EOF)
|
|
return EOF;
|
|
if (c == '*') {
|
|
do {
|
|
c = getchar_update_lineno();
|
|
} while (c == '*');
|
|
if (c == '/')
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
ungetc(c, stdin);
|
|
return '/';
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Read and return a token. Tokens are delimited by whitespace or by
|
|
* [(),{}]. The latter are all returned as single characters.
|
|
*/
|
|
static char *
|
|
read_token(void)
|
|
{
|
|
int c;
|
|
char *buf;
|
|
unsigned int alc, off;
|
|
const char* delims = "(),{}";
|
|
|
|
while (1) {
|
|
c = getchar_skipping_comments();
|
|
if (c == EOF)
|
|
return NULL;
|
|
if (!isspace(c))
|
|
break;
|
|
}
|
|
alc = 16;
|
|
buf = xmalloc(alc + 1);
|
|
off = 0;
|
|
if (strchr(delims, c) != NULL) {
|
|
buf[off] = c;
|
|
++off;
|
|
} else {
|
|
while (1) {
|
|
if (off >= alc) {
|
|
alc *= 2;
|
|
buf = xrealloc(buf, alc + 1);
|
|
}
|
|
buf[off] = c;
|
|
++off;
|
|
c = getchar_skipping_comments();
|
|
if (c == EOF)
|
|
break;
|
|
if (isspace(c) || strchr(delims, c) != NULL) {
|
|
if (c == '\n')
|
|
lineno--;
|
|
ungetc(c, stdin);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
buf[off] = '\0';
|
|
return buf;
|
|
}
|
|
|
|
/* Read a token, giving an error on EOF. */
|
|
static char *
|
|
read_token_no_eof(void)
|
|
{
|
|
char *token = read_token();
|
|
if (token == NULL)
|
|
bad_eof();
|
|
return token;
|
|
}
|
|
|
|
/* Read the package clause, and return the package name. */
|
|
static char *
|
|
read_package(void)
|
|
{
|
|
char *token;
|
|
|
|
token = read_token_no_eof();
|
|
if (token == NULL)
|
|
sysfatal("%s:%ud: no token\n", file, lineno);
|
|
if (strcmp(token, "package") != 0) {
|
|
sysfatal("%s:%ud: expected \"package\", got \"%s\"\n",
|
|
file, lineno, token);
|
|
}
|
|
return read_token_no_eof();
|
|
}
|
|
|
|
/* Read and copy preprocessor lines. */
|
|
static void
|
|
read_preprocessor_lines(void)
|
|
{
|
|
while (1) {
|
|
int c;
|
|
|
|
do {
|
|
c = getchar_skipping_comments();
|
|
} while (isspace(c));
|
|
if (c != '#') {
|
|
ungetc(c, stdin);
|
|
break;
|
|
}
|
|
putchar(c);
|
|
do {
|
|
c = getchar_update_lineno();
|
|
putchar(c);
|
|
} while (c != '\n');
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Read a type in Go syntax and return a type in C syntax. We only
|
|
* permit basic types and pointers.
|
|
*/
|
|
static char *
|
|
read_type(void)
|
|
{
|
|
char *p, *op, *q;
|
|
int pointer_count;
|
|
unsigned int len;
|
|
|
|
p = read_token_no_eof();
|
|
if (*p != '*')
|
|
return p;
|
|
op = p;
|
|
pointer_count = 0;
|
|
while (*p == '*') {
|
|
++pointer_count;
|
|
++p;
|
|
}
|
|
len = strlen(p);
|
|
q = xmalloc(len + pointer_count + 1);
|
|
memcpy(q, p, len);
|
|
while (pointer_count > 0) {
|
|
q[len] = '*';
|
|
++len;
|
|
--pointer_count;
|
|
}
|
|
q[len] = '\0';
|
|
free(op);
|
|
return q;
|
|
}
|
|
|
|
/* Return the size of the given type. */
|
|
static int
|
|
type_size(char *p)
|
|
{
|
|
int i;
|
|
|
|
if(p[strlen(p)-1] == '*')
|
|
return type_table[Uintptr].size;
|
|
|
|
for(i=0; type_table[i].name; i++)
|
|
if(strcmp(type_table[i].name, p) == 0)
|
|
return type_table[i].size;
|
|
if(!gcc) {
|
|
sysfatal("%s:%ud: unknown type %s\n", file, lineno, p);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Read a list of parameters. Each parameter is a name and a type.
|
|
* The list ends with a ')'. We have already read the '('.
|
|
*/
|
|
static struct params *
|
|
read_params(int *poffset)
|
|
{
|
|
char *token;
|
|
struct params *ret, **pp, *p;
|
|
int offset, size, rnd;
|
|
|
|
ret = NULL;
|
|
pp = &ret;
|
|
token = read_token_no_eof();
|
|
offset = 0;
|
|
if (strcmp(token, ")") != 0) {
|
|
while (1) {
|
|
p = xmalloc(sizeof(struct params));
|
|
p->name = token;
|
|
p->type = read_type();
|
|
p->next = NULL;
|
|
*pp = p;
|
|
pp = &p->next;
|
|
|
|
size = type_size(p->type);
|
|
rnd = size;
|
|
if(rnd > structround)
|
|
rnd = structround;
|
|
if(offset%rnd)
|
|
offset += rnd - offset%rnd;
|
|
offset += size;
|
|
|
|
token = read_token_no_eof();
|
|
if (strcmp(token, ",") != 0)
|
|
break;
|
|
token = read_token_no_eof();
|
|
}
|
|
}
|
|
if (strcmp(token, ")") != 0) {
|
|
sysfatal("%s:%ud: expected '('\n",
|
|
file, lineno);
|
|
}
|
|
if (poffset != NULL)
|
|
*poffset = offset;
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Read a function header. This reads up to and including the initial
|
|
* '{' character. Returns 1 if it read a header, 0 at EOF.
|
|
*/
|
|
static int
|
|
read_func_header(char **name, struct params **params, int *paramwid, struct params **rets)
|
|
{
|
|
int lastline;
|
|
char *token;
|
|
|
|
lastline = -1;
|
|
while (1) {
|
|
token = read_token();
|
|
if (token == NULL)
|
|
return 0;
|
|
if (strcmp(token, "func") == 0) {
|
|
if(lastline != -1)
|
|
printf("\n");
|
|
break;
|
|
}
|
|
if (lastline != lineno) {
|
|
if (lastline == lineno-1)
|
|
printf("\n");
|
|
else
|
|
printf("\n#line %d \"%s\"\n", lineno, file);
|
|
lastline = lineno;
|
|
}
|
|
printf("%s ", token);
|
|
}
|
|
|
|
*name = read_token_no_eof();
|
|
|
|
token = read_token();
|
|
if (token == NULL || strcmp(token, "(") != 0) {
|
|
sysfatal("%s:%ud: expected \"(\"\n",
|
|
file, lineno);
|
|
}
|
|
*params = read_params(paramwid);
|
|
|
|
token = read_token();
|
|
if (token == NULL || strcmp(token, "(") != 0)
|
|
*rets = NULL;
|
|
else {
|
|
*rets = read_params(NULL);
|
|
token = read_token();
|
|
}
|
|
if (token == NULL || strcmp(token, "{") != 0) {
|
|
sysfatal("%s:%ud: expected \"{\"\n",
|
|
file, lineno);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* Write out parameters. */
|
|
static void
|
|
write_params(struct params *params, int *first)
|
|
{
|
|
struct params *p;
|
|
|
|
for (p = params; p != NULL; p = p->next) {
|
|
if (*first)
|
|
*first = 0;
|
|
else
|
|
printf(", ");
|
|
printf("%s %s", p->type, p->name);
|
|
}
|
|
}
|
|
|
|
/* Write a 6g function header. */
|
|
static void
|
|
write_6g_func_header(char *package, char *name, struct params *params,
|
|
int paramwid, struct params *rets)
|
|
{
|
|
int first, n;
|
|
|
|
printf("void\n%s·%s(", package, name);
|
|
first = 1;
|
|
write_params(params, &first);
|
|
|
|
/* insert padding to align output struct */
|
|
if(rets != NULL && paramwid%structround != 0) {
|
|
n = structround - paramwid%structround;
|
|
if(n & 1)
|
|
printf(", uint8");
|
|
if(n & 2)
|
|
printf(", uint16");
|
|
if(n & 4)
|
|
printf(", uint32");
|
|
}
|
|
|
|
write_params(rets, &first);
|
|
printf(")\n{\n");
|
|
}
|
|
|
|
/* Write a 6g function trailer. */
|
|
static void
|
|
write_6g_func_trailer(struct params *rets)
|
|
{
|
|
struct params *p;
|
|
|
|
for (p = rets; p != NULL; p = p->next)
|
|
printf("\tFLUSH(&%s);\n", p->name);
|
|
printf("}\n");
|
|
}
|
|
|
|
/* Define the gcc function return type if necessary. */
|
|
static void
|
|
define_gcc_return_type(char *package, char *name, struct params *rets)
|
|
{
|
|
struct params *p;
|
|
|
|
if (rets == NULL || rets->next == NULL)
|
|
return;
|
|
printf("struct %s_%s_ret {\n", package, name);
|
|
for (p = rets; p != NULL; p = p->next)
|
|
printf(" %s %s;\n", p->type, p->name);
|
|
printf("};\n");
|
|
}
|
|
|
|
/* Write out the gcc function return type. */
|
|
static void
|
|
write_gcc_return_type(char *package, char *name, struct params *rets)
|
|
{
|
|
if (rets == NULL)
|
|
printf("void");
|
|
else if (rets->next == NULL)
|
|
printf("%s", rets->type);
|
|
else
|
|
printf("struct %s_%s_ret", package, name);
|
|
}
|
|
|
|
/* Write out a gcc function header. */
|
|
static void
|
|
write_gcc_func_header(char *package, char *name, struct params *params,
|
|
struct params *rets)
|
|
{
|
|
int first;
|
|
struct params *p;
|
|
|
|
define_gcc_return_type(package, name, rets);
|
|
write_gcc_return_type(package, name, rets);
|
|
printf(" %s_%s(", package, name);
|
|
first = 1;
|
|
write_params(params, &first);
|
|
printf(") asm (\"");
|
|
if (prefix != NULL)
|
|
printf("%s.", prefix);
|
|
printf("%s.%s\");\n", package, name);
|
|
write_gcc_return_type(package, name, rets);
|
|
printf(" %s_%s(", package, name);
|
|
first = 1;
|
|
write_params(params, &first);
|
|
printf(")\n{\n");
|
|
for (p = rets; p != NULL; p = p->next)
|
|
printf(" %s %s;\n", p->type, p->name);
|
|
}
|
|
|
|
/* Write out a gcc function trailer. */
|
|
static void
|
|
write_gcc_func_trailer(char *package, char *name, struct params *rets)
|
|
{
|
|
if (rets == NULL)
|
|
;
|
|
else if (rets->next == NULL)
|
|
printf("return %s;\n", rets->name);
|
|
else {
|
|
struct params *p;
|
|
|
|
printf(" {\n struct %s_%s_ret __ret;\n", package, name);
|
|
for (p = rets; p != NULL; p = p->next)
|
|
printf(" __ret.%s = %s;\n", p->name, p->name);
|
|
printf(" return __ret;\n }\n");
|
|
}
|
|
printf("}\n");
|
|
}
|
|
|
|
/* Write out a function header. */
|
|
static void
|
|
write_func_header(char *package, char *name,
|
|
struct params *params, int paramwid,
|
|
struct params *rets)
|
|
{
|
|
if (gcc)
|
|
write_gcc_func_header(package, name, params, rets);
|
|
else
|
|
write_6g_func_header(package, name, params, paramwid, rets);
|
|
printf("#line %d \"%s\"\n", lineno, file);
|
|
}
|
|
|
|
/* Write out a function trailer. */
|
|
static void
|
|
write_func_trailer(char *package, char *name,
|
|
struct params *rets)
|
|
{
|
|
if (gcc)
|
|
write_gcc_func_trailer(package, name, rets);
|
|
else
|
|
write_6g_func_trailer(rets);
|
|
}
|
|
|
|
/*
|
|
* Read and write the body of the function, ending in an unnested }
|
|
* (which is read but not written).
|
|
*/
|
|
static void
|
|
copy_body(void)
|
|
{
|
|
int nesting = 0;
|
|
while (1) {
|
|
int c;
|
|
|
|
c = getchar_no_eof();
|
|
if (c == '}' && nesting == 0)
|
|
return;
|
|
putchar(c);
|
|
switch (c) {
|
|
default:
|
|
break;
|
|
case '{':
|
|
++nesting;
|
|
break;
|
|
case '}':
|
|
--nesting;
|
|
break;
|
|
case '/':
|
|
c = getchar_update_lineno();
|
|
putchar(c);
|
|
if (c == '/') {
|
|
do {
|
|
c = getchar_no_eof();
|
|
putchar(c);
|
|
} while (c != '\n');
|
|
} else if (c == '*') {
|
|
while (1) {
|
|
c = getchar_no_eof();
|
|
putchar(c);
|
|
if (c == '*') {
|
|
do {
|
|
c = getchar_no_eof();
|
|
putchar(c);
|
|
} while (c == '*');
|
|
if (c == '/')
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case '"':
|
|
case '\'':
|
|
{
|
|
int delim = c;
|
|
do {
|
|
c = getchar_no_eof();
|
|
putchar(c);
|
|
if (c == '\\') {
|
|
c = getchar_no_eof();
|
|
putchar(c);
|
|
c = '\0';
|
|
}
|
|
} while (c != delim);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Process the entire file. */
|
|
static void
|
|
process_file(void)
|
|
{
|
|
char *package, *name;
|
|
struct params *params, *rets;
|
|
int paramwid;
|
|
|
|
package = read_package();
|
|
read_preprocessor_lines();
|
|
while (read_func_header(&name, ¶ms, ¶mwid, &rets)) {
|
|
write_func_header(package, name, params, paramwid, rets);
|
|
copy_body();
|
|
write_func_trailer(package, name, rets);
|
|
free(name);
|
|
free_params(params);
|
|
free_params(rets);
|
|
}
|
|
free(package);
|
|
}
|
|
|
|
static void
|
|
usage(void)
|
|
{
|
|
sysfatal("Usage: goc2c [--6g | --gc] [--go-prefix PREFIX] [file]\n");
|
|
}
|
|
|
|
void
|
|
main(int argc, char **argv)
|
|
{
|
|
char *goarch;
|
|
|
|
argv0 = argv[0];
|
|
while(argc > 1 && argv[1][0] == '-') {
|
|
if(strcmp(argv[1], "-") == 0)
|
|
break;
|
|
if(strcmp(argv[1], "--6g") == 0)
|
|
gcc = 0;
|
|
else if(strcmp(argv[1], "--gcc") == 0)
|
|
gcc = 1;
|
|
else if (strcmp(argv[1], "--go-prefix") == 0 && argc > 2) {
|
|
prefix = argv[2];
|
|
argc--;
|
|
argv++;
|
|
} else
|
|
usage();
|
|
argc--;
|
|
argv++;
|
|
}
|
|
|
|
if(argc <= 1 || strcmp(argv[1], "-") == 0) {
|
|
file = "<stdin>";
|
|
process_file();
|
|
exit(0);
|
|
}
|
|
|
|
if(argc > 2)
|
|
usage();
|
|
|
|
file = argv[1];
|
|
if(freopen(file, "r", stdin) == 0) {
|
|
sysfatal("open %s: %r\n", file);
|
|
}
|
|
|
|
if(!gcc) {
|
|
// 6g etc; update size table
|
|
goarch = getenv("GOARCH");
|
|
if(goarch != NULL && strcmp(goarch, "amd64") == 0) {
|
|
type_table[Uintptr].size = 8;
|
|
type_table[String].size = 16;
|
|
type_table[Slice].size = 8+4+4;
|
|
type_table[Eface].size = 8+8;
|
|
structround = 8;
|
|
}
|
|
}
|
|
|
|
process_file();
|
|
exit(0);
|
|
}
|