| // 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 .cgo file into a .c file. A .cgo 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 <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <errno.h> | |
| /* 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 */ | |
| "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; | |
| /* Unexpected EOF. */ | |
| static void | |
| bad_eof(void) | |
| { | |
| fprintf(stderr, "%s:%u: unexpected EOF\n", file, lineno); | |
| exit(1); | |
| } | |
| /* Out of memory. */ | |
| static void | |
| bad_mem(void) | |
| { | |
| fprintf(stderr, "%s:%u: out of memory\n", file, lineno); | |
| exit(1); | |
| } | |
| /* 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 (strcmp(token, "package") != 0) { | |
| fprintf(stderr, | |
| "%s:%u: expected \"package\", got \"%s\"\n", | |
| file, lineno, token); | |
| exit(1); | |
| } | |
| 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; | |
| fprintf(stderr, "%s:%u: unknown type %s\n", file, lineno, p); | |
| exit(1); | |
| 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 = 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) { | |
| fprintf(stderr, "%s:%u: expected '('\n", | |
| file, lineno); | |
| exit(1); | |
| } | |
| 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) { | |
| fprintf(stderr, "%s:%u: expected \"(\"\n", | |
| file, lineno); | |
| exit(1); | |
| } | |
| *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) { | |
| fprintf(stderr, "%s:%u: expected \"{\"\n", | |
| file, lineno); | |
| exit(1); | |
| } | |
| 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 (\"%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) | |
| { | |
| fprintf(stderr, "Usage: cgo2c [--6g | --gc] [file]\n"); | |
| exit(1); | |
| } | |
| int | |
| main(int argc, char **argv) | |
| { | |
| char *goarch; | |
| 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 | |
| usage(); | |
| argc--; | |
| argv++; | |
| } | |
| if(argc <= 1 || strcmp(argv[1], "-") == 0) { | |
| file = "<stdin>"; | |
| process_file(); | |
| return 0; | |
| } | |
| if(argc > 2) | |
| usage(); | |
| file = argv[1]; | |
| if(freopen(file, "r", stdin) == 0) { | |
| fprintf(stderr, "open %s: %s\n", file, strerror(errno)); | |
| exit(1); | |
| } | |
| 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(); | |
| return 0; | |
| } |