/* ctrans.c -- Backend that generates C89 code Copyright © 2012-2017 Samuel Lidén Borell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #define _POSIX_C_SOURCE 2 #include "../backend.h" #include "../constexpr.h" #include "../misc.h" #include "../platform.h" #include "../string.h" #include "../verify.h" #if ENABLE_BACKEND_CTRANS != 0 #include #include #define STDDEFS_VERSION "1" static void add_std_defs(FILE *file) { /* Get definitions for standard C types */ /* FIXME how to get the correct sizes of non-standard types? */ fputs( "#if !defined(LRL_STDDEFS) || LRL_STDDEFS-0 < 1\n" "# if defined(__STDC__) && __STDC_VERSION__ >= 199901L\n" "# include \n" "# include \n" " typedef long long int128_t;\n" " typedef unsigned long long uint128_t;\n" " typedef long double float80;\n" " typedef long double float128;\n", file); fputs( "# elif defined(__arm__) || defined(__i386) || defined(_X86_) || defined(_M_IX86_)\n" /* Common 32-bit systems */ " typedef enum {false, true} bool;\n" " typedef signed char int8_t;\n" " typedef unsigned char uint8_t;\n" " typedef short int16_t;\n" " typedef unsigned short uint16_t;\n" " typedef int int32_t;\n" " typedef unsigned int uint32_t;\n" " typedef long long int64_t;\n" " typedef unsigned long long uint64_t;\n", file); fputs( "# define INT8_C(num) num\n" "# define UINT8_C(num) num\n" "# define INT16_C(num) num\n" "# define UINT16_C(num) num\n" "# define INT32_C(num) num\n" "# define UINT32_C(num) num##U\n" "# define INT64_C(num) num##LL\n" "# define UINT64_C(num) num##ULL\n", file); fputs( "# else\n" /* Assume 64-bit system */ " typedef enum {false, true} bool;\n" " typedef signed char int8_t;\n" " typedef unsigned char uint8_t;\n" " typedef short int16_t;\n" " typedef unsigned short uint16_t;\n" " typedef int int32_t;\n" " typedef unsigned int uint32_t;\n" " typedef long int64_t;\n" " typedef unsigned long uint64_t;\n", file); fputs( "# define INT8_C(num) num\n" "# define UINT8_C(num) num\n" "# define INT16_C(num) num\n" "# define UINT16_C(num) num\n" "# define INT32_C(num) num\n" "# define UINT32_C(num) num##U\n" "# define INT64_C(num) num##L\n" "# define UINT64_C(num) num##UL\n" "# endif\n", file); fputs( " typedef float float16;\n" /* no such type in C */ " typedef float float32;\n" " typedef double float64;\n", file); /* Get definitions of standard C functions */ fputs( "# include \n" "# include \n" /* For abs() */ "# include \n" /* For memcpy() */ "# include \n" /* For fmod() */ "# include \n", file); /* LRL-specific definitions */ fputs( " typedef struct _LRLPrivate _LRLPrivate;\n" "# if defined(__STDC__) && __STDC_VERSION__ >= 199901L\n" "# define _LRL_inline inline\n" "# else\n" "# define _LRL_inline\n" "# endif\n" "# define _LRL_array_iter_next(it,res) ((it)->elem != (it)->end ? (memcpy(res, (it)->elem++, sizeof(*res)), 1) : 0)\n", file); fputs( "# define _LRL_mkmodi(T,t) static _LRL_inline T _LRL_mod##t(T a, T n) { return a>=0 ? (n>=0?a%n:n+a%-n) : (n>=0?n-(-a)%n:-(-a%-n)); }\n" "# define _LRL_mkmodf(T,t,modf) static _LRL_inline T _LRL_mod##t(T a, T n) { return a>=0 ? (n>=0?modf(a,n):n+modf(a,-n)) : (n>=0?n-modf(-a,n):-modf(-a,-n)); }\n" " _LRL_mkmodi(int,i)\n" /* also used for int8 and int16 */ " _LRL_mkmodi(long,l)\n" " _LRL_mkmodi(long long,ll)\n" " _LRL_mkmodi(int32_t,32)\n" " _LRL_mkmodi(int64_t,64)\n", file); fputs( " _LRL_mkmodf(double,d,fmod)\n" "# if defined(__STDC__) && __STDC_VERSION__ >= 199901L\n" " _LRL_mkmodf(float,f,fmodf)\n" " _LRL_mkmodf(long double,ld,fmodl)\n" "# endif\n" "#endif\n", file); /* Assert macro */ fputs( "# ifdef __ELF__\n" "# define _LRL_assert(val, lrlexpr, file, lineno, func) do { \\\n" " if (!(val)) { __assert_fail((lrlexpr), (file), (lineno), (func)); } \\\n" " } while (0)\n" "# else\n" "# define _LRL_assert(val, lrlexpr, file, lineno, func) \\\n" " assert(val)\n" "# endif\n", file); /* Linkage keywords controlling visibility */ fputs( "\n" "#if defined(_WIN32) || defined(__CYGWIN__)\n" " #define _LRL_export __declspec(dllexport)\n" " #define _LRL_import __declspec(dllimport)\n" " #define _LRL_hidden\n" "#elif defined(__has_attribute)\n" " #if __has_attribute(visibility)\n" " #define _LRL_export __attribute__((visibility(\"default\")))\n" " #define _LRL_import __attribute__((visibility(\"default\")))\n" " #define _LRL_hidden __attribute__((visibility(\"hidden\")))\n" " #else\n", file); fputs( " #define _LRL_export\n" " #define _LRL_import\n" " #define _LRL_hidden\n" " #endif\n" "#elif __GNUC__ >= 4\n" " #define _LRL_export __attribute__((visibility(\"default\")))\n" " #define _LRL_import __attribute__((visibility(\"default\")))\n" " #define _LRL_hidden __attribute__((visibility(\"hidden\")))\n" "#else\n" " #define _LRL_export\n" " #define _LRL_import\n" " #define _LRL_hidden\n" "#endif\n" "\n", file); /* Unreachable indicator */ fputs( "\n" "#if __GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)\n" " #define _LRL_unreachable __builtin_unreachable()\n" "#elif defined(__has_builtin)\n" " #if __has_builtin(__builtin_unreachable)\n" " #define _LRL_unreachable __builtin_unreachable()\n" " #else\n" " #define _LRL_unreachable abort()\n" " #endif\n" "#else\n" " #define _LRL_unreachable abort()\n" "#endif\n", file); fputs( "\n" "#if !defined(LRL_STDDEFS) || LRL_STDDEFS-0 < " STDDEFS_VERSION "\n" "# define LRL_STDDEFS " STDDEFS_VERSION "\n" "#endif\n" "\n", file); } typedef struct { FILE *file; LRLCtx *ctx; size_t bodies_size, bodies_capacity; LRLASTDef **bodies; /* function bodies as variable initializations */ size_t alloced_temporary, used_temporary; size_t direct_use_temporary; int num_unbound; LinProbSet printed_anontypes; /* avoid printing anontypes twice */ /** Identifier of the current function. Used in assertion messages */ const LRLIdent *current_function_ident; } CTranCtx; typedef enum { IS_A_DEF = 0x1, NEED_COMPLETE = 0x2, HIDE_CAST = 0x4, ADDR_OF = 0x8, HIDE_IDENT = 0x10 } PrintFlags; #define in_def(flags) (((flags) & IS_A_DEF) != 0) #define need_complete(flags) (((flags) & NEED_COMPLETE) != 0) typedef struct { LRLASTExpr assignexpr; LRLASTExpr varexpr; LRLASTExpr addrexpr; LRLASTType ptrtype; LRLASTDefData def; } PtrAssignData; typedef struct { LRLASTExpr identexpr; LRLASTExpr derefexpr; LRLASTType ptrtype; } DerefIdentData; typedef struct { LRLASTExpr assignexpr, identexpr; } ForLoopDirectData; typedef struct { LRLASTExpr assignexpr, memberexpr, structexpr, ptrexpr; LRLASTType ptrtype; } ForLoopArrayData; static void print_type(CTranCtx *cctx, const LRLIdent *ident, const LRLASTType *type, PrintFlags flags); static void print_typeref(CTranCtx *cctx, const LRLIdent *ident, const LRLTypeRef *typeref, PrintFlags flags); static void print_nonscalar_temporary(CTranCtx *cctx, LRLASTExpr *expr, PrintFlags flags); static int valid_lvalue(const LRLASTExpr *expr); static void trans_expression(CTranCtx *cctx, LRLASTExpr *expr, PrintFlags flags); static void trans_typedef(CTranCtx *cctx, LRLASTDefType *deftype, const LRLTypeRef *refby); static void trans_varinit(CTranCtx *cctx, LRLASTDefData *defdata); static void declare_expr_temps(CTranCtx *cctx, const LRLASTExpr *expr, int indent); static void ensure_expr_defined(CTranCtx *cctx, LRLASTExpr *expr); static void make_assign_op(const LRLASTDefData *def, LRLASTExpr *expr, LRLASTExpr *varexpr); static void make_pointer_type(LRLASTExpr *expr, LRLASTType *ptrtype, LRLTypeRef *ptrtr); static void make_pointer_assign_op(LRLIdent *tempident, LRLASTExpr *operand, PtrAssignData *data); static void make_ident_expr(const LRLIdent *tempident, const LRLToken *from, const LRLToken *to, const LRLTypeRef typeref, DerefIdentData *data); static void make_for_loop_array_exprs(const LRLASTStmt *stmt, ForLoopArrayData *data); static void make_for_loop_direct_exprs(const LRLASTStmt *stmt, ForLoopDirectData *data); static void print_anonymous_type(FILE *file, unsigned long id); static void ensure_type_defined(CTranCtx *cctx, const LRLTypeRef *tr, PrintFlags flags); static LRLHashCode calc_unique_id(const LRLTypeRef *tr); /* Used when generating array iterator structs */ static const LRLASTType internal_struct_type = { LRL_AST_Type_Struct, LRL_Qual_Var, 0, NULL, NULL, { { NULL, NULL, NULL, NULL } } }; /** * Returns 1 if the given identifier conflicts with a standards library * identifier declared or included by the code generated by add_std_defs(). * The check is currently somewhat simplified and doesn't support namespaces * yet, so e.g. pthread_* will not match pthread:create. */ static int is_included_ident(const LRLIdent *ident) { static const char included[][15] = { "INT16_C", "INT32_C", "INT64_C", "INT8_C", "UIN32_C", "UINT16_C", "UINT64_C", "UINT8_C", "_Bool", "_LRL", "__acos", "__assert", "__assert_fail", "__assert_perr*", "__asin", "__atan", "__atan2", "__bzero", "__caddr_t", "__ceil", "__compar_fn_t", "__cos", "__cosh", "__ctype_*", "__daylight", "__exp", "__fabs*", "__finite*", "__floor", "__fmod", "__fpclassify*", "__frexp", "__fsid_t", "__int8_t", "__int_least8_t", "__int16_t", "__int_least16_t", "__int32_t", "__int_least32_t", "__int64_t", "__int_least64_t", "__iseqsig*", "__isinf*", "__isnan*", "__issignaling*", "__ldexp", "__log", "__log10", "__modf", "__pow", "__pthread_*", "__signbit*", "__sin", "__sinh", "__sqrt", "__strtok_r", "__tan", "__tanh", "__timezone", "abort", "abs", "acos", "asin", "atan", "atan2", "atexit", "atof", "atoi", "atol", "bool", "bsearch", "calloc", "ceil", "char", "cos", "cosh", "div", "div_t", "exit", "exp", "fabs", "false", "float128", "float16", "float32", "float64", "float80", "floor", "fmod", "free", "frexp", "getenv", "int", "int128_t", "int16_t", "int32_t", "int64_t", "int8_t", "int_fast16_t", "int_fast32_t", "int_fast64_t", "int_fast8_t", "int_least16_t", "int_least32_t", "int_least64_t", "int_least8_t", "intmax_t", "intptr_t", "labs", "ldexp", "ldiv", "ldiv_t", "log", "log10", "long", "malloc", "mblen", "mbstowcs", "mbtowc", "memchr", "memcmp", "memcpy", "memmove", "memset", "modf", "pow", "pthread_*", "ptrdiff_t", "qsort", "rand", "realloc", "short", "sin", "sinh", "size_t", "sqrt", "srand", "strcat", "strchr", "strcmp", "strcoll", "strcpy", "strcspn", "strerror", "strlen", "strncat", "strncmp", "strncpy", "strpbrk", "strrchr", "strspn", "strstr", "strtod", "strtok", "strtol", "strtoul", "strxfrm", "system", "tan", "tanh", "true", "uint128_t", "uint16_t", "uint32_t", "uint64_t", "uint8_t", "uint_fast16_t", "uint_fast32_t", "uint_fast64_t", "uint_fast8_t", "uint_least16_t", "uint_least32_t", "uint_least64_t", "uint_least8_t", "uintmax_t", "uintptr_t", "wchar_t", "wcstombs", "wctomb" }; size_t i; const char *name; size_t namelen; if (!ident) return 0; if (ident->linkname) { if (ident->linkname->loc.length <= 2) return 0; name = ident->linkname->loc.start+1; namelen = ident->linkname->loc.length-2; } else { /* Check that the parent identifiers don't have a non-empty linkname */ const LRLIdent *parent = ident->scope; while (parent) { if (parent->linkname) { if (parent->linkname->loc.length <= 2) break; else return 0; } /* Implicit linkname prefix from name or token */ if (parent->name || parent->def_token) return 0; parent = parent->scope; } if (ident->name) { name = ident->name; namelen = strlen(name); } else if (ident->def_token) { name = ident->def_token->loc.start; namelen = ident->def_token->loc.length; } else { /* this shouldn't happen */ return 0; } } /* TODO optimize We can do at least two optimizations: 1) use binary search 2) hash everything in advance and compare the hashes */ for (i = 0; i < sizeof(included)/sizeof(included[0]); i++) { const char *incl = included[i]; size_t incllen = strlen(incl); size_t cmplen; if (incl[incllen-1] == '*') { /* Look for a prefix only */ if (namelen < incllen-1) continue; cmplen = incllen-1; } else { /* Compare full name */ if (namelen != incllen) continue; cmplen = incllen; } if (!strncmp(name, incl, cmplen)) return 1; } return 0; } static void print_loc(FILE *file, const LRLCodeLocation *loc) { fprintf(file, "%.*s", size2int(loc->length), loc->start); } /** * Prints a raw string. Escapes characters that have a special meaning or * are not allowed in C. */ static void print_raw_string(FILE *file, const char *s, size_t length) { const char *end = s+length; while (s < end) { unsigned char ch = (unsigned char)*(s++); /* ? could form a trigraph */ if (ch < 32 || ch > 126 || ch == '\\' || ch == '"' || ch == '?') { fputc('\\', file); switch (ch) { case '\\': case '"': break; case 0: ch = '0'; break; case 7: ch = 'a'; break; case 8: ch = 'b'; break; case 9: ch = 't'; break; case 10: ch = 'n'; break; case 11: ch = 'v'; break; case 12: ch = 'f'; break; case 13: ch = 'r'; break; case '?': ch = '?'; break; default: { /* Hex escape */ static const char hex[16] = {'0','1','2','3','4','5', '6','7','8','9','a','b','c','d','e','f'}; char esc[3]; esc[0] = 'x'; esc[1] = hex[ch>>4]; esc[2] = hex[ch&0xF]; fwrite(esc, 1, 3, file); continue; } } } fputc(ch, file); } } /** * Prints the contents of a string token. Unescapes LRL escape sequences and * escapes any characters that have special meaning or are not allowed in C. * No surrounding quotes are printed. */ static void print_string(FILE *file, const LRLToken *tok) { const char *source = tok->loc.start; char *unescaped = malloc(tok->loc.length); char *output_end = unescaped; ++source; /* skip " */ lrl_string_parse(NULL, &source, &output_end); print_raw_string(file, unescaped, output_end-unescaped); free(unescaped); } /** * Prints the C identifier (link name). Usually this means that : is replaced * with _ in the fully qualified identifier name, but it is also possible to * specify a different name with the "linkname" keyword. */ static int print_c_ident(CTranCtx *cctx, const LRLIdent *ident) { int printed_parent; FILE *file = cctx->file; if (!ident) { /* The verifier is supposed to check that all identifiers are bound. If we get here, then there's a bug in the verifier (or perhaps in AST nodes generated by constexpr or by an interop) */ fprintf(file, "_LRLUnbound_%d", cctx->num_unbound++); fprintf(stderr, "c backend: unbound identifier!\n"); return 1; } if (ident->linkname) { /* C identifier has been overriden */ print_string(file, ident->linkname); return ident->linkname->loc.length > 2; /* not just "" (two chars) */ } ident = lrl_ident_deref_links((LRLIdent*)ident); printed_parent = ident->scope && (ident->flags & LRL_IdFl_StructMember) == 0 && print_c_ident(cctx, ident->scope); if (ident->def_token) { if (printed_parent) fputc('_', file); print_loc(file, &ident->def_token->loc); return 1; } else if (ident->name) { if (strcmp(ident->name, "-") != 0) { if (printed_parent) fputc('_', file); fputs(ident->name, file); return 1; } } return printed_parent; } /** * Prints the LRL identifier, without namespaces etc. */ static int print_ident_without_namespace(CTranCtx *cctx, const LRLIdent *ident) { FILE *file = cctx->file; if (!ident) return 0; ident = lrl_ident_deref_links((LRLIdent*)ident); if (ident->def_token) { print_loc(file, &ident->def_token->loc); return 1; } else if (ident->name) { if (strcmp(ident->name, "-") != 0) { fputs(ident->name, file); return 1; } } return 0; } static void print_indent(FILE *file, int indent) { while (indent--) fprintf(file, " "); } static void get_nameless_ident(size_t id, LRLIdent *ident) { memset(ident, 0, sizeof(LRLIdent)); ident->name = malloc(8+21+1); /* _LRLNameless + integer + null terminator */ sprintf(ident->name, "_LRLNameless%d", size2int(id)); } static void print_structtype_contents(CTranCtx *cctx, const LRLASTDefList *list, const LRLTypeRef *refby, char separator) { size_t id = 0; FILE *file = cctx->file; if (separator == ';' && list) { fputs("\n ", file); } for (; list; list = list->next) { LRLTypeRef nestedtr; LRLIdent *ident; LRLIdent temp; if ((list->def.kind.data.flags & LRL_DeFl_Incomplete) || list->def.kind.data.type->ast_type == LRL_AST_Type_Private) { /* Not supported in C */ break; } ident = (list->def.kind.data.ident ? list->def.kind.data.ident : (get_nameless_ident(id, &temp), &temp)); nestedtr = typeref_nested(list->def.kind.data.type, refby); lrl_vfy_substitute_type_params(&nestedtr.type, nestedtr.prm); /* TODO need special handling of nested bitfield types */ print_typeref(cctx, ident, &nestedtr, NEED_COMPLETE); if (separator == ';') { fputs(list->next ? ";\n " : ";\n", file); } else if (list->next) { fputs(", ", file); } id++; } } /* TODO need to prefix values' identifiers in anonymous enum types, otherwise they might collide */ static void print_enum_constants_enum(CTranCtx *cctx, LRLASTDefList *list) { FILE *file = cctx->file; fputs("\n ", file); for (; list; list = list->next) { print_c_ident(cctx, list->def.kind.data.ident); list->def.kind.data.flags |= LRL_DeFl_Internal_DefinedByBackend; if (list->def.kind.data.value && list->def.kind.data.value->ast_type != LRL_AST_Value_Undefined) { fputc('=', file); trans_expression(cctx, list->def.kind.data.value, HIDE_CAST|IS_A_DEF); } fputs(list->next ? ",\n " : "\n", file); } } static void print_enum_constants_defines(CTranCtx *cctx, LRLASTDefList *list) { FILE *file = cctx->file; for (; list; list = list->next) { fputs("#define ", file); print_c_ident(cctx, list->def.kind.data.ident); list->def.kind.data.flags |= LRL_DeFl_Internal_DefinedByBackend; if (list->def.kind.data.value) { if (list->def.kind.data.value->ast_type == LRL_AST_Value_Undefined) { /* non-integer enums may not have undefined values */ fail("ctrans_printenumdefs_undef"); } fputc(' ', file); trans_expression(cctx, list->def.kind.data.value, IS_A_DEF); } fputc('\n', file); } } static void print_enum_constants_data(CTranCtx *cctx, LRLASTDefList *list, LRLASTType *basetype) { for (; list; list = list->next) { LRLASTDefData defdata = list->def.kind.data; defdata.type = basetype; if (!(defdata.flags & LRL_DeFl_Internal_DefinedByBackend)) { list->def.kind.data.flags |= LRL_DeFl_Internal_DefinedByBackend; trans_varinit(cctx, &defdata); } } } static void print_struct(CTranCtx *cctx, const LRLTypeRef *typeref, const LRLIdent *ident) { const LRLASTType *type = typeref->type; FILE *file = cctx->file; LRLASTDefList *members; lrl_vfy_substitute_type_params(&type, typeref->prm); fprintf(file, type->ast_type == LRL_AST_Type_Union ? "union " : "struct "); if (ident) { /* Print name for use as incomplete type */ print_c_ident(cctx, ident); fputc(' ', file); } fputc('{', file); members = type->kind.struc.members; if (!members || (members->def.kind.data.flags & LRL_DeFl_Incomplete) || members->def.kind.data.type->ast_type == LRL_AST_Type_Private) { /* Empty structs are not allowed in C */ fputs("char _LRLDummy;", file); } else { print_structtype_contents(cctx, members, typeref, ';'); } fputc('}', file); } static void print_bitfield_contents(CTranCtx *cctx, const LRLASTType *bittype) { FILE *file = cctx->file; const LRLASTDefList *member; for (member = bittype->kind.bitfield.members; member; member = member->next) { /* TODO default type should depend on the number of bits */ const LRLASTType *type = (member->def.kind.data.type ? member->def.kind.data.type : lrl_builtin_get_type(LRL_BT_uint)); fputs("\n ", file); print_type(cctx, member->def.kind.data.ident, type, NEED_COMPLETE); /* Print number of bits */ fputc(':', file); if (member->def.kind.data.value) { trans_expression(cctx, member->def.kind.data.value, HIDE_CAST|IS_A_DEF); } else { fputc('1', file); } fputs(member->next ? ";" : ";\n", file); } } static const LRLIdent name_value = { 0,NULL,0, NULL,NULL, "value",NULL, NULL, NULL, 0,NULL, { 0,0,0,NULL } }; #define def_with_storage(fl) (((fl) & (LRL_DeFl_Import|LRL_DeFl_DeclOnly)) == 0) static int is_pointer(const LRLASTType *type) { type = lrl_vfy_find_real_type(type); return type && type->ast_type == LRL_AST_Type_Pointer; } static int is_int(const LRLASTType *type) { type = lrl_vfy_find_real_type(type); return type && type->ast_type == LRL_AST_Type_Builtin && type->kind.builtin == LRL_BT_int; } static int is_float(const LRLASTType *type) { type = lrl_vfy_find_real_type(type); if (type->ast_type != LRL_AST_Type_Builtin) return 0; switch (type->kind.builtin) { LRL_case_bt_floats return 1; LRL_case_bt_signedints LRL_case_bt_unsignedints LRL_case_bt_bool LRL_case_bt_specials return 0; default: fail("ctrans_isfloat_switch"); } } static int is_castable(const LRLTypeRef *typeref) { const LRLTypeRef tr = lrl_vfy_find_real_typeref(typeref); const LRLASTType *type = tr.type; return type->ast_type != LRL_AST_Type_Function && type->ast_type != LRL_AST_Type_Array && type->ast_type != LRL_AST_Type_Struct && type->ast_type != LRL_AST_Type_Union && !(type->ast_type == LRL_AST_Type_Bitfield && !type->kind.bitfield.base_type) && !(type->ast_type == LRL_AST_Type_Optional && !is_pointer(type->kind.optional.type)); } static int is_valid_switch_type(const LRLASTType *type) { type = lrl_vfy_find_real_type(type); return (type->ast_type == LRL_AST_Type_Builtin && !is_float(type)) || (type->ast_type == LRL_AST_Type_Bitfield && is_valid_switch_type(type->kind.bitfield.base_type)) || (type->ast_type == LRL_AST_Type_Enum && is_valid_switch_type(type->kind.enu.base_type)); } /* It seems to be the same */ #define is_inlineable_type is_castable #define is_scalar_type is_castable /** Returns 1 if the given expression can be translated to NULL. */ static int is_c_null(const LRLASTExpr *expr) { LRLTypeRef realtyperef; if (expr->ast_type != LRL_AST_Value_None) { return 0; } realtyperef = lrl_vfy_find_real_typeref(&expr->typeref); if (realtyperef.type->ast_type == LRL_AST_Type_Pointer) { /* Raw pointer. Translate "none" to "NULL" */ return 1; } else if (realtyperef.type->ast_type == LRL_AST_Type_Optional) { /* Optional pointer. Translate "none" to "NULL" */ LRLTypeRef valuetr; realtyperef.type = realtyperef.type->kind.optional.type; valuetr = lrl_vfy_find_real_typeref(&realtyperef); return valuetr.type->ast_type == LRL_AST_Type_Pointer; } else { fail("ctrans_badnonetype"); } } /** * Returns the identifier that the given identifier/typeidentifier * expression points to. "typeassert" identifiers are dereferened to * the backing identifier. */ static const LRLIdent *get_expr_ident(const LRLASTExpr *expr) { const LRLIdent *ident = expr->kind.ident.identref.ident; while (ident) { const LRLASTDefOrStmt *defnode; defnode = ident->def_node; if (!defnode || (defnode->ast_type != LRL_AST_Def_Data && defnode->ast_type != LRL_AST_Stmt_Decl) || (defnode->def.kind.data.flags & LRL_DeFl_Internal_TypeAssert) == 0) { /* Normal identifier */ return ident; } /* Identifier from typeassert statement */ expr = defnode->def.kind.data.value; if (expr->ast_type != LRL_AST_Value_Ident) { fail("ctrans_getexprident_typeassert_badvalue"); } ident = expr->kind.ident.identref.ident; } fail("ctrans_getexprident_null"); } /** * Creates the identifier for the temporary variable with the given id. */ static void get_temp_ident(size_t id, LRLIdent *ident) { memset(ident, 0, sizeof(LRLIdent)); ident->name = malloc(8+21+1); /* _LRLTemp + integer + null terminator */ sprintf(ident->name, "_LRLTemp%d", size2int(id)); } /** * Like get_temp_ident, but accesses a struct member of the tempident instead. * The "is_ptr" controls whether the tempident will be accessed as a pointer * (with "->") or as a normal struct (with "."). */ static void get_temp_ident_member(size_t id, LRLIdent *ident, int is_ptr, const char *member, size_t memblen) { memset(ident, 0, sizeof(LRLIdent)); ident->name = malloc(8+21+2+memblen+1); sprintf(ident->name, "_LRLTemp%d%s%.*s", size2int(id), is_ptr ? "->" : ".", size2int(memblen), member); } /** * Like get_temp_ident_member, but takes the information from a * member identifier from a struct. */ static void get_temp_ident_structptrmember(size_t id, LRLIdent *ident, char nameless[8+21+1], size_t memberindex, const LRLIdent *memberident) { if (!memberident) { /* Anonymous member */ int len = sprintf(nameless, "_LRLNameless%d", size2int(memberindex)); get_temp_ident_member(id, ident, 1, nameless, len); } else if (memberident->def_token) { get_temp_ident_member(id, ident, 1, memberident->def_token->loc.start, memberident->def_token->loc.length); } else if (memberident->name) { get_temp_ident_member(id, ident, 1, memberident->name, strlen(memberident->name)); } else { fail("ctrans_gettempident_noidentname"); } } /** * Prints the corresponding C type of a builtin type. */ static void print_builtin_type(CTranCtx *cctx, LRLBuiltinType bt) { /* FIXME calculations with "char" need to be translated to "unsigned char" and then casted to "char" to ensure that the result is the same on all platforms. This also applies to comparison operators. */ static const char translation[LRL_BT_NumTypes][19] = { /* Platform specific types (C compatible) */ "size_t", "size_t", "unsigned int", "unsigned int", "int", "unsigned int", "short", "unsigned short", "unsigned short", "unsigned short", "long", "unsigned long", "unsigned long", "unsigned long", "long long", "unsigned long long", "unsigned long long", "unsigned long long", /* Fixed width types */ "uint8_t", "char", "uint8_t", "uint8_t", "int8_t", "uint8_t", "uint16_t", "uint16_t", "int16_t", "uint16_t", "uint32_t", "uint32_t", "int32_t", "uint32_t", "uint64_t", "uint64_t", "int64_t", "uint64_t", "uint128_t", "uint128_t", "int128_t", "uint128_t", /* Floating point - TODO how to translate float##? */ "double", "float16", "float32", "float64", "float80", "float128", /* C compatible floating point */ "float", "double", "long double", "bool" }; fputs(translation[bt], cctx->file); } static void print_anonymous_type(FILE *file, unsigned long id) { fprintf(file, "_LRLAnonType%lu", id); } /** * Prints an enum definition. Non-integer enums are not defined by this * function, instead their type is typedef'd to the base type directly * and their values are either #define'd or declared as constants. */ static void print_enum(CTranCtx *cctx, const LRLASTType *type, const LRLIdent *ident) { FILE *file = cctx->file; fprintf(file, "enum "); if (ident) { /* Print name for use as incomplete type */ print_c_ident(cctx, ident); fputc(' ', file); } fputc('{', file); if (type->kind.enu.values) { print_enum_constants_enum(cctx, type->kind.enu.values); } else { /* Empty enum. Add a single "dummy value" */ fputs("_LRLEnumDummy_", file); print_c_ident(cctx, ident); } fputc('}', file); } static LRLHashCode get_ident_hash(const LRLIdent *ident) { LRLHashCode h = 0; for (; ident; ident = ident->scope) { h = hash_merge(h, ident->hash); } return h; } /** * Calculates the unique identifier for a given anonymous type. * * XXX qualifiers are currently ignored. Qualifiers do not work the same way * in LRL as in C. In C everything is var by default and const overrides * this for the whole type it's specified on. In LRL a type defaults to * being "qualifier agnostic", but const or var can also be specified. * Hence, to implement qualifiers in the translation, one would have to * generate two types for each LRL type: one with "const", and one with * the "var" qualifier */ /* TODO Possible optimizations: - We could compute the uniq_id on the type only and then "merge" it with hashes of the parameters (hash of identifiers?) and the bound types (but we might need to filter out irrelevant parameters) - or we could store the uniqe_id in the typeref. */ static LRLHashCode calc_unique_id(const LRLTypeRef *tr) { const LRLASTType *type = tr->type; LRLTypeRef nestedtr; LRLHashCode h; /* if (type->unique_id) return type->unique_id;*/ lrl_vfy_substitute_type_params(&type, tr->prm); h = hash_merge(type->ast_type, /*type->quals*/ 0); switch (type->ast_type) { case LRL_AST_Type_Array: nestedtr = typeref_nested(type->kind.array.type, tr); h = hash_merge(h, calc_unique_id(&nestedtr)); /* TODO hash the length expr */ break; case LRL_AST_Type_Builtin: h = hash_merge(h, type->kind.builtin); break; case LRL_AST_Type_Enum: { LRLASTDefList *member = type->kind.enu.values; nestedtr = typeref_nested(type->kind.enu.base_type, tr); h = hash_merge(h, calc_unique_id(&nestedtr)); for (; member; member = member->next) { if (member->def.ast_type != LRL_AST_Def_Data) fail("ctrans_uniqueid_membernotdefdata"); h = hash_merge(h, get_ident_hash(member->def.kind.data.ident)); } break; } case LRL_AST_Type_Function: nestedtr = typeref_nested(type->kind.function.ret, tr); h = hash_merge(h, calc_unique_id(&nestedtr)); nestedtr = typeref_nested(type->kind.function.args, tr); h = hash_merge(h, calc_unique_id(&nestedtr)); h ^= type->kind.function.flags; break; case LRL_AST_Type_Ident: { h = hash_merge(h, get_ident_hash(type->kind.identref.ident)); break; } case LRL_AST_Type_Optional: nestedtr = typeref_nested(type->kind.optional.type, tr); h = hash_merge(h, calc_unique_id(&nestedtr)); break; case LRL_AST_Type_Parametric: nestedtr = typeref_nested(type->kind.parametric.type, tr); h = hash_merge(h, calc_unique_id(&nestedtr)); if (type->quals & LRL_InternQual_ParametricStorage) { /* The params must come in the same order as they are defined for the type, so we can simply loop over them here. */ const LRLASTTypeList *param = type->kind.parametric.params; for (; param; param = param->next) { nestedtr = typeref_root(param->type, 0); lrl_vfy_substitute_type_params(&nestedtr.type, tr->prm); h = hash_merge(h, calc_unique_id(&nestedtr)); } } break; case LRL_AST_Type_Pointer: nestedtr = typeref_nested(type->kind.pointer.type, tr); h = hash_merge(h, calc_unique_id(&nestedtr)); break; case LRL_AST_Type_Private: case LRL_AST_Type_Any: /* Private types are always distinct, even if they are from the same place */ /* XXX what to do here? can we use the address of the container? */ /*fail("ctrans_uniqueid_privatetype");*/ break; case LRL_AST_Type_Struct: case LRL_AST_Type_Union: { LRLASTDefList *member = type->kind.struc.members; for (; member; member = member->next) { if (member->def.ast_type != LRL_AST_Def_Data) fail("ctrans_uniqueid_membernotdefdata"); nestedtr = typeref_nested(member->def.kind.data.type, tr); h = hash_merge(h, calc_unique_id(&nestedtr)); if (member->def.kind.data.ident) { h = hash_merge(0-h, member->def.kind.data.ident->hash); } } break; } case LRL_AST_Type_Bitfield: { LRLASTDefList *member = type->kind.bitfield.members; for (; member; member = member->next) { if (member->def.ast_type != LRL_AST_Def_Data) fail("ctrans_uniqueid_membernotdefdata"); if (member->def.kind.data.type) { nestedtr = typeref_nested(member->def.kind.data.type, tr); h = hash_merge(h, calc_unique_id(&nestedtr)); } /* TODO merge the bits expr */ if (member->def.kind.data.ident) { h = hash_merge(0-h, member->def.kind.data.ident->hash); } } break; } LRL_case_except_ast_types default: fail("ctrans_uniqueid_switch"); break; } h = labs((long)h); /*((LRLASTType*)type)->unique_id = h;*/ return h; } /* Hashes for internal C backend types */ #define UNIQUEID_STRUCTWRAP(h) hash_merge(h, 0x1000) /** * Prints the innermost type, e.g. "int" in "int^" and "int#[]". * This type is written to the left in C, e.g. "int a[];". */ static void print_innermost_type(CTranCtx *cctx, const LRLTypeRef *typeref, PrintFlags flags) { const LRLASTType *type = typeref->type; LRLTypeRef nestedtr; FILE *file = cctx->file; /* TODO should print qualifiers also */ lrl_vfy_substitute_type_params(&type, typeref->prm); if (!in_def(flags) && type->ast_type == LRL_AST_Type_Parametric && (type->quals & LRL_InternQual_ParametricStorage) != 0) { /* Parametric type that was instantiated as an anonymous type */ print_anonymous_type(file, calc_unique_id(typeref)); return; } switch (type->ast_type) { case LRL_AST_Type_Array: /* For example, int a[]; */ nestedtr = typeref_nested(type->kind.array.type, typeref); print_innermost_type(cctx, &nestedtr, flags); break; case LRL_AST_Type_Builtin: print_builtin_type(cctx, type->kind.builtin); break; case LRL_AST_Type_Enum: { LRLASTType *base_type = type->kind.enu.base_type; if (type == lrl_builtin_get_type(LRL_BT_bool)) { fputs("bool", file); } else if (is_int(base_type)) { /* Use indirection through a named type, so we can cast to this type. */ /* TODO need to define this anon type also! */ print_anonymous_type(file, calc_unique_id(typeref)); } else { nestedtr = typeref_nested(base_type, typeref); print_innermost_type(cctx, &nestedtr, flags); } break; } case LRL_AST_Type_Function: /* Return type is written on the left */ if (type->kind.function.ret->ast_type != LRL_AST_Type_Struct || type->kind.function.ret->kind.struc.members) { nestedtr = typeref_nested(type->kind.function.ret, typeref); print_innermost_type(cctx, &nestedtr, flags); } else { fprintf(file, "void"); } break; case LRL_AST_Type_Ident: { LRLASTType *ref = type->kind.identref.ident->def_node->def.kind.type.type; /* Identifiers are written on the left */ if (ref->ast_type == LRL_AST_Type_Struct || (ref->ast_type == LRL_AST_Type_Optional && !is_pointer(ref->kind.optional.type))) { fprintf(file, "struct "); } else if (ref->ast_type == LRL_AST_Type_Union) { fprintf(file, "union "); } else if (ref->ast_type == LRL_AST_Type_Enum && ref != lrl_builtin_get_type(LRL_BT_bool) && is_int(ref->kind.enu.base_type)) { fprintf(file, "enum "); } else if (ref->ast_type == LRL_AST_Type_Builtin) { print_builtin_type(cctx, ref->kind.builtin); break; } print_c_ident(cctx, type->kind.identref.ident); break; } case LRL_AST_Type_Optional: /* Can be merged into pointers. Otherwise its becomes a struct */ if (is_pointer(type->kind.optional.type)) { nestedtr = typeref_nested(type->kind.optional.type, typeref); print_innermost_type(cctx, &nestedtr, flags); } else { /* An anonymous struct has been generated before, use it */ print_anonymous_type(file, calc_unique_id(typeref)); } break; case LRL_AST_Type_Parametric: { LRLTypeRef boundtr = *typeref; lrl_vfy_bind_type_params(&type, &boundtr.prm); nestedtr = typeref_nested(type, &boundtr); print_innermost_type(cctx, &nestedtr, flags); break; } case LRL_AST_Type_Pointer: /* For example, int *a; */ nestedtr = typeref_nested(type->kind.pointer.type, typeref); if (lrl_vfy_is_private_type(cctx->ctx, &nestedtr)) { /* Convert to "void*" so it can be assigned to */ fputs("/*opaque*/void", file); } else { print_innermost_type(cctx, &nestedtr, flags); } break; case LRL_AST_Type_Private: fputs("_LRLPrivate", file); break; case LRL_AST_Type_Any: /* "any" can be used in pointers, like "void*" in C */ fputs("void", file); break; case LRL_AST_Type_Struct: case LRL_AST_Type_Union: /* Use indirection through a named type, so we can cast to this type. */ print_anonymous_type(file, calc_unique_id(typeref)); break; case LRL_AST_Type_Bitfield: if (type->kind.bitfield.base_type) { nestedtr = typeref_nested(type->kind.bitfield.base_type, typeref); print_innermost_type(cctx, &nestedtr, flags); } else { /* It translates to a struct */ print_anonymous_type(file, calc_unique_id(typeref)); } break; LRL_case_except_ast_types default: fail("ctrans_printinnermosttype_switch"); } } static void print_type_prefix(CTranCtx *cctx, const LRLTypeRef *typeref, int needs_no_parens) { const LRLASTType *type = typeref->type; LRLTypeRef nestedtr; FILE *file = cctx->file; lrl_vfy_substitute_type_params(&type, typeref->prm); switch (type->ast_type) { case LRL_AST_Type_Array: nestedtr = typeref_nested(type->kind.array.type, typeref); print_type_prefix(cctx, &nestedtr, 0); if (!needs_no_parens) fputc('(', file); break; case LRL_AST_Type_Enum: /* Might be of an array of pointer */ nestedtr = typeref_nested(type->kind.enu.base_type, typeref); print_type_prefix(cctx, &nestedtr, 0); break; case LRL_AST_Type_Function: fputc('(', file); if (lrl_vfy_find_real_type(type->kind.function.ret)->ast_type != LRL_AST_Type_Array) { /* Array return types aren't allowed in C and are wrapped in an anon struct */ nestedtr = typeref_nested(type->kind.function.ret, typeref); print_type_prefix(cctx, &nestedtr, 0); } if (!needs_no_parens) fputc('(', file); break; case LRL_AST_Type_Pointer: nestedtr = typeref_nested(type->kind.pointer.type, typeref); if (!lrl_vfy_is_private_type(cctx->ctx, &nestedtr)) { print_type_prefix(cctx, &nestedtr, 0); } fputc('*', file); if (!needs_no_parens) fputc('(', file); break; case LRL_AST_Type_Optional: /* Can be merged into pointers. Otherwise its becomes a struct */ if (type->kind.optional.type->ast_type == LRL_AST_Type_Pointer) { nestedtr = typeref_nested(type->kind.optional.type, typeref); print_type_prefix(cctx, &nestedtr, needs_no_parens); } break; case LRL_AST_Type_Parametric: { LRLTypeRef boundtr = *typeref; lrl_vfy_bind_type_params(&type, &boundtr.prm); nestedtr = typeref_nested(type, &boundtr); print_type_prefix(cctx, &nestedtr, needs_no_parens); break; } case LRL_AST_Type_Ident: case LRL_AST_Type_Bitfield: case LRL_AST_Type_Struct: case LRL_AST_Type_Union: case LRL_AST_Type_Builtin: case LRL_AST_Type_Private: case LRL_AST_Type_Any: break; LRL_case_except_ast_types default: fail("ctrans_typeprefix_switch"); } } static int expr_is_zero(const LRLASTExpr *expr) { return expr->ast_type == LRL_AST_Value_Scalar && expr->kind.scalar.token->type == LRL_TT_Integer && expr->kind.scalar.token->loc.length == 1 && expr->kind.scalar.token->loc.start[0] == '0'; } static void print_type_suffix(CTranCtx *cctx, const LRLTypeRef *typeref, int needs_no_parens) { const LRLASTType *type = typeref->type; LRLTypeRef nestedtr; FILE *file = cctx->file; lrl_vfy_substitute_type_params(&type, typeref->prm); switch (type->ast_type) { case LRL_AST_Type_Array: { LRLASTExpr *length = type->kind.array.length; if (!needs_no_parens) fputc(')', file); /* Don't print the length for undefined-length or runtime-length arrays. In those cases we assume that the array is wrapped in a pointer (so it can still be used as an array in C). */ if (length->ast_type != LRL_AST_Value_Undefined && lrl_constexpr_expr_is_evaluatable(NULL, length) == 1) { fputc('[', file); if (!expr_is_zero(length)) { trans_expression(cctx, length, IS_A_DEF); } else { fputs("1", file); } fputc(']', file); } nestedtr = typeref_nested(type->kind.array.type, typeref); print_type_suffix(cctx, &nestedtr, 0); break; } case LRL_AST_Type_Enum: nestedtr = typeref_nested(type->kind.enu.base_type, typeref); print_type_suffix(cctx, &nestedtr, 0); break; case LRL_AST_Type_Function: if (!needs_no_parens) fputc(')', file); fputc('(', file); if (type->kind.function.args->kind.struc.members) { /* TODO wrap array types in structs to make them passed by value */ print_structtype_contents(cctx, type->kind.function.args->kind.struc.members, typeref, ','); if (type->kind.function.args->kind.struc.flags & LRL_SF_CVarArg) { fputs(", ...", file); } } else { fputs("void", file); } fprintf(file, "))"); if (lrl_vfy_find_real_type(type->kind.function.ret)->ast_type != LRL_AST_Type_Array) { /* Array return types aren't allowed in C and are wrapped in an anon struct */ nestedtr = typeref_nested(type->kind.function.ret, typeref); print_type_suffix(cctx, &nestedtr, 0); } break; case LRL_AST_Type_Pointer: if (!needs_no_parens) fputc(')', file); nestedtr = typeref_nested(type->kind.pointer.type, typeref); if (!lrl_vfy_is_private_type(cctx->ctx, &nestedtr)) { print_type_suffix(cctx, &nestedtr, 0); } break; case LRL_AST_Type_Optional: /* Can be merged into pointers. Otherwise its becomes a struct */ if (type->kind.optional.type->ast_type == LRL_AST_Type_Pointer) { nestedtr = typeref_nested(type->kind.optional.type, typeref); print_type_suffix(cctx, &nestedtr, needs_no_parens); } break; case LRL_AST_Type_Parametric: { LRLTypeRef boundtr = *typeref; lrl_vfy_bind_type_params(&type, &boundtr.prm); nestedtr = typeref_nested(type, &boundtr); print_type_suffix(cctx, &nestedtr, needs_no_parens); break; } case LRL_AST_Type_Ident: case LRL_AST_Type_Bitfield: case LRL_AST_Type_Struct: case LRL_AST_Type_Union: case LRL_AST_Type_Builtin: case LRL_AST_Type_Private: case LRL_AST_Type_Any: break; LRL_case_except_ast_types default: fail("ctrans_typesuffix_switch"); } } static void print_typeref(CTranCtx *cctx, const LRLIdent *ident, const LRLTypeRef *typeref, PrintFlags flags) { /* TODO take typeref quals into account! */ FILE *file = cctx->file; const LRLASTType *type = typeref->type; int anon_printed = 0; /* int#[1]^ a; --> int (*a)[1]; */ if (!in_def(flags) && type->ast_type == LRL_AST_Type_Parametric && (type->quals & LRL_InternQual_ParametricStorage) != 0) { /* Parametric type that was instantiated as an anonymous type */ print_anonymous_type(file, calc_unique_id(typeref)); if (ident && (flags & HIDE_IDENT) == 0) { fputc(' ', file); print_c_ident(cctx, ident); } return; } lrl_vfy_substitute_type_params(&type, typeref->prm); if (in_def(flags)) { /* Allow "typedef ident = (some, anonymous, type);" */ if (type->ast_type == LRL_AST_Type_Struct || type->ast_type == LRL_AST_Type_Union) { print_struct(cctx, typeref, ident); anon_printed = 1; } else if (type->ast_type == LRL_AST_Type_Enum) { if (type == lrl_builtin_get_type(LRL_BT_bool)) { fail("ctrans_printtyperef_definingbool"); } else if (is_int(type->kind.enu.base_type)) { print_enum(cctx, type, ident); anon_printed = 1; } else { LRLTypeRef tr; tr.quals = 0; tr.prm = NULL; tr.type = type->kind.enu.base_type; print_typeref(cctx, ident, &tr, IS_A_DEF); return; } } else if (type->ast_type == LRL_AST_Type_Bitfield && !type->kind.bitfield.base_type) { fputs("struct ", file); print_c_ident(cctx, ident); fputc('{', file); print_bitfield_contents(cctx, type); fputc('}', file); anon_printed = 1; } else if (type->ast_type == LRL_AST_Type_Optional && !is_pointer(type->kind.optional.type)) { LRLTypeRef valuetr; fputs("struct ", file); print_c_ident(cctx, ident); fputs("{char has_value;", file); valuetr = typeref_nested(type->kind.optional.type, typeref); print_typeref(cctx, (LRLIdent*)&name_value, &valuetr, NEED_COMPLETE); fprintf(file, ";}"); anon_printed = 1; } } if (type->ast_type == LRL_AST_Type_Function) { LRLASTType *rettype = type->kind.function.ret; if (lrl_vfy_find_real_type(rettype)->ast_type == LRL_AST_Type_Array) { /* Array return values must be wrapped in a struct */ LRLTypeRef rettr = typeref_nested(rettype, typeref); print_anonymous_type(file, UNIQUEID_STRUCTWRAP(calc_unique_id(&rettr))); anon_printed = 1; } } if (!anon_printed) { print_innermost_type(cctx, typeref, flags); } fputc(' ', file); /* Print the right part of the type, with the identifier (if any). If there's no identfier we need to hide the parentheses */ print_type_prefix(cctx, typeref, ident ? 0 : 1); if (ident && (flags & HIDE_IDENT) == 0) print_c_ident(cctx, ident); print_type_suffix(cctx, typeref, ident ? 0 : 1); } static void print_type(CTranCtx *cctx, const LRLIdent *ident, const LRLASTType *type, PrintFlags flags) { LRLTypeRef typeref; typeref.quals = 0; typeref.prm = NULL; typeref.type = type; print_typeref(cctx, ident, &typeref, flags); } /** * Types that can be written as "struct name" or "enum name" can be used as * incomplete types without having been declared. */ static int needs_type_alias(const LRLASTType *type) { /* TODO what about incomplete structs and private types? */ if (type->ast_type == LRL_AST_Type_Struct) return 0; /* "struct X" */ if (type->ast_type == LRL_AST_Type_Optional && !is_pointer(type->kind.optional.type)) return 0; /* "struct X" */ if (type->ast_type == LRL_AST_Type_Bitfield && !type->kind.bitfield.base_type) return 0; /* "struct X" */ if (type->ast_type == LRL_AST_Type_Union) return 0; /* "union X" */ else return 1; /* type alias must be defined */ } /** * The name of an incomplete type must be declared before use in some cases * (e.g. in function argument lists). */ static void define_incomplete_type_name(CTranCtx *cctx, const LRLASTDef *def) { const LRLASTType *type = def->kind.type.type; if ((def->kind.type.flags & LRL_DeFl_Internal_DefinedByBackend) || is_included_ident(def->kind.type.ident)) return; if (type->ast_type == LRL_AST_Type_Struct || (type->ast_type == LRL_AST_Type_Optional && !is_pointer(type->kind.optional.type)) || (type->ast_type == LRL_AST_Type_Bitfield && !type->kind.bitfield.base_type)) { /* "struct X" */ fputs("struct ", cctx->file); print_c_ident(cctx, def->kind.ident); fputs(";\n", cctx->file); } else if (type->ast_type == LRL_AST_Type_Union) { /* "union X" */ fputs("union ", cctx->file); print_c_ident(cctx, def->kind.ident); fputs(";\n", cctx->file); } else { fail("ctrans_defincompletename_badtype"); } } /** * C does not have structural compatibility for all types, so for those types * we define a name based on the unique hash of the type. */ static void print_anontype_decl(CTranCtx *cctx, const LRLTypeRef *typeref) { LRLHashCode unid; LRLIdent anonymous; LRLTypeRef nestedtr; char name[12+21+1]; unid = calc_unique_id(typeref); /* calculate hash */ if (linprobset_add_hashcode(&cctx->printed_anontypes, unid)) return; memset(&anonymous, 0, sizeof(LRLIdent)); anonymous.name = name; sprintf(name, "_LRLAnonType%lu", unid); if (typeref->type->ast_type == LRL_AST_Type_Parametric && (typeref->type->quals & LRL_InternQual_ParametricStorage) != 0) { /* Bind the parameters */ nestedtr = *typeref; lrl_vfy_bind_type_params(&nestedtr.type, &nestedtr.prm); nestedtr.type = nestedtr.type->kind.identref.ident->def_node->def.kind.type.type; typeref = &nestedtr; /* Make sure everything needed for the type has been defined */ ensure_type_defined(cctx, typeref, NEED_COMPLETE); } fprintf(cctx->file, "#ifndef _LRL_ANONTYPE_%lu_DEFINED\n" "#define _LRL_ANONTYPE_%lu_DEFINED\n" "typedef ", unid, unid); print_typeref(cctx, &anonymous, typeref, IS_A_DEF); fprintf(cctx->file, ";\n#endif\n"); } /* * Like the function above, but used when an array value is to be returned. * This is not allowed in C so we must wrap it in a struct. */ static void print_structwrapper_anontype_decl(CTranCtx *cctx, const LRLTypeRef *typeref) { LRLIdent valuefield; LRLHashCode unid; unid = UNIQUEID_STRUCTWRAP(calc_unique_id(typeref)); if (linprobset_add_hashcode(&cctx->printed_anontypes, unid)) return; memset(&valuefield, 0, sizeof(LRLIdent)); valuefield.name = "value"; fprintf(cctx->file, "#ifndef _LRL_ANONTYPE_%lu_DEFINED\n" "#define _LRL_ANONTYPE_%lu_DEFINED\n" "typedef struct { ", unid, unid); print_typeref(cctx, &valuefield, typeref, IS_A_DEF|NEED_COMPLETE); fprintf(cctx->file, "; } _LRLAnonType%lu;\n#endif\n", unid); } /** * Ensures that the type is defined and all of the types that it needs. * Anonymous types are created as needed for structural types. * The "need_complete" parameter controls whether the types should be defined * in full to be possible to allocate or dereference/access, or if an * incomplete type is acceptable. */ static void ensure_type_defined(CTranCtx *cctx, const LRLTypeRef *tr, PrintFlags flags) { /* XXX pass typeref by value instead? */ LRLTypeRef substtr; const LRLASTType *type = tr->type; LRLTypeRef nestedtr; PrintFlags newflags = (flags & ~IS_A_DEF); if (lrl_vfy_is_private_type(cctx->ctx, tr)) { return; } if (!in_def(flags) && type->ast_type == LRL_AST_Type_Parametric && (type->quals & LRL_InternQual_ParametricStorage) != 0) { print_anontype_decl(cctx, tr); return; } substtr = *tr; lrl_vfy_substitute_type_params(&type, substtr.prm); substtr.type = type; tr = &substtr; switch (type->ast_type) { case LRL_AST_Type_Array: nestedtr = typeref_nested(type->kind.array.type, tr); ensure_type_defined(cctx, &nestedtr, newflags); break; case LRL_AST_Type_Builtin: case LRL_AST_Type_Private: case LRL_AST_Type_Any: /* Nothing needs to be done */ break; case LRL_AST_Type_Enum: if (type->kind.enu.scope && type->kind.enu.scope->def_node && (type->kind.enu.scope->def_node->ast_type == LRL_AST_Def_Type || type->kind.enu.scope->def_node->ast_type == LRL_AST_Stmt_DefType)) { /* Redefining a non-anonymous enum (Def_Type/Stmt_DefType) as an anonymous type would cause duplicate values */ if (type->kind.enu.scope->def_node->def.kind.type.flags & LRL_DeFl_Internal_DefinedByBackend) break; } nestedtr = typeref_nested(type->kind.enu.base_type, tr); ensure_type_defined(cctx, &nestedtr, newflags); /* For integer enums, we can translate them directly to C enum. Otherwise we need a special translation of the enum constants. */ if (!is_inlineable_type(&nestedtr)) { print_enum_constants_data(cctx, type->kind.enu.values, type->kind.enu.base_type); } else if (!is_int(type->kind.enu.base_type)) { print_enum_constants_defines(cctx, type->kind.enu.values); } else if (!in_def(flags) && type != lrl_builtin_get_type(LRL_BT_bool)) { /* Give the enum a unique name, so it's values don't conflict */ if (type->kind.enu.values) { /*LRLASTDefList *member = type->kind.enu.values; for (; member; member = member->next) {*/ LRLIdent *enum_ns = (LRLIdent*)type->kind.enu.values->def.kind.ident->scope; if (!enum_ns->def_token && !enum_ns->name) { char *name = malloc(12+20+1); sprintf(name, "_LRLAnonEnum%lu", calc_unique_id(tr)); enum_ns->name = name; /* XXX ugly, modifies the AST */ } } print_anontype_decl(cctx, tr); } /* TODO define anonymous enum that are directly referenced */ break; case LRL_AST_Type_Function: /* Don't define "void" */ if (type->kind.function.ret->ast_type != LRL_AST_Type_Struct || type->kind.function.ret->kind.struc.members) { nestedtr = typeref_nested(type->kind.function.ret, tr); ensure_type_defined(cctx, &nestedtr, newflags); } nestedtr = typeref_nested(type->kind.function.args, tr); ensure_type_defined(cctx, &nestedtr, newflags|IS_A_DEF); /* Define wrapping struct type for array return values */ { LRLASTType *rettype = type->kind.function.ret; if (lrl_vfy_find_real_type(rettype)->ast_type == LRL_AST_Type_Array) { LRLTypeRef rettr = typeref_nested(rettype, tr); print_structwrapper_anontype_decl(cctx, &rettr); } } break; case LRL_AST_Type_Ident: { LRLASTDef *def = &type->kind.identref.ident->def_node->def; if (need_complete(flags) || needs_type_alias(def->kind.type.type)) { /* Define it */ trans_typedef(cctx, &def->kind.type, tr); } else { /* Must define the name at least */ define_incomplete_type_name(cctx, def); } break; } case LRL_AST_Type_Optional: nestedtr = typeref_nested(type->kind.optional.type, tr); ensure_type_defined(cctx, &nestedtr, newflags); if (!in_def(flags) && !is_pointer(type->kind.optional.type)) { /* It will generate a struct, so handle it like one */ print_anontype_decl(cctx, tr); } break; case LRL_AST_Type_Parametric: if (type->quals & LRL_InternQual_ParametricStorage) { /* Parametric type needs to be instantiated as an anonymous type. This call will also define any nested types. */ print_anontype_decl(cctx, tr); } else { nestedtr = *tr; lrl_vfy_bind_type_params(&nestedtr.type, &nestedtr.prm); ensure_type_defined(cctx, &nestedtr, newflags); } break; case LRL_AST_Type_Pointer: nestedtr = typeref_nested(type->kind.pointer.type, tr); if (!lrl_vfy_is_private_type(cctx->ctx, &nestedtr)) { ensure_type_defined(cctx, &nestedtr, 0); } break; case LRL_AST_Type_Struct: case LRL_AST_Type_Union: { LRLASTDefList *entry = type->kind.struc.members; for (; entry; entry = entry->next) { nestedtr = typeref_nested(entry->def.kind.data.type, tr); ensure_type_defined(cctx, &nestedtr, newflags|NEED_COMPLETE); } if (!in_def(flags)) { /* Define anonymous structs that are directly referenced */ print_anontype_decl(cctx, tr); } break; } case LRL_AST_Type_Bitfield: { LRLASTDefList *entry = type->kind.bitfield.members; for (; entry; entry = entry->next) { if (!entry->def.kind.data.type) continue; nestedtr = typeref_nested(entry->def.kind.data.type, tr); ensure_type_defined(cctx, &nestedtr, newflags|NEED_COMPLETE); } if (!in_def(flags) && !type->kind.bitfield.base_type) { /* It will generate a struct, so handle it like one */ print_anontype_decl(cctx, tr); } break; } LRL_case_except_ast_types default: fail("ctrans_ensuretype_switch"); } } /** * Defines a type if it hasn't been defined already. Typenames are erased for * simplicity (they could probably be implemented with preprocessor macros) * * The refby parameter should be used if we might have some bound type * parameters where this typedef is used. In that case, types that would * otherwise be private can be "instantiated" with the type parameters. */ static void trans_typedef(CTranCtx *cctx, LRLASTDefType *deftype, const LRLTypeRef *refby) { LRLTypeRef tr; if ((deftype->type->quals & LRL_InternQual_ParametricStorage) != 0 && refby) { /* Incomplete type that needs type parameters to be useful */ tr = typeref_nested(deftype->type, refby); print_anontype_decl(cctx, &tr); } if ((deftype->flags & LRL_DeFl_Internal_DefinedByBackend) || lrl_is_builtin(deftype->type)) return; deftype->flags |= LRL_DeFl_Internal_DefinedByBackend; if (is_included_ident(deftype->ident)) return; tr = typeref_root(deftype->type, 0); ensure_type_defined(cctx, &tr, IS_A_DEF|NEED_COMPLETE); if ((deftype->type->quals & LRL_InternQual_ParametricStorage) != 0) { /* Unparametrized version of the type is private */ fprintf(cctx->file, "typedef _LRLPrivate "); print_c_ident(cctx, deftype->ident); } else if ((deftype->ident->flags & LRL_IdFl_FromInterop) != 0 && (!needs_type_alias(deftype->type) || deftype->type->ast_type == LRL_AST_Type_Enum)) { /* Tag types can be used without a typedef, and in case of code from C headers it might in fact be required, because identifiers in the tag and normal namespaces may conflict. */ print_type(cctx, deftype->ident, deftype->type, IS_A_DEF|HIDE_IDENT); } else { fprintf(cctx->file, "typedef "); print_type(cctx, deftype->ident, deftype->type, IS_A_DEF); } fprintf(cctx->file, ";\n"); } /** * Prints linkage keywords, such as static and extern, but also special * ones like _LRL_hidden which evaluates to a system-dependent attribute. */ static void print_linkage_keyword(CTranCtx *cctx, LRLDefFlags flags) { const char *keyword; /* "extern" is printed only for data defs, in trans_datadef */ if (flags & LRL_DeFl_Local) { keyword = "static "; } else if (flags & LRL_DeFl_Export) { keyword = "_LRL_export "; } else if (flags & LRL_DeFl_Import) { keyword = "_LRL_import "; } else { keyword = "_LRL_hidden "; } fputs(keyword, cctx->file); } /** * Declares a variable, but doesn't set the initial value (this is deferred) */ static void trans_datadef(CTranCtx *cctx, LRLASTDef *def) { LRLTypeRef tr; LRLASTDefData *defdata = &def->kind.data; if (defdata->flags & LRL_DeFl_Internal_DefinedByBackend) return; defdata->flags |= LRL_DeFl_Internal_DefinedByBackend; if (is_included_ident(defdata->ident)) return; tr = typeref_root(defdata->type, 0); ensure_type_defined(cctx, &tr, NEED_COMPLETE); /* static/extern/normal keyword */ if (defdata->flags & LRL_DeFl_DeclOnly) { fputs("extern ", cctx->file); } print_linkage_keyword(cctx, defdata->flags); /* Declaration without initial value */ print_type(cctx, defdata->ident, defdata->type, def_with_storage(defdata->flags) ? NEED_COMPLETE : 0); if (defdata->value && defdata->value->ast_type != LRL_AST_Value_Undefined) { /* Queue initialization */ list_push(&cctx->bodies, &cctx->bodies_size, &cctx->bodies_capacity, def); } fprintf(cctx->file, ";\n"); } /** * Declares a function prototype. */ static void trans_funcdef(CTranCtx *cctx, LRLASTDef *def) { LRLTypeRef tr; LRLASTDefFunction *deffunc = &def->kind.function; if (deffunc->flags & LRL_DeFl_Internal_DefinedByBackend) return; deffunc->flags |= LRL_DeFl_Internal_DefinedByBackend; if (is_included_ident(deffunc->ident)) return; tr = typeref_root(&deffunc->type, 0); ensure_type_defined(cctx, &tr, NEED_COMPLETE); print_linkage_keyword(cctx, deffunc->flags); /* Declaration without function body */ print_type(cctx, deffunc->ident, &deffunc->type, 0); if (deffunc->code) { /* Queue declaration (code block) */ list_push(&cctx->bodies, &cctx->bodies_size, &cctx->bodies_capacity, def); } fprintf(cctx->file, ";\n"); } static void trans_namespace(CTranCtx *cctx, LRLASTNamespace *ns) { LRLASTDefList *entry; for (entry = ns->list; entry; entry = entry->next) { switch (entry->def.ast_type) { case LRL_AST_Def_Type: trans_typedef(cctx, &entry->def.kind.type, NULL); break; case LRL_AST_Def_Data: trans_datadef(cctx, &entry->def); break; case LRL_AST_Def_Function: trans_funcdef(cctx, &entry->def); break; case LRL_AST_Namespace: trans_namespace(cctx, entry->def.kind.namespac); break; case LRL_AST_Uses: /* Ignored */ break; case LRL_AST_Interop: { LRLASTNamespace wrapper; wrapper.ident = NULL; wrapper.list = entry->def.kind.interop.translated; trans_namespace(cctx, &wrapper); break; } LRL_case_except_ast_namespaces_defs default: fail("ctrans_transnamespace_switch"); } } } /** * Translates an "exprlist", e.g. a function argument list or an * array element list */ static void trans_exprlist(CTranCtx *cctx, LRLASTExprList *list, const char *sep, PrintFlags flags) { size_t i; size_t count = list->num_args; for (i = 0; i < count; i++) { trans_expression(cctx, list->values[i], flags); if (i < count-1) fputs(sep, cctx->file); } } #define BAD "ERR" static const char c_ops[LRL_NumOps][4] = { "+","-","*","/","%","<<",">>","~","&","|","^",BAD,BAD,"*","&",BAD, BAD,BAD,BAD,BAD,BAD,BAD,BAD,"!","&&","||",BAD, "==","!=","<","<=",">",">=","=","+=","-=","*=","/=","<<=",">>=", }; static const char size_ops[4][9] = { "sizeof","sizeof","offsetof","_Alignof" }; static void trans_unary_expr(CTranCtx *cctx, LRLASTExpr *expr, PrintFlags flags) { switch (expr->kind.unary_op.token_type) { case LRL_Op_AddrOf: case LRL_Op_Deref: case LRL_Op_LNot: case LRL_Op_Plus: case LRL_Op_Minus: case LRL_Op_Compl: { /* Mostly the same behaviour as in C. All are prefix operators */ LRLTokenType optype = expr->kind.unary_op.token_type; LRLASTExpr *op = expr->kind.unary_op.operand; if (optype == LRL_Op_AddrOf && lrl_expr_is_literal(op)) { /* E.g. "int^ x = @0;" */ LRLIdent tempvar; size_t tempid = cctx->used_temporary++; get_temp_ident(tempid, &tempvar); if (!in_def(flags)) { LRLASTExpr assign, tempexpr; LRLASTDefData def; def.ident = &tempvar; def.flags = 0; def.type = NULL; def.value = op; make_assign_op(&def, &assign, &tempexpr); tempexpr.typeref = assign.typeref = expr->kind.unary_op.operand->typeref; fputc('(', cctx->file); trans_expression(cctx, &assign, flags|HIDE_CAST); fputc(',', cctx->file); } fputc('&', cctx->file); print_c_ident(cctx, &tempvar); if (!in_def(flags)) { fputc(')', cctx->file); } } else if (optype == LRL_Op_AddrOf) { /* The operand might not be an l-value*, so pass the ADDR_OF flag instead of printing the "&" operator directly. *) E.g. in internal "make_assign_op" expressions. */ trans_expression(cctx, op, flags|ADDR_OF|HIDE_CAST); } else if (optype == LRL_Op_Minus && op->ast_type == LRL_AST_Value_Scalar) { /* For scalar values, the "-" sign and number are printed together, so hide the cast and don't print the sign here. */ trans_expression(cctx, op, flags|HIDE_CAST); } else { fputs(c_ops[optype-LRL_FirstOp], cctx->file); trans_expression(cctx, op, flags); } break; } case LRL_Op_SizeOf: case LRL_Op_MinSizeOf: case LRL_Op_OffsetOf: case LRL_Op_AlignOf: fputs(size_ops[expr->kind.unary_op.token_type-LRL_Op_SizeOf], cctx->file); fputc('(', cctx->file); if (!expr->kind.unary_op.operand->typeref.type) fail("ctrans_sizeof_notype"); print_typeref(cctx, NULL, &expr->kind.unary_op.operand->typeref, NEED_COMPLETE); fputc(')', cctx->file); break; case LRL_Op_OptionalValue: { LRLTypeRef realtyperef = lrl_vfy_find_real_typeref(&expr->kind.unary_op.operand->typeref); if (realtyperef.type->ast_type != LRL_AST_Type_Optional) fail("ctrans_exproptional_notopt"); trans_expression(cctx, expr->kind.unary_op.operand, flags); /* Non-pointer types need a special none/null value */ if (!is_pointer(realtyperef.type->kind.optional.type)) { fprintf(cctx->file, ".value"); } break; } case LRL_Op_EnumBase: /* Nothing needs to be done in C */ trans_expression(cctx, expr->kind.unary_op.operand, flags|HIDE_CAST); break; case LRL_Op_MakeOpt: { LRLTypeRef realtyperef = lrl_vfy_find_real_typeref(&expr->kind.unary_op.operand->typeref); if (realtyperef.type->ast_type == LRL_AST_Type_Pointer) { /* C pointers are always optional types, so we don't need to do anything here */ trans_expression(cctx, expr->kind.unary_op.operand, flags|HIDE_CAST); } else if (in_def(flags)) { /* Non-pointer types must be wrapped in a struct. Inside definitions, this may be done using a literal value. */ fprintf(cctx->file, "{1,"); trans_expression(cctx, expr->kind.unary_op.operand, flags); fputc('}', cctx->file); } else { /* Non-pointer types must be wrapped in a struct. For simplicity, this is done using a temporary. */ LRLASTExpr assign, tempexpr; LRLASTDefData def; LRLIdent tempident; size_t tempid = cctx->used_temporary++; get_temp_ident_member(tempid, &tempident, 0, "value", 5); def.ident = &tempident; def.flags = 0; def.type = NULL; def.value = expr->kind.unary_op.operand; make_assign_op(&def, &assign, &tempexpr); tempexpr.typeref = assign.typeref = realtyperef; trans_expression(cctx, &assign, (flags&~ADDR_OF)|HIDE_CAST); fprintf(cctx->file, ", "); get_temp_ident(tempid, &tempident); print_c_ident(cctx, &tempident); fprintf(cctx->file, ".has_value=1, "); if ((flags & ADDR_OF) != 0) { fputc('&', cctx->file); } print_c_ident(cctx, &tempident); } break; } LRL_case_except_tt_unops default: fail("ctrans_transunaryexpr_switch"); } } /** * For pointers "none" values are simply NULL. For other types, the optional * type is made into a struct { char has_value; T value; }. In the latter * case we need to handle "none" value assignment and comparison specially. */ static int is_none_struct(const LRLASTExpr *expr, const LRLTypeRef *realtyperef) { return realtyperef->type->ast_type == LRL_AST_Type_Optional && expr->ast_type == LRL_AST_Value_None && lrl_vfy_find_real_type(realtyperef->type->kind.optional.type)->ast_type != LRL_AST_Type_Pointer; } /** * Returns 1 if the l-value in the given binary op needs a temporary. * The realtyperef may be NULL, and will be computed in that case. */ static int lvalue_needs_temporary(const LRLASTExpr *expr, const LRLTypeRef *realtyperef) { const LRLTypeRef *op2typeref = &expr->kind.binary_op.operand2->typeref; LRLTypeRef trtemp; if (expr->ast_type != LRL_AST_Expr_BinaryOp) { fail("ctrans_notbinop"); } if (!realtyperef) { const LRLTypeRef *op1typeref = &expr->kind.binary_op.operand1->typeref; trtemp = lrl_vfy_find_real_typeref(op1typeref); realtyperef = &trtemp; } /* Parametric types get transformed into anonymous types in some cases, and these can't be assigned to/from the non-anonymous parametric type, unless the type has been translated to a scalar type (e.g. a pointer) */ if (op2typeref->prm && !is_scalar_type(realtyperef)) { return 1; } return expr->kind.binary_op.operand2->ast_type == LRL_AST_Value_Array || realtyperef->type->ast_type == LRL_AST_Type_Array || is_none_struct(expr->kind.binary_op.operand2, realtyperef); } /** * Returns 1 if the signedness of two expressions differ. * Typerefs and signedness is written to output parameters. */ static int signedness_differs(const LRLASTExpr *expr1, const LRLASTExpr *expr2, LRLTypeRef *realtr1, LRLTypeRef *realtr2, int *signed1, int *signed2) { LRLBuiltinGroup btg1, btg2; /* Determine the category of integer type of expr1. eint's can be ignored, because they are by definition the common subset between uints and signed ints. */ *realtr1 = lrl_vfy_find_real_typeref(&expr1->typeref); btg1 = realtr1->type->ast_type == LRL_AST_Type_Builtin ? lrl_builtin_info[realtr1->type->kind.builtin].btgroup : 0; if (btg1 == LRL_BTG_signed || btg1 == LRL_BTG_unsigned || btg1 == LRL_BTG_wrapping) { /* Determine the category of integer type of expr2 */ *realtr2 = lrl_vfy_find_real_typeref(&expr2->typeref); btg2 = realtr2->type->ast_type == LRL_AST_Type_Builtin ? lrl_builtin_info[realtr2->type->kind.builtin].btgroup : 0; /* Check for differing signedness */ *signed1 = (btg1 == LRL_BTG_signed)?1:0; *signed2 = (btg2 == LRL_BTG_signed)?1:0; if (*signed1 != *signed2) { return 1; } } return 0; } static void trans_binary_expr(CTranCtx *cctx, LRLASTExpr *expr, PrintFlags flags) { const LRLTokenType op_type = expr->kind.binary_op.token_type; PrintFlags maybe_hide_cast = 0; LRLASTExpr *operand1 = expr->kind.binary_op.operand1; LRLASTExpr *operand2 = expr->kind.binary_op.operand2; switch (op_type) { case LRL_Op_Assign: { /* Special handling for assignments that are not supported in C: - Struct literal assignment: translated to multiple assignments - Array assignment: translated to memcpy() - None literal assignment: translated to memset() - Parametric type assignment: translated to pointer assingment. */ const LRLTypeRef *op1typeref = &operand1->typeref; const LRLTypeRef realtyperef = lrl_vfy_find_real_typeref(op1typeref); if (operand2->ast_type == LRL_AST_Value_Struct) { trans_expression(cctx, operand1, flags|HIDE_CAST); fprintf(cctx->file, "="); print_nonscalar_temporary(cctx, operand2, 0); break; } else if (lvalue_needs_temporary(expr, &realtyperef)) { LRLIdent tempident; PtrAssignData data; /* Assign pointer to temporary */ get_temp_ident(cctx->used_temporary++, &tempident); make_pointer_assign_op(&tempident, operand1, &data); trans_expression(cctx, &data.assignexpr, HIDE_CAST); if (operand2->ast_type == LRL_AST_Value_Array) { /* Array literal. These need special treatment */ fprintf(cctx->file, ", memcpy("); print_c_ident(cctx, &tempident); fprintf(cctx->file, ", "); print_nonscalar_temporary(cctx, operand2, ADDR_OF); fprintf(cctx->file, ", sizeof("); print_typeref(cctx, NULL, op1typeref, NEED_COMPLETE); fprintf(cctx->file, ")), "); } else if (realtyperef.type->ast_type == LRL_AST_Type_Array) { /* Normal array assignment */ fprintf(cctx->file, ", memcpy("); print_c_ident(cctx, &tempident); fprintf(cctx->file, ", "); /* TODO if op2 is inside a function return value then it must be placed in a temporary */ trans_expression(cctx, operand2, flags|ADDR_OF); fprintf(cctx->file, ", sizeof("); print_typeref(cctx, NULL, op1typeref, NEED_COMPLETE); fprintf(cctx->file, ")), "); } else if (is_none_struct(operand2, &realtyperef)) { /* Assigning none to a non-pointer optional type. */ fprintf(cctx->file, ", memset("); print_c_ident(cctx, &tempident); fprintf(cctx->file, ", 0, sizeof("); print_typeref(cctx, NULL, op1typeref, NEED_COMPLETE); fprintf(cctx->file, ")), "); } else if (operand2->typeref.prm && !is_scalar_type(&realtyperef)) { /* Assigning a parametric non-pointer type via a pointer */ fprintf(cctx->file, ", *"); print_c_ident(cctx, &tempident); fputc('=', cctx->file); trans_expression(cctx, operand2, flags&~ADDR_OF); fprintf(cctx->file, ", "); } /* Dereference pointer, and use as the expression's value */ if ((flags & ADDR_OF) == 0) { fputc('*', cctx->file); } print_c_ident(cctx, &tempident); break; } /* TODO handle assignment of bits in bitfields with a base_type (these aren't translated to C bitfields) */ /* Fall throught */ } case LRL_Op_PlusAssign: case LRL_Op_MinusAssign: case LRL_Op_TimesAssign: case LRL_Op_DivideAssign: case LRL_Op_ShiftLAssign: case LRL_Op_ShiftRAssign: /* Don't cast left-hand-side in assignments */ maybe_hide_cast |= HIDE_CAST; goto normal_binary; case LRL_Op_Equal: case LRL_Op_NotEqual: { const LRLTypeRef *op1tr = &operand1->typeref; LRLTypeRef realtr = lrl_vfy_find_real_typeref(op1tr); if (is_none_struct(operand1, &realtr)) { /* Comparing with the "none" value is implemented as comparing the "has_value" struct member */ trans_expression(cctx, operand2, flags); fputs(".has_value", cctx->file); fputs(c_ops[op_type-LRL_FirstOp], cctx->file); fputc('0', cctx->file); break; } else if (is_none_struct(operand2, &realtr)) { trans_expression(cctx, operand1, flags); fputs(".has_value", cctx->file); fputs(c_ops[op_type-LRL_FirstOp], cctx->file); fputc('0', cctx->file); break; } else if (realtr.type->ast_type == LRL_AST_Type_Optional && !is_pointer(realtr.type->kind.optional.type)) { /* Optional types of non-pointer type consist of a struct */ LRLIdent op1tempident, op2tempident; LRLIdent valueident1, valueident2; PtrAssignData op1data, op2data; DerefIdentData derefdata1, derefdata2; LRLASTExpr comparison; size_t tempid1, tempid2; const LRLTypeRef *op2tr = &operand2->typeref; LRLTypeRef realtr2 = lrl_vfy_find_real_typeref(op2tr); get_temp_ident(tempid1=cctx->used_temporary++, &op1tempident); get_temp_ident(tempid2=cctx->used_temporary++, &op2tempident); make_pointer_assign_op(&op1tempident, operand1, &op1data); make_pointer_assign_op(&op2tempident, operand2, &op2data); trans_expression(cctx, &op1data.assignexpr, HIDE_CAST); fprintf(cctx->file, ", "); trans_expression(cctx, &op2data.assignexpr, HIDE_CAST); fprintf(cctx->file, ", "); if (op_type == LRL_Op_NotEqual) { fputc('!', cctx->file); } /* If both are none... */ fputs("((!", cctx->file); print_c_ident(cctx, &op1tempident); fputs("->has_value && !", cctx->file); print_c_ident(cctx, &op2tempident); fputs("->has_value) || (", cctx->file); /* ...or if both have a value, and have the same value */ print_c_ident(cctx, &op1tempident); fputs("->has_value && ", cctx->file); print_c_ident(cctx, &op2tempident); fputs("->has_value && ", cctx->file); /* Build a (tmp1->value == tmp2->value) expression. Since the inversion for != is done above, we always generate a == here */ get_temp_ident_member(tempid1, &valueident1, 1, "value", 5); get_temp_ident_member(tempid2, &valueident2, 1, "value", 5); make_ident_expr(&valueident1, operand1->from, operand1->to, typeref_nested( realtr.type->kind.optional.type, &realtr), &derefdata1); make_ident_expr(&valueident2, operand2->from, operand2->to, typeref_nested( realtr2.type->kind.optional.type, &realtr2), &derefdata2); comparison.ast_type = LRL_AST_Expr_BinaryOp; comparison.from = expr->from; comparison.to = expr->to; comparison.kind.binary_op.token_type = LRL_Op_Equal; comparison.kind.binary_op.operand1 = &derefdata1.identexpr; comparison.kind.binary_op.operand2 = &derefdata2.identexpr; comparison.typeref = typeref_root(lrl_builtin_get_type(LRL_BT_bool), 0); trans_expression(cctx, &comparison, HIDE_CAST); fputs("))", cctx->file); break; } else if (realtr.type->ast_type == LRL_AST_Type_Struct) { /* Structs can not be compared directly in C, and we can also not use memcmp due to padding between the members. The expression is translated to: tmpa=&a, tmpb=&b, (tmpa->memb1==tmpb->memb1 && tmpa->memb2==tmpb->memb2 ...) */ LRLIdent op1tempident, op2tempident; PtrAssignData op1data, op2data; const LRLASTDefList *memberdef1, *memberdef2; const LRLTypeRef *op2tr = &operand2->typeref; LRLTypeRef realtr2 = lrl_vfy_find_real_typeref(op2tr); size_t tempid1, tempid2, i; get_temp_ident(tempid1=cctx->used_temporary++, &op1tempident); get_temp_ident(tempid2=cctx->used_temporary++, &op2tempident); make_pointer_assign_op(&op1tempident, operand1, &op1data); make_pointer_assign_op(&op2tempident, operand2, &op2data); trans_expression(cctx, &op1data.assignexpr, HIDE_CAST); fprintf(cctx->file, ", "); trans_expression(cctx, &op2data.assignexpr, HIDE_CAST); fprintf(cctx->file, ", "); if (op_type == LRL_Op_NotEqual) { fputc('!', cctx->file); } fputc('(', cctx->file); memberdef1 = realtr.type->kind.struc.members; memberdef2 = realtr2.type->kind.struc.members; i = 0; for (;;) { LRLIdent tmpmember1, tmpmember2; char nameless1[8+21+1]; /* "_LRLNameless" n "\0" */ char nameless2[8+21+1]; LRLASTExpr comparison, identexpr1, identexpr2; if (!memberdef1 && !memberdef2) break; /* end of struct */ else if (!memberdef1 || !memberdef2) { fail("ctrans_equalitybinop_endofstruct"); } get_temp_ident_structptrmember(tempid1, &tmpmember1, nameless1, i, memberdef1->def.kind.data.ident); get_temp_ident_structptrmember(tempid2, &tmpmember2, nameless2, i, memberdef2->def.kind.data.ident); identexpr1.ast_type = LRL_AST_Value_Ident; identexpr1.from = operand1->from; identexpr1.to = operand1->to; identexpr1.kind.ident.identref.ident = &tmpmember1; identexpr1.kind.ident.type_params = NULL; identexpr1.typeref = typeref_nested( memberdef1->def.kind.data.type, op1tr); identexpr2.ast_type = LRL_AST_Value_Ident; identexpr2.from = operand2->from; identexpr2.to = operand2->to; identexpr2.kind.ident.identref.ident = &tmpmember2; identexpr2.kind.ident.type_params = NULL; identexpr2.typeref = typeref_nested( memberdef1->def.kind.data.type, op2tr); comparison.ast_type = LRL_AST_Expr_BinaryOp; comparison.from = expr->from; comparison.to = expr->to; comparison.kind.binary_op.token_type = LRL_Op_Equal; comparison.kind.binary_op.operand1 = &identexpr1; comparison.kind.binary_op.operand2 = &identexpr2; comparison.typeref = typeref_root(lrl_builtin_get_type(LRL_BT_bool), 0); trans_expression(cctx, &comparison, HIDE_CAST); memberdef1 = memberdef1->next; memberdef2 = memberdef2->next; if (memberdef1) { fprintf(cctx->file, " && "); } i++; } fprintf(cctx->file, ")"); break; /* FIXME add special case for arrays containing structs */ } else if (!is_scalar_type(&realtr)) { /* Arrays and multi-dimensional arrays with a scalar element type can be compared with memcmp */ LRLIdent op1tempident, op2tempident; PtrAssignData op1data, op2data; get_temp_ident(cctx->used_temporary++, &op1tempident); get_temp_ident(cctx->used_temporary++, &op2tempident); make_pointer_assign_op(&op1tempident, operand1, &op1data); make_pointer_assign_op(&op2tempident, operand2, &op2data); trans_expression(cctx, &op1data.assignexpr, HIDE_CAST); fprintf(cctx->file, ", "); trans_expression(cctx, &op2data.assignexpr, HIDE_CAST); fprintf(cctx->file, ", "); if (op_type == LRL_Op_NotEqual) { fputc('!', cctx->file); /* !! converts to boolean */ } fprintf(cctx->file, "!memcmp("); print_c_ident(cctx, &op1tempident); fprintf(cctx->file, ", "); print_c_ident(cctx, &op2tempident); fprintf(cctx->file, ", sizeof("); print_typeref(cctx, NULL, &operand1->typeref, NEED_COMPLETE); fprintf(cctx->file, "))"); break; } } /* Fall through */ case LRL_Op_Less: case LRL_Op_LessEqual: case LRL_Op_Greater: case LRL_Op_GreaterEqual: { /* Check if we compare integers, and if so handle differing signedness. Note that C uses "unsigned int" in case of mixed signedness, so it's safe to compare with eint directly because it's translated to a signed type. */ /* TODO check what happens with long, longlong etc. */ LRLTypeRef realtr1, realtr2; int lsign, rsign; if (signedness_differs(operand1, operand2, &realtr1, &realtr2, &lsign, &rsign)) { int which; LRLIdent ltemp, rtemp; get_temp_ident(cctx->used_temporary++, <emp); get_temp_ident(cctx->used_temporary++, &rtemp); /* Assign to temporaries first */ print_c_ident(cctx, <emp); fputc('=', cctx->file); trans_expression(cctx, operand1, flags); fputc(',', cctx->file); print_c_ident(cctx, &rtemp); fputc('=', cctx->file); trans_expression(cctx, operand2, flags); fputc(',', cctx->file); /* Check sign of the signed operand */ switch (op_type) { case LRL_Op_Equal: which = 1; break; case LRL_Op_NotEqual: which = 0; break; case LRL_Op_Less: case LRL_Op_LessEqual: which = rsign; break; case LRL_Op_Greater: case LRL_Op_GreaterEqual: which = lsign; break; LRL_case_except_tt_equality_binops default: fail("ctrans_equalitybinop_switch"); } print_c_ident(cctx, lsign ? <emp : &rtemp); fputs(which ? ">=0 && " : "<0 || ", cctx->file); /* Make actual comparison */ print_c_ident(cctx, <emp); fputs(c_ops[op_type-LRL_FirstOp], cctx->file); print_c_ident(cctx, &rtemp); break; } goto normal_binary; } case LRL_Op_Plus: case LRL_Op_Minus: case LRL_Op_Times: case LRL_Op_Divide: case LRL_Op_ShiftL: case LRL_Op_ShiftR: case LRL_Op_BitAnd: case LRL_Op_BitOr: case LRL_Op_BitXor: case LRL_Op_LAnd: case LRL_Op_LOr: normal_binary: /* Same behaviour as in C */ trans_expression(cctx, operand1, flags|maybe_hide_cast); fputs(c_ops[op_type-LRL_FirstOp], cctx->file); trans_expression(cctx, operand2, flags); break; case LRL_Op_Modulo: { /* Modulo is always positive in LRL and supports both ints and floats */ LRLTypeRef realtyperef = lrl_vfy_find_real_typeref(&expr->typeref); const char *modfunction = NULL; if (!realtyperef.type) { fail("ctrans_transbinaryexpr_notype"); return; } else if (realtyperef.type->ast_type != LRL_AST_Type_Builtin) { fail("ctrans_transbinaryexpr_badtype"); return; } switch (realtyperef.type->kind.builtin) { /* Nothing needs to be done for positive integers */ LRL_case_bt_unsignedints break; case LRL_BT_int8: case LRL_BT_int16: case LRL_BT_short: case LRL_BT_int: modfunction = "i"; break; case LRL_BT_int32: modfunction = "32"; break; case LRL_BT_long: modfunction = "l"; break; case LRL_BT_int64: modfunction = "64"; break; case LRL_BT_int128: case LRL_BT_longlong: modfunction = "ll"; break; case LRL_BT_float16: case LRL_BT_float32: case LRL_BT_cfloat: modfunction = "f"; break; case LRL_BT_float: case LRL_BT_float64: case LRL_BT_cdouble: modfunction = "d"; break; case LRL_BT_float80: case LRL_BT_float128: case LRL_BT_clongdouble: modfunction = "ld"; break; case LRL_BT_bool: case LRL_BT_NumTypes: fail("ctrans_transbinaryexpr_notnumbertype"); } /* Modulo */ if (!modfunction) { /* Unsigned integer modulo using the % operator */ trans_expression(cctx, operand1, flags); fputs(c_ops[LRL_Op_Modulo-LRL_FirstOp], cctx->file); trans_expression(cctx, operand2, flags); } else { /* Use a LRL-defined function (which in turn might call something in math.h) */ fprintf(cctx->file, "_LRL_mod%s(", modfunction); trans_expression(cctx, operand1, flags); fputc(',', cctx->file); trans_expression(cctx, operand2, flags); fputc(')', cctx->file); } break; } case LRL_Op_LXor: /* Doesn't exist in C. Use (!(a) ^ !(b)) */ fputc('!', cctx->file); trans_expression(cctx, operand1, flags); fprintf(cctx->file, "^!"); trans_expression(cctx, operand2, flags); break; LRL_case_except_tt_binops default: fail("ctrans_transbinaryexpr_switch"); } } static int is_integral_number(const LRLCodeLocation *loc) { const char *start = loc->start; const char *end = loc->start + loc->length; while (start < end) { char ch = *(start++); if ((ch < '0' || ch > '9') && ch != '_') return 0; } return 1; } static void print_number(CTranCtx *cctx, const LRLCodeLocation *loc) { FILE *file = cctx->file; const char *s = loc->start; size_t len = loc->length; /* Skip initial zeros (would cause octal interpretation in C) */ while (len > 1 && (*s == '0' || *s == '_') && s[1] != 'x') { s++; len--; } while (len > 0) { char c = *s; if (c != '_') { fputc(c, file); } len--; s++; } } static const char *const minint_plusone[] = { "32767", "2147483647", "9223372036854775807", "170141183460469231731687303715884105727" }; /** * Check if the number is possibly the minimum value of it's C type. * If it is, then it returns the corresponding index of the number+1 in the * "minint_plusone" table. Otherwise, it returns -1. */ static int match_min_int(LRLBuiltinType builtin_type, const LRLCodeLocation *num) { static const struct { int len; const char *num; } decmin[] = { { 5, "32768" }, { 10, "2147483648" }, { 19, "9223372036854775808" }, { 39, "170141183460469231731687303715884105728" }, }; short minsize, maxsize; const char *s = num->start; size_t len = num->length; /* Check possible minimum values */ switch (builtin_type) { case LRL_BT_int8: /* C literal types are never smaller than int */ return -1; case LRL_BT_short: minsize = 0; maxsize = 3; break; case LRL_BT_int: minsize = 0; maxsize = 3; break; case LRL_BT_long: minsize = 1; maxsize = 3; break; case LRL_BT_longlong: minsize = 2; maxsize = 3; break; case LRL_BT_int16: minsize = 0; maxsize = 0; break; case LRL_BT_int32: minsize = 1; maxsize = 1; break; case LRL_BT_int64: minsize = 2; maxsize = 2; break; case LRL_BT_int128: minsize = 3; maxsize = 3; break; LRL_case_bt_unsignedints LRL_case_bt_floats LRL_case_bt_bool LRL_case_bt_specials default: fail("ctrans_minint_badtype"); } /* Skip initial zeros (would cause octal interpretation in C) */ while (len > 1 && (*s == '0' || *s == '_') && s[1] != 'x') { s++; len--; } if (len > 2 && strncmp(s, "0x", 2) == 0) { /* Hexadecimal */ /* Match against 0x8000... and check size */ int numzeros = 0; s += 2; len -= 2; while (len && (*s == '0' || *s == '_')) { s++; len--; } if (*s != '8') return 1; /* All min ints start with 0x8... */ s++; len--; while (len && (*s == '0' || *s == '_')) { if (*s == '0') numzeros++; s++; len--; } if (len) return -1; /* Now check if it was the minimum integer */ if (numzeros == 3 && minsize == 0) return 0; else if (numzeros == 5 && minsize <= 1) return 1; else if (numzeros == 15 && minsize <= 2) return 2; else if (numzeros == 31 && minsize <= 3) return 3; else return -1; } else { /* Decimal */ size_t len1 = len; const char *s1 = s; short i; int numdigits = 0; while (len1) { if (*s1 != '_') numdigits++; s1++; len1--; } for (i = minsize; i <= maxsize; i++) { const char *min; len1 = len; s1 = s; /* Return 1 if equal */ if (decmin[i].len != numdigits) continue; min = decmin[i].num; while (len1) { if (*s1 != '_') { if (*s1 != *min) goto nextsize; min++; } s1++; len1--; } return i; nextsize:; } return -1; } } static void print_scalar(CTranCtx *cctx, const LRLASTExpr *expr) { switch (expr->kind.scalar.token->type) { case LRL_TT_String: fputc('"', cctx->file); print_string(cctx->file, expr->kind.scalar.token); fputc('"', cctx->file); break; case LRL_TT_Integer: case LRL_TT_Real: { LRLTypeRef exprtyperef; const LRLCodeLocation *num = &expr->kind.scalar.token->loc; const char *prefix = NULL, *suffix = NULL; int is_float_type = 0; int minint; /* Determine how to print the type */ exprtyperef = lrl_vfy_find_real_typeref(&expr->typeref); if (exprtyperef.type) { if (exprtyperef.type->ast_type != LRL_AST_Type_Builtin) fail("ctrans_transexpr_intscalarnotbuiltintype"); /* For fixed-size integers we use the C99 macros, for system-dependent size number types we use suffixes XXX L, LL and F have platform-specific sizes, corresponding to long, long long and float */ switch (exprtyperef.type->kind.builtin) { case LRL_BT_int: case LRL_BT_eint: case LRL_BT_short: case LRL_BT_eshort: case LRL_BT_float: case LRL_BT_cdouble: case LRL_BT_float64: /* No suffix */ break; case LRL_BT_count: case LRL_BT_wcount: case LRL_BT_uint: case LRL_BT_wuint: case LRL_BT_ushort: case LRL_BT_wushort: suffix = "U"; break; case LRL_BT_long: case LRL_BT_elong: case LRL_BT_clongdouble: case LRL_BT_float80: case LRL_BT_float128: suffix = "L"; break; case LRL_BT_ulong: case LRL_BT_wulong: suffix = "UL"; break; case LRL_BT_longlong: case LRL_BT_elonglong: suffix = "LL"; break; case LRL_BT_ulonglong: case LRL_BT_wulonglong: suffix = "ULL"; break; case LRL_BT_cfloat: case LRL_BT_float16: case LRL_BT_float32: suffix = "F"; break; case LRL_BT_int8: case LRL_BT_eint8: prefix = "INT8_C("; suffix = ")"; break; case LRL_BT_byte: case LRL_BT_uint8: case LRL_BT_wuint8: prefix = "UINT8_C("; suffix = ")"; break; case LRL_BT_char: prefix = "((char)UINT8_C("; suffix = "))"; break; case LRL_BT_int16: case LRL_BT_eint16: prefix = "INT16_C("; suffix = ")"; break; case LRL_BT_uint16: case LRL_BT_wuint16: prefix = "UINT16_C("; suffix = ")"; break; case LRL_BT_eint32: case LRL_BT_int32: prefix = "INT32_C("; suffix = ")"; break; case LRL_BT_uint32: case LRL_BT_wuint32: prefix = "UINT32_C("; suffix = ")"; break; case LRL_BT_int64: case LRL_BT_eint64: prefix = "INT64_C("; suffix = ")"; break; case LRL_BT_uint64: case LRL_BT_wuint64: prefix = "UINT64_C("; suffix = ")"; break; case LRL_BT_int128: case LRL_BT_eint128: /* int128 literals aren't supported in C */ suffix = "LL"; break; case LRL_BT_uint128: case LRL_BT_wuint128: suffix = "ULL"; break; case LRL_BT_bool: case LRL_BT_NumTypes: fail("ctrans_transscalar_notnumtype"); } is_float_type = is_float(exprtyperef.type); } /* Print the number */ if (prefix) fputs(prefix, cctx->file); if (exprtyperef.type && expr->kind.scalar.is_negative && !is_float_type && (minint = match_min_int(exprtyperef.type->kind.builtin, num)) >= 0) { /* Special handling for -(2^n) which gets the wrong type in C. We avoid these numbers by instead printing (n+1)-1 */ fputc('-', cctx->file); fputs(minint_plusone[minint], cctx->file); if (suffix) fputs(suffix, cctx->file); fputc('-', cctx->file); if (prefix) fputs(prefix, cctx->file); fputc('1', cctx->file); } else { /* Numbers except for -(2^n) are printed as usual */ if (expr->kind.scalar.is_negative) { fputc('-', cctx->file); } print_number(cctx, num); if (exprtyperef.type) { /* Need to add .0 on integral reals */ if (is_float_type && is_integral_number(num)) { fputs(".0", cctx->file); } } } if (suffix) fputs(suffix, cctx->file); break; } case LRL_TT_Ident: case LRL_TT_Undefined: case LRL_TT_None: case LRL_TT_NaN: case LRL_TT_Inf: case LRL_KW_Here: LRL_case_except_tt_values default: fail("ctrans_transscalar_switch"); break; } } /** Used internally in print_nonscalar_temporary to print nested arrays and structs. */ typedef struct NonScalarElement { const struct NonScalarElement *container; /* name/index of this element */ const LRLIdent *ident; int index; } NonScalarElement; /** * Recursively print all parts of an element identifer. E.g. * LRLTemp0.a.b#[1,30].c.d#[1].e */ static void handle_nonscalar_ident(CTranCtx *cctx, const NonScalarElement *elem) { if (elem->container) { handle_nonscalar_ident(cctx, elem->container); } if (elem->ident) { if (elem->container) fputc('.', cctx->file); print_c_ident(cctx, elem->ident); } else { fprintf(cctx->file, "[%d]", elem->index); } } static void handle_nonscalar_element(CTranCtx *cctx, const NonScalarElement *container, const LRLASTExpr *expr) { size_t i; const LRLASTDefList *memberdef; const int is_struct = (expr->ast_type == LRL_AST_Value_Struct); /* TODO add support for bitfields and handle bitfields inside structs */ if (is_struct) { LRLTypeRef structref = lrl_vfy_find_real_typeref(&expr->typeref); if (structref.type->ast_type != LRL_AST_Type_Struct) fail("ctrans_transexpr_notstructtype"); memberdef = structref.type->kind.struc.members; } /* Assign each element in the temp var */ for (i = 0; i < expr->kind.exprlist.num_args; i++) { if (expr->kind.exprlist.values[i]->ast_type != LRL_AST_Value_Undefined) { /* Print array index / struct member in temp var */ LRLASTExpr *value = expr->kind.exprlist.values[i]; NonScalarElement elem; elem.container = container; if (is_struct) { /* Struct member */ LRLIdent temp; elem.ident = (memberdef->def.kind.data.ident ? memberdef->def.kind.data.ident : (get_nameless_ident(i, &temp), &temp)); if (!elem.ident) fail("ctrans_transexpr_memberwithouident"); } else { /* Array index */ elem.ident = NULL; elem.index = size2int(i); } if (value->ast_type == LRL_AST_Value_Array || value->ast_type == LRL_AST_Value_Struct) { /* Go inside nested structures and assign each of their elements */ handle_nonscalar_element(cctx, &elem, value); } else { /* Print names/indices of element */ handle_nonscalar_ident(cctx, &elem); /* Value */ fputc('=', cctx->file); trans_expression(cctx, value, 0); fputs(", ", cctx->file); } } if (is_struct) { memberdef = memberdef->next; } } } /** * Translates an array or struct literal using a temporary variable. * "none" values that are represented as structs are also handled by this * function. * * If "use_address" is set then it will evaluate to an address to the * temporary, instead of the temporary variable itself. */ static void print_nonscalar_temporary(CTranCtx *cctx, LRLASTExpr *expr, PrintFlags flags) { LRLIdent tempvar; /* Get temporary variable */ size_t tempid = cctx->used_temporary++; get_temp_ident(tempid, &tempvar); fputc('(', cctx->file); if (expr->ast_type == LRL_AST_Value_None) { fprintf(cctx->file, "memset(&("); print_c_ident(cctx, &tempvar); fprintf(cctx->file, "), 0, sizeof("); print_c_ident(cctx, &tempvar); fprintf(cctx->file, ")), "); } else { /* Struct or array */ NonScalarElement root; root.container = NULL; root.ident = &tempvar; root.index = 0; handle_nonscalar_element(cctx, &root, expr); } if (flags & ADDR_OF) { fputc('&', cctx->file); } print_c_ident(cctx, &tempvar); fputc(')', cctx->file); } /** Returns 1 if the value is a valid lvalue in C. Note that this may be different from LRL */ static int valid_lvalue(const LRLASTExpr *expr) { LRLASTNodeType ast_type = expr->ast_type; for (;;) { if (ast_type == LRL_AST_Expr_As) { expr = expr->kind.asexpr.expr; } else if (ast_type == LRL_AST_Expr_TypeAssert) { expr = expr->kind.typeassert.expr; } else break; ast_type = expr->ast_type; } switch (ast_type) { case LRL_AST_Expr_UnaryOp: if (expr->kind.unary_op.token_type != LRL_Op_AddrOf && expr->kind.unary_op.token_type != LRL_Op_Deref && expr->kind.unary_op.token_type != LRL_Op_OptionalValue) { return 0; } /* Fall through */ case LRL_AST_Value_Ident: case LRL_AST_Value_TypeIdent: case LRL_AST_Expr_ArrayIndex: case LRL_AST_Expr_Member: /* but not "function members" */ return 1; case LRL_AST_Value_Undefined: case LRL_AST_Value_None: case LRL_AST_Value_NaN: case LRL_AST_Value_Inf: case LRL_AST_Value_Scalar: case LRL_AST_Value_Struct: case LRL_AST_Value_Array: case LRL_AST_Expr_FuncMember: case LRL_AST_Expr_Call: case LRL_AST_Expr_As: case LRL_AST_Expr_TypeAssert: case LRL_AST_Expr_BinaryOp: case LRL_AST_Expr_Conditional: return 0; case LRL_AST_Expr_Evaluated: return valid_lvalue(expr->kind.evaluated.evaluated); LRL_case_except_ast_exprs_values default: fail("ctrans_validlvalue_switch"); } } static void trans_expression(CTranCtx *cctx, LRLASTExpr *expr, PrintFlags flags) { int extra_paren = 0; int needs_no_parens; if (expr->ast_type == LRL_AST_Expr_Evaluated) { trans_expression(cctx, expr->kind.evaluated.evaluated, flags); return; } needs_no_parens = (expr->ast_type == LRL_AST_Value_Ident || expr->ast_type == LRL_AST_Value_TypeIdent || expr->ast_type == LRL_AST_Value_None || expr->ast_type == LRL_AST_Value_NaN || expr->ast_type == LRL_AST_Value_Inf || expr->ast_type == LRL_AST_Value_Scalar || expr->ast_type == LRL_AST_Value_Array || expr->ast_type == LRL_AST_Value_Struct || (expr->ast_type == LRL_AST_Expr_UnaryOp && expr->kind.unary_op.token_type==LRL_Op_MakeOpt && in_def(flags))); /* Cast to the right type. Functions (not function pointers) and arrays can't be casted. */ /* TODO remove casts to the same type */ if ((flags & HIDE_CAST) == 0 && expr->typeref.type != &internal_struct_type) { if (is_castable(&expr->typeref)) { fputc('(', cctx->file); print_typeref(cctx, NULL, &expr->typeref, NEED_COMPLETE); fputc(')', cctx->file); } else if (!in_def(flags) && expr->typeref.type->ast_type != LRL_AST_Type_Function && expr->typeref.type->ast_type != LRL_AST_Type_Array) { /* Non-scalars can't be casted in C. But we can cast a pointer to the value, then dereference the pointer. */ if (valid_lvalue(expr)) { fputs("(*(", cctx->file); print_typeref(cctx, NULL, &expr->typeref, NEED_COMPLETE); fputs("*)", cctx->file); flags |= ADDR_OF; needs_no_parens = 0; extra_paren = 1; } } } flags &= ~HIDE_CAST; if ((flags & ADDR_OF) && valid_lvalue(expr)) { /* Time to add the "address of" operator. For some types of expressions, a comma expression is used to compute some value and put it in a temporary. In such cases, the & is added to the final operand inside the comma expression. */ fputc('&', cctx->file); flags &= ~ADDR_OF; } if (!needs_no_parens) fputc('(', cctx->file); switch (expr->ast_type) { case LRL_AST_Value_TypeIdent: case LRL_AST_Value_Ident: { const LRLIdent *ident = get_expr_ident(expr); print_c_ident(cctx, ident); /* TODO what about type params on identifiers? */ break; } case LRL_AST_Value_Undefined: /* TODO what to do? */ fprintf(cctx->file, "UNDEFINED/*FIXME how to translate undefined to C */"); break; case LRL_AST_Value_None: { if (is_c_null(expr)) { fprintf(cctx->file, "NULL"); } else if (in_def(flags)) { fprintf(cctx->file, "{0,}"); /* has_value = 0. The rest becomes zero too. */ } else { print_nonscalar_temporary(cctx, expr, flags); } break; } case LRL_AST_Value_NaN: fprintf(cctx->file, "NAN"); break; case LRL_AST_Value_Inf: fprintf(cctx->file, "INFINITY"); break; case LRL_AST_Value_Scalar: /* Scalar. Depends on token type */ print_scalar(cctx, expr); break; case LRL_AST_Value_Array: case LRL_AST_Value_Struct: { if (in_def(flags)) { /* Don't use a temporary in static initializations! */ fputc('{', cctx->file); if (expr->kind.exprlist.num_args) { trans_exprlist(cctx, &expr->kind.exprlist, ", ", IS_A_DEF); } else { /* Empty is not allowed in C */ fputs("0/*dummy*/", cctx->file); } fputc('}', cctx->file); } else { print_nonscalar_temporary(cctx, expr, flags); } break; } case LRL_AST_Expr_ArrayIndex: /* a#[x,y] --> a[x][y] */ trans_expression(cctx, expr->kind.index.array, HIDE_CAST); /* don't cast arrays */ fputc('[', cctx->file); trans_expression(cctx, expr->kind.index.index, 0); fputc(']', cctx->file); break; case LRL_AST_Expr_Call: { const LRLTypeRef rettyperef = lrl_vfy_find_real_typeref(&expr->typeref); int is_array_ret = rettyperef.type->ast_type == LRL_AST_Type_Array; LRLIdent tempvar_ret; /* TODO should always use a temporary if we take the address of a return value */ if (is_array_ret) { size_t tempid = cctx->used_temporary++; get_temp_ident(tempid, &tempvar_ret); print_c_ident(cctx, &tempvar_ret); fputc('=', cctx->file); } else if (flags & ADDR_OF) { fail("ctrans_transexpr_addrofreturn_notimpl"); } /* TODO should handle array arguments correctly (should be passed as values/structs) */ if (expr->kind.call.function->ast_type == LRL_AST_Expr_FuncMember) { /* obj->f(x,y) --> f(obj,x,y) */ LRLASTExpr *memberexpr = expr->kind.call.function; print_c_ident(cctx, memberexpr->kind.member.ident); fputc('(', cctx->file); /* "this" argument */ trans_expression(cctx, memberexpr->kind.member.struc, 0); if (expr->kind.call.args.num_args) { fputs(", ", cctx->file); trans_exprlist(cctx, &expr->kind.call.args, ", ", 0); } fputc(')', cctx->file); } else { /* a(x,y) --> a(x,y) */ trans_expression(cctx, expr->kind.call.function, HIDE_CAST); fputc('(', cctx->file); trans_exprlist(cctx, &expr->kind.call.args, ", ", 0); fputc(')', cctx->file); } if (is_array_ret) { fputc(',', cctx->file); if (flags & ADDR_OF) { fputc('&', cctx->file); flags &= ~ADDR_OF; } print_c_ident(cctx, &tempvar_ret); /* If the return type is an array, it gets wrapped in a struct in the C code (since C doesn't allow returning arrays) */ fputs(".value", cctx->file); } break; } case LRL_AST_Expr_Member: /* a.x --> a.x */ /* TODO need special handling for bits in bitfields with a base_type (these aren't translated to C bitfields) */ trans_expression(cctx, expr->kind.member.struc, 0); /* TODO we need to skip this for merged bitfields (i.e. if expr->kind.member.ident is a bitfield) */ fputc('.', cctx->file); print_c_ident(cctx, expr->kind.member.ident); break; case LRL_AST_Expr_FuncMember: fail("ctrans_transexpr_barefunctionmember"); break; case LRL_AST_Expr_As: trans_expression(cctx, expr->kind.asexpr.expr, flags); break; case LRL_AST_Expr_TypeAssert: /* TODO actually implement the range check */ trans_expression(cctx, expr->kind.typeassert.expr, flags); break; case LRL_AST_Expr_UnaryOp: trans_unary_expr(cctx, expr, flags); break; case LRL_AST_Expr_BinaryOp: trans_binary_expr(cctx, expr, flags); break; case LRL_AST_Expr_Conditional: trans_expression(cctx, expr->kind.conditional.condexpr, flags); fputc('?', cctx->file); trans_expression(cctx, expr->kind.conditional.trueexpr, flags); fputc(':', cctx->file); trans_expression(cctx, expr->kind.conditional.falseexpr, flags); break; case LRL_AST_Expr_Evaluated: /* handled above */ LRL_case_except_ast_exprs_values default: fail("ctrans_transexpr_switch"); } if (!needs_no_parens) fputc(')', cctx->file); if (extra_paren) fputc(')', cctx->file); } static void ensure_exprlist_defined(CTranCtx *cctx, LRLASTExprList *list) { size_t i; for (i = 0; i < list->num_args; i++) { ensure_expr_defined(cctx, list->values[i]); } } static void ensure_defstmt_defined(CTranCtx *cctx, LRLASTDefOrStmt *defstmt) { switch (defstmt->ast_type) { case LRL_AST_Def_Data: trans_datadef(cctx, &defstmt->def); break; case LRL_AST_Def_Function: trans_funcdef(cctx, &defstmt->def); break; case LRL_AST_Def_Type: trans_typedef(cctx, &defstmt->def.kind.type, NULL); break; case LRL_AST_Namespace: case LRL_AST_Uses: case LRL_AST_Interop: case LRL_AST_Stmt_Decl: /* Do nothing */ break; LRL_case_except_ast_namespaces_defs_defstmt default: fail("ctrans_ensuredef_switch"); } } static void ensure_expr_defined(CTranCtx *cctx, LRLASTExpr *expr) { if (expr->typeref.type) { /* it's NULL for e.g. LRL_AST_Value_Undefined */ if (expr->ast_type != LRL_AST_Expr_Call || expr->typeref.type->ast_type != LRL_AST_Type_Struct || expr->typeref.type->kind.struc.members) { /* Not a void return type */ ensure_type_defined(cctx, &expr->typeref, NEED_COMPLETE); } } switch (expr->ast_type) { case LRL_AST_Value_TypeIdent: case LRL_AST_Value_Ident: { /* Ensure the identifier is defined */ const LRLIdent *ident = get_expr_ident(expr); if (ident && ident->def_node) { ensure_defstmt_defined(cctx, ident->def_node); } /* TODO type parameters */ break; } case LRL_AST_Value_Struct: ensure_exprlist_defined(cctx, &expr->kind.struc.values); break; case LRL_AST_Value_Array: ensure_exprlist_defined(cctx, &expr->kind.array.values); break; case LRL_AST_Value_None: case LRL_AST_Value_NaN: case LRL_AST_Value_Inf: case LRL_AST_Value_Scalar: case LRL_AST_Value_Undefined: /* Do nothing */ break; case LRL_AST_Expr_ArrayIndex: ensure_expr_defined(cctx, expr->kind.index.array); ensure_expr_defined(cctx, expr->kind.index.index); break; case LRL_AST_Expr_Call: ensure_expr_defined(cctx, expr->kind.call.function); ensure_exprlist_defined(cctx, &expr->kind.call.args); break; case LRL_AST_Expr_Member: case LRL_AST_Expr_FuncMember: ensure_expr_defined(cctx, expr->kind.member.struc); break; case LRL_AST_Expr_UnaryOp: ensure_expr_defined(cctx, expr->kind.unary_op.operand); break; case LRL_AST_Expr_BinaryOp: ensure_expr_defined(cctx, expr->kind.binary_op.operand1); ensure_expr_defined(cctx, expr->kind.binary_op.operand2); break; case LRL_AST_Expr_Conditional: ensure_expr_defined(cctx, expr->kind.conditional.condexpr); ensure_expr_defined(cctx, expr->kind.conditional.trueexpr); ensure_expr_defined(cctx, expr->kind.conditional.falseexpr); break; case LRL_AST_Expr_As: ensure_expr_defined(cctx, expr->kind.asexpr.expr); break; case LRL_AST_Expr_TypeAssert: ensure_expr_defined(cctx, expr->kind.typeassert.expr); break; case LRL_AST_Expr_Evaluated: ensure_expr_defined(cctx, expr->kind.evaluated.evaluated); break; LRL_case_except_ast_exprs_values default: fail("ctrans_ensureexpr_switch"); } } /** * Declares any temporary variables needed by an initialization expression. * E.g. for pointers to literals. */ static void declare_init_temps(CTranCtx *cctx, const LRLASTExpr *expr) { switch (expr->ast_type) { case LRL_AST_Value_Scalar: case LRL_AST_Value_Undefined: case LRL_AST_Value_NaN: case LRL_AST_Value_Inf: case LRL_AST_Value_Ident: case LRL_AST_Value_TypeIdent: case LRL_AST_Value_None: /* Nothing to do */ break; case LRL_AST_Expr_ArrayIndex: declare_init_temps(cctx, expr->kind.index.array); declare_init_temps(cctx, expr->kind.index.index); break; case LRL_AST_Expr_As: declare_init_temps(cctx, expr->kind.asexpr.expr); break; case LRL_AST_Expr_TypeAssert: declare_init_temps(cctx, expr->kind.typeassert.expr); break; case LRL_AST_Expr_UnaryOp: declare_init_temps(cctx, expr->kind.unary_op.operand); if (expr->kind.unary_op.token_type == LRL_Op_AddrOf && lrl_expr_is_literal(expr->kind.unary_op.operand)) { /* Put expression in a temporary, so it can be referenced with the & operator in C. */ LRLASTExpr *op = expr->kind.unary_op.operand; LRLIdent name; get_temp_ident(cctx->alloced_temporary++, &name); print_typeref(cctx, &name, &op->typeref, NEED_COMPLETE); fputs(" = ", cctx->file); trans_expression(cctx, op, IS_A_DEF); fputs(";\n", cctx->file); } break; case LRL_AST_Expr_BinaryOp: declare_init_temps(cctx, expr->kind.binary_op.operand1); declare_init_temps(cctx, expr->kind.binary_op.operand2); break; case LRL_AST_Expr_Conditional: declare_init_temps(cctx, expr->kind.conditional.condexpr); declare_init_temps(cctx, expr->kind.conditional.trueexpr); declare_init_temps(cctx, expr->kind.conditional.falseexpr); break; case LRL_AST_Expr_Call: { size_t i; for (i = 0; i < expr->kind.call.args.num_args; i++) { declare_init_temps(cctx, expr->kind.call.args.values[i]); } declare_init_temps(cctx, expr->kind.call.function); break; } case LRL_AST_Expr_Member: case LRL_AST_Expr_FuncMember: declare_init_temps(cctx, expr->kind.member.struc); break; case LRL_AST_Expr_Evaluated: declare_init_temps(cctx, expr->kind.evaluated.evaluated); break; case LRL_AST_Value_Array: case LRL_AST_Value_Struct: { size_t i; for (i = 0; i < expr->kind.exprlist.num_args; i++) { declare_init_temps(cctx, expr->kind.exprlist.values[i]); } break; } LRL_case_except_ast_exprs_values default: fail("ctrans_declinittemps_switch"); } } static void trans_varinit(CTranCtx *cctx, LRLASTDefData *defdata) { LRLASTExpr *value; if (defdata->flags & LRL_DeFl_DeclOnly) return; ensure_expr_defined(cctx, defdata->value); declare_init_temps(cctx, defdata->value); /* static/extern/normal keyword */ print_linkage_keyword(cctx, defdata->flags); /* Declaration */ print_type(cctx, defdata->ident, defdata->type, def_with_storage(defdata->flags) ? NEED_COMPLETE : 0); /* Initial value */ value = defdata->value; if (value && value->ast_type != LRL_AST_Value_Undefined) { fprintf(cctx->file, " = "); /* Enum values are never handled by constexpr in the verifier. For non-int enums, we need to evaluate the value here. */ if ((value->ast_type == LRL_AST_Value_Ident || value->ast_type == LRL_AST_Value_TypeIdent) && value->kind.ident.identref.ident && (value->kind.ident.identref.ident->flags & LRL_IdFl_EnumValue) && value->kind.ident.identref.ident->def_node) { const LRLASTType *type = lrl_vfy_find_real_typeref(&value->typeref).type; const LRLASTDefOrStmt *defnode = value->kind.ident.identref.ident->def_node; if (type->ast_type == LRL_AST_Type_Enum && type != lrl_builtin_get_type(LRL_BT_bool) && !is_int(type->kind.enu.base_type) && (defnode->ast_type == LRL_AST_Def_Data || defnode->ast_type == LRL_AST_Stmt_Decl) && defnode->def.kind.data.value) { LRLASTExpr *evaluated = lrl_constexpr_eval(cctx->ctx, defnode->def.kind.data.value); if (evaluated) { value = evaluated; } } } trans_expression(cctx, value, IS_A_DEF); } fprintf(cctx->file, ";\n"); } /** * Allocates a temporary variable. This is required when translating * non-scalar literals, which aren't supported in C (except in initializers) */ static void alloc_temporary(CTranCtx *cctx, const LRLTypeRef *typeref, int indent) { LRLIdent name; get_temp_ident(cctx->alloced_temporary++, &name); print_indent(cctx->file, indent); print_typeref(cctx, &name, typeref, NEED_COMPLETE); fputs(";\n", cctx->file); } /** * Declares temporaries for "leaf node" elements in array and struct * literals, i.e. not nested array or struct literals but only the * deepest values. The intention is to generate only as many temporaries * as print_nonscalar_temporary uses. */ static void declare_nonscalar_temps(CTranCtx *cctx, const LRLASTExpr *expr, int indent) { size_t i; for (i = 0; i < expr->kind.exprlist.num_args; i++) { const LRLASTExpr *elem = expr->kind.exprlist.values[i]; if (elem->ast_type == LRL_AST_Value_Array || elem->ast_type == LRL_AST_Value_Struct) { declare_nonscalar_temps(cctx, elem, indent); } else { declare_expr_temps(cctx, elem, indent); } } } /** * Declares any temporary variables needed by an expression. E.g. for * non-scalar literals. */ static void declare_expr_temps(CTranCtx *cctx, const LRLASTExpr *expr, int indent) { size_t i; switch (expr->ast_type) { case LRL_AST_Value_Scalar: case LRL_AST_Value_Undefined: case LRL_AST_Value_NaN: case LRL_AST_Value_Inf: case LRL_AST_Value_Ident: case LRL_AST_Value_TypeIdent: /* Nothing to do */ break; case LRL_AST_Expr_ArrayIndex: declare_expr_temps(cctx, expr->kind.index.array, indent); declare_expr_temps(cctx, expr->kind.index.index, indent); break; case LRL_AST_Expr_As: declare_expr_temps(cctx, expr->kind.asexpr.expr, indent); break; case LRL_AST_Expr_TypeAssert: declare_expr_temps(cctx, expr->kind.typeassert.expr, indent); break; case LRL_AST_Expr_UnaryOp: if (expr->kind.unary_op.token_type == LRL_Op_AddrOf && lrl_expr_is_literal(expr->kind.unary_op.operand)) { LRLASTExpr assign, tempexpr; LRLASTDefData def; /* This temporary is assigned inside the expression */ alloc_temporary(cctx, &expr->kind.unary_op.operand->typeref, indent); /* Since we translate this into an assignment expression, that might also generate temporaries. E.g. in the l-value for arrays. So check the translated assignment expr for temps */ def.ident = NULL; def.flags = 0; def.type = NULL; def.value = expr->kind.unary_op.operand; make_assign_op(&def, &assign, &tempexpr); tempexpr.typeref = assign.typeref = expr->kind.unary_op.operand->typeref; declare_expr_temps(cctx, &assign, indent); break; } else if (expr->kind.unary_op.token_type == LRL_Op_MakeOpt) { LRLTypeRef realtyperef = lrl_vfy_find_real_typeref( &expr->kind.unary_op.operand->typeref); if (realtyperef.type->ast_type != LRL_AST_Type_Pointer) { /* Non-pointer types must be wrapped in a struct. For simplicity, this is done using a temporary. */ LRLASTExpr assign, tempexpr; LRLASTDefData def; alloc_temporary(cctx, &expr->typeref, indent); def.ident = NULL; def.flags = 0; def.type = NULL; def.value = expr->kind.unary_op.operand; make_assign_op(&def, &assign, &tempexpr); tempexpr.typeref = assign.typeref = expr->kind.unary_op.operand->typeref; declare_expr_temps(cctx, &assign, indent); break; } /* continue */ } declare_expr_temps(cctx, expr->kind.unary_op.operand, indent); break; case LRL_AST_Expr_BinaryOp: { LRLTokenType op_type = expr->kind.binary_op.token_type; LRLASTExpr *operand1 = expr->kind.binary_op.operand1; LRLASTExpr *operand2 = expr->kind.binary_op.operand2; LRLTypeRef *op1typeref = &operand1->typeref; LRLTypeRef realtr1 = lrl_vfy_find_real_typeref(op1typeref); LRLTypeRef realtr2; int lsign, rsign; switch (op_type) { case LRL_Op_Assign: if (operand2->ast_type == LRL_AST_Value_Struct) { /* In this case, no temporary pointer is needed */ declare_expr_temps(cctx, operand1, indent); declare_expr_temps(cctx, operand2, indent); break; } else if (lvalue_needs_temporary(expr, &realtr1)) { /* Array types, "none" literals, parametric type: These get a temporary pointer, which is assigned first, so allocate it here. */ PtrAssignData data; make_pointer_assign_op(NULL, operand1, &data); alloc_temporary(cctx, &data.addrexpr.typeref, indent); declare_expr_temps(cctx, &data.assignexpr, indent); if (!is_none_struct(operand2, &realtr1)) { /* "none" assignment is translated to a memset, so no temporaries are needed for the r-value */ declare_expr_temps(cctx, operand2, indent); } break; } /* Fall through */ case LRL_Op_PlusAssign: case LRL_Op_MinusAssign: case LRL_Op_TimesAssign: case LRL_Op_DivideAssign: case LRL_Op_ShiftLAssign: case LRL_Op_ShiftRAssign: goto normal_binary; case LRL_Op_Equal: case LRL_Op_NotEqual: if (is_none_struct(operand1, &realtr1)) { declare_expr_temps(cctx, operand2, indent); break; } else if (is_none_struct(operand2, &realtr1)) { declare_expr_temps(cctx, operand1, indent); break; } else if (realtr1.type->ast_type == LRL_AST_Type_Optional && !is_pointer(realtr1.type->kind.optional.type)) { LRLIdent op1tempident, op2tempident; PtrAssignData op1data, op2data; DerefIdentData derefdata1, derefdata2; LRLASTExpr comparison; get_temp_ident(cctx->alloced_temporary, &op1tempident); get_temp_ident(cctx->alloced_temporary+1, &op2tempident); make_pointer_assign_op(&op1tempident, operand1, &op1data); make_pointer_assign_op(&op2tempident, operand2, &op2data); alloc_temporary(cctx, &op1data.addrexpr.typeref, indent); alloc_temporary(cctx, &op2data.addrexpr.typeref, indent); declare_expr_temps(cctx, &op1data.assignexpr, indent); declare_expr_temps(cctx, &op2data.assignexpr, indent); make_ident_expr(NULL, operand1->from, operand1->to, typeref_nested( realtr1.type->kind.optional.type, &realtr1), &derefdata1); realtr2 = lrl_vfy_find_real_typeref(&operand2->typeref); make_ident_expr(NULL, operand2->from, operand2->to, typeref_nested( realtr2.type->kind.optional.type, &realtr2), &derefdata2); comparison.ast_type = LRL_AST_Expr_BinaryOp; comparison.from = expr->from; comparison.to = expr->to; comparison.kind.binary_op.token_type = LRL_Op_Equal; comparison.kind.binary_op.operand1 = &derefdata1.identexpr; comparison.kind.binary_op.operand2 = &derefdata2.identexpr; comparison.typeref = typeref_root(lrl_builtin_get_type(LRL_BT_bool), 0); declare_expr_temps(cctx, &comparison, indent); break; } else if (realtr1.type->ast_type == LRL_AST_Type_Struct) { /* Struct comparisons get translated to: tmpa=&a, tmpb=&b, (tmpa->memb1==tmpb->memb1 && tmpa->memb2==tmpb->memb2 ...) */ LRLIdent op1tempident, op2tempident; PtrAssignData op1data, op2data; const LRLASTDefList *memberdef1, *memberdef2; get_temp_ident(cctx->alloced_temporary, &op1tempident); get_temp_ident(cctx->alloced_temporary+1, &op2tempident); make_pointer_assign_op(&op1tempident, operand1, &op1data); make_pointer_assign_op(&op2tempident, operand2, &op2data); alloc_temporary(cctx, &op1data.addrexpr.typeref, indent); alloc_temporary(cctx, &op2data.addrexpr.typeref, indent); declare_expr_temps(cctx, &op1data.assignexpr, indent); declare_expr_temps(cctx, &op2data.assignexpr, indent); realtr2 = lrl_vfy_find_real_typeref(&operand2->typeref); memberdef1 = realtr1.type->kind.struc.members; memberdef2 = realtr2.type->kind.struc.members; for (;;) { LRLIdent tmpmember1, tmpmember2; LRLASTExpr comparison, identexpr1, identexpr2; if (!memberdef1 && !memberdef2) break; /* end of struct */ else if (!memberdef1 || !memberdef2) { fail("ctrans_equalitybinop_endofstruct"); } memset(&tmpmember1, 0, sizeof(tmpmember1)); memset(&tmpmember2, 0, sizeof(tmpmember2)); tmpmember1.name = "@"; tmpmember2.name = "@"; identexpr1.ast_type = LRL_AST_Value_Ident; identexpr1.from = operand1->from; identexpr1.to = operand1->to; identexpr1.kind.ident.identref.ident = &tmpmember1; identexpr1.kind.ident.type_params = NULL; identexpr1.typeref = typeref_nested( memberdef1->def.kind.data.type, &operand1->typeref); identexpr2.ast_type = LRL_AST_Value_Ident; identexpr2.from = operand2->from; identexpr2.to = operand2->to; identexpr2.kind.ident.identref.ident = &tmpmember2; identexpr2.kind.ident.type_params = NULL; identexpr2.typeref = typeref_nested( memberdef1->def.kind.data.type, &operand2->typeref); comparison.ast_type = LRL_AST_Expr_BinaryOp; comparison.from = expr->from; comparison.to = expr->to; comparison.kind.binary_op.token_type = LRL_Op_Equal; comparison.kind.binary_op.operand1 = &identexpr1; comparison.kind.binary_op.operand2 = &identexpr2; comparison.typeref = typeref_root(lrl_builtin_get_type(LRL_BT_bool), 0); declare_expr_temps(cctx, &comparison, indent); memberdef1 = memberdef1->next; memberdef2 = memberdef2->next; } break; /* FIXME add special case for arrays containing structs */ } else if (!is_scalar_type(&realtr1)) { /* Array comparisons get translated to: tmpa=&a, tmpb=&b, !memcmp(a, b, sizeof(type)) */ LRLIdent op1tempident, op2tempident; PtrAssignData op1data, op2data; get_temp_ident(cctx->alloced_temporary, &op1tempident); get_temp_ident(cctx->alloced_temporary+1, &op2tempident); make_pointer_assign_op(&op1tempident, operand1, &op1data); make_pointer_assign_op(&op2tempident, operand2, &op2data); alloc_temporary(cctx, &op1data.addrexpr.typeref, indent); alloc_temporary(cctx, &op2data.addrexpr.typeref, indent); declare_expr_temps(cctx, &op1data.assignexpr, indent); declare_expr_temps(cctx, &op2data.assignexpr, indent); break; } /* Fall through */ case LRL_Op_Less: case LRL_Op_LessEqual: case LRL_Op_Greater: case LRL_Op_GreaterEqual: realtr2 = lrl_vfy_find_real_typeref(&operand2->typeref); if (signedness_differs(operand1, operand2, &realtr1, &realtr2, &lsign, &rsign)) { /* We must check the sign of the operand of the signed type before the actual comparison, so we need a temporary for it (but for now we use temporaries for both operands). */ alloc_temporary(cctx, &operand1->typeref, indent); alloc_temporary(cctx, &operand2->typeref, indent); } /* Fall through */ case LRL_Op_Plus: case LRL_Op_Minus: case LRL_Op_Times: case LRL_Op_Divide: case LRL_Op_ShiftL: case LRL_Op_ShiftR: case LRL_Op_BitAnd: case LRL_Op_BitOr: case LRL_Op_BitXor: case LRL_Op_LAnd: case LRL_Op_LOr: case LRL_Op_LXor: case LRL_Op_Modulo: normal_binary: declare_expr_temps(cctx, operand1, indent); declare_expr_temps(cctx, operand2, indent); break; LRL_case_except_tt_binops default: fail("ctrans_declexprtemps_binop_switch"); } break; } case LRL_AST_Expr_Conditional: declare_expr_temps(cctx, expr->kind.conditional.condexpr, indent); declare_expr_temps(cctx, expr->kind.conditional.trueexpr, indent); declare_expr_temps(cctx, expr->kind.conditional.falseexpr, indent); break; case LRL_AST_Expr_Call: { const LRLTypeRef rettyperef = lrl_vfy_find_real_typeref(&expr->typeref); if (rettyperef.type->ast_type == LRL_AST_Type_Array) { /* Create a struct temporary to wrap the array in */ LRLIdent tempname; LRLHashCode unid; unid = UNIQUEID_STRUCTWRAP(calc_unique_id(&rettyperef)); print_indent(cctx->file, indent); print_anonymous_type(cctx->file, unid); get_temp_ident(cctx->alloced_temporary++, &tempname); fputc(' ', cctx->file); print_c_ident(cctx, &tempname); fputs(";\n", cctx->file); } for (i = 0; i < expr->kind.call.args.num_args; i++) { declare_expr_temps(cctx, expr->kind.call.args.values[i], indent); } declare_expr_temps(cctx, expr->kind.call.function, indent); break; } case LRL_AST_Expr_Member: case LRL_AST_Expr_FuncMember: declare_expr_temps(cctx, expr->kind.member.struc, indent); break; case LRL_AST_Expr_Evaluated: declare_expr_temps(cctx, expr->kind.evaluated.evaluated, indent); break; case LRL_AST_Value_None: { const LRLTypeRef opttyperef = lrl_vfy_find_real_typeref(&expr->typeref); /* For pointers we simply use NULL. Otherwise we need a struct */ if (opttyperef.type->ast_type != LRL_AST_Type_Pointer) { LRLTypeRef valtr = typeref_nested( opttyperef.type->kind.optional.type, &opttyperef); valtr = lrl_vfy_find_real_typeref(&valtr); if (valtr.type->ast_type != LRL_AST_Type_Pointer) { alloc_temporary(cctx, &expr->typeref, indent); } } break; } case LRL_AST_Value_Array: case LRL_AST_Value_Struct: { alloc_temporary(cctx, &expr->typeref, indent); declare_nonscalar_temps(cctx, expr, indent); break; } LRL_case_except_ast_exprs_values default: fail("ctrans_declexprtemps_switch"); } } /** * Builds an assignment expression out of a data definition with an * initial value. The expression is written to expr, and varexpr is * used to store an identifier expr that references the defined identifier. */ static void make_assign_op(const LRLASTDefData *def, LRLASTExpr *expr, LRLASTExpr *varexpr) { varexpr->ast_type = LRL_AST_Value_Ident; varexpr->from = varexpr->to = NULL; varexpr->kind.ident.identref.ident = def->ident; varexpr->kind.ident.type_params = NULL; expr->ast_type = LRL_AST_Expr_BinaryOp; expr->from = expr->to = NULL; expr->kind.binary_op.token_type = LRL_Op_Assign; expr->kind.binary_op.operand1 = varexpr; expr->kind.binary_op.operand2 = def->value; varexpr->typeref.quals = expr->typeref.quals = 0; varexpr->typeref.prm = expr->typeref.prm = NULL; varexpr->typeref.type = expr->typeref.type = def->type; } /** * Creates a type that can point to a temporary for the given expr. */ static void make_pointer_type(LRLASTExpr *expr, LRLASTType *ptrtype, LRLTypeRef *ptrtr) { ptrtype->ast_type = LRL_AST_Type_Pointer; ptrtype->from = expr->from; ptrtype->to = expr->to; ptrtype->quals = 0; ptrtype->unique_id = 0; ptrtype->kind.pointer.type = (LRLASTType*)expr->typeref.type; ptrtype->kind.pointer.flags = 0; if (ptrtr) { *ptrtr = expr->typeref; ptrtr->type = ptrtype; } } /** * Creates an expression where the address of "operand" is assigned to * a temporary of pointer type. The resulting expression (and data needed by * it) is stored in the "data" parameter. */ static void make_pointer_assign_op(LRLIdent *tempident, LRLASTExpr *operand, PtrAssignData *data) { data->addrexpr.ast_type = LRL_AST_Expr_UnaryOp; data->addrexpr.from = operand->from; data->addrexpr.to = operand->to; data->addrexpr.kind.unary_op.token_type = LRL_Op_AddrOf; data->addrexpr.kind.unary_op.operand = operand; make_pointer_type(operand, &data->ptrtype, &data->addrexpr.typeref); data->def.ident = tempident; data->def.flags = 0; data->def.type = &data->ptrtype; data->def.value = &data->addrexpr; make_assign_op(&data->def, &data->assignexpr, &data->varexpr); /* contains the operand */ } /** * Creates a struct member expression. */ static void make_ident_expr(const LRLIdent *tempident, const LRLToken *from, const LRLToken *to, const LRLTypeRef typeref, DerefIdentData *data) { data->identexpr.ast_type = LRL_AST_Value_Ident; data->identexpr.from = from; data->identexpr.to = to; data->identexpr.kind.ident.identref.ident = tempident; data->identexpr.kind.ident.type_params = NULL; data->identexpr.typeref = typeref; } static const LRLIdent loop_iter_ident = LRL_IDENT_PLAIN("_LRL_iter"); static const LRLIdent loop_array_next_ident = LRL_IDENT_PLAIN("_LRL_array_iter_next"); static const LRLIdent loop_elem_ident = LRL_IDENT_PLAIN("elem"); static const LRLIdent loop_end_ident = LRL_IDENT_PLAIN("end"); static const LRLToken tok_elem[2] = { { LRL_TT_Ident, { "elem", 4 } }, { LRL_TT_EOF, LRL_NULL_LOCATION } }; /** * Creates an expression to set up the iterator variable for a "for" loop * with a direct iterator (LRL_AST_LT_DirectIter) */ static void make_for_loop_direct_exprs(const LRLASTStmt *stmt, ForLoopDirectData *data) { LRLASTExpr *iterexpr = stmt->kind.forstm.iterexpr; const LRLASTType *itertype = iterexpr->typeref.type; data->assignexpr.ast_type = LRL_AST_Expr_BinaryOp; data->assignexpr.from = data->assignexpr.to = NULL; data->assignexpr.typeref.quals = 0; data->assignexpr.typeref.prm = NULL; data->assignexpr.typeref.type = itertype; data->assignexpr.kind.binary_op.token_type = LRL_Op_Assign; data->assignexpr.kind.binary_op.operand1 = &data->identexpr; data->assignexpr.kind.binary_op.operand2 = iterexpr; data->identexpr.ast_type = LRL_AST_Value_Ident; data->identexpr.from = data->identexpr.to = NULL; data->identexpr.typeref.quals = LRL_Qual_Var; data->identexpr.typeref.prm = NULL; data->identexpr.typeref.type = itertype; data->identexpr.kind.ident.identref.ident = &loop_iter_ident; data->identexpr.kind.ident.type_params = NULL; } /** * Creates an expression to set up the iterator variable for a "for" loop * which iterates over an array (LRL_AST_LT_ArrayIter) */ static void make_for_loop_array_exprs(const LRLASTStmt *stmt, ForLoopArrayData *data) { data->ptrtype.ast_type = LRL_AST_Type_Pointer; data->ptrtype.from = stmt->kind.forstm.iterexpr->from; data->ptrtype.to = stmt->kind.forstm.iterexpr->to; data->ptrtype.quals = LRL_Qual_Var; data->ptrtype.unique_id = 0; data->ptrtype.kind.pointer.type = stmt->kind.forstm.valuedef.kind.data.type; data->assignexpr.ast_type = LRL_AST_Expr_BinaryOp; data->assignexpr.from = data->assignexpr.to = NULL; data->assignexpr.typeref.quals = 0; data->assignexpr.typeref.prm = NULL; data->assignexpr.typeref.type = &data->ptrtype; data->assignexpr.kind.binary_op.token_type = LRL_Op_Assign; data->assignexpr.kind.binary_op.operand1 = &data->memberexpr; data->assignexpr.kind.binary_op.operand2 = &data->ptrexpr; data->memberexpr.ast_type = LRL_AST_Expr_Member; data->memberexpr.from = data->memberexpr.to = NULL; data->memberexpr.typeref.quals = LRL_Qual_Var; data->memberexpr.typeref.prm = NULL; data->memberexpr.typeref.type = &data->ptrtype; data->memberexpr.kind.member.struc = &data->structexpr; data->memberexpr.kind.member.ident = &loop_elem_ident; data->memberexpr.kind.member.token = &tok_elem[0]; data->structexpr.ast_type = LRL_AST_Value_Ident; data->structexpr.from = data->structexpr.to = NULL; data->structexpr.typeref.quals = LRL_Qual_Var; data->structexpr.typeref.prm = NULL; data->structexpr.typeref.type = &internal_struct_type; data->structexpr.kind.ident.identref.ident = &loop_iter_ident; data->structexpr.kind.ident.type_params = NULL; data->ptrexpr.ast_type = LRL_AST_Expr_UnaryOp; data->ptrexpr.from = data->ptrexpr.to = NULL; data->ptrexpr.typeref.quals = 0; data->ptrexpr.typeref.prm = NULL; data->ptrexpr.typeref.type = &data->ptrtype; data->ptrexpr.kind.binary_op.token_type = LRL_Op_AddrOf; data->ptrexpr.kind.binary_op.operand1 = stmt->kind.forstm.iterexpr; } /** * Declares any temporary variables needed by a statement * (including any nested statements) */ static void declare_stmt_temps(CTranCtx *cctx, const LRLASTStmt *stmt, int indent) { switch (stmt->ast_type) { case LRL_AST_Stmt_Compound: { /* The nicest design would be to skip over temporaries in nested statements, but that would require an extra function to just calculate how many temporaries there are in the nested statements (or keeping one counter per compound statement). So let's just print all temporaries including nested temporaries at once, to keep the code simpler. */ LRLASTStmtList *entry; for (entry = stmt->kind.compound; entry; entry = entry->next) { declare_stmt_temps(cctx, entry->statement, indent); } break; } case LRL_AST_Stmt_Break: case LRL_AST_Stmt_Continue: case LRL_AST_Stmt_Goto: case LRL_AST_Stmt_SkipTo: case LRL_AST_Stmt_RepeatFrom: case LRL_AST_Stmt_Unreachable: case LRL_AST_Stmt_Label: case LRL_AST_Stmt_DefType: case LRL_AST_Stmt_TypeAssert: /* No temporaries */ break; case LRL_AST_Stmt_Decl: { const LRLASTExpr *initvalue = stmt->kind.data.value; if (initvalue->ast_type != LRL_AST_Value_Undefined && !is_c_null(initvalue)) { /* Not undefined or NULL, which doesn't need temporaries */ LRLASTExpr expr, varexpr; make_assign_op(&stmt->kind.data, &expr, &varexpr); declare_expr_temps(cctx, &expr, indent); } break; } case LRL_AST_Stmt_Expr: declare_expr_temps(cctx, stmt->kind.expr, indent); break; case LRL_AST_Stmt_If: { LRLASTStmt *elseif; declare_expr_temps(cctx, stmt->kind.ifstm.boolexpr, indent); declare_stmt_temps(cctx, stmt->kind.ifstm.body_true, indent); elseif = stmt->kind.ifstm.body_false; while (elseif && elseif->ast_type == LRL_AST_Stmt_If) { declare_expr_temps(cctx, elseif->kind.ifstm.boolexpr, indent); declare_stmt_temps(cctx, elseif->kind.ifstm.body_true, indent); elseif = elseif->kind.ifstm.body_false; } if (elseif) { /* Final "else" statement */ declare_stmt_temps(cctx, elseif, indent); } break; } case LRL_AST_Stmt_Switch: { LRLASTStmt *defaultstmt; size_t ci; if (is_valid_switch_type( stmt->kind.switchstm.switchexpr->typeref.type)) { /* Translated directly to a switch statement */ declare_expr_temps(cctx, stmt->kind.switchstm.switchexpr, indent); for (ci = 0; ci < stmt->kind.switchstm.num_cases; ci++) { const LRLASTCase *casenode = &stmt->kind.switchstm.cases[ci]; size_t ei; for (ei = 0; ei < casenode->num_matchvalues; ei++) { LRLASTCaseMatch *matchval = &casenode->matchvalues[ei]; declare_expr_temps(cctx, matchval->expr, indent); if (matchval->withstm) { declare_stmt_temps(cctx, matchval->withstm, indent); } } declare_stmt_temps(cctx, casenode->stmt, indent); } } else { /* Type that can't be handled by the C switch statement. The switch expression is allocated a temporary, but the comparisons in the "if"s might also need additional temporaries (e.g. for a memcmp operation to compare arrays) */ LRLASTExpr assign, varexpr; LRLASTDefData def; LRLASTExpr comparison; alloc_temporary(cctx, &stmt->kind.switchstm.switchexpr->typeref, indent); def.ident = NULL; def.flags = 0; def.type = NULL; def.value = stmt->kind.switchstm.switchexpr; make_assign_op(&def, &assign, &varexpr); varexpr.typeref = assign.typeref = stmt->kind.switchstm.switchexpr->typeref; declare_expr_temps(cctx, &assign, indent); comparison.ast_type = LRL_AST_Expr_BinaryOp; comparison.typeref = varexpr.typeref; comparison.kind.binary_op.token_type = LRL_Op_Equal; comparison.kind.binary_op.operand1 = &varexpr; for (ci = 0; ci < stmt->kind.switchstm.num_cases; ci++) { const LRLASTCase *casenode = &stmt->kind.switchstm.cases[ci]; size_t ei; for (ei = 0; ei < casenode->num_matchvalues; ei++) { LRLASTExpr *caseexpr = casenode->matchvalues[ei].expr; comparison.from = caseexpr->from; comparison.to = caseexpr->to; comparison.kind.binary_op.operand2 = caseexpr; declare_expr_temps(cctx, &comparison, indent); if (casenode->matchvalues[ei].withstm) { declare_stmt_temps(cctx, casenode->matchvalues[ei].withstm, indent); } } declare_stmt_temps(cctx, casenode->stmt, indent); } } defaultstmt = stmt->kind.switchstm.defaultstm; if (defaultstmt) { declare_stmt_temps(cctx, defaultstmt, indent); } break; } case LRL_AST_Stmt_Return: if (stmt->kind.retstm.retexpr) { const LRLTypeRef rettyperef = lrl_vfy_find_real_typeref( &stmt->kind.retstm.retexpr->typeref); if (rettyperef.type->ast_type == LRL_AST_Type_Array) { LRLIdent tempname; LRLHashCode unid; unid = UNIQUEID_STRUCTWRAP(calc_unique_id(&rettyperef)); print_indent(cctx->file, indent); print_anonymous_type(cctx->file, unid); get_temp_ident(cctx->alloced_temporary++, &tempname); fputc(' ', cctx->file); print_c_ident(cctx, &tempname); fputs(";\n", cctx->file); } declare_expr_temps(cctx, stmt->kind.retstm.retexpr, indent); } break; case LRL_AST_Stmt_While: declare_expr_temps(cctx, stmt->kind.whilestm.boolexpr, indent); declare_stmt_temps(cctx, stmt->kind.whilestm.body, indent); break; case LRL_AST_Stmt_DoWhile: declare_stmt_temps(cctx, stmt->kind.whilestm.body, indent); declare_expr_temps(cctx, stmt->kind.whilestm.boolexpr, indent); break; case LRL_AST_Stmt_For: { const LRLASTCtlForTemp *temp = stmt->kind.forstm.temp; if (temp->loop_type == LRL_AST_LT_DirectIter) { ForLoopDirectData data; make_for_loop_direct_exprs(stmt, &data); declare_expr_temps(cctx, &data.assignexpr, indent); } else if (temp->loop_type == LRL_AST_LT_IndirectIter) { /* TODO */ fail("not_implemented"); } else if (temp->loop_type == LRL_AST_LT_ArrayIter) { ForLoopArrayData data; make_for_loop_array_exprs(stmt, &data); declare_expr_temps(cctx, &data.assignexpr, indent); declare_expr_temps(cctx, temp->length_expr, indent); } else { fail("ctrans_transstmt_badforloop"); } declare_stmt_temps(cctx, stmt->kind.forstm.body, indent); if (stmt->kind.forstm.body_end) { declare_stmt_temps(cctx, stmt->kind.forstm.body_end, indent); } if (stmt->kind.forstm.body_empty) { declare_stmt_temps(cctx, stmt->kind.forstm.body_empty, indent); } break; } case LRL_AST_Stmt_Assert: declare_expr_temps(cctx, stmt->kind.assertstm.boolexpr, indent); break; LRL_case_except_ast_stmts default: fail("ctrans_declstmttemps_switch"); } } static int get_loopid(LRLASTStmt *loopstmt) { switch (loopstmt->ast_type) { case LRL_AST_Stmt_While: case LRL_AST_Stmt_DoWhile: return loopstmt->kind.whilestm.loopid; case LRL_AST_Stmt_For: return loopstmt->kind.forstm.loopid; case LRL_AST_Stmt_Compound: case LRL_AST_Stmt_Decl: case LRL_AST_Stmt_Expr: case LRL_AST_Stmt_DefType: case LRL_AST_Stmt_If: case LRL_AST_Stmt_Switch: case LRL_AST_Stmt_Break: case LRL_AST_Stmt_Continue: case LRL_AST_Stmt_Goto: case LRL_AST_Stmt_SkipTo: case LRL_AST_Stmt_RepeatFrom: case LRL_AST_Stmt_Unreachable: case LRL_AST_Stmt_Label: case LRL_AST_Stmt_TypeAssert: case LRL_AST_Stmt_Return: case LRL_AST_Stmt_Assert: LRL_case_except_ast_stmts default: fail("ctrans_getloopid_switch"); } } /** * Translates a statement. If the indentation level is negative, then no * indentation is done for this statement, but only for nested statements. */ static void trans_statement(CTranCtx *cctx, LRLASTStmt *stmt, int indent) { if (stmt->ast_type == LRL_AST_Stmt_Decl && stmt->kind.data.value->ast_type == LRL_AST_Value_Undefined) return; /* Nothing to do here */ if (indent > 0) { print_indent(cctx->file, indent); } else { indent = -indent; } switch (stmt->ast_type) { case LRL_AST_Stmt_Compound: { LRLASTStmtList *entry; if (indent) { fputs("{\n", cctx->file); } /* Declare variables (including temporaries) first */ for (entry = stmt->kind.compound; entry; entry = entry->next) { LRLASTStmt *inner = entry->statement; if (inner->ast_type == LRL_AST_Stmt_Decl) { print_indent(cctx->file, indent); print_type(cctx, inner->kind.data.ident, inner->kind.data.type, NEED_COMPLETE); fputs(";\n", cctx->file); } } /* Print the inner statements */ for (entry = stmt->kind.compound; entry; entry = entry->next) { trans_statement(cctx, entry->statement, indent+1); } if (indent) { print_indent(cctx->file, indent); fputs("}\n", cctx->file); } break; } case LRL_AST_Stmt_Decl: { /* Variable has been defined in compound statement already, print it as if it was an assignment expression */ LRLASTExpr expr, varexpr; make_assign_op(&stmt->kind.data, &expr, &varexpr); trans_expression(cctx, &expr, 0); fputs(";\n", cctx->file); break; } case LRL_AST_Stmt_Expr: trans_expression(cctx, stmt->kind.expr, HIDE_CAST); fputs(";\n", cctx->file); break; case LRL_AST_Stmt_If: /* TODO nested ifs should be wrapped in {} */ fputs("if (", cctx->file); trans_expression(cctx, stmt->kind.ifstm.boolexpr, 0); fputs(")\n", cctx->file); trans_statement(cctx, stmt->kind.ifstm.body_true, indent+1); if (stmt->kind.ifstm.body_false) { const char *else_str; int else_indent; print_indent(cctx->file, indent); if (stmt->kind.ifstm.body_false->ast_type == LRL_AST_Stmt_If) { /* Don't ident "else if" */ else_str = "else "; else_indent = -indent; /* don't indent the "if" itself */ } else { else_str = "else\n"; else_indent = indent+1; } fputs(else_str, cctx->file); trans_statement(cctx, stmt->kind.ifstm.body_false, else_indent); } break; case LRL_AST_Stmt_Switch: { if (is_valid_switch_type(stmt->kind.switchstm.switchexpr->typeref.type)) { /* Translate directly to a switch statement */ size_t ci; fputs("switch (", cctx->file); trans_expression(cctx, stmt->kind.switchstm.switchexpr, 0); fputs(") {\n", cctx->file); indent++; for (ci = 0; ci < stmt->kind.switchstm.num_cases; ci++) { const LRLASTCase *casenode = &stmt->kind.switchstm.cases[ci]; size_t ei; int caseid, has_with = 0; for (ei = 0; ei < casenode->num_matchvalues; ei++) { if (casenode->matchvalues[ei].withstm) { has_with = 1; caseid = size2int(cctx->direct_use_temporary++); break; } } for (ei = 0; ei < casenode->num_matchvalues; ei++) { LRLASTCaseMatch *matchval = &casenode->matchvalues[ei]; print_indent(cctx->file, indent); fputs("case ", cctx->file); trans_expression(cctx, matchval->expr, 0); fputs(":\n", cctx->file); if (has_with) { if (matchval->withstm) { trans_statement(cctx, matchval->withstm, indent+1); } if (ei != casenode->num_matchvalues-1) { print_indent(cctx->file, indent+1); fprintf(cctx->file, "goto _LRL_casestart%d;\n", caseid); } } } if (has_with) { print_indent(cctx->file, indent); fprintf(cctx->file, "_LRL_casestart%d:;\n", caseid); } trans_statement(cctx, casenode->stmt, indent+1); print_indent(cctx->file, indent); fputs("break;\n", cctx->file); } if (stmt->kind.switchstm.defaultstm) { print_indent(cctx->file, indent); fputs("default:\n", cctx->file); trans_statement(cctx, stmt->kind.switchstm.defaultstm, indent+1); } print_indent(cctx->file, --indent); fputs("}\n", cctx->file); } else { /* Types that can't be handled by the C switch statement are translated to a series of "if"s. This requires a temporary to avoid multiple evaluations of the switch expression. */ LRLASTExpr assign, varexpr; LRLASTDefData def; LRLIdent tempident; size_t tempid = cctx->used_temporary++; size_t ci; LRLASTExpr comparison; get_temp_ident(tempid, &tempident); def.ident = &tempident; def.flags = 0; def.type = NULL; def.value = stmt->kind.switchstm.switchexpr; make_assign_op(&def, &assign, &varexpr); varexpr.typeref = assign.typeref = stmt->kind.switchstm.switchexpr->typeref; trans_expression(cctx, &assign, HIDE_CAST); fputs(";\n", cctx->file); comparison.ast_type = LRL_AST_Expr_BinaryOp; comparison.typeref = varexpr.typeref; comparison.kind.binary_op.token_type = LRL_Op_Equal; comparison.kind.binary_op.operand1 = &varexpr; for (ci = 0; ci < stmt->kind.switchstm.num_cases; ci++) { const LRLASTCase *casenode = &stmt->kind.switchstm.cases[ci]; size_t ei; int caseid, has_with = 0; for (ei = 0; ei < casenode->num_matchvalues; ei++) { if (casenode->matchvalues[ei].withstm) { has_with = 1; caseid = size2int(cctx->direct_use_temporary++); break; } } if (!has_with) { /* Translate as if (x || y || z) { ... } */ print_indent(cctx->file, indent); fputs(ci > 0 ? "else if (" : "if (", cctx->file); for (ei = 0; ei < casenode->num_matchvalues; ei++) { LRLASTExpr *caseexpr = casenode->matchvalues[ei].expr; fputs(ei > 0 ? ") || (" : "(", cctx->file); comparison.from = caseexpr->from; comparison.to = caseexpr->to; comparison.kind.binary_op.operand2 = caseexpr; trans_expression(cctx, &comparison, 0); } fputs("))\n", cctx->file); trans_statement(cctx, casenode->stmt, indent+1); } else { /* Translate as if (x) { WITH-STMT; goto start; } else if (y) WITH-STMT; goto start; } else if (z) { WITH-STMT; start: ...; } */ if (!casenode->num_matchvalues) fail("ctrans_nocasevalues"); for (ei = 0; ei < casenode->num_matchvalues; ei++) { LRLASTExpr *caseexpr = casenode->matchvalues[ei].expr; print_indent(cctx->file, indent); fputs(ci > 0 ? "else if (" : "if (", cctx->file); comparison.from = caseexpr->from; comparison.to = caseexpr->to; comparison.kind.binary_op.operand2 = caseexpr; trans_expression(cctx, &comparison, 0); fprintf(cctx->file, ") {\n"); if (casenode->matchvalues[ei].withstm) { trans_statement(cctx, casenode->matchvalues[ei].withstm, indent+1); } if (ei != casenode->num_matchvalues-1) { print_indent(cctx->file, indent); fprintf(cctx->file, "goto _LRL_casestart%d; }\n", caseid); } } print_indent(cctx->file, indent); fprintf(cctx->file, "_LRL_casestart%d:;\n", caseid); trans_statement(cctx, casenode->stmt, indent+1); print_indent(cctx->file, indent); fprintf(cctx->file, "}\n"); } } if (stmt->kind.switchstm.defaultstm) { print_indent(cctx->file, indent); fputs(stmt->kind.switchstm.num_cases ? "else /* default */\n" : "/* default */\n", cctx->file); trans_statement(cctx, stmt->kind.switchstm.defaultstm, indent+1); } } break; } case LRL_AST_Stmt_While: fputs("while (", cctx->file); trans_expression(cctx, stmt->kind.whilestm.boolexpr, 0); fputs(")\n", cctx->file); stmt->kind.whilestm.loopid = size2int(cctx->direct_use_temporary++); trans_statement(cctx, stmt->kind.whilestm.body, indent+1); print_indent(cctx->file, indent); fprintf(cctx->file, "_LRL_loopbreak%d:;\n", stmt->kind.whilestm.loopid); break; case LRL_AST_Stmt_DoWhile: fputs("do\n", cctx->file); stmt->kind.whilestm.loopid = size2int(cctx->direct_use_temporary++); trans_statement(cctx, stmt->kind.whilestm.body, indent+1); print_indent(cctx->file, indent); fputs("while (", cctx->file); trans_expression(cctx, stmt->kind.whilestm.boolexpr, 0); fputs(");\n", cctx->file); print_indent(cctx->file, indent); fprintf(cctx->file, "_LRL_loopbreak%d:;\n", stmt->kind.whilestm.loopid); break; case LRL_AST_Stmt_For: { LRLASTExpr *iterexpr = stmt->kind.forstm.iterexpr; const LRLASTCtlForTemp *temp = stmt->kind.forstm.temp; const LRLIdent *nextelem_ident; int loopid; /* Define the loop variable */ fputs("{\n", cctx->file); print_indent(cctx->file, indent); print_type(cctx, stmt->kind.forstm.valuedef.kind.data.ident, stmt->kind.forstm.valuedef.kind.data.type, NEED_COMPLETE); fputs(";\n", cctx->file); /* Define iterator data (if any) */ if (temp->loop_type == LRL_AST_LT_DirectIter) { const LRLASTType *itertype = iterexpr->typeref.type; ForLoopDirectData data; /* Define the _LRL_iter variable */ print_indent(cctx->file, indent); print_type(cctx, &loop_iter_ident, itertype, NEED_COMPLETE); fputs(";\n", cctx->file); /* Generate code for "_LRL_iter = iter expr"; */ make_for_loop_direct_exprs(stmt, &data); print_indent(cctx->file, indent); trans_expression(cctx, &data.assignexpr, HIDE_CAST); fputs(";\n", cctx->file); nextelem_ident = temp->next_element_ident; } else if (temp->loop_type == LRL_AST_LT_IndirectIter) { /* TODO */ fail("not_implemented"); nextelem_ident = NULL; } else if (temp->loop_type == LRL_AST_LT_ArrayIter) { ForLoopArrayData data; make_for_loop_array_exprs(stmt, &data); /* Define the _LRL_iter variable */ print_indent(cctx->file, indent); fputs("struct { ", cctx->file); print_type(cctx, &loop_elem_ident, &data.ptrtype, NEED_COMPLETE); fputs("; ", cctx->file); print_type(cctx, &loop_end_ident, &data.ptrtype, NEED_COMPLETE); fputs("; } _LRL_iter;\n", cctx->file); /* Generate code for initializing _LRL_iter */ print_indent(cctx->file, indent); trans_expression(cctx, &data.assignexpr, HIDE_CAST); fputs(";\n", cctx->file); print_indent(cctx->file, indent); fputs("_LRL_iter.end = _LRL_iter.elem + (", cctx->file); trans_expression(cctx, temp->length_expr, HIDE_CAST); fputs(");\n", cctx->file); nextelem_ident = &loop_array_next_ident; } else { fail("ctrans_transstmt_badforloop"); } /* Preperations for empty/end statements */ stmt->kind.forstm.loopid = loopid = size2int(cctx->direct_use_temporary++); if (stmt->kind.forstm.body_empty) { print_indent(cctx->file, indent); fprintf(cctx->file, "bool _LRL_loopempty%d = true;\n", loopid); } /* Loop header */ /* TODO print a goto label here */ print_indent(cctx->file, indent); fputs("while (", cctx->file); print_c_ident(cctx, nextelem_ident); /* a function */ fputs("(&_LRL_iter, &", cctx->file); print_c_ident(cctx, stmt->kind.forstm.valuedef.kind.data.ident); fputs("))\n", cctx->file); /* Loop body */ if (stmt->kind.forstm.body_empty) { print_indent(cctx->file, indent); fprintf(cctx->file, "{_LRL_loopempty%d = false;\n", loopid); } trans_statement(cctx, stmt->kind.forstm.body, indent+1); if (stmt->kind.forstm.body_empty) { print_indent(cctx->file, indent); fprintf(cctx->file, "}\n"); } /* End block */ if (stmt->kind.forstm.body_end) { trans_statement(cctx, stmt->kind.forstm.body_end, indent); } /* Empty block */ if (stmt->kind.forstm.body_empty) { print_indent(cctx->file, indent); fprintf(cctx->file, "if (_LRL_loopempty%d)\n", loopid); trans_statement(cctx, stmt->kind.forstm.body_empty, indent+1); } /* This jump target is jumped to when breaking the loop. Most importantly it skips the "end" block above. */ print_indent(cctx->file, indent); fprintf(cctx->file, "_LRL_loopbreak%d:;\n", loopid); print_indent(cctx->file, indent); fputs("}\n", cctx->file); break; } case LRL_AST_Stmt_Break: fprintf(cctx->file, "goto _LRL_loopbreak%d;\n", get_loopid(stmt->kind.breakstm.outer_stmt)); break; case LRL_AST_Stmt_Continue: fprintf(cctx->file, "continue;\n"); break; case LRL_AST_Stmt_Goto: case LRL_AST_Stmt_SkipTo: case LRL_AST_Stmt_RepeatFrom: fputs("goto ", cctx->file); print_c_ident(cctx, stmt->kind.gotostm.identref.ident); fputs(";\n", cctx->file); break; case LRL_AST_Stmt_Label: fputc('\n', cctx->file); print_c_ident(cctx, stmt->kind.labelstm.ident); fputs(":;\n", cctx->file); /* semicolon prevents "label at end of compound statement" error */ break; case LRL_AST_Stmt_Return: fputs("return", cctx->file); if (stmt->kind.retstm.retexpr) { LRLTypeRef realtr = lrl_vfy_find_real_typeref( &stmt->kind.retstm.retexpr->typeref); fputc(' ', cctx->file); if (realtr.type->ast_type == LRL_AST_Type_Array) { /* For arrays, we wrap the value in a struct */ LRLIdent structtmp; size_t tempid = cctx->used_temporary++; get_temp_ident(tempid, &structtmp); fputs("(memcpy(&", cctx->file); print_c_ident(cctx, &structtmp); fputc(',', cctx->file); trans_expression(cctx, stmt->kind.retstm.retexpr, ADDR_OF); fputs(",sizeof(", cctx->file); print_c_ident(cctx, &structtmp); fputs(")),", cctx->file); print_c_ident(cctx, &structtmp); fputc(')', cctx->file); } else { trans_expression(cctx, stmt->kind.retstm.retexpr, 0); } } fputs(";\n", cctx->file); break; case LRL_AST_Stmt_Unreachable: fputs("_LRL_unreachable;\n", cctx->file); break; case LRL_AST_Stmt_TypeAssert: /* TODO add range check here */ break; case LRL_AST_Stmt_DefType: /* Already translated in ensure_statement_defined */ break; case LRL_AST_Stmt_Assert: { const char *sourcefile; int line, column; /* Use __assert_fail(...expr...) call if available, for nicer error messages. Otherwise we simply call assert(). __assert_fail is defined by POSIX. There's also __assert which is required by the ELF specification, but the parameter order differs between platforms. */ fputs("_LRL_assert(", cctx->file); /* C expr */ trans_expression(cctx, stmt->kind.assertstm.boolexpr, 0); fputs(", \"", cctx->file); /* LRL expr */ print_raw_string(cctx->file, stmt->kind.assertstm.exprstart, stmt->kind.assertstm.exprlength); fprintf(cctx->file, "\", \""); /* Source file name and line number */ /* TODO add a cache parameter to lrl_ctx_find_source, to avoid scanning the whole file over and over again (only scan the new parts) */ lrl_ctx_find_source(cctx->ctx, stmt->kind.assertstm.exprstart, &sourcefile, &line, &column); print_raw_string(cctx->file, sourcefile, strlen(sourcefile)); fprintf(cctx->file, "\", %d, \"", line); /* Function name */ print_ident_without_namespace(cctx, cctx->current_function_ident); fprintf(cctx->file, "\");\n"); break; } LRL_case_except_ast_stmts default: fail("ctrans_transstmt_switch"); /* shouldn't happen */ } } /** * Ensures that identifiers referenced by the statement are declared. */ static void ensure_statement_defined(CTranCtx *cctx, LRLASTStmt *stmt) { LRLTypeRef tr; switch (stmt->ast_type) { case LRL_AST_Stmt_Compound: { LRLASTStmtList *entry; for (entry = stmt->kind.compound; entry; entry = entry->next) { ensure_statement_defined(cctx, entry->statement); } break; } case LRL_AST_Stmt_Decl: tr = typeref_root(stmt->kind.data.type, 0); ensure_type_defined(cctx, &tr, NEED_COMPLETE); if (stmt->kind.data.value) { ensure_expr_defined(cctx, stmt->kind.data.value); } break; case LRL_AST_Stmt_Expr: ensure_expr_defined(cctx, stmt->kind.expr); break; case LRL_AST_Stmt_DefType: trans_typedef(cctx, &stmt->kind.deftype, NULL); break; case LRL_AST_Stmt_If: ensure_expr_defined(cctx, stmt->kind.ifstm.boolexpr); ensure_statement_defined(cctx, stmt->kind.ifstm.body_true); if (stmt->kind.ifstm.body_false) { ensure_statement_defined(cctx, stmt->kind.ifstm.body_false); } break; case LRL_AST_Stmt_Switch: { size_t ci; ensure_expr_defined(cctx, stmt->kind.switchstm.switchexpr); for (ci = 0; ci < stmt->kind.switchstm.num_cases; ci++) { const LRLASTCase *casenode = &stmt->kind.switchstm.cases[ci]; size_t ei; for (ei = 0; ei < casenode->num_matchvalues; ei++) { LRLASTCaseMatch *matchval = &casenode->matchvalues[ei]; ensure_expr_defined(cctx, matchval->expr); if (matchval->withstm) { ensure_statement_defined(cctx, matchval->withstm); } } ensure_statement_defined(cctx, casenode->stmt); } if (stmt->kind.switchstm.defaultstm) { ensure_statement_defined(cctx, stmt->kind.switchstm.defaultstm); } break; } case LRL_AST_Stmt_While: case LRL_AST_Stmt_DoWhile: ensure_expr_defined(cctx, stmt->kind.whilestm.boolexpr); ensure_statement_defined(cctx, stmt->kind.whilestm.body); break; case LRL_AST_Stmt_For: { const LRLASTCtlForTemp *temp = stmt->kind.forstm.temp; tr = typeref_root(stmt->kind.forstm.valuedef.kind.data.type, 0); ensure_type_defined(cctx, &tr, NEED_COMPLETE); ensure_expr_defined(cctx, stmt->kind.forstm.iterexpr); if (temp->loop_type == LRL_AST_LT_DirectIter) { const LRLIdent *ident = temp->next_element_ident; if (ident && ident->def_node) { ensure_defstmt_defined(cctx, ident->def_node); } } ensure_statement_defined(cctx, stmt->kind.forstm.body); break; } case LRL_AST_Stmt_Break: case LRL_AST_Stmt_Continue: case LRL_AST_Stmt_Goto: case LRL_AST_Stmt_SkipTo: case LRL_AST_Stmt_RepeatFrom: case LRL_AST_Stmt_Unreachable: case LRL_AST_Stmt_Label: case LRL_AST_Stmt_TypeAssert: /* Do nothing */ break; case LRL_AST_Stmt_Return: if (stmt->kind.retstm.retexpr) { ensure_expr_defined(cctx, stmt->kind.retstm.retexpr); } break; case LRL_AST_Stmt_Assert: ensure_expr_defined(cctx, stmt->kind.assertstm.boolexpr); break; LRL_case_except_ast_stmts default: fail("ctrans_ensurestmt_switch"); } } static void trans_funcbody(CTranCtx *cctx, LRLASTDef *def) { LRLASTStmt *code = def->kind.function.code; LRLASTDefList *list; cctx->current_function_ident = def->kind.function.ident; /* Ensure everything is declared, except for the arguments which are declared in the function definition */ for (list = def->kind.function.type.kind.function.args->kind.struc.members; list; list = list->next) { list->def.kind.data.flags |= LRL_DeFl_Internal_DefinedByBackend; } ensure_statement_defined(cctx, code); /* static/extern/normal keyword */ print_linkage_keyword(cctx, def->kind.function.flags); /* Declaration */ print_type(cctx, def->kind.function.ident, &def->kind.function.type, 0); /* Function body */ fputs("\n{\n", cctx->file); declare_stmt_temps(cctx, code, 1); trans_statement(cctx, code, code->ast_type == LRL_AST_Stmt_Compound ? 0 : 1); fputs("}\n\n", cctx->file); cctx->current_function_ident = NULL; } /** * Main function of C backend. */ static int ctrans_process(LRLCtx *ctx, LRLASTNamespace *root, const LRLBackendOptions *options) { FILE *file; CTranCtx cctx; size_t i; if (strchr(options->output, QUOTECHAR)) { /* TODO escape or use execv+pipe instead of popen */ fprintf(stderr, "error: c backend does not yet allow ' and \" in filenames\n"); return 2; } switch (options->op) { case LRL_BO_MakeObject: case LRL_BO_MakeExec: { const char *cc = lrl_config_get(options->config, "backend c", "cc", "cc"); const char *ccopt = lrl_config_get(options->config, "backend c", "ccopt", "-std=c99 -x c -lm -"); const char *cmdfmt = "%s %s%s -o " QUOTESTR "%s" QUOTESTR; char *cmd = malloc(strlen(cmdfmt)-(4*2) + strlen(cc)+3+strlen(ccopt)+ strlen(options->output)+1); sprintf(cmd, cmdfmt, cc, (options->op == LRL_BO_MakeObject ? "-c " : ""), ccopt, options->output); file = popen(cmd, "w"); free(cmd); break; } case LRL_BO_DumpIR: file = stdout; break; default: fail("ctrans_process_badoperation"); return 2; } /* Add standard declarations */ add_std_defs(file); memset(&cctx, 0, sizeof(cctx)); cctx.file = file; cctx.ctx = ctx; init_list(&cctx.bodies, &cctx.bodies_size, &cctx.bodies_capacity, 16); /* Translate the declarations */ trans_namespace(&cctx, root); /* Translate function bodies and variable initializations */ for (i = 0; i < cctx.bodies_size; i++) { LRLASTDef *def = cctx.bodies[i]; if (def->ast_type == LRL_AST_Def_Data) { trans_varinit(&cctx, &def->kind.data); } else if (def->ast_type == LRL_AST_Def_Function) { trans_funcbody(&cctx, def); } else { fail("ctrans_process_wrongbodytype"); } } free(cctx.bodies); if (options->op != LRL_BO_DumpIR) { int status = pclose(file); if (!pclose_exit_ok(status)) return 1; } return 0; } const LRLBackend ctrans_backend = { "ctrans", &ctrans_process, }; #endif