| // 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. |
| |
| #include "a.h" |
| |
| /* |
| * 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. |
| */ |
| |
| static char *input; |
| static Buf *output; |
| #define EOF -1 |
| |
| enum |
| { |
| use64bitint = 1, |
| }; |
| |
| static int |
| xgetchar(void) |
| { |
| int c; |
| |
| c = *input; |
| if(c == 0) |
| return EOF; |
| input++; |
| return c; |
| } |
| |
| static void |
| xungetc(void) |
| { |
| input--; |
| } |
| |
| static void |
| xputchar(char c) |
| { |
| bwrite(output, &c, 1); |
| } |
| |
| static int |
| xisspace(int c) |
| { |
| return c == ' ' || c == '\t' || c == '\r' || c == '\n'; |
| } |
| |
| /* Whether we're emitting for gcc */ |
| static int gcc; |
| |
| /* File and line number */ |
| static const char *file; |
| static unsigned int lineno; |
| |
| /* 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. |
| * spelling as in package runtime, so intgo/uintgo not int/uint. |
| */ |
| {"bool", 1}, |
| {"float", 4}, |
| {"intgo", 4}, |
| {"uintgo", 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}, |
| {"rune", 4}, |
| {"uint32", 4}, |
| {"int64", 8}, |
| {"uint64", 8}, |
| |
| {nil, 0}, |
| }; |
| |
| /* Fixed structure alignment (non-gcc only) */ |
| int structround = 4; |
| |
| /* Unexpected EOF. */ |
| static void |
| bad_eof(void) |
| { |
| fatal("%s:%ud: unexpected EOF\n", file, lineno); |
| } |
| |
| /* Free a list of parameters. */ |
| static void |
| free_params(struct params *p) |
| { |
| while (p != nil) { |
| struct params *next; |
| |
| next = p->next; |
| xfree(p->name); |
| xfree(p->type); |
| xfree(p); |
| p = next; |
| } |
| } |
| |
| /* Read a character, tracking lineno. */ |
| static int |
| getchar_update_lineno(void) |
| { |
| int c; |
| |
| c = xgetchar(); |
| 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 = xgetchar(); |
| 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 { |
| xungetc(); |
| return '/'; |
| } |
| } |
| } |
| |
| /* |
| * Read and return a token. Tokens are string or character literals |
| * or else delimited by whitespace or by [(),{}]. |
| * The latter are all returned as single characters. |
| */ |
| static char * |
| read_token(void) |
| { |
| int c, q; |
| char *buf; |
| unsigned int alc, off; |
| char* delims = "(),{}"; |
| |
| while (1) { |
| c = getchar_skipping_comments(); |
| if (c == EOF) |
| return nil; |
| if (!xisspace(c)) |
| break; |
| } |
| alc = 16; |
| buf = xmalloc(alc + 1); |
| off = 0; |
| if(c == '"' || c == '\'') { |
| q = c; |
| buf[off] = c; |
| ++off; |
| while (1) { |
| if (off+2 >= alc) { // room for c and maybe next char |
| alc *= 2; |
| buf = xrealloc(buf, alc + 1); |
| } |
| c = getchar_no_eof(); |
| buf[off] = c; |
| ++off; |
| if(c == q) |
| break; |
| if(c == '\\') { |
| buf[off] = getchar_no_eof(); |
| ++off; |
| } |
| } |
| } else if (xstrrchr(delims, c) != nil) { |
| 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 (xisspace(c) || xstrrchr(delims, c) != nil) { |
| if (c == '\n') |
| lineno--; |
| xungetc(); |
| 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 == nil) |
| 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 == nil) |
| fatal("%s:%ud: no token\n", file, lineno); |
| if (!streq(token, "package")) { |
| fatal("%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 (xisspace(c)); |
| if (c != '#') { |
| xungetc(); |
| break; |
| } |
| xputchar(c); |
| do { |
| c = getchar_update_lineno(); |
| xputchar(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 != '*' && !streq(p, "int") && !streq(p, "uint")) |
| return p; |
| op = p; |
| pointer_count = 0; |
| while (*p == '*') { |
| ++pointer_count; |
| ++p; |
| } |
| len = xstrlen(p); |
| q = xmalloc(len + 2 + pointer_count + 1); |
| xmemmove(q, p, len); |
| |
| // Turn int/uint into intgo/uintgo. |
| if((len == 3 && xmemcmp(q, "int", 3) == 0) || (len == 4 && xmemcmp(q, "uint", 4) == 0)) { |
| q[len++] = 'g'; |
| q[len++] = 'o'; |
| } |
| |
| while (pointer_count-- > 0) |
| q[len++] = '*'; |
| |
| q[len] = '\0'; |
| xfree(op); |
| return q; |
| } |
| |
| /* Return the size of the given type. */ |
| static int |
| type_size(char *p) |
| { |
| int i; |
| |
| if(p[xstrlen(p)-1] == '*') |
| return type_table[Uintptr].size; |
| |
| for(i=0; type_table[i].name; i++) |
| if(streq(type_table[i].name, p)) |
| return type_table[i].size; |
| fatal("%s:%ud: unknown type %s\n", file, lineno, p); |
| return 0; |
| } |
| |
| /* |
| * 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 = nil; |
| pp = &ret; |
| token = read_token_no_eof(); |
| offset = 0; |
| if (!streq(token, ")")) { |
| while (1) { |
| p = xmalloc(sizeof(struct params)); |
| p->name = token; |
| p->type = read_type(); |
| p->next = nil; |
| *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 (!streq(token, ",")) |
| break; |
| token = read_token_no_eof(); |
| } |
| } |
| if (!streq(token, ")")) { |
| fatal("%s:%ud: expected '('\n", |
| file, lineno); |
| } |
| if (poffset != nil) |
| *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 == nil) |
| return 0; |
| if (streq(token, "func")) { |
| if(lastline != -1) |
| bwritef(output, "\n"); |
| break; |
| } |
| if (lastline != lineno) { |
| if (lastline == lineno-1) |
| bwritef(output, "\n"); |
| else |
| bwritef(output, "\n#line %d \"%s\"\n", lineno, file); |
| lastline = lineno; |
| } |
| bwritef(output, "%s ", token); |
| } |
| |
| *name = read_token_no_eof(); |
| |
| token = read_token(); |
| if (token == nil || !streq(token, "(")) { |
| fatal("%s:%ud: expected \"(\"\n", |
| file, lineno); |
| } |
| *params = read_params(paramwid); |
| |
| token = read_token(); |
| if (token == nil || !streq(token, "(")) |
| *rets = nil; |
| else { |
| *rets = read_params(nil); |
| token = read_token(); |
| } |
| if (token == nil || !streq(token, "{")) { |
| fatal("%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 != nil; p = p->next) { |
| if (*first) |
| *first = 0; |
| else |
| bwritef(output, ", "); |
| bwritef(output, "%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; |
| |
| bwritef(output, "void\n%s·%s(", package, name); |
| first = 1; |
| write_params(params, &first); |
| |
| /* insert padding to align output struct */ |
| if(rets != nil && paramwid%structround != 0) { |
| n = structround - paramwid%structround; |
| if(n & 1) |
| bwritef(output, ", uint8"); |
| if(n & 2) |
| bwritef(output, ", uint16"); |
| if(n & 4) |
| bwritef(output, ", uint32"); |
| } |
| |
| write_params(rets, &first); |
| bwritef(output, ")\n{\n"); |
| } |
| |
| /* Write a 6g function trailer. */ |
| static void |
| write_6g_func_trailer(struct params *rets) |
| { |
| struct params *p; |
| |
| for (p = rets; p != nil; p = p->next) |
| bwritef(output, "\tFLUSH(&%s);\n", p->name); |
| bwritef(output, "}\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 == nil || rets->next == nil) |
| return; |
| bwritef(output, "struct %s_%s_ret {\n", package, name); |
| for (p = rets; p != nil; p = p->next) |
| bwritef(output, " %s %s;\n", p->type, p->name); |
| bwritef(output, "};\n"); |
| } |
| |
| /* Write out the gcc function return type. */ |
| static void |
| write_gcc_return_type(char *package, char *name, struct params *rets) |
| { |
| if (rets == nil) |
| bwritef(output, "void"); |
| else if (rets->next == nil) |
| bwritef(output, "%s", rets->type); |
| else |
| bwritef(output, "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); |
| bwritef(output, " %s_%s(", package, name); |
| first = 1; |
| write_params(params, &first); |
| bwritef(output, ") asm (\"%s.%s\");\n", package, name); |
| write_gcc_return_type(package, name, rets); |
| bwritef(output, " %s_%s(", package, name); |
| first = 1; |
| write_params(params, &first); |
| bwritef(output, ")\n{\n"); |
| for (p = rets; p != nil; p = p->next) |
| bwritef(output, " %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 == nil) { |
| // nothing to do |
| } |
| else if (rets->next == nil) |
| bwritef(output, "return %s;\n", rets->name); |
| else { |
| struct params *p; |
| |
| bwritef(output, " {\n struct %s_%s_ret __ret;\n", package, name); |
| for (p = rets; p != nil; p = p->next) |
| bwritef(output, " __ret.%s = %s;\n", p->name, p->name); |
| bwritef(output, " return __ret;\n }\n"); |
| } |
| bwritef(output, "}\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); |
| bwritef(output, "#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; |
| xputchar(c); |
| switch (c) { |
| default: |
| break; |
| case '{': |
| ++nesting; |
| break; |
| case '}': |
| --nesting; |
| break; |
| case '/': |
| c = getchar_update_lineno(); |
| xputchar(c); |
| if (c == '/') { |
| do { |
| c = getchar_no_eof(); |
| xputchar(c); |
| } while (c != '\n'); |
| } else if (c == '*') { |
| while (1) { |
| c = getchar_no_eof(); |
| xputchar(c); |
| if (c == '*') { |
| do { |
| c = getchar_no_eof(); |
| xputchar(c); |
| } while (c == '*'); |
| if (c == '/') |
| break; |
| } |
| } |
| } |
| break; |
| case '"': |
| case '\'': |
| { |
| int delim = c; |
| do { |
| c = getchar_no_eof(); |
| xputchar(c); |
| if (c == '\\') { |
| c = getchar_no_eof(); |
| xputchar(c); |
| c = '\0'; |
| } |
| } while (c != delim); |
| } |
| break; |
| } |
| } |
| } |
| |
| /* Process the entire file. */ |
| static void |
| process_file(void) |
| { |
| char *package, *name, *p, *n; |
| struct params *params, *rets; |
| int paramwid; |
| |
| package = read_package(); |
| read_preprocessor_lines(); |
| while (read_func_header(&name, ¶ms, ¶mwid, &rets)) { |
| // name may have a package override already |
| n = xstrstr(name, "·"); |
| if(n != nil) { |
| p = xmalloc(n - name + 1); |
| xmemmove(p, name, n - name); |
| p[n - name] = 0; |
| n += xstrlen("·"); |
| } else { |
| p = package; |
| n = name; |
| } |
| write_func_header(p, n, params, paramwid, rets); |
| copy_body(); |
| write_func_trailer(p, n, rets); |
| xfree(name); |
| if(p != package) xfree(p); |
| free_params(params); |
| free_params(rets); |
| } |
| xfree(package); |
| } |
| |
| void |
| goc2c(char *goc, char *c) |
| { |
| Buf in, out; |
| |
| binit(&in); |
| binit(&out); |
| |
| file = goc; |
| readfile(&in, goc); |
| |
| // TODO: set gcc=1 when using gcc |
| |
| if(!gcc) { |
| if(streq(goarch, "amd64")) { |
| type_table[Uintptr].size = 8; |
| type_table[Eface].size = 8+8; |
| type_table[String].size = 16; |
| if(use64bitint) { |
| type_table[Int].size = 8; |
| type_table[Uint].size = 8; |
| } |
| type_table[Slice].size = 8+2*type_table[Int].size; |
| structround = 8; |
| } else { |
| // NOTE: These are set in the initializer, |
| // but they might have been changed by a |
| // previous invocation of goc2c, so we have |
| // to restore them. |
| type_table[Uintptr].size = 4; |
| type_table[String].size = 8; |
| type_table[Slice].size = 16; |
| type_table[Eface].size = 4+4; |
| type_table[Int].size = 4; |
| type_table[Uint].size = 4; |
| structround = 4; |
| } |
| } |
| |
| bprintf(&out, "// auto generated by go tool dist\n// goos=%s goarch=%s\n\n", goos, goarch); |
| input = bstr(&in); |
| output = &out; |
| |
| lineno = 1; |
| process_file(); |
| |
| writefile(&out, c, 0); |
| } |