From cdc1bfe267edc34bfa3237cad00e7a327c4e9c69 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Mon, 16 Dec 2019 11:28:39 +0100 Subject: [PATCH] Start using UnaryOp/PostUnaryOp ++/-- now works post/pre and also on pointers. Fix ++/-- on any number. Added examples. c3c now compiles files by default. Extended symbol resolution. Addition of 'extern'. Multi-level paths. --- CMakeLists.txt | 4 +- resources/c3.l | 3 + resources/examples/base64.c3 | 121 ++ resources/examples/binarydigits.c3 | 21 + resources/examples/gameoflife.c3 | 77 + resources/examples/hash.c3 | 212 ++ resources/examples/http.c3 | 26 + resources/examples/levenshtein.c3 | 24 + resources/examples/madlibs.c3 | 31 + resources/examples/map.c3 | 105 + resources/examples/toml_parser_c2.c3 | 762 ++++++++ resources/examples/toml_tokenizer_c2.c3 | 359 ++++ resources/examples/vector.c3 | 70 + resources/examples/window.c3 | 35 + resources/grammar.y | 35 +- resources/testfragments/super_simple.c3 | 9 +- resources/testproject/bar.c3 | 11 + resources/testproject/foo.c3 | 20 + resources/testproject/hello_world.c3 | 11 + resources/testproject/project.toml | 10 + resources/testproject/zab.c3 | 6 + src/build/build_internal.h | 17 + src/build/build_options.c | 6 +- src/build/build_options.h | 22 +- src/build/builder.c | 28 + src/build/project.c | 154 ++ src/build/project_creation.c | 4 +- src/build/project_creation.h | 4 +- src/compiler/ast.c | 50 +- src/compiler/bigint.c | 4 +- src/compiler/bigint.h | 4 +- src/compiler/casts.c | 4 +- src/compiler/compiler.c | 121 +- src/compiler/compiler.h | 9 +- src/compiler/compiler_internal.h | 90 +- src/compiler/context.c | 133 +- src/compiler/diagnostics.c | 4 +- src/compiler/dwarf.h | 4 +- src/compiler/enums.h | 25 +- src/compiler/expr_analysis.c | 53 +- src/compiler/lexer.c | 4 +- src/compiler/llvm_codegen.c | 23 +- src/compiler/llvm_codegen_debug_info.c | 4 +- src/compiler/llvm_codegen_expr.c | 212 +- src/compiler/llvm_codegen_function.c | 62 +- src/compiler/llvm_codegen_internal.h | 8 +- src/compiler/llvm_codegen_module.c | 7 +- src/compiler/llvm_codegen_stmt.c | 6 +- src/compiler/llvm_codegen_type.c | 18 +- src/compiler/module.c | 60 +- src/compiler/parser.c | 221 ++- src/compiler/sema_name_resolution.c | 133 ++ src/compiler/semantic_analyser.c | 141 +- src/compiler/source_file.c | 12 +- src/compiler/symtab.c | 12 +- src/compiler/tokens.c | 20 +- src/compiler/types.c | 4 +- src/compiler_tests/benchmark.c | 4 +- src/compiler_tests/benchmark.h | 4 +- src/compiler_tests/shorttest.c | 4 +- src/compiler_tests/tests.c | 4 +- src/compiler_tests/tests.h | 4 +- src/main.c | 6 +- src/utils/common.h | 8 +- src/utils/errors.c | 4 +- src/utils/errors.h | 4 +- src/utils/file_utils.c | 79 +- src/utils/file_utils.h | 9 - src/utils/lib.h | 11 +- src/utils/malloc.c | 6 +- src/utils/malloc.h | 4 +- src/utils/stringutils.c | 4 +- src/utils/toml.c | 2365 +++++++++++++++++++++++ src/utils/toml.h | 226 +++ 74 files changed, 5924 insertions(+), 457 deletions(-) create mode 100644 resources/examples/base64.c3 create mode 100644 resources/examples/binarydigits.c3 create mode 100644 resources/examples/gameoflife.c3 create mode 100644 resources/examples/hash.c3 create mode 100644 resources/examples/http.c3 create mode 100644 resources/examples/levenshtein.c3 create mode 100644 resources/examples/madlibs.c3 create mode 100644 resources/examples/map.c3 create mode 100644 resources/examples/toml_parser_c2.c3 create mode 100644 resources/examples/toml_tokenizer_c2.c3 create mode 100644 resources/examples/vector.c3 create mode 100644 resources/examples/window.c3 create mode 100644 resources/testproject/bar.c3 create mode 100644 resources/testproject/foo.c3 create mode 100644 resources/testproject/hello_world.c3 create mode 100644 resources/testproject/project.toml create mode 100644 resources/testproject/zab.c3 create mode 100644 src/build/build_internal.h create mode 100644 src/build/builder.c create mode 100644 src/build/project.c create mode 100644 src/compiler/sema_name_resolution.c delete mode 100644 src/utils/file_utils.h create mode 100644 src/utils/toml.c create mode 100644 src/utils/toml.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e1dcacbb..a1a918ecd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -152,7 +152,9 @@ add_executable(c3c src/compiler/llvm_codegen_debug_info.c src/compiler/llvm_codegen_module.c src/compiler/llvm_codegen_type.c - src/compiler/llvm_codegen_function.c) + src/compiler/llvm_codegen_function.c + src/build/builder.c + src/utils/toml.c src/build/project.c src/build/build_internal.h src/compiler/sema_name_resolution.c) target_compile_options(c3c PRIVATE -Wimplicit-int -Werror -Wall -Wextra -Wno-unused-function -Wno-unused-variable -Wno-unused-parameter) diff --git a/resources/c3.l b/resources/c3.l index 13e5952f6..cf4ba89ac 100644 --- a/resources/c3.l +++ b/resources/c3.l @@ -136,6 +136,9 @@ L?\"(\\.|[^\\"])*\" { count(); return(STRING_LITERAL); } "^" { count(); return('^'); } "|" { count(); return('|'); } "?" { count(); return('?'); } +"+%" { count(); return(ADDW); } +"*%" { count(); return(MULTW); } +"-%" { count(); return(SUBW); } "({" { count(); return(FN_BLOCK_BEGIN); } "{)" { count(); return(FN_BLOCK_END); } [ \t\v\n\f] { count(); } diff --git a/resources/examples/base64.c3 b/resources/examples/base64.c3 new file mode 100644 index 000000000..a40c9b1f8 --- /dev/null +++ b/resources/examples/base64.c3 @@ -0,0 +1,121 @@ +module base64; +// Based on the C2 version. + +const char[] LUT_ENC = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/', +}; + +const byte ERR = 0xFF; + +const byte[256] LUT_DEC = { + // '+', ',', '-', '.', '/', '0', '1', '2' + 62, ERR, ERR, ERR, 63, 52, 53, 54, + // '3', '4', '5', '6', '7', '8', '9', ':' + 55, 56, 57, 58, 59, 60, 61, ERR, + // ';', '<', '=', '>', '?', '@', 'A', 'B' + ERR, ERR, ERR, ERR, ERR, ERR, 0, 1, + // 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J' + 2, 3, 4, 5, 6, 7, 8, 9, + // 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R' + 10, 11, 12, 13, 14, 15, 16, 17, + // 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' + 18, 19, 20, 21, 22, 23, 24, 25, + // '[', '\', ']', '^', '_', '`', 'a', 'b' + ERR, ERR, ERR, ERR, ERR, ERR, 26, 27, + // 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j' + 28, 29, 30, 31, 32, 33, 34, 35, + // 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r' + 36, 37, 38, 39, 40, 41, 42, 43, + // 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' + 44, 45, 46, 47, 48, 49, 50, 51, +}; + + +const char PAD = '='; +const char FIRST = '+'; +const char LAST = 'z'; + +public error Base64Error +{ + INVALID_CHARACTER +} + +public func void encode(byte[] in, string *out) +{ + int j = 0; + + for (int i = 0; i < in.len; i++); + { + switch (i % 3) + { + case 0: + out[j++] = LUT_ENC[(in[i] >> 2) & 0x3F]; + case 1: + out[j++] = LUT_ENC[(in[i-1] & 0x3) << 4 + ((in[i] >> 4) & 0xF)]; + case 2: + out[j++] = LUT_ENC[(in[i-1] & 0xF) << 2 + ((in[i] >> 6) & 0x3)]; + out[j++] = LUT_ENC[in[i] & 0x3F]; + } + i++; + } + + // move back + int last = in.len - 1; + + // check the last and add padding + switch (last % 3) + { + case 0: + out[j++] = LUT_ENC[(in[last] & 0x3) << 4]; + out[j++] = PAD; + out[j++] = PAD; + case 1: + out[j++] = LUT_ENC[(in[last] & 0xF) << 2]; + out[j++] = PAD; + } +} + +public func int decode(string in, byte[] out) throws Base64Error +{ + int j = 0; + + for (int i = 0; i < len; i++) + { + char value = in[i]; + + if (value == PAD) return j; + + if (value < FIRST || in[i] > LAST) throw INVALID_CHARACTER; + byte c = LUT_DEC[in[i] - FIRST); + if (c == ERR) throw INVALID_CHARACTER; + + switch (i % 4) + { + case 0: + out[j] = c << 2; + case 1: + out[j++] += (c >> 4) & 0x3; + // if not last char with padding + if (i < (len - 3) || in[len - 2] != PAD) + { + out[j] = (c & 0xF) << 4; + } + case 2: + out[j++] += (c >> 2) & 0xF; + if (i < (len -2) || in[len -1] != PAD) + { + out[j] = (c & 0x3) << 6; + } + case 3: + out[j++] += c; + } + } + return j; +} \ No newline at end of file diff --git a/resources/examples/binarydigits.c3 b/resources/examples/binarydigits.c3 new file mode 100644 index 000000000..39b602471 --- /dev/null +++ b/resources/examples/binarydigits.c3 @@ -0,0 +1,21 @@ +module binarydigits; + +func int main() +{ + fot (int i = 0; i < 20; i++) + { + printf("%s\n", bin(i)); + } +} + +func string@ bin(int x) +{ + int bits = (x == 0) ? 1 : log10(cast(double, x)) / log10(2); + string@ ret = string.make_repeat('0', bits); + for (int i = 0; i < bits; i++) + { + ret[bits - i - 1] = x & 1 ? '1' : '0'; + x >>= 1; + } + return ret; +} \ No newline at end of file diff --git a/resources/examples/gameoflife.c3 b/resources/examples/gameoflife.c3 new file mode 100644 index 000000000..4c8c2bd2f --- /dev/null +++ b/resources/examples/gameoflife.c3 @@ -0,0 +1,77 @@ +module game_of_life; + +struct GameBoard +{ + int h; + int w; + byte* world; + byte* temp; +} + +func void GameBoard.show(GameBoard *board) +{ + printf("\033[H"); + byte* current = board.world; + for (int y = 0; y < board.h; y++) + { + for (int x = 0; x < board.w; x++) + { + printf(*current ? "\033[07m \033[m" : " "); + } + printf("\033[E"); + } + stdout.fflush(); +} + +func void GameBoard.evolve(GameBoard *board) +{ + for (int y = 0; y < board.h; y++) + { + for (int x = 0; x < board.w; x++) + { + int n = 0; + for (int y1 = y - 1; y1 <= y + 1; y1++) + { + for (int x1 = x - 1; x1 <= x + 1; x1++) + { + int actualX = (x1 + w) % w; + int actualY = (y1 + h) % h; + if (board.world[x + y * w]) n++; + } + } + if (board.world(x + y * w)) n--; + board.temp[x + y * w] = (n == 3 || (n == 2 && board.world(x + y * w))); + } + } + for (int i = 0; i < w * h; i++) + { + board.world[i] = board.temp[i]; + } +} + +int main(int c, string[] v) +{ + int w = 0, h = 0; + if (c > 1) w = atoi(v[1]); + if (c > 2) h = atoi(v[2]); + if (w <= 0) w = 30; + if (h <= 0) h = 30; + + GameBoard board; + board.w = w; + board.h = h; + board.board = malloc(h * w); + board.temp = malloc(h * w); + + for (int i = h * w - 1; i >= 0; i--) + { + board.world[i] = rand() < RAND_MAX / 10 ? 1 : 0; + } + while (1) + { + + board.show(); + board.evolve(); + usleep(200000); + } +} \ No newline at end of file diff --git a/resources/examples/hash.c3 b/resources/examples/hash.c3 new file mode 100644 index 000000000..e28a04058 --- /dev/null +++ b/resources/examples/hash.c3 @@ -0,0 +1,212 @@ +module hash; + +// Code adapted from Odin's hash.odin +// The code below should not be considered *correct* +// They are merely done to illustrate the language syntax. + +public func uint adler32(byte[] data) +{ + const uint ADLER_CONST = 65521; + uint a = 1; + uint b = 0; + for (byte x : data) + { + a = (a + x) % ADLER_CONST; + b = (b + a) % ADLER_CONST; + } + return (b << 16) | a; +} + +public func uint crc32(byte[] data) +{ + uint result = ~cast(uint, 0); + for (byte x : data) + { + result = (result >> 8) ^ CRC32_TABLE[result ^ x) & 255]; + } + return ~result; +} + +public func uint crc64(byte[*] data) +{ + ulong result = ~cast(ulong, 0); + for (byte x : data) + { + result = (result >> 8) ^ CRC64_TABLE[(result ^ x) & 255]; + } + return ~result; +} + +public func uint fnv32(byte[*] data) +{ + uint h = 0x811c9dc5; + for (byte x : data) + { + h = (h *% 0x01000193) ^ x; + } + return h; +} + +public func ulong fnv64(byte[] data) +{ + ulong h = 0xcbf29ce484222325; + for (byte x : data) + { + h = (h *% 0x100000001b3) ^ x; + } + return h; +} + +public func uint fnv32a(byte[] data) +{ + uint h = 0x811c9dc5; + for (byte x : data) + { + h = (h ^ b) *% 0x01000193; + } + return h; +} + +public func ulong fnv32a(byte[] data) +{ + ulong h = 0xcbf29ce484222325; + for (byte x in data) + { + h = (h ^ b) *% 0x100000001b3; + } + return h; +} + +const uint[256] CRC32_TABLE = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, + 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, + 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, + 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, + 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, + 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, + 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, + 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, + 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, + 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, + 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, + 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, + 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, + 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, + 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, + 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, + 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, + 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, +}; + +const ulong[256] CRC64_TABLE = { + 0x0000000000000000, 0x42f0e1eba9ea3693, 0x85e1c3d753d46d26, 0xc711223cfa3e5bb5, + 0x493366450e42ecdf, 0x0bc387aea7a8da4c, 0xccd2a5925d9681f9, 0x8e224479f47cb76a, + 0x9266cc8a1c85d9be, 0xd0962d61b56fef2d, 0x17870f5d4f51b498, 0x5577eeb6e6bb820b, + 0xdb55aacf12c73561, 0x99a54b24bb2d03f2, 0x5eb4691841135847, 0x1c4488f3e8f96ed4, + 0x663d78ff90e185ef, 0x24cd9914390bb37c, 0xe3dcbb28c335e8c9, 0xa12c5ac36adfde5a, + 0x2f0e1eba9ea36930, 0x6dfeff5137495fa3, 0xaaefdd6dcd770416, 0xe81f3c86649d3285, + 0xf45bb4758c645c51, 0xb6ab559e258e6ac2, 0x71ba77a2dfb03177, 0x334a9649765a07e4, + 0xbd68d2308226b08e, 0xff9833db2bcc861d, 0x388911e7d1f2dda8, 0x7a79f00c7818eb3b, + 0xcc7af1ff21c30bde, 0x8e8a101488293d4d, 0x499b3228721766f8, 0x0b6bd3c3dbfd506b, + 0x854997ba2f81e701, 0xc7b97651866bd192, 0x00a8546d7c558a27, 0x4258b586d5bfbcb4, + 0x5e1c3d753d46d260, 0x1cecdc9e94ace4f3, 0xdbfdfea26e92bf46, 0x990d1f49c77889d5, + 0x172f5b3033043ebf, 0x55dfbadb9aee082c, 0x92ce98e760d05399, 0xd03e790cc93a650a, + 0xaa478900b1228e31, 0xe8b768eb18c8b8a2, 0x2fa64ad7e2f6e317, 0x6d56ab3c4b1cd584, + 0xe374ef45bf6062ee, 0xa1840eae168a547d, 0x66952c92ecb40fc8, 0x2465cd79455e395b, + 0x3821458aada7578f, 0x7ad1a461044d611c, 0xbdc0865dfe733aa9, 0xff3067b657990c3a, + 0x711223cfa3e5bb50, 0x33e2c2240a0f8dc3, 0xf4f3e018f031d676, 0xb60301f359dbe0e5, + 0xda050215ea6c212f, 0x98f5e3fe438617bc, 0x5fe4c1c2b9b84c09, 0x1d14202910527a9a, + 0x93366450e42ecdf0, 0xd1c685bb4dc4fb63, 0x16d7a787b7faa0d6, 0x5427466c1e109645, + 0x4863ce9ff6e9f891, 0x0a932f745f03ce02, 0xcd820d48a53d95b7, 0x8f72eca30cd7a324, + 0x0150a8daf8ab144e, 0x43a04931514122dd, 0x84b16b0dab7f7968, 0xc6418ae602954ffb, + 0xbc387aea7a8da4c0, 0xfec89b01d3679253, 0x39d9b93d2959c9e6, 0x7b2958d680b3ff75, + 0xf50b1caf74cf481f, 0xb7fbfd44dd257e8c, 0x70eadf78271b2539, 0x321a3e938ef113aa, + 0x2e5eb66066087d7e, 0x6cae578bcfe24bed, 0xabbf75b735dc1058, 0xe94f945c9c3626cb, + 0x676dd025684a91a1, 0x259d31cec1a0a732, 0xe28c13f23b9efc87, 0xa07cf2199274ca14, + 0x167ff3eacbaf2af1, 0x548f120162451c62, 0x939e303d987b47d7, 0xd16ed1d631917144, + 0x5f4c95afc5edc62e, 0x1dbc74446c07f0bd, 0xdaad56789639ab08, 0x985db7933fd39d9b, + 0x84193f60d72af34f, 0xc6e9de8b7ec0c5dc, 0x01f8fcb784fe9e69, 0x43081d5c2d14a8fa, + 0xcd2a5925d9681f90, 0x8fdab8ce70822903, 0x48cb9af28abc72b6, 0x0a3b7b1923564425, + 0x70428b155b4eaf1e, 0x32b26afef2a4998d, 0xf5a348c2089ac238, 0xb753a929a170f4ab, + 0x3971ed50550c43c1, 0x7b810cbbfce67552, 0xbc902e8706d82ee7, 0xfe60cf6caf321874, + 0xe224479f47cb76a0, 0xa0d4a674ee214033, 0x67c58448141f1b86, 0x253565a3bdf52d15, + 0xab1721da49899a7f, 0xe9e7c031e063acec, 0x2ef6e20d1a5df759, 0x6c0603e6b3b7c1ca, + 0xf6fae5c07d3274cd, 0xb40a042bd4d8425e, 0x731b26172ee619eb, 0x31ebc7fc870c2f78, + 0xbfc9838573709812, 0xfd39626eda9aae81, 0x3a28405220a4f534, 0x78d8a1b9894ec3a7, + 0x649c294a61b7ad73, 0x266cc8a1c85d9be0, 0xe17dea9d3263c055, 0xa38d0b769b89f6c6, + 0x2daf4f0f6ff541ac, 0x6f5faee4c61f773f, 0xa84e8cd83c212c8a, 0xeabe6d3395cb1a19, + 0x90c79d3fedd3f122, 0xd2377cd44439c7b1, 0x15265ee8be079c04, 0x57d6bf0317edaa97, + 0xd9f4fb7ae3911dfd, 0x9b041a914a7b2b6e, 0x5c1538adb04570db, 0x1ee5d94619af4648, + 0x02a151b5f156289c, 0x4051b05e58bc1e0f, 0x87409262a28245ba, 0xc5b073890b687329, + 0x4b9237f0ff14c443, 0x0962d61b56fef2d0, 0xce73f427acc0a965, 0x8c8315cc052a9ff6, + 0x3a80143f5cf17f13, 0x7870f5d4f51b4980, 0xbf61d7e80f251235, 0xfd913603a6cf24a6, + 0x73b3727a52b393cc, 0x31439391fb59a55f, 0xf652b1ad0167feea, 0xb4a25046a88dc879, + 0xa8e6d8b54074a6ad, 0xea16395ee99e903e, 0x2d071b6213a0cb8b, 0x6ff7fa89ba4afd18, + 0xe1d5bef04e364a72, 0xa3255f1be7dc7ce1, 0x64347d271de22754, 0x26c49cccb40811c7, + 0x5cbd6cc0cc10fafc, 0x1e4d8d2b65facc6f, 0xd95caf179fc497da, 0x9bac4efc362ea149, + 0x158e0a85c2521623, 0x577eeb6e6bb820b0, 0x906fc95291867b05, 0xd29f28b9386c4d96, + 0xcedba04ad0952342, 0x8c2b41a1797f15d1, 0x4b3a639d83414e64, 0x09ca82762aab78f7, + 0x87e8c60fded7cf9d, 0xc51827e4773df90e, 0x020905d88d03a2bb, 0x40f9e43324e99428, + 0x2cffe7d5975e55e2, 0x6e0f063e3eb46371, 0xa91e2402c48a38c4, 0xebeec5e96d600e57, + 0x65cc8190991cb93d, 0x273c607b30f68fae, 0xe02d4247cac8d41b, 0xa2dda3ac6322e288, + 0xbe992b5f8bdb8c5c, 0xfc69cab42231bacf, 0x3b78e888d80fe17a, 0x7988096371e5d7e9, + 0xf7aa4d1a85996083, 0xb55aacf12c735610, 0x724b8ecdd64d0da5, 0x30bb6f267fa73b36, + 0x4ac29f2a07bfd00d, 0x08327ec1ae55e69e, 0xcf235cfd546bbd2b, 0x8dd3bd16fd818bb8, + 0x03f1f96f09fd3cd2, 0x41011884a0170a41, 0x86103ab85a2951f4, 0xc4e0db53f3c36767, + 0xd8a453a01b3a09b3, 0x9a54b24bb2d03f20, 0x5d45907748ee6495, 0x1fb5719ce1045206, + 0x919735e51578e56c, 0xd367d40ebc92d3ff, 0x1476f63246ac884a, 0x568617d9ef46bed9, + 0xe085162ab69d5e3c, 0xa275f7c11f7768af, 0x6564d5fde549331a, 0x279434164ca30589, + 0xa9b6706fb8dfb2e3, 0xeb46918411358470, 0x2c57b3b8eb0bdfc5, 0x6ea7525342e1e956, + 0x72e3daa0aa188782, 0x30133b4b03f2b111, 0xf7021977f9cceaa4, 0xb5f2f89c5026dc37, + 0x3bd0bce5a45a6b5d, 0x79205d0e0db05dce, 0xbe317f32f78e067b, 0xfcc19ed95e6430e8, + 0x86b86ed5267cdbd3, 0xc4488f3e8f96ed40, 0x0359ad0275a8b6f5, 0x41a94ce9dc428066, + 0xcf8b0890283e370c, 0x8d7be97b81d4019f, 0x4a6acb477bea5a2a, 0x089a2aacd2006cb9, + 0x14dea25f3af9026d, 0x562e43b4931334fe, 0x913f6188692d6f4b, 0xd3cf8063c0c759d8, + 0x5dedc41a34bbeeb2, 0x1f1d25f19d51d821, 0xd80c07cd676f8394, 0x9afce626ce85b507, +}; diff --git a/resources/examples/http.c3 b/resources/examples/http.c3 new file mode 100644 index 000000000..e39384838 --- /dev/null +++ b/resources/examples/http.c3 @@ -0,0 +1,26 @@ +import curl; + +int main(void) +{ + try Curl curl = Curl.new(); + + catch (error e) + { + printf("Failed to create new curl: %s\n", e.message); + exit(FAILURE); + } + + curl.setopt(URL, "http://www.rosettacode.org/"); + curl.setopt(FOLLOWLOCATION, 1); + + try curl.perform(); + + catch (CurlError e) + { + printf("Error making request: %s\n", e.message); + exit(FAILURE); + } + + exit(SUCCESS); + +} diff --git a/resources/examples/levenshtein.c3 b/resources/examples/levenshtein.c3 new file mode 100644 index 000000000..75a55da59 --- /dev/null +++ b/resources/examples/levenshtein.c3 @@ -0,0 +1,24 @@ +module levenshtein; + +func int levenshtein(string s, string t) +{ + // if either string is empty, difference is inserting all chars + // from the other + if (!s.size) return t.size; + if (!t.size) return s.size; + + // if last letters are the same, the difference is whatever is + // required to edit the rest of the strings + if (s[s.size - 1] == t[t.size - 1]) return levenshtein(s.slice(0, s.size - 1), t.slice(0, t.size - 1)); + + // else try: + // changing last letter of s to that of t; or + // remove last letter of s; or + // remove last letter of t, + // any of which is 1 edit plus editing the rest of the strings + int a = levenshtein(s.slice(0, s.size - 1), t.slice(0, t.size - 1)); + int b = levenshtein(s, t.slice(0, t.size - 1); + int c = levenshtein(s.slice(0, s.size - 1), t); + + return @max(@max(a, b), c) + 1; +} \ No newline at end of file diff --git a/resources/examples/madlibs.c3 b/resources/examples/madlibs.c3 new file mode 100644 index 000000000..7cdf9322d --- /dev/null +++ b/resources/examples/madlibs.c3 @@ -0,0 +1,31 @@ +module madlibs; +import regex, stdio; + +func void main() +{ + println("Enter a story template, terminated by an empty line:"); + string@ story = ""; + while (1) + { + try string line = stdin.readln().strip(); + if (!line.size) break; + story += line + "\n"; + } + + Regex* r = try regex::regexWithOptions("<.+?>", RegexOpt.GLOBAL) catch @unreachable; + + foreach (RegexMatch* match : r.match(story)) + { + string s = match.string; + printf("Enter a value for '%s': ", s.slice(1, s.size - 2)); + try string word = strin.readln().strip(); + story = story.replace(s, word); + } + + println("\nThe story becomes:\n%s\n", story); + + catch (error e) + { + // Ignore any error + } +} \ No newline at end of file diff --git a/resources/examples/map.c3 b/resources/examples/map.c3 new file mode 100644 index 000000000..f60510ae6 --- /dev/null +++ b/resources/examples/map.c3 @@ -0,0 +1,105 @@ +module map(Key, Type); + +public struct Entry +{ + Key key; + Type *value; + usize hash; + Entry *next; +} + +public struct Map +{ + usize size; + void* map; + uint mod; +} + +public func Map@ Map.new() +{ + return @calloc(Map); +} + +public func Type *Map.valueForKey(Map *map, Key key) +{ + if (!map.map) return nil; + usize hash = key.hash(); + usize pos = hash & map.mod; + Entry *entry = &map.map[pop]; + if () return nil; + while (entry) + { + if (entry.hash == hash && entry.key == key) return entry.value; + entry = entry.next; + } + return nil; +} + +public func Type *Map.setValueForKey(Map *map, Key key, Type *value) +{ + if (!map.map) + { + map.map = @calloc(Entry, 16); + map.mod = 0x0F; + } + + usize hash = key.hash(); + usize pos = hash & map.mod; + Entry *entry = &map.map[pop]; + while (1) + { + if (!entry.value) + { + entry.value = value; + entry.hash = hash; + entry.key = key; + return nil; + } + if (entry.hash == hash && entry.key == key) + { + Type *old = entry.value; + entry.value = value; + return old; + } + if (entry.next) + { + entry = entry.next; + } + entry.next = @malloc(Entry); + entry = entry.next; + } +} + +public func usize Map.size(Vector *vector) +{ + return vector.array.size; +} + +public func void Map.removeLast(Vector *vector) +{ + vector.array.pop(); +} + +public macro Vector.@foreach(Vector *vector : @body(Type value)) +{ + for (usize i = 0, i < vector.array.size; i++) + { + @body(vector.array[i]); + } +} + +test +{ + define IntVector = Vector(int); + IntVector *vector = @calloc(IntVector); + vector.add(1); + vector.add(2); + foreach (int i : vector) + { + printDigit(i); + } + vector.@foreach(int i) + { + printDigit(i); + } +} \ No newline at end of file diff --git a/resources/examples/toml_parser_c2.c3 b/resources/examples/toml_parser_c2.c3 new file mode 100644 index 000000000..29ca4938c --- /dev/null +++ b/resources/examples/toml_parser_c2.c3 @@ -0,0 +1,762 @@ +// This is the toml_parser.c2 changed to c3 to compare + +module toml; + +import stdio; +import stdlib; +import string; +import file_utils; +import csetjmp; + +const uint NamesCacheSize = 8; +const uint MaxNodes = 1024; +const uint MaxNames = 4096; +const uint MaxValues = 4096 * 128; +const uint MaxDepth = 8; + +//#define DEBUG_NODES + +$if (DEBUG_NODES) + +func void Blocks.dump(const Blocks* b) +{ + printf("Nodes (%u/%u) (%u bytes)\n", b.nodeCount, b.maxNodes, b.nodeCount * sizeof(Node)); + for (uint i = 0; i < b.nodeCount; i++) + { + // @ensure const(n) + Node* n = &b.nodes[i]; + uint nameOffset = getValue(n.nameOffset); + NodeKind kind = getKind(n.nameOffset); + switch (kind) + { + case NodeKind.TABLE: + case NodeKind.TABLE_ARRAY: + printf(" [%3u] %s name %3u next %3u child %3u (%s)\n", + i, kind.name, nameOffset, n.nextNode, n.child, &b.names[nameOffset]); + case NodeKind.VALUE_ARRAY: + case NodeKind.VALUE: + ValueType t = getRawType(n.rawValue); + uint offset = getRawValue(n.rawValue); + printf(" [%3u] %s name %3u next %3u value %5u(%s) (%s)\n", + i, kind.name, nameOffset, n.nextNode, offset, type2str(t), &b.names[nameOffset]); + case NodeKind.ARRAY_CHILD: + printf(" [%3u] %s name --- next %3u child %3u\n", + i, kind.name, n.nextNode, n.child); + } + } + printf("Names (%u/%u)\n", b.namesOffset, b.namesSize); + uint i = 1; + uint start = i; + while (i < b.namesOffset) + { + if (b.names[i] == 0) { + printf(" [%3u] %s\n", start, &b.names[start]); + i++; + start = i; + } + else + { + i++; + } + } + printf("Values (%u/%u)\n", b.valuesOffset, b.valuesSize); + i = 1; + start = i; + while (i < b.valuesOffset) + { + if (b.values[i] == 0) + { + printf(" [%3u] %s\n", start, &b.values[start]); + i++; + start = i; + } + else + { + i++; + } + } +} + +$endif + +/** + * @ensure const(a), const(b) + */ +func bool same(char* a, char* b) +{ + uint i = 0; + while (a[i] == b[i]) + { + if (a[i] == 0) return true; + ++i; + } + return false; +} + +struct Parser +{ + Tokenizer tokenizer; + Token tok; + JmpBuf jump_err; + char* errorMsg; + + Blocks* blocks; + Node*[MaxDepth] parents; + Node*[MaxDepth] lastChild; + uint numParents; + Node* topParent; +} + +/** + * @ensure const(input) + */ +func Parser.parse(Parser* p, char* input, char* diagMsg, Blocks* blocks) throws +{ + p.tokenizer.init(input); + p.tok.init(); + p.errorMsg = diagMsg; + p.errorMsg[0] = 0; + + p.blocks = blocks; + memset(p.parents, 0, sizeof(Node*)*MaxDepth); + memset(p.lastChild, 0, sizeof(Node*)*MaxDepth); + p.numParents = 0; + p.topParent = nil; + + try p.consumeToken(); + try p.parseTopLevel(); + return true; + + catch (error e) + { + return false; + } +} + +func void Parser.parseTopLevel(Parser* p) throws ParseError +{ + // key = value + // | [[array]] + // | [table] + while (p.tok.isNot(TokenKind.EOF)) + { + switch (p.tok.kind) + { + case TokenKind.WORD: + p.parseKeyValue(); + case TokenKind.LBRACE: + p.parseTable(); + case TokenKind.Lbrace2: + p.parseTableArray(); + default: + sprintf(p.errorMsg, "syntax error %s", p.tok.loc.str()); + throw ParseError.SYNTAX_ERROR; + } + } +} + +func uint getRawValue(uint raw) @(inline) +{ + return raw & ~RawValueMask; +} + +func ValueType getRawType(uint raw) @(inline) +{ + return cast(ValueType, (raw >> ValueTypeOffset) & 0x3); +} + +func uint addType(uint raw, ValueType t) @(inline) +{ + return raw | (t << ValueTypeOffset); +} + +func void Parser.parseKeyValue(Parser* p) throws ParseError +{ + //printf("parseKeyValue()\n"); + char[MaxText] key; + strcpy(key, p.tok.text); + try p.consumeToken(); + try p.expectAndConsume(TokenKind.Equals); + u32 value = try p.parseValue(); + bool isArray = value & ValueIsArray != 0; + u32 off = p.blocks.addNode(key, isArray ? NodeKind.ValueArray : NodeKind.Value); + Node* node = &p.blocks.nodes[off]; + node.rawValue = value; + if (p.lastChild[p.numParents]) + { + p.lastChild[p.numParents].nextNode = off; + } + else + { + if (p.topParent) p.topParent.child = off; + } + p.lastChild[p.numParents] = node; +} + +func void Parser.parseTable(Parser* p) throws ParseError +{ + //printf("parseTable()\n"); + try p.consumeToken(); + try p.expect(TokenKind.Word); + char* name = p.tok.text; + uint depth = 0; + bool isTop = p.nextToken().isNot(TokenKind.DOT); + depth += p.addTable(name, depth, isTop, NodeKind.Table); + p.consumeToken(); + + while (p.tok.is(TokenKind.DOT)) + { + depth++; + p.consumeToken(); + p.expect(TokenKind.WORD); + name = p.tok.text; + isTop = p.nextToken().isNot(TokenKind.DOT); + depth += p.addTable(name, depth, isTop, NodeKind.TABLE); + p.consumeToken(); + } + p.expectAndConsume(TokenKind.Rbrace); +} + +func void Parser.parseTableArray(Parser* p) +{ + //printf("parseTableArray()\n"); + p.consumeToken(); + p.expect(TokenKind.Word); + const char* name = p.tok.text; + u32 depth = 0; + bool isTop = p.nextToken().isNot(TokenKind.Dot); + depth += p.addTable(name, depth, isTop, NodeKind.TableArray); + p.consumeToken(); + + while (p.tok.is(TokenKind.Dot)) { + depth++; + p.consumeToken(); + p.expect(TokenKind.Word); + name = p.tok.text; + isTop = p.nextToken().isNot(TokenKind.Dot); + depth += p.addTable(name, depth, isTop, NodeKind.TableArray); + p.consumeToken(); + } + p.expectAndConsume(TokenKind.Rbrace2); +} + +func u32 Parser.parseValue(Parser* p) { + //printf("parseValue()\n"); + u32 value = 0; + switch (p.tok.kind) { + case TokenKind.Word: + sprintf(p.errorMsg, "unexpected word at %s", p.tok.loc.str()); + longjmp(p.jump_err, 1); + break; + case TokenKind.Text: + value = p.blocks.addValue(p.tok.text); + value = addType(value, ValueType.Text); + p.consumeToken(); + break; + case TokenKind.Number: + // TODO negative numbers + value = addType(p.tok.number, ValueType.Number); + p.consumeToken(); + break; + case TokenKind.Kw_true: fallthrough; + case TokenKind.Kw_false: + value = addType(p.tok.number, ValueType.Boolean); + p.consumeToken(); + break; + case TokenKind.Lbrace: + value = p.parseArrayValues(); + break; + default: + break; + } + return value; +} + +func u32 Parser.parseArrayValues(Parser* p) { + //printf("parseArrayValues()\n"); + p.consumeToken(); + u32 value = p.parseValue() | ValueIsArray; + while (p.tok.is(TokenKind.Comma)) { + p.consumeToken(); + if (p.tok.is(TokenKind.Rbrace)) break; // trailing comma is allowed + p.parseValue(); + } + p.expectAndConsume(TokenKind.Rbrace); + p.blocks.addNull(); + return value; +} + +func u32 Parser.addTable(Parser* p, const char* name, u32 depth, bool isTop, NodeKind kind) { + //printf("addTable %s\n", name); + Blocks* blocks = p.blocks; + if (!isTop && p.numParents > depth && same(blocks.getName(p.parents[depth]), name)) { + if (getKind(p.parents[depth].nameOffset) == NodeKind.TableArray) return 1; + // Do nothing + } else { + if (kind == NodeKind.TableArray) { + // TODO also check if previous is also TableArray + if (p.numParents > depth && same(blocks.getName(p.parents[depth]), name)) { + p.numParents = depth + 1; + } else { + u32 off = blocks.addNode(name, kind); + if (p.numParents > depth) p.parents[depth].nextNode = off; + Node* node = &blocks.nodes[off]; + p.parents[depth] = node; + + if (p.lastChild[depth]) { + p.lastChild[depth].nextNode = off; + } else { + if (depth > 0) p.parents[depth - 1].child = off; + } + p.numParents = depth + 1; + p.topParent = node; + p.lastChild[depth] = node; + p.lastChild[depth + 1] = nil; + } + if (isTop) { + // add iterator node as child or next + u32 off = blocks.addNode("", NodeKind.ArrayChild); + Node* iter = &blocks.nodes[off]; + if (p.lastChild[depth].child) { // already has children + p.lastChild[depth + 1].nextNode = off; + } else { + p.lastChild[depth].child = off; + } + p.lastChild[depth + 1] = iter; + p.parents[depth + 1] = iter; + p.lastChild[depth + 2] = nil; + p.topParent = iter; + p.numParents++; + } + return 1; + } + u32 off = blocks.addNode(name, kind); + if (p.numParents > depth) p.parents[depth].nextNode = off; + Node* node = &blocks.nodes[off]; + p.parents[depth] = node; + + if (p.lastChild[depth]) { + p.lastChild[depth].nextNode = off; + } else { + if (depth > 0) p.parents[depth-1].child = off; + } + p.numParents = depth + 1; + p.topParent = node; + p.lastChild[depth] = node; + p.lastChild[depth + 1] = nil; + } + return 0; +} + +func Location Parser.consumeToken(Parser* p) { + Location prev = p.tok.loc; + p.tokenizer.lex(&p.tok); + if (p.tok.is(TokenKind.Error)) { + strcpy(p.errorMsg, p.tok.text); + longjmp(p.jump_err, 1); + } + return prev; +} + +func Token* Parser.nextToken(Parser* p) { + return p.tokenizer.lookahead(); +} + +func void Parser.expectAndConsume(Parser* p, TokenKind k) { + if (p.tok.isNot(k)) { + sprintf(p.errorMsg, "expected '%s' at %s", token2str(k), p.tok.loc.str()); + longjmp(p.jump_err, 1); + } + p.consumeToken(); +} + +func void Parser.expect(Parser* p, TokenKind k) { + if (p.tok.isNot(k)) { + sprintf(p.errorMsg, "expected '%s' at %s", token2str(k), p.tok.loc.str()); + longjmp(p.jump_err, 1); + } +} + +const u32 MaxDiag = 128; + +public struct TomlReader @opaque +{ + char[MaxDiag] message; + Blocks* blocks; +} + +public func TomlReader* TomlReader.create() +{ + TomlReader* r = @malloc(TomlReader); + r.blocks = @malloc(Blocks); + r.blocks.init(); + return r; +} + +public func void TomlReader.destroy(TomlReader* r) +{ + r.blocks.destroy(); + free(r.blocks); + free(r); +} + +public func const char* TomlReader.getMsg(const TomlReader* r) +{ + return r.message; +} + +public func void TomlReader.parse(TomlReader* r, string filename) throws ParseError, FileError +{ + Reader file; + + try file.open(filename); + + defer file.close(); + + if (file.isEmpty()) + { + printf("file %s is empty\n", filename); + throw ParseError.EMPTY_FILE; + } + + Parser parser; + parser.parse(file.data(), r.message, r.blocks); + +$if (DEBUG_NODES) + r.blocks.dump(); +$endif + return status; +} + +// -------------------------------------------------------------- +// Getters+iters + +func const Node* Reader.findNode(const Reader* r, const char* key) { + char[MaxText] name; + const char* cp = key; + const char* start = cp; + u32 len = 0; + Node* node = nil; + while (1) { + switch (*cp) { + case 0: + len = cast(cp - start); + memcpy(name, start, len); + name[len] = 0; + node = r.blocks.findNode(name, node); + return node; + case '.': + len = cast(cp - start); + memcpy(name, start, len); + name[len] = 0; + start = cp + 1; + node = r.blocks.findNode(name, node); + if (!node) return nil; + if (getKind(node.nameOffset) == NodeKind.Value) return nil; + break; + default: + break; + } + cp++; + } + return nil; +} + +public func const char* Reader.getValue(const Reader* r, const char* key) { + const Node* node = r.findNode(key); + if (!node) return nil; + if (getKind(node.nameOffset) != NodeKind.Value) return nil; + ValueType t = getRawType(node.rawValue); + if (t != ValueType.Text) return nil; + return &r.blocks.values[getRawValue(node.rawValue)]; +} + +public func bool Reader.getNumber(const Reader* r, const char* key, u32* result) { + const Node* node = r.findNode(key); + if (!node) return false; + if (getKind(node.nameOffset) != NodeKind.Value) return false; + ValueType t = getRawType(node.rawValue); + if (t != ValueType.Number) return false; + *result = getRawValue(node.rawValue); + return true; +} + +public func bool Reader.getBool(const Reader* r, const char* key, bool* result) { + const Node* node = r.findNode(key); + if (!node) return false; + if (getKind(node.nameOffset) != NodeKind.Value) return false; + ValueType t = getRawType(node.rawValue); + if (t != ValueType.Boolean) return false; + *result = getRawValue(node.rawValue); + return true; +} + +public type NodeIter struct { + const Blocks* blocks; + const Node* node; +} + +public func bool NodeIter.done(const NodeIter* i) { + return i.node == nil; +} + +public func void NodeIter.next(NodeIter* i) { + if (i.node == nil) return; + u32 next = i.node.nextNode; + if (next == 0) i.node = nil; + else i.node = &i.blocks.nodes[next]; +} + +public func const char* NodeIter.getValue(const NodeIter* i, const char* key) { + const Node* child = i.blocks.findNode(key, i.node); + if (!child) return nil; + if (getKind(child.nameOffset) != NodeKind.Value) return nil; + ValueType t = getRawType(child.rawValue); + if (t != ValueType.Text) return nil; + return &i.blocks.values[getRawValue(child.rawValue)]; +} + +public func bool NodeIter.getNumber(const NodeIter* i, const char* key, u32* result) { + const Node* child = i.blocks.findNode(key, i.node); + if (!child) return false; + if (getKind(child.nameOffset) != NodeKind.Value) return false; + ValueType t = getRawType(child.rawValue); + if (t != ValueType.Number) return false; + *result = getRawValue(child.rawValue); + return true; +} + +public func bool NodeIter.getBool(const NodeIter* i, const char* key, bool* result) { + const Node* child = i.blocks.findNode(key, i.node); + if (!child) return false; + if (getKind(child.nameOffset) != NodeKind.Value) return false; + ValueType t = getRawType(child.rawValue); + if (t != ValueType.Boolean) return false; + *result = getRawValue(child.rawValue); + return true; +} + +public func NodeIter Reader.getNodeIter(const Reader* r, const char* key) { + const Node* node = r.findNode(key); + if (node && getKind(node.nameOffset) == NodeKind.TableArray) { + node = &r.blocks.nodes[node.child]; + } + NodeIter iter = { r.blocks, node} + return iter; +} + + +public type ValueIter struct { + const char* values; + bool isArray; +} + +func ValueIter ValueIter.create(const char* values, bool isArray) { + ValueIter iter = { values, isArray } + return iter; +} + +public func bool ValueIter.done(const ValueIter* i) { + return i.values[0] == 0; +} + +public func void ValueIter.next(ValueIter* i) { + if (i.values[0] == 0) return; + while (i.values[0] != 0) i.values++; + if (i.isArray) i.values++; // skip 0-terminator +} + +public func const char* ValueIter.getValue(const ValueIter* i) { + return i.values; +} + +public func ValueIter Reader.getValueIter(const Reader* r, const char* key) { + const Node* node = r.findNode(key); + if (node) { + switch (getKind(node.nameOffset)) { + case NodeKind.Table: fallthrough; + case NodeKind.TableArray: + break; + case NodeKind.ValueArray: + // TODO support arrays of Numbers/Booleans as well + return ValueIter.create(&r.blocks.values[getRawValue(node.rawValue)], true); + case NodeKind.Value: + return ValueIter.create(&r.blocks.values[getRawValue(node.rawValue)], false); + case NodeKind.ArrayChild: + // TODO + break; + } + } + return ValueIter.create(&r.blocks.values[0], false); +} + + +// -------------------------------------------------------------- +// Blocks + +type NodeKind enum u8 { + Table = 0, + TableArray, + ValueArray, + Value, + ArrayChild, +} + +type ValueType enum u8 { + Text = 0, + Number, + Boolean, +} +const u32 ValueIsArray = (1 << 31); +const u32 ValueTypeOffset = 29; +const u32 RawValueMask = (0x7 << 29); + +func const char* type2str(ValueType t) { + switch (t) { + case ValueType.Text: return "T"; + case ValueType.Number: return "N"; + case ValueType.Boolean: return "B"; + } + return ""; +} + + +public type Node struct { + u32 nameOffset; + u32 nextNode; + union { + u32 child; + u32 rawValue; // bit 31 isArray, bit 29-30 ValueType + } +} @(opaque, packed) + +public type Blocks struct { + Node* nodes; + u32 nodeCount; + + char* names; + u32 namesOffset; + u32 namesSize; + + u32[NamesCacheSize] namesCache; + u32 lastCache; + + char* values; + u32 valuesOffset; + u32 valuesSize; +} @(opaque) + +func void Blocks.init(Blocks* b) { + memset(b, 0, sizeof(Blocks)); + b.nodes = calloc(MaxNodes, sizeof(Node)); + + b.namesSize = MaxNames; + b.names = calloc(1, b.namesSize); + b.names[0] = 0; + b.namesOffset = 1; // 0 indicates no name + + b.valuesSize = MaxValues; + b.values = calloc(1, b.valuesSize); + b.values[0] = 0; + b.valuesOffset = 1; // 0 indicates no value + + b.lastCache = 0; + //memset(b.namesCache, 0, sizeof(b.namesCache)); // sizeof(struct member) not supported yet + memset(b.namesCache, 0, sizeof(u32)*NamesCacheSize); +} + +func void Blocks.destroy(Blocks* b) { + free(b.values); + free(b.names); + free(b.nodes); +} + +func u32 Blocks.searchNameCache(Blocks* b, const char* name) { + for (u32 i=0; i(strlen(name)) + 1; + nameOffset = b.namesOffset; + node.nameOffset = nameOffset; + char* newname = &b.names[nameOffset]; + memcpy(newname, name, len); + b.namesCache[b.lastCache] = nameOffset; + b.lastCache = (b.lastCache + 1) % NamesCacheSize; + b.namesOffset += len; + } + } + node.nameOffset = addKind(node.nameOffset, k); + return off; +} + +func u32 Blocks.addValue(Blocks* b, const char* value) { + if (value[0] == 0) return 0; + u32 off = b.valuesOffset; + u32 len = cast(strlen(value)) + 1; + memcpy(&b.values[off], value, len); + b.valuesOffset += len; + return off; +} + +func void Blocks.addNull(Blocks* b) { + b.values[b.valuesOffset] = 0; + b.valuesOffset++; +} + +func Node* Blocks.findNode(const Blocks* b, const char* name, const Node* parent) { + if (b.nodeCount == 0) return nil; + Node* node = &b.nodes[0]; + if (parent) { + if (!parent.child) return nil; + node = &b.nodes[parent.child]; + } + while (1) { + const char* nodeName = &b.names[getValue(node.nameOffset)]; + if (same(name, nodeName)) return node; + if (!node.nextNode) return nil; + node = &b.nodes[node.nextNode]; + } + return nil; +} + + + +const u32 NodeKindOffset = 29; + +func u32 addKind(u32 value, NodeKind k) @(inline) { + return value | (k << NodeKindOffset); +} + +func NodeKind getKind(u32 value) @(inline) { + return cast(value >> NodeKindOffset); +} + +func u32 getValue(u32 value) @(inline) { + return value & ~(0x7 << NodeKindOffset); +} diff --git a/resources/examples/toml_tokenizer_c2.c3 b/resources/examples/toml_tokenizer_c2.c3 new file mode 100644 index 000000000..80b29abe1 --- /dev/null +++ b/resources/examples/toml_tokenizer_c2.c3 @@ -0,0 +1,359 @@ +// This is the toml_tokenizer.c2 changed to c3 to compare +module toml; +import stdio; +import string; +import ctype; +import stdlib; + +const uint MaxText = 1024; + +enum TokenKind : byte (string name) +{ + WORD("word"), + TEXT("text"), + NUMBER("number"), + KW_TRUE("true"), + KW_FALSE("false"), + LBRACE("["), + LBRACE2("[[") + RBRACE("]"), + RBRACE2("]]"), + EQUALS("="), + DOT("."), + COMMA(","), + EOF("eof"), + ERROR("error"), +} + +func void Location.init(Location* l, uint line = 0, uint col = 0) +{ + l.line = line; + l.column = col; +} + +func string Location.str(Location* l) +{ + static char[32] msg; + sprintf(msg, "line %u:%u", l.line, l.column); + return msg; +} + +struct Token +{ + Location loc; + TokenKind kind; + // TODO union? + union + { + string text; + uint number; + } +} + +func void Token.init(Token* t) +{ + t.loc.init(0, 0); + t.kind = TokenKind.EOF; + t.text = nil; + t.number = 0; +} + +func void Token.clear(Token* t) +{ + t.text = nil; + t.number = 0; +} + +func void Token.setLocation(Token* t, Location l) +{ + t.loc = l; +} + +func bool Token.is(Token* t, TokenKind k) +{ + return t.kind == k; +} + +func bool Token.isNot(Token* t, TokenKind k) +{ + return t.kind != k; +} + +func string Token.getName(Token* t) +{ + return t.kind.name; +} + +struct Tokenizer +{ + char* dataStart; + char* current; + Location loc; + char[MaxText] text; + Token nextToken; + bool haveNext; +} + +func void Tokenizer.init(Tokenizer* t, char* input) +{ + t.dataStart = input; + t.current = input; + t.loc.init(1, 1); + t.haveNext = false; + t.text[0] = 0; +} + +func void Tokenizer.lex(Tokenizer* t, Token* result) +{ + if (t.haveNext) + { + // Q: ptr assign or copy? + *result = t.nextToken; + t.haveNext = false; + return; + } + result.clear(); + while (1) + { + switch (t.current[0]) + { + case 0: + result.loc = t.loc; + result.kind = TokenKind.EOF; + return; + case '#': + if (t.loc.column != 1) + { + sprintf(t.text, "unexpected '#' after line start at %s", t.loc.str()); + result.kind = TokenKind.ERROR; + result.text = t.text; + return; + } + t.parseComment(); + case ' ': + case '\t': + t.advance(1); + case '\n': + t.current++; + t.loc.line++; + t.loc.column = 1; + case '=': + result.loc = t.loc; + result.kind = TokenKind.EQUALS; + t.advance(1); + return; + case '.': + result.loc = t.loc; + result.kind = TokenKind.DOT; + t.advance(1); + return; + case ',': + result.loc = t.loc; + result.kind = TokenKind.COMMA; + t.advance(1); + return; + case '[': + result.loc = t.loc; + if (t.current[1] == '[') + { + t.advance(2); + result.kind = TokenKind.LBRACE2; + } + else + { + t.advance(1); + result.kind = TokenKind.LBRACE; + } + return; + case ']': + result.loc = t.loc; + if (t.current[1] == ']') + { + t.advance(2); + result.kind = TokenKind.RBRACE2; + } + else + { + t.advance(1); + result.kind = TokenKind.RBRACE; + } + return; + case '"': + if (t.current[1] == '"' && t.current[2] == '"') + { + t.parseMultiText(result); + } + else + { + t.parseText(result); + } + return; + default: + // key or number + result.loc = t.loc; + if (isdigit(t.current[0])) + { + t.parseNumber(result); + return; + } + if (t.current[0] == 'f' && strncmp("false", t.current, 5) == 0) { + t.advance(5); + result.number = 0; + result.kind = TokenKind.Kw_false; + return; + } + if (t.current[0] == 't' && strncmp("true", t.current, 4) == 0) { + t.advance(4); + result.number = 1; + result.kind = TokenKind.Kw_true; + return; + } + if (isalpha(t.current[0])) + { + t.parseKey(result); + return; + } + sprintf(t.text, "unexpected char '%c' at %s", t.current[0], t.loc.str()); + result.kind = TokenKind.Error; + result.text = t.text; + return; + } + } +} + +func Token* Tokenizer.lookahead(Tokenizer* t) +{ + if (!t.haveNext) + { + t.lex(&t.nextToken); + t.haveNext = true; + } + return &t.nextToken; +} + +func void Tokenizer.advance(Tokenizer* t, uint amount) +{ + t.loc.column += amount; + t.current += amount; +} + +func void Tokenizer.parseComment(Tokenizer* t) +{ + while (1) + { + switch (t.current[0]) + { + case 0: + return; + case '\n': + t.current++; + t.loc.line++; + t.loc.column = 1; + return; + default: + t.current++; + t.loc.column++; + break; + } + } +} + +func void Tokenizer.parseText(Tokenizer* t, Token* result) +{ + // TODO handle literal strings ' .. ' -> no escaping + // TODO handle escape chars for normal strings " .. \" \r \n " + t.advance(1); + result.loc = t.loc; + const char* start = t.current; + while (t.current[0] && t.current[0] != '"') t.current++; + + uint len = cast(uint, t.current - start); + // assert(len < MaxText); + memcpy(t.text, start, len); + t.text[len] = 0; + result.kind = TokenKind.Text; + result.text = t.text; + t.loc.column += len; + t.advance(1); +} + +func void Tokenizer.parseMultiText(Tokenizer* t, Token* result) +{ + t.advance(3); + if (t.current[0] == '\n') + { + t.current++; + t.loc.line++; + t.loc.column = 1; + } + result.loc = t.loc; + char* start = t.current; + while (1) + { + if (t.current[0] == 0) + { + sprintf(t.text, "missing end \"\"\" %s", t.loc.str()); + result.kind = TokenKind.Error; + result.text = t.text; + return; + } + if (t.current[0] == '\n') + { + t.loc.line++; + t.loc.column = 1; + } + else + { + t.loc.column++; + } + if (t.current[0] == '"' && t.current[1] == '"' && t.current[2] == '"') break; + t.current++; + } + + uint len = cast(uint, t.current - start); + // assert(len < MaxText); + memcpy(t.text, start, len); + t.text[len] = 0; + result.kind = TokenKind.Text; + result.text = t.text; + t.advance(3); +} + +func void Tokenizer.parseNumber(Tokenizer* t, Token* result) +{ + // TODO handle prefix +/- + // handle hexadecimal/ocal/binary number + // handle '_', like 1_000_000 + + u32 number = cast(atoi(t.current)); + result.kind = TokenKind.Number; + result.number = number; + while (t.current[0] && isdigit(t.current[0])) + { + t.current++; + t.loc.column++; + } +} + +func bool isKeyChar(u8 c) +{ + if (c >= 128) return true; + if (isalpha(c)) return true; + if (isdigit(c)) return true; + if (c == '_' || c == '-') return true; + return false; +} + +func void Tokenizer.parseKey(Tokenizer* t, Token* result) +{ + char* start = t.current; + while (t.current[0] && isKeyChar(cast(t.current[0]))) t.current++; + + uint len = cast(uint, t.current - start); + // assert(len < MaxText); + memcpy(t.text, start, len); + t.text[len] = 0; + result.kind = TokenKind.Word; + result.text = t.text; + t.loc.column += len; +} diff --git a/resources/examples/vector.c3 b/resources/examples/vector.c3 new file mode 100644 index 000000000..2552ec61b --- /dev/null +++ b/resources/examples/vector.c3 @@ -0,0 +1,70 @@ +module vector(Type); + +public struct Vector +{ + Type[*] array; +} + +public func Vector@ newVector() +{ + return @calloc(Vector); +} + +public func void Vector.add(Vector *vector, Type type) +{ + vector.array += type; +} + +public func usize Vector.size(Vector *vector) +{ + return vector.array.size; +} + +public func void Vector.removeLast(Vector *vector) +{ + vector.array.pop(); +} + +public func void Vector.removefirst(Vector *vector) +{ + vector.array.removeAt(0); +} + +public func void Type *Vector.first(Vector *vector) +{ + return &vector.array.first; +} + +public func void Type *Vector.last(Vector *vector) +{ + return &vector.array.last(); +} + +public func bool Vector.empty(Vector *vector) +{ + return !vector.array.size; +} + +public macro Vector.@foreach(Vector *vector : @body(Type value)) +{ + for (usize i = 0, i < vector.array.size; i++) + { + @body(vector.array[i]); + } +} + +test +{ + define IntVector = Vector(int); + IntVector *vector = @calloc(IntVector); + vector.add(1); + vector.add(2); + foreach (int i : vector) + { + printDigit(i); + } + vector.@foreach(int i) + { + printDigit(i); + } +} \ No newline at end of file diff --git a/resources/examples/window.c3 b/resources/examples/window.c3 new file mode 100644 index 000000000..6b3322379 --- /dev/null +++ b/resources/examples/window.c3 @@ -0,0 +1,35 @@ +import gtk; + +const string CLICK_ME = "Click Me"; +uint counter = 0; + +func void clickedme(GtkButton *o, void *d) +{ + cast(GtkLabel*, d).set_text(string.format("You clicked me %d times", ++counter)); +} + +int main(int argc, string[] argv) +{ + gtk::init(&argc, &argv); + + GtkWindow *win = gtk::windowCreate(GtkWindow::TOPLEVEL); + win.set_title(CLICK_ME); + + GtkButton *button = gtk::buttonCreateWithLabel(CLICK_ME); + + GtkLabel *label = GtkLabel.new("There have been no clicks yet"); + label.setSingleLineMode(true); + + GtkVBox vbox = gtk::vBoxCreate(true, 1); + vbox.add(label); + vbox.add(button); + + win.add(vbox); + + win.connectSignal("delete-event", gtk::mainQuit, NULL); + button.connectSignal("clicked", &clickedme, label); + + win.showAll(); + gtk::main(); + return 0; +} \ No newline at end of file diff --git a/resources/grammar.y b/resources/grammar.y index 2c1b30961..537f90896 100644 --- a/resources/grammar.y +++ b/resources/grammar.y @@ -25,6 +25,7 @@ void yyerror(char *s); %token THROWS THROW TRY CATCH SCOPE PUBLIC DEFER ATTRIBUTE IN %token FN_BLOCK_START FN_BLOCK_END +%token MULTW ADDW SUBW %start translation_unit %% @@ -34,6 +35,10 @@ path | path IDENT SCOPE ; +import_path + : IDENT + | import_path SCOPE IDENT + ; ident_expression : CONST_IDENT @@ -83,6 +88,7 @@ unary_operator | '*' | '+' | '-' + | SUBW | '~' | '!' ; @@ -91,6 +97,7 @@ unary_operator multiplicative_expression : unary_expression | multiplicative_expression '*' unary_expression + | multiplicative_expression MULTW unary_expression | multiplicative_expression '/' unary_expression | multiplicative_expression '%' unary_expression ; @@ -111,7 +118,9 @@ bit_expression additive_expression : bit_expression | additive_expression '+' bit_expression + | additive_expression ADDW bit_expression | additive_expression '-' bit_expression + | additive_expression SUBW bit_expression ; relational_expression @@ -631,15 +640,29 @@ module_params ; module - : MODULE IDENT ';' - | MODULE IDENT '(' module_params ')' ';' + : MODULE import_path ';' + | MODULE import_path '(' module_params ')' ';' + ; + +specified_import + : IDENT AS IDENT + | IDENT + | TYPE + | CONST + | MACRO + | TYPE AS TYPE + | CONST AS CONST + | MACRO AS MACRO + ; + +specified_import_list + : specified_import + | specified_import_list ',' specified_import ; import_decl - : IMPORT IDENT ';' - | IMPORT IDENT AS IDENT ';' - | IMPORT IDENT AS IDENT LOCAL ';' - | IMPORT IDENT LOCAL ';' + : IMPORT import_path ';' + | IMPORT import_path ':' specified_import_list ';' ; imports diff --git a/resources/testfragments/super_simple.c3 b/resources/testfragments/super_simple.c3 index c6ecefc3b..8fcc4b2df 100644 --- a/resources/testfragments/super_simple.c3 +++ b/resources/testfragments/super_simple.c3 @@ -94,7 +94,7 @@ func int test3() typedef func void(int) as Foo; //typedef int as Foo; -func void printf(char *hello); +extern func void printf(char *hello); macro @hello() { @@ -110,8 +110,13 @@ func void bob() long deee = (eok ? a : b) + c; } + + func int main(int x) { + int aa = x++; + int bb = x--; + int cc = ++x; for (int ok = 0; ok < 10; ok++) { printf("ok"); @@ -132,6 +137,7 @@ func int main(int x) { printf("helo\n"); } + Test baok = { 1 }; Test2 efe; efe.t.a = 3; if (efe.t.a > 2) printf("Works!\n"); @@ -183,6 +189,7 @@ JUMP: func void test2(int* x, int y, int z) { + x++; z = 0; z ? y : z; x += 1; diff --git a/resources/testproject/bar.c3 b/resources/testproject/bar.c3 new file mode 100644 index 000000000..2bf7ad476 --- /dev/null +++ b/resources/testproject/bar.c3 @@ -0,0 +1,11 @@ +module bar; + +import baz::foo; +import testbaz; + +public func void test() +{ + Foo x; + foo::test(); + Zab z; +} \ No newline at end of file diff --git a/resources/testproject/foo.c3 b/resources/testproject/foo.c3 new file mode 100644 index 000000000..bff7459da --- /dev/null +++ b/resources/testproject/foo.c3 @@ -0,0 +1,20 @@ +module baz::foo; + +import bar; + +public struct Foo +{ + int a; +} + +extern func void printf(char *hello); + +local func void ofke() +{} + +func void exple() {} + +public func void test() +{ + printf("Hello from baz::foo::test()!\n"); +} \ No newline at end of file diff --git a/resources/testproject/hello_world.c3 b/resources/testproject/hello_world.c3 new file mode 100644 index 000000000..662269858 --- /dev/null +++ b/resources/testproject/hello_world.c3 @@ -0,0 +1,11 @@ +module hello_world; +import bar; + +extern func void printf(char *hello); + +func int main(int x) +{ + printf("Hello World!\n"); + bar::test(); + return 1; +} \ No newline at end of file diff --git a/resources/testproject/project.toml b/resources/testproject/project.toml new file mode 100644 index 000000000..56c022685 --- /dev/null +++ b/resources/testproject/project.toml @@ -0,0 +1,10 @@ +[[executable]] +name = "hello_world" +version = "0.1.0" +authors = ["John Doe "] +langrev = "1" +warnings = ["no-unused"] +# sources compiled +sources = ["./**"] +# libraries to use +libs = [] \ No newline at end of file diff --git a/resources/testproject/zab.c3 b/resources/testproject/zab.c3 new file mode 100644 index 000000000..35071ee76 --- /dev/null +++ b/resources/testproject/zab.c3 @@ -0,0 +1,6 @@ +module testbaz::zab; + +public struct Zab +{ + double a; +} \ No newline at end of file diff --git a/src/build/build_internal.h b/src/build/build_internal.h new file mode 100644 index 000000000..d43596fe7 --- /dev/null +++ b/src/build/build_internal.h @@ -0,0 +1,17 @@ +#pragma once + +// Copyright (c) 2020 Christoffer Lerno. All rights reserved. +// Use of this source code is governed by a LGPLv3.0 +// a copy of which can be found in the LICENSE file. + +#include "utils/lib.h" +#include "utils/toml.h" +#include "build_options.h" + +typedef struct +{ + BuildTarget **targets; +} Project; + +Project *project_load(void); +BuildTarget *project_select_target(Project *project, const char *optional_target); \ No newline at end of file diff --git a/src/build/build_options.c b/src/build/build_options.c index b2e3ad62e..201993b87 100644 --- a/src/build/build_options.c +++ b/src/build/build_options.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "build_options.h" #include @@ -323,7 +323,7 @@ void parse_arguments(int argc, const char *argv[]) build_options.debug_info = false; build_options.command = COMMAND_MISSING; build_options.symtab_size = DEFAULT_SYMTAB_SIZE; - build_options.files = VECNEW(const char *, MAX_FILES); + build_options.files = NULL; for (int i = DIAG_NONE; i < DIAG_WARNING_TYPE; i++) { build_options.severity[i] = DIAG_IGNORE; diff --git a/src/build/build_options.h b/src/build/build_options.h index 8d2c89017..fbe14faf3 100644 --- a/src/build/build_options.h +++ b/src/build/build_options.h @@ -1,8 +1,8 @@ #pragma once // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "../utils/common.h" @@ -95,6 +95,7 @@ typedef struct const char* target; const char* path; const char* cpu; + const char* target_triple; CompilerCommand command; uint32_t symtab_size; CompileOption compile_option; @@ -114,6 +115,23 @@ typedef struct +typedef enum +{ + TARGET_TYPE_EXECUTABLE, + TARGET_TYPE_STATIC_LIB, + TARGET_TYPE_DYNAMIC_LIB, +} TargetType; + +typedef struct +{ + TargetType type; + const char *name; + const char *version; + const char *langrev; + const char **sources; + const char **libraries; + const char *target_triple; +} BuildTarget; extern BuildOptions build_options; diff --git a/src/build/builder.c b/src/build/builder.c new file mode 100644 index 000000000..e05844a17 --- /dev/null +++ b/src/build/builder.c @@ -0,0 +1,28 @@ +// Copyright (c) 2019 Christoffer Lerno. All rights reserved. +// Use of this source code is governed by a LGPLv3.0 +// a copy of which can be found in the LICENSE file. + +#include +#include "build_internal.h" +#include "build_options.h" + +void load_library_files(void) {} +void load_files(void) {} +void compile_files(BuildTarget *target); + +void build(void) +{ + // Locate the project.toml + file_find_top_dir(); + // Parse it + Project *project = project_load(); + BuildTarget *target = project_select_target(project, build_options.target); + + if (!target->target_triple && build_options.target_triple) + { + target->target_triple = build_options.target_triple; + } + + load_library_files(); + compile_files(target); +} \ No newline at end of file diff --git a/src/build/project.c b/src/build/project.c new file mode 100644 index 000000000..658ea29f2 --- /dev/null +++ b/src/build/project.c @@ -0,0 +1,154 @@ +// Copyright (c) 2020 Christoffer Lerno. All rights reserved. +// Use of this source code is governed by a LGPLv3.0 +// a copy of which can be found in the LICENSE file. + +#include +#include "build_internal.h" + +TomlArray *get_array(TomlTable *table, const char *key) +{ + TomlValue *value = toml_table_get(table, key); + if (!value) return NULL; + if (value->type != TOML_ARRAY) + { + error_exit("The key '%s' was not an array element. Did you type '[%s]' instead of '[[%s]]'?"); + } + return value->value.array; +} + +static inline const char *copy_toml_string(TomlString *string) +{ + size_t len = string->len; + char *new_str = malloc_arena(len + 1); + memcpy(new_str, string->str, len); + new_str[len] = '\0'; + return new_str; +} + +const char *get_valid_string(TomlTable *table, const char *key, const char *category, bool mandatory) +{ + TomlValue *value = toml_table_get(table, key); + if (!value) + { + if (mandatory) + { + error_exit("%s was missing a mandatory '%s' field, please add it.", category, key); + } + return NULL; + } + if (value->type != TOML_STRING) + { + error_exit("%s had an invalid mandatory '%s' field that was not a string, please correct it.", category, key); + } + return copy_toml_string(value->value.string); +} + + +static const char **get_valid_array(TomlTable *table, const char *key, const char *category, bool mandatory) +{ + TomlValue *value = toml_table_get(table, key); + if (!value) + { + if (mandatory) + { + error_exit("Error reading %s: %s was missing a mandatory '%s' field, please add it.", PROJECT_TOML, category, key); + } + return NULL; + } + if (value->type != TOML_ARRAY) + { + error_exit("Error reading %s: %s had an invalid mandatory '%s' field that was not an array, please correct it.", PROJECT_TOML, category, key); + } + const char **values = NULL; + for (unsigned i = 0; i < value->value.array->len; i++) + { + TomlValue *val = value->value.array->elements[i]; + if (val->type != TOML_STRING) + { + error_exit("Error reading %s: %s had an invalid mandatory '%s' array that did not only hold strings, please correct it.", PROJECT_TOML, category, key); + } + vec_add(values, copy_toml_string(val->value.string)); + } + return values; +} + +void project_add_target(Project *project, TomlValue *wrapped_table, const char *type, const char *type_key, TargetType target_type) +{ + if (wrapped_table->type != TOML_TABLE) + { + error_exit("The %s had an invalid %s. Please check your [[%s]] configurations.", PROJECT_TOML, type, type_key); + } + BuildTarget *target = CALLOCS(BuildTarget); + vec_add(project->targets, target); + TomlTable *table = wrapped_table->value.table; + + target->name = get_valid_string(table, "name", type, true); + VECEACH(project->targets, i) + { + BuildTarget *other_target = project->targets[i]; + if (other_target == target) continue; + if (strcmp(other_target->name, target->name) == 0) + { + error_exit("More %s contained more than one target with the name %s. Please make all target names unique.", PROJECT_TOML, target->name); + } + } + type = strformat("%s %s", type, target->name); + target->version = get_valid_string(table, "version", type, false); + if (!target->version) target->version = "1.0.0"; + target->langrev = get_valid_string(table, "langrev", type, false); + target->sources = get_valid_array(table, "sources", type, true); + target->libraries = get_valid_array(table, "libs", type, false); +} + +static void project_add_targets(Project *project, TomlTable *table, const char *type, const char *type_key, TargetType target_type) +{ + TomlArray *targets = get_array(table, type_key); + if (!targets) return; + for (unsigned i = 0; i < targets->len; i++) + { + project_add_target(project, targets->elements[i], type, type_key, target_type); + } +} + +static BuildTarget *project_select_default_target(Project *project) +{ + VECEACH(project->targets, i) + { + BuildTarget *target = project->targets[i]; + if (target->type == TARGET_TYPE_EXECUTABLE) return target; + } + return project->targets[0]; +} + +BuildTarget *project_select_target(Project *project, const char *optional_target) +{ + if (!vec_size(project->targets)) + { + error_exit("No targets could be found in %s. Please define at least one target, for example a [[executable]] and try again.", PROJECT_TOML); + } + if (!optional_target) + { + return project_select_default_target(project); + } + VECEACH(project->targets, i) + { + BuildTarget *target = project->targets[i]; + if (strcmp(target->name, optional_target) == 0) return target; + } + error_exit("No build target named '%s' was found in %s. Was it misspelled?", optional_target, PROJECT_TOML); +} + +Project *project_load(void) +{ + Project *project = CALLOCS(Project); + TomlErr err = { .code = TOML_OK }; + TomlTable *toml = toml_load_filename(PROJECT_TOML, &err); + if (err.code != TOML_OK) + { + error_exit("%s could not be read. Can you please check the read permissions on the file?", PROJECT_TOML); + } + project_add_targets(project, toml, "executable", "executable", TARGET_TYPE_EXECUTABLE); + project_add_targets(project, toml, "dynamic library", "dynamic-lib", TARGET_TYPE_DYNAMIC_LIB); + project_add_targets(project, toml, "static library", "static-lib", TARGET_TYPE_STATIC_LIB); + return project; +} diff --git a/src/build/project_creation.c b/src/build/project_creation.c index f7d994ab0..2b77841f9 100644 --- a/src/build/project_creation.c +++ b/src/build/project_creation.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include #include diff --git a/src/build/project_creation.h b/src/build/project_creation.h index 1ead0ec40..5afcd5337 100644 --- a/src/build/project_creation.h +++ b/src/build/project_creation.h @@ -1,8 +1,8 @@ #pragma once // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. void create_project(void); \ No newline at end of file diff --git a/src/compiler/ast.c b/src/compiler/ast.c index 11962e161..161c4c259 100644 --- a/src/compiler/ast.c +++ b/src/compiler/ast.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "compiler_internal.h" @@ -18,6 +18,19 @@ Type poisoned_type = { .type_kind = TYPE_POISONED }; TypeInfo poisoned_type_info = { .kind = TYPE_INFO_POISON }; +void decl_set_external_name(Decl *decl) +{ + if (decl->visibility == VISIBLE_EXTERN) + { + decl->external_name = decl->name.string; + return; + } + char buffer[1024]; + uint32_t len = sprintf(buffer, "%s::%s", decl->module->name->module, decl->name.string); + assert(len); + TokenType type = TOKEN_INVALID_TOKEN; + decl->external_name = symtab_add(buffer, len, fnv1a(buffer, len), &type); +} Decl *decl_new_with_type(Token name, DeclKind decl_type, Visibility visibility) { @@ -229,6 +242,10 @@ UnaryOp unary_op[256] = { [TOKEN_MINUSMINUS] = UNARYOP_DEC, }; +PostUnaryOp post_unary_op[256] = { + [TOKEN_PLUSPLUS] = POSTUNARYOP_INC, + [TOKEN_MINUSMINUS] = POSTUNARYOP_DEC, +}; AssignOp assignop_from_token(TokenType type) @@ -273,6 +290,20 @@ TokenType unaryop_to_token(UnaryOp type) return TOKEN_INVALID_TOKEN; } +PostUnaryOp post_unaryop_from_token(TokenType type) +{ + return post_unary_op[type]; +} + +TokenType postunaryop_to_token(PostUnaryOp type) +{ + for (unsigned i = 0; i < 256; i++) + { + if (post_unary_op[i] == type) return (TokenType)i; + } + return TOKEN_INVALID_TOKEN; +} + Ast poisoned_ast = { .ast_kind = AST_POISONED }; void fprint_indent(FILE *file, int indent) @@ -422,14 +453,7 @@ void fprint_type_info_recursive(FILE *file, TypeInfo *type_info, int indent) case TYPE_INFO_IDENTIFIER: if (type_info->unresolved.path) { - if (type_info->unresolved.path->module.string) - { - fprintf_indented(file, indent + 1, "(unresolved %s::%s::%s)\n", type_info->unresolved.path->module.string, type_info->unresolved.path->module.string, type_info->unresolved.name_loc.string); - } - else - { - fprintf_indented(file, indent + 1, "(unresolved %s::%s)\n", type_info->unresolved.path->module.string, type_info->unresolved.name_loc.string); - } + fprintf_indented(file, indent + 1, "(unresolved %s::%s)\n", type_info->unresolved.path->module, type_info->unresolved.name_loc.string); return; } fprintf_indented(file, indent + 1, "(unresolved %s)\n", type_info->unresolved.name_loc.string); @@ -519,12 +543,12 @@ void fprint_expr_recursive(FILE *file, Expr *expr, int indent) fprint_expr_recursive(file, expr->binary_expr.right, indent + 1); break; case EXPR_UNARY: - fprintf_indented(file, indent, "(unary %s\n", token_type_to_string(expr->unary_expr.operator)); + fprintf_indented(file, indent, "(unary %s\n", token_type_to_string(unaryop_to_token(expr->unary_expr.operator))); fprint_expr_common(file, expr, indent + 1); fprint_expr_recursive(file, expr->unary_expr.expr, indent + 1); break; case EXPR_POST_UNARY: - fprintf_indented(file, indent, "(postunary %s\n", token_type_to_string(expr->post_expr.operator)); + fprintf_indented(file, indent, "(postunary %s\n", token_type_to_string(postunaryop_to_token(expr->post_expr.operator))); fprint_expr_common(file, expr, indent + 1); fprint_expr_recursive(file, expr->post_expr.expr, indent + 1); break; @@ -1015,6 +1039,6 @@ void fprint_decl(FILE *file, Decl *dec) { fprint_decl_recursive(file, dec, 0); } -Module poisoned_module = { .name = "INVALID" }; +Module poisoned_module = { .name = NULL }; Decl all_error = { .decl_kind = DECL_ERROR, .name = { .type = TOKEN_INVALID_TOKEN, .string = NULL } }; diff --git a/src/compiler/bigint.c b/src/compiler/bigint.c index 25b85e13a..bf6ceb961 100644 --- a/src/compiler/bigint.c +++ b/src/compiler/bigint.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "bigint.h" #include "../utils/lib.h" diff --git a/src/compiler/bigint.h b/src/compiler/bigint.h index 6bfb3d710..0f91b0865 100644 --- a/src/compiler/bigint.h +++ b/src/compiler/bigint.h @@ -1,8 +1,8 @@ #pragma once // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "compiler_internal.h" diff --git a/src/compiler/casts.c b/src/compiler/casts.c index b0b1644d5..c80161848 100644 --- a/src/compiler/casts.c +++ b/src/compiler/casts.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "compiler_internal.h" diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index 841c02033..0f289e851 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "compiler_internal.h" #include "../build/build_options.h" @@ -13,12 +13,12 @@ void compiler_init(void) stable_init(&compiler.global_symbols, 0x1000); } -static void compiler_lex() +static void compiler_lex(BuildTarget *target) { - VECEACH(build_options.files, i) + VECEACH(target->sources, i) { bool loaded = false; - File *file = source_file_load(build_options.files[i], &loaded); + File *file = source_file_load(target->sources[i], &loaded); if (loaded) continue; lexer_add_file_for_lexing(file); printf("# %s\n", file->full_path); @@ -33,32 +33,32 @@ static void compiler_lex() exit(EXIT_SUCCESS); } -void compiler_parse() +void compiler_parse(BuildTarget *target) { builtin_setup(); - VECEACH(build_options.files, i) + VECEACH(target->sources, i) { bool loaded = false; - File *file = source_file_load(build_options.files[i], &loaded); + File *file = source_file_load(target->sources[i], &loaded); if (loaded) continue; diag_reset(); - Context *context = context_create(file); + Context *context = context_create(file, target); parse_file(context); context_print_ast(context, stdout); } exit(EXIT_SUCCESS); } -void compiler_compile() +void compiler_compile(BuildTarget *target) { Context **contexts = NULL; - VECEACH(build_options.files, i) + VECEACH(target->sources, i) { bool loaded = false; - File *file = source_file_load(build_options.files[i], &loaded); + File *file = source_file_load(target->sources[i], &loaded); if (loaded) continue; diag_reset(); - Context *context = context_create(file); + Context *context = context_create(file, target); vec_add(contexts, context); parse_file(context); } @@ -76,6 +76,11 @@ void compiler_compile() decl->resolve_status = RESOLVE_DONE; context_register_global_decl(contexts[0], decl); */ + assert(contexts); + VECEACH(contexts, i) + { + sema_analysis_pass_process_imports(contexts[i]); + } VECEACH(contexts, i) { sema_analysis_pass_conditional_compilation(contexts[i]); @@ -93,22 +98,69 @@ void compiler_compile() exit(EXIT_SUCCESS); } -void compile_file() +static void target_expand_source_names(BuildTarget *target) { + const char **files = NULL; + VECEACH(target->sources, i) + { + const char *name = target->sources[i]; + size_t name_len = strlen(name); + if (name_len < 1) goto INVALID_NAME; + if (name[name_len - 1] == '*') + { + if (name_len == 1 || name[name_len - 2] == '/') + { + char *path = strdup(name); + path[name_len - 1] = '\0'; + file_add_wildcard_files(&files, path, false); + free(path); + continue; + } + if (name[name_len - 2] != '*') goto INVALID_NAME; + if (name_len == 2 || name[name_len - 3] == '/') + { + char *path = strdup(name); + path[name_len - 2] = '\0'; + file_add_wildcard_files(&files, path, true); + free(path); + continue; + } + goto INVALID_NAME; + } + if (name_len < 4) goto INVALID_NAME; + if (strcmp(&name[name_len - 3], ".c3") != 0) goto INVALID_NAME; + vec_add(files, name); + continue; + INVALID_NAME: + error_exit("File names must end with .c3 or they cannot be compiled: '%s' is invalid.", name); + } + target->sources = files; +} + +void compile_files(BuildTarget *target) +{ + if (!target) + { + target = CALLOCS(BuildTarget); + target->type = TARGET_TYPE_EXECUTABLE; + target->sources = build_options.files; + target->name = "a.out"; + } + target_expand_source_names(target); target_setup(); builtin_setup(); - if (!vec_size(build_options.files)) error_exit("No files to compile."); + if (!vec_size(target->sources)) error_exit("No files to compile."); switch (build_options.compile_option) { case COMPILE_LEX_ONLY: - compiler_lex(); + compiler_lex(target); break; case COMPILE_LEX_PARSE_ONLY: - compiler_parse(); + compiler_parse(target); break; default: - compiler_compile(); + compiler_compile(target); break; } TODO @@ -126,14 +178,35 @@ void compiler_add_type(Type *type) assert(type_ok(type)); VECADD(compiler.type, type); } -Module *compiler_find_or_create_module(const char *module_name) +Module *compiler_find_or_create_module(Path *module_name) { - Module *module = stable_get(&compiler.modules, module_name); - if (module) return module; + Module *module = stable_get(&compiler.modules, module_name->module); + + if (module) + { + // We might have gotten an auto-generated module, if so + // update the path here. + if (module->name->span.loc == INVALID_LOC && module_name->span.loc != INVALID_LOC) + { + module->name = module_name; + } + return module; + } + + DEBUG_LOG("Creating module %s.", module_name->module); + // Set up the module. module = CALLOCS(Module); module->name = module_name; stable_init(&module->symbols, 0x10000); - stable_set(&compiler.modules, module_name, module); + stable_set(&compiler.modules, module_name->module, module); + // Now find the possible parent array: + Path *parent_path = path_find_parent_path(module_name); + if (parent_path) + { + // Get the parent + Module *parent_module = compiler_find_or_create_module(parent_path); + vec_add(parent_module->sub_modules, module); + } return module; } @@ -142,12 +215,12 @@ void compiler_register_public_symbol(Decl *decl) Decl *prev = stable_get(&compiler.global_symbols, decl->name.string); // If the previous symbol was already declared globally, remove it. stable_set(&compiler.global_symbols, decl->name.string, prev ? &poisoned_decl : decl); - STable *sub_module_space = stable_get(&compiler.qualified_symbols, decl->module->name); + STable *sub_module_space = stable_get(&compiler.qualified_symbols, decl->module->name->module); if (!sub_module_space) { sub_module_space = malloc_arena(sizeof(*sub_module_space)); stable_init(sub_module_space, 0x100); - stable_set(&compiler.qualified_symbols, decl->module->name, sub_module_space); + stable_set(&compiler.qualified_symbols, decl->module->name->module, sub_module_space); } prev = stable_get(sub_module_space, decl->name.string); stable_set(sub_module_space, decl->name.string, prev ? &poisoned_decl : decl); diff --git a/src/compiler/compiler.h b/src/compiler/compiler.h index 9f5aaed69..2659c4872 100644 --- a/src/compiler/compiler.h +++ b/src/compiler/compiler.h @@ -1,9 +1,12 @@ #pragma once +#include "build/build_options.h" + // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. void compiler_init(); -void compile_file(); +void compile_files(BuildTarget *target); +void build(); diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 05596a968..34576469f 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -1,7 +1,7 @@ #pragma once // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "../utils/common.h" #include "../utils/errors.h" @@ -16,6 +16,7 @@ typedef uint32_t SourceLoc; #define EMPTY_TOKEN ((Token) { .string = NULL }) #define MAX_LOCALS 0xFFFF #define MAX_SCOPE_DEPTH 0xFF +#define MAX_PATH 1024 typedef struct _Ast Ast; typedef struct _Decl Decl; @@ -87,10 +88,11 @@ typedef struct const char *start; } SourcePosition; -typedef struct +typedef struct _Path { - Token module; - Token sub_module; + SourceRange span; + const char *module; + uint32_t len; } Path; typedef struct @@ -169,10 +171,9 @@ typedef struct typedef struct { - ImportType type : 3; - Token alias; - Expr** generic_parameters; - Module *module; + Path *path; + Token symbol; + bool aliased; } ImportDecl; typedef struct @@ -204,6 +205,7 @@ typedef struct { Decl *parent; Expr *expr; + uint64_t ordinal; } EnumConstantDecl; typedef struct @@ -221,6 +223,7 @@ typedef struct typedef struct _FunctionSignature { + CallConvention convention : 4; bool variadic : 1; TypeInfo *rtype; Decl** params; @@ -235,7 +238,6 @@ typedef struct typedef struct { - const char *full_name; TypeInfo *type_parent; FunctionSignature function_signature; Ast *body; @@ -251,6 +253,11 @@ typedef struct FunctionSignature attr_signature; } AttrDecl; +typedef struct +{ + Decl *import; +} AliasDecl; + typedef struct { bool is_func : 1; @@ -281,6 +288,7 @@ typedef struct typedef struct _Decl { Token name; + const char *external_name; DeclKind decl_kind : 6; Visibility visibility : 2; ResolveStatus resolve_status : 2; @@ -366,17 +374,15 @@ typedef struct typedef struct { - Expr *left; - Expr *right; - AssignOp operator; -} ExprAssign; + Expr* expr; + UnaryOp operator; +} ExprUnary; typedef struct { Expr* expr; - TokenType operator; -} ExprUnary; - + PostUnaryOp operator; +} ExprPostUnary; typedef struct { @@ -396,7 +402,7 @@ typedef struct typedef struct { - bool is_struct_function; + bool is_struct_function : 1; Expr *function; Expr **arguments; } ExprCall; @@ -458,10 +464,9 @@ struct _Expr ExprTypeRef type_access; ExprTry try_expr; ExprBinary binary_expr; - ExprAssign assign_expr; ExprTernary ternary_expr; ExprUnary unary_expr; - ExprUnary post_expr; + ExprPostUnary post_expr; ExprCall call_expr; ExprSubscript subscript_expr; ExprAccess access_expr; @@ -663,7 +668,7 @@ typedef struct _Ast typedef struct _Module { - const char *name; + Path *name; bool is_external; bool is_c_library; @@ -675,6 +680,7 @@ typedef struct _Module STable struct_functions; STable symbols; STable public_symbols; + Module **sub_modules; } Module; @@ -691,10 +697,12 @@ typedef struct _DynamicScope typedef struct _Context { - Token module_name; + BuildTarget *target; + Path *module_name; Token* module_parameters; File* file; Decl** imports; + Decl *specified_imports; Module *module; STable local_symbols; Decl **header_declarations; @@ -716,6 +724,11 @@ typedef struct _Context int in_volatile_section; Decl *locals[MAX_LOCALS]; DynamicScope scopes[MAX_SCOPE_DEPTH]; + char path_scratch[MAX_PATH]; + struct { + STable external_symbols; + Decl **external_symbol_list; + }; } Context; typedef struct @@ -756,6 +769,8 @@ extern Type t_cus, t_cui, t_cul, t_cull; extern Type t_cs, t_ci, t_cl, t_cll; extern Type t_voidstar; +extern const char *main_name; + #define AST_NEW(_kind, _token) new_ast(_kind, _token) static inline bool ast_ok(Ast *ast) { return ast == NULL || ast->ast_kind != AST_POISONED; } @@ -827,22 +842,22 @@ CastKind cast_to_bool_kind(Type *type); bool cast_to_runtime(Expr *expr); void llvm_codegen(Context *context); -void codegen(Context *context); bool sema_analyse_expr(Context *context, Type *to, Expr *expr); bool sema_analyse_decl(Context *context, Decl *decl); void compiler_add_type(Type *type); Decl *compiler_find_symbol(Token token); -Module *compiler_find_or_create_module(const char *module_name); +Module *compiler_find_or_create_module(Path *module_name); void compiler_register_public_symbol(Decl *decl); -Context *context_create(File *file); +Context *context_create(File *file, BuildTarget *target); void context_push(Context *context); void context_register_global_decl(Context *context, Decl *decl); -bool context_add_import(Context *context, Token module_name, Token alias, ImportType import_type, Expr** generic_parameters); +void context_register_external_symbol(Context *context, Decl *decl); +bool context_add_import(Context *context, Path *path, Token symbol, Token alias); bool context_set_module_from_filename(Context *context); -bool context_set_module(Context *context, Token module_name, Token *generic_parameters); +bool context_set_module(Context *context, Path *path, Token *generic_parameters); void context_print_ast(Context *context, FILE *file); Decl *context_find_ident(Context *context, const char *symbol); void context_add_header_decl(Context *context, Decl *decl); @@ -851,6 +866,7 @@ bool context_add_local(Context *context, Decl *decl); Decl *decl_new(DeclKind decl_kind, Token name, Visibility visibility); Decl *decl_new_with_type(Token name, DeclKind decl_type, Visibility visibility); Decl *decl_new_var(Token name, TypeInfo *type, VarDeclKind kind, Visibility visibility); +void decl_set_external_name(Decl *decl); const char *decl_var_to_string(VarDeclKind kind); static inline bool decl_ok(Decl *decl) { return decl->decl_kind != DECL_POISONED; } @@ -911,19 +927,31 @@ static inline void advance_and_verify(TokenType token_type) advance(); } -Decl *module_find_symbol(Module *module, const char *symbol); +typedef enum +{ + MODULE_SYMBOL_SEARCH_EXTERNAL, + MODULE_SYMBOL_SEARCH_PARENT, + MODULE_SYMBOL_SEARCH_THIS +} ModuleSymbolSearch; + +Decl *module_find_symbol(Module *module, const char *symbol, ModuleSymbolSearch search); void parse_file(Context *context); +Path *path_create_from_string(const char *string, size_t len, SourceRange span); +Path *path_find_parent_path(Path *path); const char *resolve_status_to_string(ResolveStatus status); #define SEMA_ERROR(_tok, ...) sema_error_range(_tok.span, __VA_ARGS__) void sema_init(File *file); +void sema_analysis_pass_process_imports(Context *context); void sema_analysis_pass_conditional_compilation(Context *context); void sema_analysis_pass_decls(Context *context); void sema_analysis_pass_3(Context *context); +bool sema_add_local(Context *context, Decl *decl); bool sema_analyse_statement(Context *context, Ast *statement); +Decl *sema_resolve_symbol(Context *context, const char *symbol, Path *path, Decl **ambiguous_other_decl); bool sema_resolve_type_info(Context *context, TypeInfo *type_info); bool sema_resolve_type_shallow(Context *context, TypeInfo *type_info); void sema_error_at(SourceLoc loc, const char *message, ...); @@ -940,6 +968,8 @@ File *source_file_from_position(SourceLoc loc); void source_file_append_line_end(File *file, SourceLoc loc); SourcePosition source_file_find_position_in_file(File *file, SourceLoc loc); SourcePosition source_file_find_position(SourceLoc loc); +SourceRange source_range_from_ranges(SourceRange first, SourceRange last); + void stable_init(STable *table, uint32_t initial_size); void *stable_set(STable *table, const char *key, void *value); @@ -958,6 +988,7 @@ void *target_machine(); #define TOKEN_MAX_LENGTH 0xFFFF #define TOK2VARSTR(_token) _token.span.length, _token.start bool token_is_type(TokenType type); +bool token_is_symbol(TokenType type); const char *token_type_to_string(TokenType type); static inline Token wrap(const char *string) { @@ -1061,6 +1092,9 @@ static inline bool type_is_number(Type *type) AssignOp assignop_from_token(TokenType type); UnaryOp unaryop_from_token(TokenType type); +TokenType unaryop_to_token(UnaryOp type); +PostUnaryOp post_unaryop_from_token(TokenType type); +TokenType postunaryop_to_token(PostUnaryOp type); BinaryOp binaryop_from_token(TokenType type); BinaryOp binaryop_assign_base_op(BinaryOp assign_binary_op); TokenType binaryop_to_token(BinaryOp type); diff --git a/src/compiler/context.c b/src/compiler/context.c index 45e39b230..7c919d6e6 100644 --- a/src/compiler/context.c +++ b/src/compiler/context.c @@ -6,12 +6,14 @@ Context *current_context; -Context *context_create(File *file) +Context *context_create(File *file, BuildTarget *target) { Context *context = malloc_arena(sizeof(Context)); memset(context, 0, sizeof(Context)); context->file = file; + context->target = target; stable_init(&context->local_symbols, 256); + stable_init(&context->external_symbols, 256); return context; } @@ -50,17 +52,17 @@ bool context_add_local(Context *context, Decl *decl) return true; } -static inline bool create_module_or_check_name(Context *context, Token module_name) +static inline bool create_module_or_check_name(Context *context, Path *module_name) { context->module_name = module_name; if (context->module == NULL) { - context->module = compiler_find_or_create_module(module_name.string); + context->module = compiler_find_or_create_module(module_name); return true; } - else if (context->module->name != module_name.string) + else if (context->module->name->module != module_name->module) { - SEMA_ERROR(module_name, "Module name here '%s' did not match actual module '%s'.", module_name.string, context->module->name); + sema_error_range(module_name->span, "Module name here '%s' did not match actual module '%s'.", module_name->module, context->module->name->module); return false; } return true; @@ -84,25 +86,37 @@ bool context_set_module_from_filename(Context *context) "try using an explicit module name."); return false; } - return create_module_or_check_name(context, wrap(module_name)); + Path *path = CALLOCS(Path); + path->span = INVALID_RANGE; + path->module = module_name; + path->len = len; + return create_module_or_check_name(context, path); } -bool context_set_module(Context *context, Token module_name, Token *generic_parameters) +bool context_set_module(Context *context, Path *path, Token *generic_parameters) { - DEBUG_LOG("CONTEXT: Setting module to '%s'.", module_name.string); + DEBUG_LOG("CONTEXT: Setting module to '%s'.", path->module); // Note that we allow the illegal name for now, to be able to parse further. - context->module_name = module_name; - if (!is_all_lower(module_name.string)) + context->module_name = path; + if (!is_all_lower(path->module)) { - sema_error_range(module_name.span, "A module name may not have any upper case characters."); + sema_error_range(path->span, "A module name may not have any upper case characters."); return false; } context->module_parameters = generic_parameters; - return create_module_or_check_name(context, module_name); + return create_module_or_check_name(context, path); } +void context_register_external_symbol(Context *context, Decl *decl) +{ + assert(decl->external_name && "Missing external name"); + Decl *prev = stable_get(&context->external_symbols, decl->external_name); + if (prev) return; + stable_set(&context->external_symbols, decl->external_name, decl); + vec_add(context->external_symbol_list, decl); +} void context_register_global_decl(Context *context, Decl *decl) { @@ -117,26 +131,32 @@ void context_register_global_decl(Context *context, Decl *decl) if (decl->func.type_parent) { vec_add(context->struct_functions, decl); + // TODO set name return; } else { vec_add(context->functions, decl); + decl_set_external_name(decl); } break; case DECL_VAR: vec_add(context->vars, decl); + decl_set_external_name(decl); break; case DECL_STRUCT: case DECL_UNION: case DECL_TYPEDEF: vec_add(context->types, decl); + decl_set_external_name(decl); break; case DECL_ENUM: vec_add(context->enums, decl); + decl_set_external_name(decl); break; case DECL_ERROR: vec_add(context->error_types, decl); + decl_set_external_name(decl); break; case DECL_ENUM_CONSTANT: case DECL_ERROR_CONSTANT: @@ -173,71 +193,40 @@ void context_register_global_decl(Context *context, Decl *decl) } } -bool context_add_import(Context *context, Token module_name, Token alias, ImportType import_type, Expr** generic_parameters) +bool context_add_import(Context *context, Path *path, Token token, Token alias) { - DEBUG_LOG("SEMA: Add import of '%s'.", module_name.string); - if (!is_all_lower(module_name.string)) + DEBUG_LOG("SEMA: Add import of '%s'.", path->module); + + if (!is_all_lower(path->module)) + { + sema_error_range(path->span, "A module is not expected to have any upper case characters, please change it."); + return false; + } + + Decl *import = CALLOCS(Decl); + import->decl_kind = DECL_IMPORT; + import->visibility = VISIBLE_LOCAL; + import->import.path = path; + import->import.symbol = token; + if (alias.type != TOKEN_INVALID_TOKEN) { - sema_error_range(module_name.span, "A module is not expected to have any upper case characters, please change it."); - return false; - } - Decl *decl = decl_new(DECL_IMPORT, module_name, VISIBLE_LOCAL); - decl->import.type = import_type; - decl->import.generic_parameters = generic_parameters; - if (import_type == IMPORT_TYPE_ALIAS_LOCAL || import_type == IMPORT_TYPE_ALIAS) - { - decl->import.alias = alias; - if (!is_all_lower(alias.string)) - { - sema_error_range(alias.span, "A module alias is not expected to have any upper case characters, please change it."); - return false; - } - if (alias.string == module_name.string) - { - sema_error_range(alias.span, "If a module alias would be the same as the alias, it wouldn't have any effect."); - return false; - } - if (alias.string == context->module_name.string) - { - sema_error_range(alias.span, "An alias should not be the same as the name of the current module."); - return false; - } - } - else - { - decl->import.alias.string = NULL; + if (alias.string == token.string) + { + sema_error_range(alias.span, "If an alias would be the same as the symbol aliased, it wouldn't have any effect."); + return false; + } + if (alias.string == context->module_name->module) + { + sema_error_range(alias.span, "An alias cannot have not have the same as the name of the current module."); + return false; + } + import->import.aliased = true; + TODO } - VECEACH(context->imports, i) - { - Decl *other_import = context->imports[i]; - if (other_import->name.string == module_name.string - && !other_import->import.generic_parameters - && !generic_parameters) - { - sema_error_range(module_name.span, "This module was imported earlier in the file."); - } - if (other_import->import.alias.string == module_name.string) - { - sema_error_range(other_import->import.alias.span, - "An alias should not be the same as the name of another imported module."); - return false; - } - if (decl->import.alias.string == other_import->name.string) - { - sema_error_range(decl->import.alias.span, - "An alias should not be the same as the name of another imported module."); - return false; - } - if (decl->import.alias.string && decl->import.alias.string == other_import->import.alias.string) - { - sema_error_range(decl->import.alias.span, - "This alias has already been used by an earlier import statement."); - return false; - } - } + vec_add(context->imports, import); + printf("Added import %s\n", path->module); - context->imports = VECADD(context->imports, decl); return true; } diff --git a/src/compiler/diagnostics.c b/src/compiler/diagnostics.c index cdb07b121..0d2c7a846 100644 --- a/src/compiler/diagnostics.c +++ b/src/compiler/diagnostics.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "compiler_internal.h" #include diff --git a/src/compiler/dwarf.h b/src/compiler/dwarf.h index a5eaf6f1d..ffd6dde90 100644 --- a/src/compiler/dwarf.h +++ b/src/compiler/dwarf.h @@ -1,8 +1,8 @@ #pragma once // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #define DW_TAG_array_type 0x01 #define DW_TAG_class_type 0x02 diff --git a/src/compiler/enums.h b/src/compiler/enums.h index 4afb6b515..5bc246f12 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. // Only include this from compiler_common.h @@ -250,14 +250,6 @@ typedef enum GOTO_JUMP_BACK } GotoType; -typedef enum -{ - IMPORT_TYPE_FULL, - IMPORT_TYPE_ALIAS, - IMPORT_TYPE_ALIAS_LOCAL, - IMPORT_TYPE_LOCAL -} ImportType; - typedef enum { @@ -448,6 +440,7 @@ typedef enum TOKEN_ELSE, TOKEN_ENUM, TOKEN_ERROR_TYPE, + TOKEN_EXTERN, TOKEN_FALSE, TOKEN_FOR, TOKEN_FUNC, @@ -555,6 +548,12 @@ typedef enum UNARYOP_DEC, } UnaryOp; +typedef enum +{ + POSTUNARYOP_INC, + POSTUNARYOP_DEC, +} PostUnaryOp; + typedef enum { VARDECL_CONST = 0, @@ -569,6 +568,7 @@ typedef enum VISIBLE_MODULE, VISIBLE_LOCAL, VISIBLE_PUBLIC, + VISIBLE_EXTERN, } Visibility; @@ -583,3 +583,8 @@ typedef enum ATTR_ERROR = 1 << 6, ATTR_TYPEDEF = 1 << 7 } AttributeDomains; + +typedef enum +{ + CALL_CONVENTION_NORMAL = 0, +} CallConvention; \ No newline at end of file diff --git a/src/compiler/expr_analysis.c b/src/compiler/expr_analysis.c index 763f3ba84..f60972a8a 100644 --- a/src/compiler/expr_analysis.c +++ b/src/compiler/expr_analysis.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "compiler_internal.h" @@ -91,19 +91,26 @@ static inline bool sema_expr_analyse_ternary(Context *context, Type *to, Expr *e static inline bool sema_expr_analyse_identifier(Context *context, Type *to, Expr *expr) { // TODO what about struct functions - if (expr->identifier_expr.path) + Decl *ambiguous_decl; + Decl *decl = sema_resolve_symbol(context, expr->identifier_expr.identifier.string, expr->identifier_expr.path, &ambiguous_decl); + + if (!decl) { - TODO + SEMA_ERROR(expr->identifier_expr.identifier, "Unknown symbol '%s'.", expr->identifier_expr.identifier.string); + return false; } - Decl *decl = context_find_ident(context, expr->identifier_expr.identifier.string); - if (decl == NULL) + + if (ambiguous_decl) { - decl = compiler_find_symbol(expr->identifier_expr.identifier); - if (decl && !decl_ok(decl)) return false; + SEMA_ERROR(expr->identifier_expr.identifier, "Ambiguous symbol '%s' – both defined in %s and %s, please add the module name to resolve the ambiguity", + expr->identifier_expr.identifier.string, + decl->module->name->module, ambiguous_decl->module->name->module); + return false; } - if (decl == NULL) + + if (decl->decl_kind == DECL_FUNC && !expr->identifier_expr.path && decl->module != context->module) { - SEMA_ERROR(expr->loc, "Unknown identifier %s.", expr->identifier_expr.identifier.string); + SEMA_ERROR(expr->identifier_expr.identifier, "Functions from other modules, must be prefixed with the module name"); return false; } @@ -181,6 +188,8 @@ static inline bool sema_expr_analyse_call(Context *context, Type *to, Expr *expr return sema_expr_analyse_macro_call(context, to, expr, decl); case DECL_GENERIC: return sema_expr_analyse_generic_call(context, to, expr); + case DECL_POISONED: + return false; default: SEMA_ERROR(expr->loc, "The expression cannot be called."); return false; @@ -253,7 +262,7 @@ static inline bool sema_expr_analyse_enum_constant(Context *context, Expr *expr, if (enum_constant->name.string == name) { assert(enum_constant->resolve_status == RESOLVE_DONE); - expr_replace(expr, decl->enum_constant.expr); + expr_replace(expr, enum_constant->enum_constant.expr); return true; } } @@ -335,7 +344,7 @@ static inline bool sema_expr_analyse_access(Context *context, Type *to, Expr *ex if (is_pointer) { Expr *deref = expr_new(EXPR_UNARY, expr->loc); - deref->unary_expr.operator = TOKEN_STAR; + deref->unary_expr.operator = UNARYOP_DEREF; deref->unary_expr.expr = expr->access_expr.parent; deref->resolve_status = RESOLVE_DONE; deref->type = type; @@ -1300,9 +1309,9 @@ static inline bool sema_expr_analyse_incdec(Context *context, Type *to, Expr *ex SEMA_ERROR(inner->loc, "Expression cannot be assigned to"); return false; } - if (!type_is_integer(inner->type->canonical) && inner->type->canonical->type_kind == TYPE_POINTER) + if (!type_is_number(inner->type->canonical) && inner->type->canonical->type_kind != TYPE_POINTER) { - SEMA_ERROR(inner->loc, "Expression must be an integer or pointer"); + SEMA_ERROR(inner->loc, "Expression must be a number or a pointer"); return false; } expr->type = inner->type; @@ -1399,18 +1408,18 @@ static inline bool sema_expr_analyse_unary(Context *context, Type *to, Expr *exp switch (expr->unary_expr.operator) { - case TOKEN_STAR: + case UNARYOP_DEREF: return sema_expr_analyse_deref(context, to, expr, inner); - case TOKEN_AMP: + case UNARYOP_ADDR: return sema_expr_analyse_addr(context, to, expr, inner); - case TOKEN_MINUS: + case UNARYOP_NEG: return sema_expr_analyse_neg(context, to, expr, inner); - case TOKEN_BIT_NOT: + case UNARYOP_BITNEG: return sema_expr_analyse_bit_not(context, to, expr, inner); - case TOKEN_NOT: + case UNARYOP_NOT: return sema_expr_analyse_not(context, to, expr, inner); - case TOKEN_PLUSPLUS: - case TOKEN_MINUSMINUS: + case UNARYOP_DEC: + case UNARYOP_INC: return sema_expr_analyse_incdec(context, to, expr, inner); default: UNREACHABLE @@ -1424,8 +1433,6 @@ static inline bool sema_expr_analyse_post_unary(Context *context, Type *to, Expr if (!sema_analyse_expr(context, NULL, inner)) return false; - assert(expr->post_expr.operator == TOKEN_PLUSPLUS || expr->post_expr.operator == TOKEN_MINUSMINUS); - return sema_expr_analyse_incdec(context, to, expr, inner); } diff --git a/src/compiler/lexer.c b/src/compiler/lexer.c index 5790a06b7..27e41cb1e 100644 --- a/src/compiler/lexer.c +++ b/src/compiler/lexer.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "compiler_internal.h" diff --git a/src/compiler/llvm_codegen.c b/src/compiler/llvm_codegen.c index 36b70e2a8..f26f9dfce 100644 --- a/src/compiler/llvm_codegen.c +++ b/src/compiler/llvm_codegen.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "llvm_codegen_internal.h" @@ -58,6 +58,7 @@ static void gencontext_emit_global_variable_definition(GenContext *context, Decl case VISIBLE_PUBLIC: LLVMSetVisibility(decl->var.backend_ref, LLVMDefaultVisibility); break; + case VISIBLE_EXTERN: case VISIBLE_LOCAL: LLVMSetVisibility(decl->var.backend_ref, LLVMHiddenVisibility); break; @@ -125,12 +126,11 @@ void gencontext_print_llvm_ir(GenContext *context) } - -LLVMValueRef gencontext_emit_alloca(GenContext *context, Decl *decl) +LLVMValueRef gencontext_emit_alloca(GenContext *context, LLVMTypeRef type, const char *name) { LLVMBasicBlockRef current_block = LLVMGetInsertBlock(context->builder); LLVMPositionBuilderBefore(context->builder, context->alloca_point); - LLVMValueRef alloca = LLVMBuildAlloca(context->builder, BACKEND_TYPE(decl->type), decl->name.string); + LLVMValueRef alloca = LLVMBuildAlloca(context->builder, type, name); LLVMPositionBuilderAtEnd(context->builder, current_block); return alloca; } @@ -161,11 +161,24 @@ void llvm_codegen(Context *context) gencontext_init(&gen_context, context); gencontext_begin_module(&gen_context); // EmitDeferred() + VECEACH(context->external_symbol_list, i) + { + gencontext_emit_extern_decl(&gen_context, context->external_symbol_list[i]); + } VECEACH(context->functions, i) { gencontext_emit_function_decl(&gen_context, context->functions[i]); } + VECEACH(gen_context.generated_types, i) + { + Type *type = gen_context.generated_types[i]; + type->backend_debug_type = NULL; + type->backend_type = NULL; + } + + gencontext_print_llvm_ir(&gen_context); + // Starting from here we could potentially thread this: LLVMPassManagerBuilderRef pass_manager_builder = LLVMPassManagerBuilderCreate(); LLVMPassManagerBuilderSetOptLevel(pass_manager_builder, build_options.optimization_level); diff --git a/src/compiler/llvm_codegen_debug_info.c b/src/compiler/llvm_codegen_debug_info.c index 7d95c2410..d94f7b5f4 100644 --- a/src/compiler/llvm_codegen_debug_info.c +++ b/src/compiler/llvm_codegen_debug_info.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "llvm_codegen_internal.h" diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index 4e5edeec7..80aba5f2a 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -1,10 +1,36 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "llvm_codegen_internal.h" #include "compiler_internal.h" +static inline LLVMValueRef gencontext_emit_add_int(GenContext *context, Type *type, bool use_mod, LLVMValueRef left, LLVMValueRef right) +{ + if (use_mod) + { + return LLVMBuildAdd(context->builder, left, right, "add_mod"); + } + + // TODO insert trap + return type_is_unsigned_integer(type) + ? LLVMBuildNUWAdd(context->builder, left, right, "uadd") + : LLVMBuildNSWAdd(context->builder, left, right, "add"); +} + +static inline LLVMValueRef gencontext_emit_sub_int(GenContext *context, Type *type, bool use_mod, LLVMValueRef left, LLVMValueRef right) +{ + if (use_mod) + { + return LLVMBuildSub(context->builder, left, right, "sub_mod"); + } + + // TODO insert trap + return type_is_unsigned_integer(type) + ? LLVMBuildNUWSub(context->builder, left, right, "usub") + : LLVMBuildNSWSub(context->builder, left, right, "sub"); +} + static inline LLVMValueRef gencontext_emit_subscript_addr(GenContext *context, Expr *expr) { LLVMValueRef index = gencontext_emit_expr(context, expr->subscript_expr.index); @@ -38,7 +64,7 @@ LLVMValueRef gencontext_emit_address(GenContext *context, Expr *expr) case EXPR_IDENTIFIER: return expr->identifier_expr.decl->var.backend_ref; case EXPR_UNARY: - assert(unaryop_from_token(expr->unary_expr.operator) == UNARYOP_DEREF); + assert(expr->unary_expr.operator == UNARYOP_DEREF); return gencontext_emit_expr(context, expr->unary_expr.expr); case EXPR_ACCESS: return gencontext_emit_access_addr(context, expr); @@ -130,10 +156,50 @@ static inline LLVMValueRef gencontext_emit_cast_expr(GenContext *context, Expr * return gencontext_emit_cast(context, expr->cast_expr.kind, rhs, expr->type->canonical, expr->cast_expr.expr->type->canonical); } +static inline LLVMValueRef gencontext_emit_inc_dec_change(GenContext *context, bool use_mod, LLVMValueRef current_value, Expr *expr, int diff) +{ + Type *type = expr->type->canonical; + LLVMTypeRef llvm_type = BACKEND_TYPE(type); + + if (type->type_kind == TYPE_POINTER) + { + LLVMValueRef add = LLVMConstInt(diff < 0 ? BACKEND_TYPE(type_isize) : BACKEND_TYPE(type_usize), diff, diff < 0); + return LLVMBuildGEP2(context->builder, llvm_type, current_value, &add, 1, "ptrincdec"); + } + + if (type_is_float(type)) + { + LLVMValueRef add = LLVMConstReal(llvm_type, (double)diff); + return LLVMBuildFAdd(context->builder, current_value, add, "fincdec"); + } + + LLVMValueRef diff_value = LLVMConstInt(llvm_type, 1, false); + return diff > 0 + ? gencontext_emit_add_int(context, type, use_mod, current_value, diff_value) + : gencontext_emit_sub_int(context, type, use_mod, current_value, diff_value); +} + +static inline LLVMValueRef gencontext_emit_pre_inc_dec(GenContext *context, Expr *expr, int diff, bool use_mod) +{ + LLVMValueRef addr = gencontext_emit_address(context, expr); + LLVMValueRef value = LLVMBuildLoad2(context->builder, BACKEND_TYPE(expr->type), addr, ""); + LLVMValueRef result = gencontext_emit_inc_dec_change(context, use_mod, value, expr, diff); + LLVMBuildStore(context->builder, result, addr); + return result; +} + +static inline LLVMValueRef gencontext_emit_post_inc_dec(GenContext *context, Expr *expr, int diff, bool use_mod) +{ + LLVMValueRef addr = gencontext_emit_address(context, expr); + LLVMValueRef value = LLVMBuildLoad2(context->builder, BACKEND_TYPE(expr->type), addr, ""); + LLVMValueRef result = gencontext_emit_inc_dec_change(context, use_mod, value, expr, diff); + LLVMBuildStore(context->builder, result, addr); + return value; +} + LLVMValueRef gencontext_emit_unary_expr(GenContext *context, Expr *expr) { - UnaryOp unary_op = unaryop_from_token(expr->unary_expr.operator); - switch (unary_op) + switch (expr->unary_expr.operator) { case UNARYOP_ERROR: FATAL_ERROR("Illegal unary op %s", expr->unary_expr.operator); @@ -152,20 +218,14 @@ LLVMValueRef gencontext_emit_unary_expr(GenContext *context, Expr *expr) case UNARYOP_DEREF: return LLVMBuildLoad(context->builder, gencontext_emit_expr(context, expr->unary_expr.expr), "deref"); case UNARYOP_INC: - TODO + return gencontext_emit_pre_inc_dec(context, expr->unary_expr.expr, 1, false); case UNARYOP_DEC: - TODO + return gencontext_emit_pre_inc_dec(context, expr->unary_expr.expr, -1, false); } } -static LLVMValueRef gencontext_emit_assign(GenContext *context, Expr *left, LLVMValueRef right) -{ - LLVMValueRef addr = gencontext_emit_address(context, left); - return LLVMBuildStore(context->builder, right, addr); -} - static LLVMValueRef gencontext_emit_logical_and_or(GenContext *context, Expr *expr, BinaryOp op) { // Value *ScalarExprEmitter::VisitBinLAnd(const BinaryOperator *E) @@ -205,7 +265,22 @@ static LLVMValueRef gencontext_emit_logical_and_or(GenContext *context, Expr *ex return phi; } -static LLVMValueRef gencontext_emit_binary(GenContext *context, Expr *expr, bool load_lhs_after_rhs, BinaryOp binary_op) +static inline LLVMValueRef gencontext_emit_initialization_from_expr(GenContext *context, LLVMValueRef strukt, Expr *expr) +{ + assert(expr->expr_kind == EXPR_INITIALIZER_LIST); + // TODO + return strukt; +} + +static inline LLVMValueRef gencontext_emit_struct_value_expr(GenContext *context, Expr *expr) +{ + LLVMValueRef temp_alloc = gencontext_emit_alloca(context, BACKEND_TYPE(expr->type), "temp"); + return gencontext_emit_initialization_from_expr(context, temp_alloc, expr->struct_value_expr.init_expr); +} + + + +static LLVMValueRef gencontext_emit_binary(GenContext *context, Expr *expr, LLVMValueRef lhs_addr, BinaryOp binary_op) { if (binary_op == BINARYOP_AND || binary_op == BINARYOP_OR) @@ -218,17 +293,17 @@ static LLVMValueRef gencontext_emit_binary(GenContext *context, Expr *expr, bool LLVMValueRef lhs_value; LLVMValueRef rhs_value; - if (load_lhs_after_rhs) + if (lhs_addr) { - rhs_value = gencontext_emit_expr(context, rhs); - lhs_value = gencontext_emit_expr(context, lhs); + lhs_value = LLVMBuildLoad2(context->builder, BACKEND_TYPE(lhs->type), lhs_addr, ""); } else { lhs_value = gencontext_emit_expr(context, lhs); - rhs_value = gencontext_emit_expr(context, rhs); } + rhs_value = gencontext_emit_expr(context, rhs); + bool is_float = type_is_float(type); switch (binary_op) @@ -249,6 +324,7 @@ static LLVMValueRef gencontext_emit_binary(GenContext *context, Expr *expr, bool case BINARYOP_MULT_MOD: return LLVMBuildMul(context->builder, lhs_value, rhs_value, "mul"); case BINARYOP_SUB: + case BINARYOP_SUB_MOD: if (lhs->type->canonical->type_kind == TYPE_POINTER) { if (lhs->type->canonical == rhs->type->canonical) return LLVMBuildPtrDiff(context->builder, lhs_value, rhs_value, "ptrdiff"); @@ -256,35 +332,16 @@ static LLVMValueRef gencontext_emit_binary(GenContext *context, Expr *expr, bool return LLVMBuildGEP2(context->builder, BACKEND_TYPE(lhs->type), lhs_value, &rhs_value, 1, "ptrsub"); } if (is_float) return LLVMBuildFSub(context->builder, lhs_value, rhs_value, "fsub"); - // TODO insert trap - if (type_is_unsigned_integer(lhs->type->canonical)) - { - return LLVMBuildNUWSub(context->builder, lhs_value, rhs_value, "usub"); - } - else - { - return LLVMBuildNSWSub(context->builder, lhs_value, rhs_value, "sub"); - } - case BINARYOP_SUB_MOD: - return LLVMBuildSub(context->builder, lhs_value, rhs_value, "submod"); + return gencontext_emit_sub_int(context, lhs->type->canonical, binary_op == BINARYOP_SUB_MOD, lhs_value, rhs_value); case BINARYOP_ADD: + case BINARYOP_ADD_MOD: if (lhs->type->canonical->type_kind == TYPE_POINTER) { assert(type_is_integer(rhs->type->canonical)); return LLVMBuildGEP2(context->builder, BACKEND_TYPE(lhs->type), lhs_value, &rhs_value, 1, "ptradd"); } if (is_float) return LLVMBuildFAdd(context->builder, lhs_value, rhs_value, "fadd"); - // TODO insert trap - if (type_is_unsigned_integer(lhs->type->canonical)) - { - return LLVMBuildNUWAdd(context->builder, lhs_value, rhs_value, "uadd"); - } - else - { - return LLVMBuildNSWAdd(context->builder, lhs_value, rhs_value, "sadd"); - } - case BINARYOP_ADD_MOD: - return LLVMBuildAdd(context->builder, lhs_value, rhs_value, "addmod"); + return gencontext_emit_add_int(context, lhs->type->canonical, binary_op == BINARYOP_ADD_MOD, lhs_value, rhs_value); case BINARYOP_DIV: if (is_float) return LLVMBuildFDiv(context->builder, lhs_value, rhs_value, "fdiv"); return type_is_unsigned(type) @@ -359,32 +416,7 @@ static LLVMValueRef gencontext_emit_binary(GenContext *context, Expr *expr, bool LLVMValueRef gencontext_emit_post_unary_expr(GenContext *context, Expr *expr) { - Expr *lhs = expr->unary_expr.expr; - LLVMValueRef value = gencontext_emit_expr(context, lhs); - bool is_add = expr->post_expr.operator == TOKEN_PLUSPLUS; -/* if (expr->type->canonical->type_kind == TYPE_POINTER) - { - LLVMValueRef offset = LLVMConstInt(is_add ? type_isize->backend_type : type_usize->backend_type, is_add ? 1 : -1, true); - LLVMBuildStore(context->builder, LLVMBuildGEP2(context->builder, gencontext_get_llvm_type(context, expr->type->canonical), value, &offset, 1, "postunary"), gencontext_emit_address(context, left);) - return ; - } - if (type_is_float(expr->type->canonical)) - { - LLVMValueRef offset = LLVMConstReal(LLVMTypeOf(value), is_add ? 1); - LLVMBuildAdd(context->builder, value, offset, name); - } - if (lhs->type->canonical->type_kind == TYPE_POINTER) - { - rhs_value = LLVMBuildNeg(context->builder, rhs_value, ""); - return LLVMBuildGEP2(context->builder, , lhs_value, &rhs_value, 1, "ptrsub"); - } - */ - const char *name = is_add ? "add" : "sub"; - LLVMValueRef constVal = LLVMConstInt(LLVMTypeOf(value), 1, !is_add); - LLVMValueRef result = is_add ? LLVMBuildAdd(context->builder, value, constVal, name) - : LLVMBuildSub(context->builder, value, constVal, name); - LLVMBuildStore(context->builder, result, gencontext_emit_address(context, lhs)); - return value; + return gencontext_emit_post_inc_dec(context, expr->post_expr.expr, expr->post_expr.operator == POSTUNARYOP_INC ? 1 : -1, false); } static LLVMValueRef gencontext_emit_binary_expr(GenContext *context, Expr *expr) @@ -394,17 +426,23 @@ static LLVMValueRef gencontext_emit_binary_expr(GenContext *context, Expr *expr) { BinaryOp base_op = binaryop_assign_base_op(binary_op); assert(base_op != BINARYOP_ERROR); - LLVMValueRef value = gencontext_emit_binary(context, expr, true, base_op); - gencontext_emit_assign(context, expr->binary_expr.left, value); + LLVMValueRef addr = gencontext_emit_address(context, expr->binary_expr.left); + LLVMValueRef value = gencontext_emit_binary(context, expr, addr, base_op); + LLVMBuildStore(context->builder, value, addr); return value; } if (binary_op == BINARYOP_ASSIGN) { + LLVMValueRef addr = gencontext_emit_address(context, expr->binary_expr.left); + if (expr->binary_expr.right->expr_kind == EXPR_INITIALIZER_LIST) + { + return gencontext_emit_initialization_from_expr(context, addr, expr->binary_expr.right); + } LLVMValueRef value = gencontext_emit_expr(context, expr->binary_expr.right); - gencontext_emit_assign(context, expr->binary_expr.left, value); + LLVMBuildStore(context->builder, value, addr); return value; } - return gencontext_emit_binary(context, expr, false, binary_op); + return gencontext_emit_binary(context, expr, NULL, binary_op); } LLVMValueRef gencontext_emit_elvis_expr(GenContext *context, Expr *expr) @@ -515,13 +553,19 @@ LLVMValueRef gencontext_emit_call_expr(GenContext *context, Expr *expr) { values[i] = gencontext_emit_expr(context, expr->call_expr.arguments[i]); } - LLVMValueRef func = expr->call_expr.function->identifier_expr.decl->func.backend_value; - return LLVMBuildCall2(context->builder, LLVMTYPE(expr->call_expr.function->identifier_expr.decl->type), func, values, args, "call"); + + Decl *function = expr->call_expr.function->identifier_expr.decl; + + LLVMValueRef func = function->func.backend_value; + LLVMTypeRef func_type = BACKEND_TYPE(function->type); + LLVMValueRef call = LLVMBuildCall2(context->builder, func_type, func, values, args, "call"); /* - if (fndcl->flags & FlagSystem) { - LLVMSetInstructionCallConv(fncallret, LLVMX86StdcallCallConv); + if (function->func.function_signature.convention) + { + LLVMSetFunctionCallConv(call, LLVMX86StdcallCallConv); }*/ + return call; } @@ -545,14 +589,22 @@ static inline LLVMValueRef gencontext_emit_expression_list_expr(GenContext *cont return value; } -static inline LLVMValueRef gencontext_emit_struct_value_expr(GenContext *context, Expr *expr) -{ - TODO -} - static inline LLVMValueRef gencontext_emit_initializer_list_expr(GenContext *context, Expr *expr) { - TODO + LLVMValueRef value = LLVMGetUndef(LLVMTYPE(expr->type)); + + /* + for (expr->initializer_expr) + expr->type. + else if (littype->tag == StructTag) { + LLVMValueRef strval = LLVMGetUndef(genlType(gen, littype)); + unsigned int pos = 0; + for (nodesFor(lit->args, cnt, nodesp)) + strval = LLVMBuildInsertValue(gen->builder, strval, genlExpr(gen, *nodesp), pos++, "literal"); + return strval; + } + TODO*/ + return value; } static inline LLVMValueRef gencontext_emit_struct_init_values_expr(GenContext *context, Expr *expr) diff --git a/src/compiler/llvm_codegen_function.c b/src/compiler/llvm_codegen_function.c index 6cecc4e84..acd77f8a9 100644 --- a/src/compiler/llvm_codegen_function.c +++ b/src/compiler/llvm_codegen_function.c @@ -1,16 +1,13 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. + #include "llvm_codegen_internal.h" -static char *mangle_name(char *buffer, Decl *decl) -{ - sprintf(buffer, "%*s", decl->name.span.length, decl->name.start); - return buffer; -} + bool gencontext_check_block_branch_emit(GenContext *context) { @@ -81,7 +78,7 @@ static inline void gencontext_emit_parameter(GenContext *context, Decl *decl, un assert(decl->decl_kind == DECL_VAR && decl->var.kind == VARDECL_PARAM); // Allocate room on stack and copy. - decl->var.backend_ref = gencontext_emit_alloca(context, decl); + decl->var.backend_ref = gencontext_emit_alloca(context, BACKEND_TYPE(decl->type), decl->name.string); LLVMBuildStore(context->builder, LLVMGetParam(context->function, index), decl->var.backend_ref); } @@ -148,10 +145,8 @@ void gencontext_emit_function_body(GenContext *context, Decl *decl) void gencontext_emit_function_decl(GenContext *context, Decl *decl) { assert(decl->decl_kind == DECL_FUNC); - char workbuf[2048] = { '\0' }; - char *external_name = mangle_name(workbuf, decl); // Resolve function backend type for function. - decl->func.backend_value = LLVMAddFunction(context->module, external_name, + decl->func.backend_value = LLVMAddFunction(context->module, decl->external_name, BACKEND_TYPE(decl->type)); // Specify appropriate storage class, visibility and call convention @@ -173,6 +168,7 @@ void gencontext_emit_function_decl(GenContext *context, Decl *decl) switch (decl->visibility) { case VISIBLE_LOCAL: + case VISIBLE_EXTERN: flags |= LLVMDIFlagPrivate; break; case VISIBLE_MODULE: @@ -199,3 +195,47 @@ void gencontext_emit_function_decl(GenContext *context, Decl *decl) } if (decl->func.body) gencontext_emit_function_body(context, decl); } + +void gencontext_emit_extern_decl(GenContext *context, Decl *decl) +{ + switch (decl->decl_kind) + { + case DECL_POISONED: + UNREACHABLE; + case DECL_FUNC: + decl->func.backend_value = LLVMAddFunction(context->module, decl->external_name, + BACKEND_TYPE(decl->type)); + LLVMSetVisibility(decl->func.backend_value, LLVMDefaultVisibility); + break; + case DECL_VAR: + decl->var.backend_ref = LLVMAddGlobal(context->module, BACKEND_TYPE(decl->type), decl->external_name); + LLVMSetVisibility(decl->var.backend_ref, LLVMDefaultVisibility); + break; + case DECL_TYPEDEF: + UNREACHABLE + case DECL_ENUM_CONSTANT: + TODO + case DECL_STRUCT: + case DECL_UNION: + BACKEND_TYPE(decl->type); + break; + case DECL_ENUM: + TODO + case DECL_ERROR: + TODO + case DECL_ERROR_CONSTANT: + TODO + case DECL_ARRAY_VALUE: + case DECL_IMPORT: + case DECL_MACRO: + case DECL_MULTI_DECL: + case DECL_GENERIC: + case DECL_CT_IF: + case DECL_CT_ELSE: + case DECL_CT_ELIF: + case DECL_ATTRIBUTE: + case DECL_THROWS: + UNREACHABLE + } +} + diff --git a/src/compiler/llvm_codegen_internal.h b/src/compiler/llvm_codegen_internal.h index 20b23f022..28dab769d 100644 --- a/src/compiler/llvm_codegen_internal.h +++ b/src/compiler/llvm_codegen_internal.h @@ -1,8 +1,8 @@ #pragma once // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "compiler_internal.h" #include @@ -65,6 +65,7 @@ typedef struct BreakContinue break_continue_stack[BREAK_STACK_MAX]; size_t break_continue_stack_index; LLVMTypeRef error_type; + Type **generated_types; } GenContext; @@ -75,7 +76,7 @@ LLVMValueRef gencontext_emit_expr(GenContext *context, Expr *expr); LLVMMetadataRef gencontext_get_debug_type(GenContext *context, Type *type); void gencontext_emit_debug_location(GenContext *context, SourceRange location); LLVMMetadataRef gencontext_create_builtin_debug_type(GenContext *context, Type *builtin_type); -LLVMValueRef gencontext_emit_alloca(GenContext *context, Decl *decl); +LLVMValueRef gencontext_emit_alloca(GenContext *context, LLVMTypeRef type, const char *name); void gencontext_emit_compound_stmt(GenContext *context, Ast *ast); void gencontext_emit_block(GenContext *context, LLVMBasicBlockRef next_block); void gencontext_emit_br(GenContext *context, LLVMBasicBlockRef next_block); @@ -87,6 +88,7 @@ static inline LLVMBasicBlockRef gencontext_create_free_block(GenContext *context } void gencontext_emit_function_decl(GenContext *context, Decl *decl); +void gencontext_emit_extern_decl(GenContext *context, Decl *decl); LLVMValueRef gencontext_emit_address(GenContext *context, Expr *expr); #define LLVMTYPE(type) type->backend_type static inline LLVMValueRef gencontext_load_expr(GenContext *context, LLVMValueRef value) diff --git a/src/compiler/llvm_codegen_module.c b/src/compiler/llvm_codegen_module.c index b53dae092..0b583e35f 100644 --- a/src/compiler/llvm_codegen_module.c +++ b/src/compiler/llvm_codegen_module.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "llvm_codegen_internal.h" @@ -37,13 +37,14 @@ static inline LLVMTypeRef gencontext_create_basic_llvm_type(GenContext *context, static inline void gencontext_init_basic_llvm_type(GenContext *context, Type *type) { + vec_add(context->generated_types, type); type->backend_type = gencontext_create_basic_llvm_type(context, type); } void gencontext_begin_module(GenContext *context) { assert(!context->module && "Expected no module"); const char *full_path = context->ast_context->file->full_path; - char *mangled_module_name = strformat("module:%s", context->ast_context->module->name); + char *mangled_module_name = strformat("%s-%s", context->ast_context->module->name->module, context->ast_context->file->name); context->module = LLVMModuleCreateWithNameInContext(mangled_module_name, context->context); LLVMSetModuleDataLayout(context->module, target_data_layout()); LLVMSetSourceFileName(context->module, full_path, strlen(context->ast_context->file->full_path)); diff --git a/src/compiler/llvm_codegen_stmt.c b/src/compiler/llvm_codegen_stmt.c index aed58f968..8c5f58b89 100644 --- a/src/compiler/llvm_codegen_stmt.c +++ b/src/compiler/llvm_codegen_stmt.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "llvm_codegen_internal.h" @@ -22,7 +22,7 @@ static LLVMValueRef gencontext_emit_decl(GenContext *context, Ast *ast) { Decl *decl = ast->declare_stmt; - decl->var.backend_ref = gencontext_emit_alloca(context, decl); + decl->var.backend_ref = gencontext_emit_alloca(context, BACKEND_TYPE(decl->type), decl->name.string); // TODO NRVO // TODO debug info /* diff --git a/src/compiler/llvm_codegen_type.c b/src/compiler/llvm_codegen_type.c index 942e0c4d5..34e11c1aa 100644 --- a/src/compiler/llvm_codegen_type.c +++ b/src/compiler/llvm_codegen_type.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "llvm_codegen_internal.h" @@ -48,7 +48,7 @@ static inline LLVMTypeRef gencontext_create_llvm_type_from_decl(GenContext *cont VECADD(types, BACKEND_TYPE(decl->strukt.members[i]->type)); } // TODO fix name. - LLVMTypeRef type = LLVMStructCreateNamed(LLVMCONTEXT(context), decl->name.string); + LLVMTypeRef type = LLVMStructCreateNamed(LLVMCONTEXT(context), decl->external_name); LLVMStructSetBody(type, types, vec_size(types), decl->is_packed); return type; } @@ -66,7 +66,7 @@ static inline LLVMTypeRef gencontext_create_llvm_type_from_decl(GenContext *cont max_type = type; } } - LLVMTypeRef type = LLVMStructCreateNamed(LLVMCONTEXT(context), decl->name.string); + LLVMTypeRef type = LLVMStructCreateNamed(LLVMCONTEXT(context), decl->external_name); LLVMStructSetBody(type, &max_type, 1, false); return type; } @@ -91,6 +91,7 @@ static inline LLVMTypeRef gencontext_create_llvm_type_from_decl(GenContext *cont static inline LLVMTypeRef gencontext_create_llvm_type_from_ptr(GenContext *context, Type *type) { LLVMTypeRef base_llvm_type = BACKEND_TYPE(type->pointer); + vec_add(context->generated_types, type); if (type->canonical != type) { @@ -104,6 +105,7 @@ static inline LLVMTypeRef gencontext_create_llvm_type_from_array(GenContext *con { LLVMTypeRef base_llvm_type = BACKEND_TYPE(type->array.base); + vec_add(context->generated_types, type); if (type->canonical != type) { @@ -134,7 +136,13 @@ LLVMTypeRef gencontext_create_llvm_func_type(GenContext *context, Type *type) LLVMTypeRef gencontext_get_llvm_type(GenContext *context, Type *type) { - if (type->backend_type) return type->backend_type; + if (type->backend_type) + { + assert(LLVMGetTypeContext(type->backend_type) == context->context); + return type->backend_type; + } + vec_add(context->generated_types, type); + DEBUG_LOG("Generating type %s", type->name); switch (type->type_kind) { diff --git a/src/compiler/module.c b/src/compiler/module.c index d0972ebd4..3af6b753a 100644 --- a/src/compiler/module.c +++ b/src/compiler/module.c @@ -1,11 +1,63 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "compiler_internal.h" -Decl *module_find_symbol(Module *module, const char *symbol) +Decl *module_find_symbol(Module *module, const char *symbol, ModuleSymbolSearch search) { - return stable_get(&module->symbols, symbol); + Decl *decl = stable_get(&module->symbols, symbol); + if (decl) + { + switch (decl->visibility) + { + case VISIBLE_LOCAL: + case VISIBLE_EXTERN: + decl = NULL; + break; + case VISIBLE_MODULE: + if (search == MODULE_SYMBOL_SEARCH_EXTERNAL) decl = NULL; + break; + case VISIBLE_PUBLIC: + break; + } + } + if (!decl) + { + if (search == MODULE_SYMBOL_SEARCH_THIS) search = MODULE_SYMBOL_SEARCH_PARENT; + VECEACH (module->sub_modules, i) + { + if ((decl = module_find_symbol(module->sub_modules[i], symbol, search))) break; + } + } + return decl; } +Path *path_create_from_string(const char *string, size_t len, SourceRange span) +{ + Path *path = CALLOCS(Path); + path->span = span; + TokenType type = TOKEN_IDENT; + path->module = symtab_add(string, len, fnv1a(string, len), &type); + path->len = len; + if (type != TOKEN_IDENT) + { + sema_error_range(span, "A module name was expected here."); + return NULL; + } + return path; +} + +Path *path_find_parent_path(Path *path) +{ + const char *last_scope_chars = strrchr(path->module, ':'); + // No parent + if (!last_scope_chars) return NULL; + + Path *parent_path = path_create_from_string(path->module, last_scope_chars - path->module - 1, INVALID_RANGE); + + // Should never fail. + assert(parent_path); + + return parent_path; +} \ No newline at end of file diff --git a/src/compiler/parser.c b/src/compiler/parser.c index d38614288..a75ddd309 100644 --- a/src/compiler/parser.c +++ b/src/compiler/parser.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "compiler_internal.h" @@ -140,6 +140,7 @@ static void recover_top_level(void) case TOKEN_UNION: case TOKEN_ENUM: case TOKEN_MACRO: + case TOKEN_EXTERN: return; default: advance(); @@ -188,6 +189,38 @@ do { if (!try_consume(TOKEN_COMMA) && tok.type != TOKEN_RPAREN) { \ SEMA_ERROR(tok, "Expected ',' or ')'"); return _res; } } while(0) +static inline Path *parse_module_path() +{ + assert(tok.type == TOKEN_IDENT); + char *scratch_ptr = current_context->path_scratch; + size_t offset = 0; + SourceRange span = tok.span; + memcpy(scratch_ptr, tok.start, tok.span.length); + offset += tok.span.length; + SourceRange last_range; + while (1) + { + last_range = tok.span; + if (!try_consume(TOKEN_IDENT)) + { + SEMA_ERROR(tok, "Each '::' must be followed by a regular lower case sub module name."); + return NULL; + } + if (!try_consume(TOKEN_SCOPE)) + { + span = source_range_from_ranges(span, last_range); + break; + } + scratch_ptr[offset++] = ':'; + scratch_ptr[offset++] = ':'; + memcpy(scratch_ptr + offset, tok.start, tok.span.length); + offset += tok.span.length; + } + scratch_ptr[offset] = '\0'; + return path_create_from_string(scratch_ptr, offset, span); +} + + static Ast* parse_compound_stmt() { CONSUME_OR(TOKEN_LBRACE, &poisoned_ast); @@ -213,23 +246,44 @@ static Ast* parse_function_block() return ast; } -static Path *parse_path(void) + + +static Path *parse_path_prefix() { if (tok.type != TOKEN_IDENT || next_tok.type != TOKEN_SCOPE) return NULL; - Path *path = malloc_arena(sizeof(Path)); - memset(path, 0, sizeof(Path)); + char *scratch_ptr = current_context->path_scratch; + size_t offset = 0; - path->sub_module = tok; - - if (tok.type == TOKEN_IDENT && next_tok.type == TOKEN_SCOPE) + Path *path = CALLOCS(Path); + path->span = tok.span; + memcpy(scratch_ptr, tok.start, tok.span.length); + offset += tok.span.length; + SourceRange last_range = tok.span; + advance(); + advance(); + while (tok.type == TOKEN_IDENT && next_tok.type == TOKEN_SCOPE) { + last_range = tok.span; advance(); advance(); - path->module = path->sub_module; - path->sub_module = tok; + scratch_ptr[offset++] = ':'; + scratch_ptr[offset++] = ':'; + memcpy(scratch_ptr + offset, tok.start, tok.span.length); + offset += tok.span.length; } + TokenType type = TOKEN_IDENT; + path->span = source_range_from_ranges(path->span, last_range); + path->module = symtab_add(scratch_ptr, offset, fnv1a(scratch_ptr, offset), &type); + if (type != TOKEN_IDENT) + { + sema_error_range(path->span, "A module name was expected here."); + return NULL; + + } + path->len = offset; + return path; } @@ -257,7 +311,7 @@ static Path *parse_path(void) */ static inline TypeInfo *parse_base_type(void) { - Path *path = parse_path(); + Path *path = parse_path_prefix(); if (path) { TypeInfo *type_info = type_info_new(TYPE_INFO_IDENTIFIER); @@ -1352,6 +1406,7 @@ static Ast *parse_stmt(void) case TOKEN_MACRO: case TOKEN_MODULE: case TOKEN_PUBLIC: + case TOKEN_EXTERN: case TOKEN_STRUCT: case TOKEN_THROWS: case TOKEN_TYPEDEF: @@ -1454,8 +1509,8 @@ static inline bool parse_optional_module_params(Token **tokens) /** * module - * : MODULE IDENT ';' - * | MODULE IDENT '(' module_params ')' ';' + * : MODULE path ';' + * | MODULE path '(' module_params ')' ';' */ static inline void parse_module(void) { @@ -1466,12 +1521,16 @@ static inline void parse_module(void) return; } - Token name = tok; + Path *path = parse_module_path(); // Expect the module name - if (!consume(TOKEN_IDENT, "After 'module' the name of the module should be placed.")) + if (!path) { - context_set_module(current_context, (Token) {.type = TOKEN_INVALID_TOKEN}, NULL); + path = CALLOCS(Path); + path->len = strlen("INVALID"); + path->module = "INVALID"; + path->span = INVALID_RANGE; + context_set_module(current_context, path, NULL); recover_top_level(); return; } @@ -1480,11 +1539,11 @@ static inline void parse_module(void) Token *generic_parameters = NULL; if (!parse_optional_module_params(&generic_parameters)) { - context_set_module(current_context, name, generic_parameters); + context_set_module(current_context, path, generic_parameters); recover_top_level(); return; } - context_set_module(current_context, name, generic_parameters); + context_set_module(current_context, path, generic_parameters); TRY_CONSUME_EOS_OR(); } @@ -1518,48 +1577,89 @@ static inline bool parse_macro_parameter_list(Expr*** result) } } +/** + * import_selective + * : import_spec + * | import_spec AS import_spec + * ; + * + * import_spec + * : IDENT + * | TYPE + * | MACRO + * | CONST + * ; + * + * @return true if import succeeded + */ +static inline bool parse_import_selective(Path *path) +{ + if (!token_is_symbol(tok.type)) + { + SEMA_ERROR(tok, "Expected a symbol name here, the syntax is 'import : '."); + return false; + } + Token symbol = tok; + advance(); + // Alias? + if (!try_consume(TOKEN_AS)) + { + return context_add_import(current_context, path, symbol, EMPTY_TOKEN); + } + if (tok.type != symbol.type) + { + if (!token_is_symbol(tok.type)) + { + SEMA_ERROR(tok, "Expected a symbol name here, the syntax is 'import : AS '."); + return false; + } + SEMA_ERROR(tok, "Expected the alias be the same type of name as the symbol aliased."); + return false; + } + Token alias = tok; + advance(); + return context_add_import(current_context, path, symbol, alias); + +} + /** * * import - * : IMPORT IDENT ';' - * | IMPORT IDENT AS IDENT ';' + * : IMPORT PATH ';' + * | IMPORT PATH ':' import_selective ';' * | IMPORT IDENT AS IDENT LOCAL ';' * | IMPORT IDENT LOCAL ';' * - * // TODO macro parameters (after grammar is updated) + * import_list + * : import_selective + * | import_list ',' import_selective + * ; * * @return true if import succeeded */ static inline bool parse_import() { - advance_and_verify(TOKEN_IMPORT); - Token module_name = tok; - - TRY_CONSUME_OR(TOKEN_IDENT, "Import statement should be followed by the name of the module to import.", false); - - Expr **generic_parameters = NULL; - - /* MACRO params here - if (tok.type == TOKEN_LPAREN) + if (tok.type != TOKEN_IDENT) { - if (!parse_macro_parameter_list(&generic_parameters)) return false; - }*/ - - Token alias = {}; - ImportType import_type = IMPORT_TYPE_FULL; - if (try_consume(TOKEN_AS)) - { - alias = tok; - if (!consume_ident("alias")) return false; - import_type = IMPORT_TYPE_ALIAS; + SEMA_ERROR(tok, "Import statement should be followed by the name of the module to import."); + return false; } - if (try_consume(TOKEN_LOCAL)) + + Path *path = parse_module_path(); + if (tok.type == TOKEN_COLON) { - import_type = import_type == IMPORT_TYPE_ALIAS ? IMPORT_TYPE_ALIAS_LOCAL : IMPORT_TYPE_LOCAL; + while (1) + { + if (!parse_import_selective(path)) return false; + if (!try_consume(TOKEN_COMMA)) break; + } + } + else + { + context_add_import(current_context, path, EMPTY_TOKEN, EMPTY_TOKEN); } - context_add_import(current_context, module_name, alias, import_type, generic_parameters); TRY_CONSUME_EOS_OR(false); return true; } @@ -1707,7 +1807,7 @@ static inline bool parse_attributes(Decl *parent_decl) while (tok.type == TOKEN_AT_IDENT || (tok.type == TOKEN_IDENT && next_tok.type == TOKEN_SCOPE)) { - Path *path = parse_path(); + Path *path = parse_path_prefix(); Attr *attr = malloc_arena(sizeof(Attr)); @@ -1894,7 +1994,7 @@ static inline Decl *parse_generics_declaration(Visibility visibility) { rtype = TRY_TYPE_OR(parse_type_expression(), &poisoned_decl); } - Path *path = parse_path(); + Path *path = parse_path_prefix(); Decl *decl = decl_new(DECL_GENERIC, tok, visibility); decl->generic_decl.path = path; if (!consume_ident("generic function name")) return &poisoned_decl; @@ -2284,7 +2384,7 @@ static inline Decl *parse_func_definition(Visibility visibility, bool is_interfa Decl *func = decl_new(DECL_FUNC, tok, visibility); func->func.function_signature.rtype = return_type; - Path *path = parse_path(); + Path *path = parse_path_prefix(); if (path || tok.type == TOKEN_TYPE_IDENT) { // Special case, actually an extension @@ -2433,9 +2533,6 @@ static inline Decl *parse_enum_declaration(Visibility visibility) return decl; } - - - static inline bool parse_conditional_top_level(Decl ***decls) { CONSUME_OR(TOKEN_LBRACE, false); @@ -2505,6 +2602,9 @@ static inline bool check_no_visibility_before(Visibility visibility) case VISIBLE_LOCAL: SEMA_ERROR(tok, "Unexpected 'local' before '%.*s'.", tok.span.length, tok.start); return false; + case VISIBLE_EXTERN: + SEMA_ERROR(tok, "Unexpected 'extern' before '%.*s'.", tok.span.length, tok.start); + return false; default: return true; } @@ -2541,6 +2641,9 @@ static inline Decl *parse_top_level(void) visibility = VISIBLE_LOCAL; advance(); break; + case TOKEN_EXTERN: + visibility = VISIBLE_EXTERN; + advance(); default: break; } @@ -2651,7 +2754,7 @@ static Expr *parse_unary_expr(Expr *left) TokenType operator_type = tok.type; Expr *unary = EXPR_NEW_TOKEN(EXPR_UNARY, tok); - unary->unary_expr.operator = operator_type; + unary->unary_expr.operator = unaryop_from_token(operator_type); Precedence rule_precedence = rules[operator_type].precedence; advance(); Expr *right_side = parse_precedence(rule_precedence); @@ -2667,7 +2770,7 @@ static Expr *parse_post_unary(Expr *left) assert(expr_ok(left)); Expr *unary = EXPR_NEW_TOKEN(EXPR_POST_UNARY, tok); unary->post_expr.expr = left; - unary->post_expr.operator = tok.type; + unary->post_expr.operator = post_unaryop_from_token(tok.type); advance(); return unary; } @@ -3141,9 +3244,17 @@ static Expr *parse_type_access(TypeInfo *type) advance_and_verify(TOKEN_DOT); expr->type_access.name = tok; - TRY_CONSUME_OR(TOKEN_IDENT, "Expected a function name or value", &poisoned_expr); - - return expr; + switch (tok.type) + { + case TOKEN_MACRO: + case TOKEN_IDENT: + case TOKEN_CONST_IDENT: + advance(); + return expr; + default: + SEMA_ERROR(tok, "Expected a function name, macro, or constant."); + return &poisoned_expr; + } } @@ -3202,7 +3313,7 @@ static Expr *parse_type_identifier(Expr *left) static Expr *parse_maybe_scope(Expr *left) { assert(!left && "Unexpected left hand side"); - Path *path = parse_path(); + Path *path = parse_path_prefix(); switch (tok.type) { case TOKEN_IDENT: diff --git a/src/compiler/sema_name_resolution.c b/src/compiler/sema_name_resolution.c new file mode 100644 index 000000000..62ecba834 --- /dev/null +++ b/src/compiler/sema_name_resolution.c @@ -0,0 +1,133 @@ +// Copyright (c) 2020 Christoffer Lerno. All rights reserved. +// Use of this source code is governed by a LGPLv3.0 +// a copy of which can be found in the LICENSE file. + +#include "compiler_internal.h" + + + +static inline bool matches_subpath(Path *path_to_check, Path *path_to_find) +{ + // This checks the full match. + if (path_to_find->module == path_to_check->module) return true; + + // Let's check the offset on where to start comparing to start with + // the submatch. + ssize_t compare_start = (ssize_t)path_to_check->len - (ssize_t)path_to_find->len; + + // The smallest match is the situation a::foo::bar vs foo::bar + // This means that the compare_start must be 3 or more. + if (compare_start < 3) return false; + + // We also want to make sure that the preceeding 2 characters are :: + if (path_to_check->module[compare_start - 1] != ':' || path_to_check->module[compare_start - 2] != ':') return false; + + // Ok, now we know this is a subpath, so check: + return 0 == memcmp(path_to_check->module + compare_start, path_to_find->module, path_to_find->len); +} + +static Decl *sema_resolve_path_symbol(Context *context, const char *symbol, Path *path, Decl **ambiguous_other_decl) +{ + assert(path && "Expected path."); + *ambiguous_other_decl = NULL; + Decl *decl = NULL; + VECEACH(context->imports, i) + { + Decl *import = context->imports[i]; + // Partial imports + if (import->import.symbol.string && import->import.symbol.string != symbol) continue; + // Full import, first match the subpath. + if (path->len > import->import.path->len) continue; + if (!matches_subpath(import->import.path, path)) continue; + Decl *found = module_find_symbol(import->module, symbol, MODULE_SYMBOL_SEARCH_EXTERNAL); + if (!found) continue; + if (decl) + { + *ambiguous_other_decl = found; + continue; + } + decl = found; + } + context_register_external_symbol(context, decl); + return decl; +} + +Decl *sema_resolve_symbol(Context *context, const char *symbol, Path *path, Decl **ambiguous_other_decl) +{ + if (path) + { + return sema_resolve_path_symbol(context, symbol, path, ambiguous_other_decl); + } + + *ambiguous_other_decl = NULL; + + if (context->current_scope) + { + Decl **first = &context->locals[0]; + Decl **current = context->last_local - 1; + while (current >= first) + { + if (current[0]->name.string == symbol) return current[0]; + current--; + } + } + + // Search in file scope. + Decl *decl = stable_get(&context->local_symbols, symbol); + + if (decl) return decl; + + // Search in the module and child modules. + decl = module_find_symbol(context->module, symbol, MODULE_SYMBOL_SEARCH_THIS); + + if (decl) + { + context_register_external_symbol(context, decl); + return decl; + } + + // Search in imports + VECEACH(context->imports, i) + { + Decl *import = context->imports[i]; + if (!decl_ok(import)) continue; + Decl *found = module_find_symbol(import->module, symbol, MODULE_SYMBOL_SEARCH_EXTERNAL); + if (!found) continue; + if (decl) + { + *ambiguous_other_decl = found; + continue; + } + decl = found; + } + if (!decl) return NULL; + context_register_external_symbol(context, decl); + return decl; +} + +bool sema_add_local(Context *context, Decl *decl) +{ + Decl *dummy; + Decl *other = sema_resolve_symbol(context, decl->name.string, NULL, &dummy); + if (other) + { + sema_shadow_error(decl, other); + decl_poison(decl); + decl_poison(other); + return false; + } + Decl *** vars = &context->active_function_for_analysis->func.annotations->vars; + unsigned num_vars = vec_size(*vars); + if (num_vars == MAX_LOCALS - 1 || context->last_local == &context->locals[MAX_LOCALS - 1]) + { + SEMA_ERROR(decl->name, "Reached the maximum number of locals."); + return false; + } + decl->var.id = num_vars; + *vars = VECADD(*vars, decl); + context->last_local[0] = decl; + context->last_local++; + return true; +} + + diff --git a/src/compiler/semantic_analyser.c b/src/compiler/semantic_analyser.c index 226c45a10..80c3dc345 100644 --- a/src/compiler/semantic_analyser.c +++ b/src/compiler/semantic_analyser.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "compiler_internal.h" @@ -32,8 +32,6 @@ void sema_shadow_error(Decl *decl, Decl *old) sema_prev_at_range(old->name.span, "The previous use of '%s' was here.", decl->name.string); } - - Decl *context_find_ident(Context *context, const char *symbol) { Decl **first = &context->locals[0]; @@ -43,11 +41,28 @@ Decl *context_find_ident(Context *context, const char *symbol) if (current[0]->name.string == symbol) return current[0]; current--; } - Decl *found = module_find_symbol(context->module, symbol); + Decl *found = module_find_symbol(context->module, symbol, MODULE_SYMBOL_SEARCH_THIS); if (found) return found; - return NULL; -} + Decl *symbol_found = NULL; + VECEACH(context->imports, i) + { + Decl *import = context->imports[i]; + // Partial imports + if (import->import.symbol.string && import->import.symbol.string != symbol) continue; + + Decl *decl = module_find_symbol(import->module, symbol, MODULE_SYMBOL_SEARCH_EXTERNAL); + + if (!decl) continue; + if (symbol_found) + { + symbol_found = NULL; + continue; + } + symbol_found = decl; + } + return symbol_found; +} static inline void context_push_scope_with_flags(Context *context, ScopeFlags flags) @@ -361,7 +376,7 @@ static inline bool sema_analyse_var_decl(Context *context, Decl *decl) { if (!sema_analyse_expr(context, decl->type, decl->var.init_expr)) return decl_poison(decl); } - if (!context_add_local(context, decl)) return decl_poison(decl); + if (!sema_add_local(context, decl)) return decl_poison(decl); return true; } @@ -963,7 +978,7 @@ static inline bool sema_analyse_function_body(Context *context, Decl *func) Decl **params = func->func.function_signature.params; VECEACH(params, i) { - if (!context_add_local(context, params[i])) return false; + if (!sema_add_local(context, params[i])) return false; } if (!sema_analyse_compound_statement_no_scope(context, func->func.body)) return false; if (context->current_scope->exit != EXIT_RETURN) @@ -977,6 +992,7 @@ static inline bool sema_analyse_function_body(Context *context, Decl *func) } func->func.labels = context->labels; context_pop_scope(context); + context->current_scope = NULL; return true; } @@ -1017,6 +1033,15 @@ static inline bool sema_analyse_func(Context *context, Decl *decl) if (!sema_analyse_method_function(context, decl)) return decl_poison(decl); } if (decl->func.body && !sema_analyse_function_body(context, decl)) return decl_poison(decl); + if (decl->name.string == main_name) + { + if (decl->visibility == VISIBLE_LOCAL) + { + SEMA_ERROR(decl->name, "'main' cannot have local visibility."); + return false; + } + decl->visibility = VISIBLE_EXTERN; + } DEBUG_LOG("Function analysis done.") return true; } @@ -1083,9 +1108,43 @@ static inline bool sema_analyse_generic(Context *context, Decl *decl) static inline bool sema_analyse_enum(Context *context, Decl *decl) { - if (!sema_resolve_type_info(context, decl->typedef_decl.type_info)) return false; - // TODO assign numbers to constants - return true; + if (!sema_resolve_type_info(context, decl->enums.type_info)) return false; + uint64_t value = 0; + Type *type = decl->enums.type_info->type; + bool success = true; + VECEACH(decl->enums.values, i) + { + Decl *enum_value = decl->enums.values[i]; + enum_value->enum_constant.ordinal = i; + assert(enum_value->resolve_status == RESOLVE_NOT_DONE); + assert(enum_value->decl_kind == DECL_ENUM_CONSTANT); + enum_value->resolve_status = RESOLVE_RUNNING; + Expr *expr = enum_value->enum_constant.expr; + if (!expr) + { + expr = expr_new(EXPR_CONST, EMPTY_TOKEN); + expr->type = type; + expr->resolve_status = RESOLVE_DONE; + expr->const_expr.type = CONST_INT; + expr->const_expr.i = value; + enum_value->enum_constant.expr = expr; + } + if (!sema_analyse_expr(context, type, expr)) + { + success = false; + enum_value->resolve_status = RESOLVE_DONE; + continue; + } + assert(type_is_integer(expr->type->canonical)); + if (expr->expr_kind != EXPR_CONST) + { + SEMA_ERROR(expr->loc, "Expected a constant expression for enum"); + success = false; + } + enum_value->resolve_status = RESOLVE_DONE; + enum_value->type = decl->type; + } + return success; } static inline bool sema_analyse_error(Context *context, Decl *decl) @@ -1107,6 +1166,7 @@ bool sema_analyse_decl(Context *context, Decl *decl) } decl->resolve_status = RESOLVE_RUNNING; + decl->module = context->module; switch (decl->decl_kind) { case DECL_THROWS: @@ -1114,27 +1174,29 @@ bool sema_analyse_decl(Context *context, Decl *decl) case DECL_STRUCT: case DECL_UNION: if (!sema_analyse_struct_union(context, decl)) return decl_poison(decl); - context_add_header_decl(context, decl); + decl_set_external_name(decl); break; case DECL_FUNC: if (!sema_analyse_func(context, decl)) return decl_poison(decl); - context_add_header_decl(context, decl); + decl_set_external_name(decl); break; case DECL_MACRO: if (!sema_analyse_macro(context, decl)) return decl_poison(decl); break; case DECL_VAR: if (!sema_analyse_global(context, decl)) return decl_poison(decl); - context_add_header_decl(context, decl); + decl_set_external_name(decl); break; case DECL_TYPEDEF: if (!sema_analyse_typedef(context, decl)) return decl_poison(decl); break; case DECL_ENUM: if (!sema_analyse_enum(context, decl)) return decl_poison(decl); + decl_set_external_name(decl); break; case DECL_ERROR: if (!sema_analyse_error(context, decl)) return decl_poison(decl); + decl_set_external_name(decl); break; case DECL_GENERIC: if (!sema_analyse_generic(context, decl)) return decl_poison(decl); @@ -1200,10 +1262,23 @@ static inline bool sema_analyse_top_level_if(Context *context, Decl *ct_if) return true; } - -static inline void sema_process_imports(Context *context) +void sema_analysis_pass_process_imports(Context *context) { - // TODO + VECEACH(context->imports, i) + { + Decl *import = context->imports[i]; + import->resolve_status = RESOLVE_RUNNING; + // IMPROVE error on importing twice. + Path *path = import->import.path; + Module *module = stable_get(&compiler.modules, path->module); + if (!module) + { + SEMA_ERROR(import->name, "No module named '%s' could be found.", path->module); + decl_poison(import); + continue; + } + import->module = module; + } } void sema_analysis_pass_conditional_compilation(Context *context) @@ -1245,15 +1320,16 @@ void sema_analysis_pass_decls(Context *context) } +bool sema_resolve_type_info(Context *context, TypeInfo *type_info) +{ + if (!sema_resolve_type_shallow(context, type_info)) return false; + return true; +} + static bool sema_resolve_type_identifier(Context *context, TypeInfo *type_info) { - assert(!type_info->unresolved.path && "TODO"); - - Decl *decl = stable_get(&context->local_symbols, type_info->unresolved.name_loc.string); - if (!decl) - { - decl = module_find_symbol(context->module, type_info->unresolved.name_loc.string); - } + Decl *ambiguous_decl; + Decl *decl = sema_resolve_symbol(context, type_info->unresolved.name_loc.string, type_info->unresolved.path, &ambiguous_decl); if (!decl) { @@ -1261,6 +1337,12 @@ static bool sema_resolve_type_identifier(Context *context, TypeInfo *type_info) return type_info_poison(type_info); } + if (ambiguous_decl) + { + SEMA_ERROR(type_info->unresolved.name_loc, "Ambiguous type '%s' – both defined in %s and %s, please add the module name to resolve the ambiguity", type_info->unresolved.name_loc.string, + decl->module->name->module, ambiguous_decl->module->name->module); + return type_info_poison(type_info); + } switch (decl->decl_kind) { case DECL_THROWS: @@ -1306,6 +1388,7 @@ static bool sema_resolve_type_identifier(Context *context, TypeInfo *type_info) UNREACHABLE } + bool sema_resolve_type_shallow(Context *context, TypeInfo *type_info) { if (type_info->resolve_status == RESOLVE_DONE) return type_info_ok(type_info); @@ -1338,10 +1421,4 @@ bool sema_resolve_type_shallow(Context *context, TypeInfo *type_info) return sema_resolve_ptr_type(context, type_info); } -} - -bool sema_resolve_type_info(Context *context, TypeInfo *type_info) -{ - if (!sema_resolve_type_shallow(context, type_info)) return false; - return true; -} +} \ No newline at end of file diff --git a/src/compiler/source_file.c b/src/compiler/source_file.c index 16df81714..0877f1df2 100644 --- a/src/compiler/source_file.c +++ b/src/compiler/source_file.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include #include @@ -67,6 +67,14 @@ void source_file_append_line_end(File *file, SourceLoc loc) VECADD(file->line_start, file->current_line_start); } +SourceRange source_range_from_ranges(SourceRange first, SourceRange last) +{ + return (SourceRange) { + .loc = first.loc, + .length = last.loc - first.loc + last.length + }; +} + SourcePosition source_file_find_position(SourceLoc loc) { File *file = source_file_from_position(loc); diff --git a/src/compiler/symtab.c b/src/compiler/symtab.c index 499533fff..ed04fe86a 100644 --- a/src/compiler/symtab.c +++ b/src/compiler/symtab.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "compiler_internal.h" @@ -34,6 +34,8 @@ typedef struct _Entry static SymTab symtab; +const char *main_name; + void symtab_init(uint32_t capacity) { assert (is_power_of_two(capacity) && "Must be a power of two"); @@ -41,7 +43,7 @@ void symtab_init(uint32_t capacity) { free(symtab.entries); } - size_t size = capacity * sizeof(SymEntry); + size_t size = capacity *sizeof(SymEntry); symtab.entries = MALLOC(size); memset(symtab.entries, 0, size); symtab.count = 0; @@ -61,8 +63,10 @@ void symtab_init(uint32_t capacity) const char* interned = symtab_add(name, (uint32_t)strlen(name), fnv1a(name, len), &type); assert(type == (TokenType)i); assert(symtab_add(name, (uint32_t)strlen(name), fnv1a(name, len), &type) == interned); - } + // Init some constant idents + TokenType type = TOKEN_IDENT; + main_name = symtab_add("main", 4, fnv1a("main", 4), &type); } static inline SymEntry *entry_find(const char *key, uint32_t key_len, uint32_t hash) diff --git a/src/compiler/tokens.c b/src/compiler/tokens.c index 85e02bfd0..2411c25cf 100644 --- a/src/compiler/tokens.c +++ b/src/compiler/tokens.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "compiler_internal.h" @@ -190,6 +190,8 @@ const char *token_type_to_string(TokenType type) return "else"; case TOKEN_ENUM: return "enum"; + case TOKEN_EXTERN: + return "extern"; case TOKEN_ERROR_TYPE: return "error"; case TOKEN_FALSE: @@ -351,6 +353,20 @@ const char *token_type_to_string(TokenType type) UNREACHABLE } +bool token_is_symbol(TokenType type) +{ + switch (type) + { + case TOKEN_MACRO: + case TOKEN_CONST: + case TOKEN_IDENT: + case TOKEN_TYPE_IDENT: + return true; + default: + return false; + } +} + bool token_is_type(TokenType type) { return type >= TOKEN_VOID && type <= TOKEN_C_ULONGLONG; diff --git a/src/compiler/types.c b/src/compiler/types.c index 42e85ec19..e19c1e2cb 100644 --- a/src/compiler/types.c +++ b/src/compiler/types.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "compiler_internal.h" diff --git a/src/compiler_tests/benchmark.c b/src/compiler_tests/benchmark.c index 6966490ba..83bace250 100644 --- a/src/compiler_tests/benchmark.c +++ b/src/compiler_tests/benchmark.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "benchmark.h" #include diff --git a/src/compiler_tests/benchmark.h b/src/compiler_tests/benchmark.h index 6165a53fc..0ea570b00 100644 --- a/src/compiler_tests/benchmark.h +++ b/src/compiler_tests/benchmark.h @@ -1,8 +1,8 @@ #pragma once // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. void bench_begin(void); diff --git a/src/compiler_tests/shorttest.c b/src/compiler_tests/shorttest.c index 61357986d..138c1fbf6 100644 --- a/src/compiler_tests/shorttest.c +++ b/src/compiler_tests/shorttest.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. static const char* test_parse = "struct Node\n" "{\n" diff --git a/src/compiler_tests/tests.c b/src/compiler_tests/tests.c index 6d8aa6e17..7a973ee10 100644 --- a/src/compiler_tests/tests.c +++ b/src/compiler_tests/tests.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "tests.h" #include diff --git a/src/compiler_tests/tests.h b/src/compiler_tests/tests.h index 7171423c9..499cd948d 100644 --- a/src/compiler_tests/tests.h +++ b/src/compiler_tests/tests.h @@ -1,8 +1,8 @@ #pragma once // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. void compiler_tests(void); \ No newline at end of file diff --git a/src/main.c b/src/main.c index 3b9a15fc6..a7b777bf0 100644 --- a/src/main.c +++ b/src/main.c @@ -18,11 +18,13 @@ int main(int argc, const char *argv[]) compiler_tests(); break; case COMMAND_COMPILE: - compile_file(); + compile_files(NULL); + break; + case COMMAND_BUILD: + build(); break; case COMMAND_COMPILE_RUN: case COMMAND_MISSING: - case COMMAND_BUILD: case COMMAND_RUN: case COMMAND_CLEAN_RUN: case COMMAND_CLEAN: diff --git a/src/utils/common.h b/src/utils/common.h index ad0d32398..4d3f23ba5 100644 --- a/src/utils/common.h +++ b/src/utils/common.h @@ -1,11 +1,12 @@ #pragma once // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include +#include #include #include #include @@ -13,4 +14,5 @@ #include "errors.h" #include -#define MAX_IDENTIFIER_LENGTH 31 \ No newline at end of file +#define MAX_IDENTIFIER_LENGTH 31 +#define PROJECT_TOML "project.toml" \ No newline at end of file diff --git a/src/utils/errors.c b/src/utils/errors.c index 93e75e567..000e3ece7 100644 --- a/src/utils/errors.c +++ b/src/utils/errors.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "errors.h" #include diff --git a/src/utils/errors.h b/src/utils/errors.h index e6c56845c..4f793c751 100644 --- a/src/utils/errors.h +++ b/src/utils/errors.h @@ -1,8 +1,8 @@ #pragma once // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include #include diff --git a/src/utils/file_utils.c b/src/utils/file_utils.c index 2eaa4fbe5..ee26f05dd 100644 --- a/src/utils/file_utils.c +++ b/src/utils/file_utils.c @@ -1,13 +1,16 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. +#include "common.h" #include "errors.h" -#include "malloc.h" #include "lib.h" -#include -#include #include +#include +#include +#include +#include +#include const char* expand_path(const char* path) { @@ -102,3 +105,69 @@ void path_get_dir_and_filename_from_full(const char *full_path, char **filename, *dir_path = malloc(dir_len + 1); strncpy(*dir_path, dir, dir_len + 1); } + + + + +void file_find_top_dir() +{ + while (1) + { + struct stat info; + int err = stat(PROJECT_TOML, &info); + + // Error and the it's not a "missing file"? + if (err && errno != ENOENT) + { + error_exit("Can't open %s: %s.", PROJECT_TOML, strerror(errno)); + } + + // Everything worked and it's a regular file? We're done! + if (!err && S_ISREG(info.st_mode)) return; // NOLINT(hicpp-signed-bitwise) + + // Otherwise just continue upwards. + // Note that symbolically linked files are ignored. + char start_path[PATH_MAX + 1]; + getcwd(start_path, PATH_MAX); + if (chdir("..")) + { + error_exit("Can't change directory to search for %s: %s.", PROJECT_TOML, strerror(errno)); + } + char new_path[PATH_MAX + 1]; + getcwd(new_path, PATH_MAX); + if (strcmp(new_path, start_path) != 0) continue; + error_exit("The root build directory containing %s could not be found. Did you use the correct directory?", PROJECT_TOML); + } +} + +void file_add_wildcard_files(const char ***files, const char *path, bool recursive) +{ + DIR *dir = opendir(path); + if (!dir) + { + error_exit("Can't open the directory '%s'. Please check the paths. %s", path, strerror(errno)); + } + struct dirent *ent; + while ((ent = readdir(dir))) + { + if (ent->d_namlen < 4) continue; + + // Doesn't end with .c3 + if (strncmp(&ent->d_name[ent->d_namlen - 3], ".c3", 3) != 0) + { + if (ent->d_type == DT_DIR && ent->d_name[0] != '.' && recursive) + { + char *new_path = strndup(ent->d_name, ent->d_namlen); + file_add_wildcard_files(files, new_path, recursive); + free(new_path); + } + continue; + } + + char *name = malloc_arena(ent->d_namlen + 1); + memcpy(name, ent->d_name, ent->d_namlen); + name[ent->d_namlen] = '\0'; + vec_add(*files, name); + } + closedir(dir); +} \ No newline at end of file diff --git a/src/utils/file_utils.h b/src/utils/file_utils.h deleted file mode 100644 index 2674c66ab..000000000 --- a/src/utils/file_utils.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -// Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -#include "common.h" - - diff --git a/src/utils/lib.h b/src/utils/lib.h index 512731642..58e89cea9 100644 --- a/src/utils/lib.h +++ b/src/utils/lib.h @@ -1,8 +1,8 @@ #pragma once // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "common.h" @@ -10,6 +10,9 @@ const char* expand_path(const char* path); char *read_file(const char *path, size_t *return_size); int filename_to_module(const char *path, char buffer[MAX_IDENTIFIER_LENGTH + 1]); void path_get_dir_and_filename_from_full(const char *full_path, char **filename, char **dir_path); +void file_find_top_dir(); +void file_add_wildcard_files(const char ***files, const char *path, bool recursive); + void init_arena(void); void *malloc_arena(unsigned long mem); void free_arena(void); @@ -293,5 +296,9 @@ static inline bool is_all_lower(const char* string) return true; } +#ifndef __printflike +#define __printflike(x, y) +#endif + char *strformat(const char *var, ...) __printflike(1, 2); diff --git a/src/utils/malloc.c b/src/utils/malloc.c index 95cf58096..c37c38c69 100644 --- a/src/utils/malloc.c +++ b/src/utils/malloc.c @@ -1,8 +1,8 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. -#include "malloc.h" +#include "common.h" static const size_t KB = 1024ul; // Use 1MB at a time. diff --git a/src/utils/malloc.h b/src/utils/malloc.h index 78adbee03..45a718bc3 100644 --- a/src/utils/malloc.h +++ b/src/utils/malloc.h @@ -1,8 +1,8 @@ #pragma once // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include "common.h" diff --git a/src/utils/stringutils.c b/src/utils/stringutils.c index e60b6fe75..5162857e7 100644 --- a/src/utils/stringutils.c +++ b/src/utils/stringutils.c @@ -1,6 +1,6 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Use of this source code is governed by the GNU LGPLv3.0 license +// a copy of which can be found in the LICENSE file. #include #include diff --git a/src/utils/toml.c b/src/utils/toml.c new file mode 100644 index 000000000..d502239f8 --- /dev/null +++ b/src/utils/toml.c @@ -0,0 +1,2365 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include +#include +#include +#include "toml.h" + +#if (defined(_MSC_VER) && _MSC_VER < 1800) || defined(_ADI_COMPILER) +#define va_copy(a, b) ((a) = (b)) +#endif + +char *toml_strdup(TOML_CONST char *str) +{ + size_t len = strlen(str) + 1; + void *new = malloc(len); + if (new == NULL) + { + return NULL; + } + + return memcpy(new, str, len); +} + +char *toml_strndup(TOML_CONST char *str, size_t n) +{ + char *result = malloc(n + 1); + if (result == NULL) + { + return NULL; + } + + result[n] = 0; + return memcpy(result, str, n); +} + +int toml_vasprintf(char **str, TOML_CONST char *format, va_list args) +{ + int size = 0; + + va_list args_copy; + va_copy(args_copy, args); + size = vsnprintf(NULL, size, format, args_copy); + va_end(args_copy); + + if (size < 0) + { + return size; + } + + *str = malloc(size + 1); + if (*str == NULL) + { + return -1; + } + + return vsprintf(*str, format, args); +} + +int toml_asprintf(char **str, TOML_CONST char *format, ...) +{ + va_list args; + va_start(args, format); + int size = toml_vasprintf(str, format, args); + va_end(args); + return size; +} + +void toml_err_init(TomlErr *err) +{ + err->code = TOML_OK; + err->message = NULL; + err->_is_literal = TOML_FALSE; +} + +void toml_set_err_v(TomlErr *err, int code, TOML_CONST char *format, va_list args) +{ + if (err != NULL) + { + assert(err->code == TOML_OK); + err->code = code; + toml_vasprintf(&err->message, format, args); + err->_is_literal = TOML_FALSE; + } +} + +void toml_set_err(TomlErr *err, int code, TOML_CONST char *format, ...) +{ + if (err != NULL) + { + assert(err->code == TOML_OK); + va_list args; + va_start(args, format); + toml_set_err_v(err, code, format, args); + va_end(args); + } +} + +void toml_set_err_literal(TomlErr *err, int code, TOML_CONST char *message) +{ + if (err != NULL) + { + assert(err->code == TOML_OK); + err->code = code; + err->message = (char *) message; + err->_is_literal = TOML_TRUE; + } +} + +void toml_clear_err(TomlErr *err) +{ + if (err) + { + if (!err->_is_literal) + { + free(err->message); + } + toml_err_init(err); + } +} + +void toml_err_move(TomlErr *to, TomlErr *from) +{ + if (to == NULL) + { + if (!from->_is_literal) + { + free(from->message); + } + } + else + { + assert(to->code == TOML_OK); + *to = *from; + toml_err_init(from); + } +} + +#ifdef _MSC_VER +static __inline size_t toml_roundup_pow_of_two_size_t(size_t x) +#else + +static inline size_t toml_roundup_pow_of_two_size_t(size_t x) +#endif +{ + size_t v = x; + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; +#if SIZE_MAX == 0xffff + v |= v >> 8; +#elif SIZE_MAX == 0xffffffff + v |= v >> 8; + v |= v >> 16; +#elif SIZE_MAX == 0xffffffffffffffffll + v |= v >> 8; + v |= v >> 16; + v |= v >> 32; +#endif + v++; + return v; +} + +TomlString *toml_string_new(TomlErr *error) +{ + TomlString *self = malloc(sizeof(TomlString)); + if (self == NULL) + { + toml_set_err_literal(error, TOML_ERR_NOMEM, "out of memory"); + return NULL; + } + + self->str = NULL; + self->len = 0; + self->_capacity = 0; + + return self; +} + +TomlString *toml_string_new_string(TOML_CONST char *str, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + TomlString *self = toml_string_new(&err); + if (err.code != TOML_OK) goto cleanup; + + toml_string_append_string(self, str, &err); + + cleanup: + if (err.code != TOML_OK) + { + free(self); + self = NULL; + } + toml_err_move(error, &err); + return self; +} + +TomlString *toml_string_new_nstring(TOML_CONST char *str, size_t len, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + TomlString *self = toml_string_new(&err); + if (err.code != TOML_OK) goto cleanup; + + toml_string_append_nstring(self, str, len, &err); + + cleanup: + if (err.code != TOML_OK) + { + free(self); + self = NULL; + } + toml_err_move(error, &err); + return self; +} + +void toml_string_check_expand(TomlString *self, size_t len_to_add, TomlErr *error) +{ + if (self->len + len_to_add + 1 > self->_capacity) + { + size_t new_capacity = toml_roundup_pow_of_two_size_t(self->len + len_to_add + 1); + new_capacity = new_capacity >= 8 ? new_capacity : 8; + void *p = realloc(self->str, new_capacity); + if (p == NULL) + { + toml_set_err_literal(error, TOML_ERR_NOMEM, "out of memory"); + return; + } + self->str = p; + self->_capacity = new_capacity; + } +} + +void toml_string_append_char(TomlString *self, char ch, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + + toml_string_check_expand(self, 1, &err); + if (err.code != TOML_OK) goto cleanup; + + self->str[self->len] = ch; + self->str[self->len + 1] = 0; + self->len++; + + cleanup: + toml_err_move(error, &err); +} + +void toml_string_append_string(TomlString *self, TOML_CONST char *str, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + + size_t len = strlen(str); + + toml_string_check_expand(self, len, &err); + if (err.code != TOML_OK) goto cleanup; + + memcpy(&self->str[self->len], str, len + 1); + self->len += len; + + cleanup: + toml_err_move(error, &err); +} + +void toml_string_append_nstring(TomlString *self, TOML_CONST char *str, size_t len, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + + toml_string_check_expand(self, len, &err); + if (err.code != TOML_OK) goto cleanup; + + memcpy(&self->str[self->len], str, len); + self->len += len; + self->str[self->len] = 0; + + cleanup: + toml_err_move(error, &err); +} + +void toml_string_free(TomlString *self) +{ + if (self != NULL) + { + free(self->str); + free(self); + } +} + +TomlString *toml_string_copy(TOML_CONST TomlString *self, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + TomlString *str = NULL; + + str = toml_string_new_nstring(self->str, self->len, &err); + if (err.code != TOML_OK) + { + toml_err_move(error, &err); + } + + return str; +} + +int toml_string_equals(TOML_CONST TomlString *self, TOML_CONST TomlString *other) +{ + if (self == other) + { + return TOML_TRUE; + } + + if (self->len != other->len) + { + return TOML_FALSE; + } + + if (self->str == other->str) + { + return TOML_TRUE; + } + + for (size_t i = 0; i < self->len; i++) + { + if (self->str[i] != other->str[i]) + { + return TOML_FALSE; + } + } + + return TOML_TRUE; +} + +struct _TomlTable +{ + TomlKeyValue *keyvals; + size_t len; + size_t capacity; +}; + +TomlTable *toml_table_new(TomlErr *err) +{ + TomlTable *self = malloc(sizeof(TomlTable)); + if (self == NULL) + { + toml_set_err_literal(err, TOML_ERR_NOMEM, "out of memory"); + } + else + { + self->keyvals = NULL; + self->len = 0; + self->capacity = 0; + } + return self; +} + +void toml_table_free(TomlTable *self) +{ + if (self != NULL) + { + for (size_t i = 0; i < self->len; i++) + { + toml_string_free(self->keyvals[i].key); + toml_value_free(self->keyvals[i].value); + } + free(self->keyvals); + free(self); + } +} + +void toml_table_check_expand(TomlTable *self, TomlErr *err) +{ + if (self->len + 1 > self->capacity) + { + size_t new_capacity = self->capacity > 0 ? self->capacity * 2 : 8; + void *p = realloc(self->keyvals, sizeof(TomlKeyValue) * new_capacity); + if (p == NULL) + { + toml_set_err_literal(err, TOML_ERR_NOMEM, "out of memory"); + return; + } + self->keyvals = p; + self->capacity = new_capacity; + } +} + +void toml_table_set_by_string(TomlTable *self, TomlString *key, + TomlValue *value, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + + TomlValue **slot = NULL; + for (size_t i = 0; i < self->len; i++) + { + if (toml_string_equals(self->keyvals[i].key, key)) + { + slot = &self->keyvals[i].value; + } + } + + if (slot == NULL) + { + toml_table_check_expand(self, &err); + if (err.code != TOML_OK) + { + toml_err_move(error, &err); + return; + } + + self->keyvals[self->len].key = key; + self->keyvals[self->len].value = value; + self->len++; + } + else + { + *slot = value; + } +} + +TomlValue *toml_table_get_by_string(TOML_CONST TomlTable *self, TOML_CONST TomlString *key) +{ + TomlValue *value = NULL; + for (size_t i = 0; i < self->len; i++) + { + if (toml_string_equals(self->keyvals[i].key, key)) + { + value = self->keyvals[i].value; + } + } + return value; +} + +TomlValue *toml_table_getn(TOML_CONST TomlTable *self, TOML_CONST char *key, size_t key_len) +{ + TomlString str = { (char *) key, key_len, 0 }; + return toml_table_get_by_string(self, &str); +} + +TomlValue *toml_table_get(TOML_CONST TomlTable *self, TOML_CONST char *key) +{ + return toml_table_getn(self, key, strlen(key)); +} + +void toml_table_setn(TomlTable *self, TOML_CONST char *key, size_t key_len, + TomlValue *value, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + TomlString *str = NULL; + + str = toml_string_new_nstring(key, key_len, &err); + if (err.code != TOML_OK) goto error; + + toml_table_set_by_string(self, str, value, &err); + if (err.code != TOML_OK) goto error; + + goto cleanup; + + error: + toml_string_free(str); + + cleanup: + toml_err_move(error, &err); +} + +void toml_table_set(TomlTable *self, TOML_CONST char *key, TomlValue *value, TomlErr *error) +{ + toml_table_setn(self, key, strlen(key), value, error); +} + +struct _TomlTableIter +{ + TomlTable *table; + TomlKeyValue *keyval; +}; + +TomlTableIter *toml_table_iter_new(TomlTable *table, TomlErr *error) +{ + TomlTableIter *self = malloc(sizeof(TomlTableIter)); + if (self == NULL) + { + toml_set_err_literal(error, TOML_ERR_NOMEM, "out of memory"); + return NULL; + } + self->table = table; + self->keyval = table->keyvals; + return self; +} + +void toml_table_iter_free(TomlTableIter *self) +{ + free(self); +} + +TomlKeyValue *toml_table_iter_get(TomlTableIter *self) +{ + return self->keyval; +} + +int toml_table_iter_has_next(TomlTableIter *self) +{ + return self->keyval != NULL; +} + +void toml_table_iter_next(TomlTableIter *self) +{ + if (self->keyval < self->table->keyvals + self->table->len) + { + self->keyval++; + } + + if (self->keyval >= self->table->keyvals + self->table->len) + { + self->keyval = NULL; + } +} + +TomlArray *toml_array_new(TomlErr *error) +{ + TomlArray *self = malloc(sizeof(TomlArray)); + if (self == NULL) + { + toml_set_err_literal(error, TOML_ERR_NOMEM, "out of memory"); + return NULL; + } + + self->elements = NULL; + self->len = 0; + self->_capacity = 0; + return self; +} + +void toml_array_free(TomlArray *self) +{ + if (self != NULL) + { + for (size_t i = 0; i < self->len; i++) + { + toml_value_free(self->elements[i]); + } + free(self->elements); + free(self); + } +} + +void toml_array_check_expand(TomlArray *self, TomlErr *error) +{ + if (self->len + 1 > self->_capacity) + { + size_t new_capacity = self->_capacity > 0 ? self->_capacity * 2 : 8; + void *p = realloc(self->elements, sizeof(TomlValue *) * new_capacity); + if (p == NULL) + { + toml_set_err_literal(error, TOML_ERR_NOMEM, "out of memory"); + return; + } + self->elements = p; + self->_capacity = new_capacity; + } +} + +void toml_array_append(TomlArray *self, TomlValue *value, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + + toml_array_check_expand(self, &err); + if (err.code != TOML_OK) goto cleanup; + + self->elements[self->len++] = value; + + cleanup: + toml_err_move(error, &err); +} + +TomlValue *toml_value_new(TomlType type, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + + TomlValue *self = malloc(sizeof(TomlValue)); + if (self == NULL) + { + toml_set_err_literal(&err, TOML_ERR_NOMEM, "out of memory"); + goto cleanup; + } + + self->type = type; + switch (type) + { + case TOML_TABLE: + self->value.table = NULL; + break; + case TOML_ARRAY: + self->value.array = NULL; + break; + case TOML_STRING: + self->value.string = NULL; + break; + case TOML_INTEGER: + self->value.integer = 0; + break; + case TOML_FLOAT: + self->value.float_ = 0.0; + break; + case TOML_BOOLEAN: + self->value.boolean = TOML_FALSE; + break; + case TOML_DATETIME: + self->value.datetime = calloc(1, sizeof(TomlDateTime)); + if (self->value.datetime == NULL) + { + toml_set_err(error, TOML_ERR_NOMEM, "out of memory"); + } + break; + } + + cleanup: + toml_err_move(error, &err); + return self; +} + +TomlValue *toml_value_new_string(TOML_CONST char *str, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + + TomlValue *self = malloc(sizeof(TomlValue)); + if (self == NULL) + { + toml_set_err_literal(&err, TOML_ERR_NOMEM, "out of memory"); + goto cleanup; + } + + self->value.string = toml_string_new_string(str, &err); + if (err.code != TOML_OK) + { + free(self); + self = NULL; + goto cleanup; + } + + self->type = TOML_STRING; + + cleanup: + toml_err_move(error, &err); + return self; +} + +TomlValue *toml_value_new_nstring(TOML_CONST char *str, size_t len, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + + TomlValue *self = malloc(sizeof(TomlValue)); + if (self == NULL) + { + toml_set_err_literal(&err, TOML_ERR_NOMEM, "out of memory"); + goto cleanup; + } + + self->value.string = toml_string_new_nstring(str, len, &err); + if (err.code != TOML_OK) + { + free(self); + self = NULL; + goto cleanup; + } + + self->type = TOML_STRING; + + cleanup: + toml_err_move(error, &err); + return self; +} + +TomlValue *toml_value_new_table(TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + + TomlValue *self = malloc(sizeof(TomlValue)); + if (self == NULL) + { + toml_set_err_literal(&err, TOML_ERR_NOMEM, "out of memory"); + goto cleanup; + } + + self->value.table = toml_table_new(&err); + if (err.code != TOML_OK) + { + free(self); + self = NULL; + goto cleanup; + } + + self->type = TOML_TABLE; + + cleanup: + toml_err_move(error, &err); + return self; +} + +TomlValue *toml_value_new_array(TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + + TomlValue *self = malloc(sizeof(TomlValue)); + if (self == NULL) + { + toml_set_err_literal(&err, TOML_ERR_NOMEM, "out of memory"); + goto cleanup; + } + + self->value.array = toml_array_new(&err); + if (err.code != TOML_OK) + { + free(self); + self = NULL; + goto cleanup; + } + + self->type = TOML_ARRAY; + + cleanup: + toml_err_move(error, &err); + return self; +} + +#if defined(_MSC_VER) +TomlValue *toml_value_new_integer(long long integer, TomlErr *error) +#else + +TomlValue *toml_value_new_integer(long integer, TomlErr *error) +#endif +{ + TomlErr err = TOML_ERR_INIT; + + TomlValue *self = malloc(sizeof(TomlValue)); + if (self == NULL) + { + toml_set_err_literal(&err, TOML_ERR_NOMEM, "out of memory"); + goto cleanup; + } + + self->value.integer = integer; + self->type = TOML_INTEGER; + + cleanup: + toml_err_move(error, &err); + return self; +} + +TomlValue *toml_value_new_float(double float_, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + + TomlValue *self = malloc(sizeof(TomlValue)); + if (self == NULL) + { + toml_set_err_literal(&err, TOML_ERR_NOMEM, "out of memory"); + goto cleanup; + } + + self->value.float_ = float_; + self->type = TOML_FLOAT; + + cleanup: + toml_err_move(error, &err); + return self; +} + +TomlValue *toml_value_new_datetime(TomlErr *error) +{ + return toml_value_new(TOML_DATETIME, error); +} + +TomlValue *toml_value_new_boolean(int boolean, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + + TomlValue *self = malloc(sizeof(TomlValue)); + if (self == NULL) + { + toml_set_err_literal(&err, TOML_ERR_NOMEM, "out of memory"); + goto cleanup; + } + + self->value.boolean = boolean; + self->type = TOML_BOOLEAN; + + cleanup: + toml_err_move(error, &err); + return self; +} + +void toml_value_free(TomlValue *self) +{ + if (self != NULL) + { + switch (self->type) + { + case TOML_STRING: + toml_string_free(self->value.string); + break; + case TOML_TABLE: + toml_table_free(self->value.table); + break; + case TOML_ARRAY: + toml_array_free(self->value.array); + break; + case TOML_DATETIME: + free(self->value.datetime); + break; + default: + break; + } + free(self); + } +} + +typedef struct _TomlParser +{ + TOML_CONST char *begin; + TOML_CONST char *end; + TOML_CONST char *ptr; + int lineno; + int colno; + char *filename; +} TomlParser; + +TomlParser *toml_parser_new(TOML_CONST char *str, size_t len, TomlErr *error) +{ + TomlParser *self = malloc(sizeof(TomlParser)); + if (self == NULL) + { + toml_set_err_literal(error, TOML_OK, "out of memory"); + return NULL; + } + + self->begin = str; + self->end = str + len; + self->ptr = str; + self->lineno = 1; + self->colno = 1; + self->filename = NULL; + + return self; +} + +void toml_parser_free(TomlParser *self) +{ + if (self != NULL) + { + free(self->filename); + free(self); + } +} + +void toml_move_next(TomlParser *self) +{ + if (self->ptr < self->end) + { + if (*self->ptr == '\n') + { + self->lineno++; + self->colno = 1; + } + else + { + self->colno++; + } + self->ptr++; + } +} + +void toml_next_n(TomlParser *self, int n) +{ + for (int i = 0; i < n; i++) + { + toml_move_next(self); + } +} + +TomlString *toml_parse_bare_key(TomlParser *self, TomlErr *error) +{ + TOML_CONST char *str = self->ptr; + size_t len = 0; + + while (self->ptr < self->end) + { + char ch = *self->ptr; + + if (!(isalnum(ch) || ch == '_' || ch == '-')) + { + break; + } + + len++; + toml_move_next(self); + } + + return toml_string_new_nstring(str, len, error); +} + +char toml_hex_char_to_int(char ch) +{ + assert(isxdigit(ch)); + if (isdigit(ch)) + { + return ch - '0'; + } + else if (islower(ch)) + { + return ch - 'a' + 10; + } + else if (isupper(ch)) + { + return ch - 'A' + 10; + } + return 0; +} + +void toml_encode_unicode_scalar(TomlString *result, TomlParser *parser, int n, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + unsigned int scalar = 0; + + if (parser->ptr + n > parser->end) + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: invalid unicode scalar", + parser->filename, parser->lineno, parser->colno); + goto cleanup; + } + + for (int i = 0; i < n; i++) + { + char ch = *parser->ptr; + if (isxdigit(ch)) + { + scalar = scalar * 16 + toml_hex_char_to_int(ch); + toml_move_next(parser); + } + else + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: invalid unicode scalar", + parser->filename, parser->lineno, parser->colno); + goto cleanup; + } + } + + if ((scalar >= 0xd800 && scalar <= 0xdfff) || + (scalar >= 0xfffe && scalar <= 0xffff)) + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: invalid unicode scalar", + parser->filename, parser->lineno, parser->colno); + goto cleanup; + } + + if (scalar <= 0x7f) + { + toml_string_append_char(result, (char) scalar, &err); + goto cleanup; + } + + if (scalar <= 0x7ff) + { + toml_string_append_char(result, 0xc0 | (scalar >> 6), &err); + if (err.code != TOML_OK) goto cleanup; + toml_string_append_char(result, 0x80 | (scalar & 0x3f), &err); + goto cleanup; + } + + if (scalar <= 0xffff) + { + toml_string_append_char(result, 0xe0 | (scalar >> 12), &err); + if (err.code != TOML_OK) goto cleanup; + toml_string_append_char(result, 0x80 | ((scalar >> 6) & 0x3f), &err); + if (err.code != TOML_OK) goto cleanup; + toml_string_append_char(result, 0x80 | (scalar & 0x3f), &err); + goto cleanup; + } + + if (scalar <= 0x1fffff) + { + toml_string_append_char(result, 0xf0 | (scalar >> 18), &err); + if (err.code != TOML_OK) goto cleanup; + toml_string_append_char(result, 0x80 | ((scalar >> 12) & 0x3f), &err); + if (err.code != TOML_OK) goto cleanup; + toml_string_append_char(result, 0x80 | ((scalar >> 6) & 0x3f), &err); + if (err.code != TOML_OK) goto cleanup; + toml_string_append_char(result, 0x80 | (scalar & 0x3f), &err); + goto cleanup; + } + + if (scalar <= 0x3ffffff) + { + toml_string_append_char(result, 0xf8 | (scalar >> 24), &err); + if (err.code != TOML_OK) goto cleanup; + toml_string_append_char(result, 0x80 | ((scalar >> 18) & 0x3f), &err); + if (err.code != TOML_OK) goto cleanup; + toml_string_append_char(result, 0x80 | ((scalar >> 12) & 0x3f), &err); + if (err.code != TOML_OK) goto cleanup; + toml_string_append_char(result, 0x80 | ((scalar >> 6) & 0x3f), &err); + if (err.code != TOML_OK) goto cleanup; + toml_string_append_char(result, 0x80 | (scalar & 0x3f), &err); + goto cleanup; + } + + if (scalar <= 0x7fffffff) + { + toml_string_append_char(result, 0xfc | (scalar >> 30), &err); + if (err.code != TOML_OK) goto cleanup; + toml_string_append_char(result, 0x80 | ((scalar >> 24) & 0x3f), &err); + if (err.code != TOML_OK) goto cleanup; + toml_string_append_char(result, 0x80 | ((scalar >> 18) & 0x3f), &err); + if (err.code != TOML_OK) goto cleanup; + toml_string_append_char(result, 0x80 | ((scalar >> 12) & 0x3f), &err); + if (err.code != TOML_OK) goto cleanup; + toml_string_append_char(result, 0x80 | ((scalar >> 6) & 0x3f), &err); + if (err.code != TOML_OK) goto cleanup; + toml_string_append_char(result, 0x80 | (scalar & 0x3f), &err); + goto cleanup; + } + + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: invalid unicode scalar", + parser->filename, parser->lineno, parser->colno); + + cleanup: + toml_err_move(error, &err); +} + +TomlString *toml_parse_basic_string(TomlParser *self, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + + TomlString *result = toml_string_new(&err); + if (err.code != TOML_OK) goto cleanup; + + while (self->ptr < self->end && *self->ptr != '\"' && *self->ptr != '\n') + { + char ch1 = *self->ptr; + if (ch1 == '\\') + { + if (self->ptr >= self->end) break; + + toml_move_next(self); + char ch2 = *self->ptr; + + if (ch2 == '\"') + { + toml_string_append_char(result, '\"', &err); + toml_move_next(self); + } + else if (ch2 == 'b') + { + toml_string_append_char(result, '\b', &err); + toml_move_next(self); + } + else if (ch2 == 't') + { + toml_string_append_char(result, '\t', &err); + toml_move_next(self); + } + else if (ch2 == 'n') + { + toml_string_append_char(result, '\n', &err); + toml_move_next(self); + } + else if (ch2 == 'f') + { + toml_string_append_char(result, '\f', &err); + toml_move_next(self); + } + else if (ch2 == 'r') + { + toml_string_append_char(result, '\r', &err); + toml_move_next(self); + } + else if (ch2 == '"') + { + toml_string_append_char(result, '\"', &err); + toml_move_next(self); + } + else if (ch2 == '\\') + { + toml_string_append_char(result, '\\', &err); + toml_move_next(self); + } + else if (ch2 == 'u') + { + toml_move_next(self); + toml_encode_unicode_scalar(result, self, 4, &err); + } + else if (ch2 == 'U') + { + toml_move_next(self); + toml_encode_unicode_scalar(result, self, 8, &err); + } + else + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: invalid escape charactor"); + goto cleanup; + } + } + else + { + toml_string_append_char(result, ch1, &err); + toml_move_next(self); + } + if (err.code != TOML_OK) goto cleanup; + } + + if (self->ptr >= self->end || *self->ptr != '\"' || *self->ptr == '\n') + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: unterminated basic string", + self->filename, self->lineno, self->colno); + goto cleanup; + } + + toml_move_next(self); + + cleanup: + toml_err_move(error, &err); + return result; +} + +TomlString *toml_parse_literal_string(TomlParser *self, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + + TomlString *result = toml_string_new(&err); + if (err.code != TOML_OK) goto cleanup; + + while (self->ptr < self->end && *self->ptr != '\'' && *self->ptr != '\n') + { + toml_string_append_char(result, *self->ptr, &err); + if (err.code != TOML_OK) goto cleanup; + toml_move_next(self); + } + + if (self->ptr >= self->end || *self->ptr != '\'' || *self->ptr == '\n') + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: unterminated literal string", + self->filename, self->lineno, self->colno); + } + + toml_move_next(self); + + cleanup: + toml_err_move(error, &err); + return result; +} + +TomlValue *toml_parse_basic_string_value(TomlParser *self, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + + TomlValue *value = NULL; + + TomlString *string = toml_parse_basic_string(self, &err); + if (err.code != TOML_OK) goto cleanup; + + value = toml_value_new(TOML_STRING, &err); + if (err.code != TOML_OK) goto cleanup; + + value->value.string = string; + + cleanup: + toml_err_move(error, &err); + return value; +} + +TomlValue *toml_parse_literal_string_value(TomlParser *self, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + + TomlValue *value = NULL; + + TomlString *string = toml_parse_literal_string(self, &err); + if (err.code != TOML_OK) goto cleanup; + + value = toml_value_new(TOML_STRING, &err); + if (err.code != TOML_OK) goto cleanup; + + value->value.string = string; + + cleanup: + toml_err_move(error, &err); + return value; +} + +TomlValue *toml_parse_multi_line_basic_string(TomlParser *self, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + TomlValue *value = NULL; + + TomlString *result = toml_string_new(&err); + if (err.code != TOML_OK) goto cleanup; + + if (*self->ptr == '\n') + { + toml_move_next(self); + } + + while (self->ptr + 3 <= self->end && strncmp(self->ptr, "\"\"\"", 3) != 0) + { + char ch1 = *self->ptr; + + if (ch1 == '\\') + { + if (self->ptr + 3 > self->end || strncmp(self->ptr, "\"\"\"", 3) == 0) + { + break; + } + + toml_move_next(self); + char ch2 = *self->ptr; + + if (ch2 == '\"') + { + toml_string_append_char(result, '\"', &err); + toml_move_next(self); + } + else if (ch2 == 'b') + { + toml_string_append_char(result, '\b', &err); + toml_move_next(self); + } + else if (ch2 == 't') + { + toml_string_append_char(result, '\t', &err); + toml_move_next(self); + } + else if (ch2 == 'n') + { + toml_string_append_char(result, '\n', &err); + toml_move_next(self); + } + else if (ch2 == 'f') + { + toml_string_append_char(result, '\f', &err); + toml_move_next(self); + } + else if (ch2 == 'r') + { + toml_string_append_char(result, '\r', &err); + toml_move_next(self); + } + else if (ch2 == '"') + { + toml_string_append_char(result, '\"', &err); + toml_move_next(self); + } + else if (ch2 == '\\') + { + toml_string_append_char(result, '\\', &err); + toml_move_next(self); + } + else if (ch2 == 'u') + { + toml_move_next(self); + toml_encode_unicode_scalar(result, self, 4, &err); + } + else if (ch2 == 'U') + { + toml_move_next(self); + toml_encode_unicode_scalar(result, self, 8, &err); + } + else if (ch2 == '\n') + { + do + { + toml_move_next(self); + } while (self->ptr + 3 <= self->end && isspace(*self->ptr)); + } + else + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: invalid escape charactor", + self->filename, self->lineno, self->colno); + goto cleanup; + } + } + else + { + toml_string_append_char(result, ch1, &err); + toml_move_next(self); + } + } + if (err.code != TOML_OK) goto cleanup; + + if (self->ptr + 3 > self->end || strncmp(self->ptr, "\"\"\"", 3) != 0) + { + toml_set_err(&err, TOML_ERR_SYNTAX, + "%s:%d:%d: unterminated multi-line basic string", + self->filename, self->lineno, self->colno); + goto cleanup; + } + + toml_next_n(self, 3); + + value = toml_value_new(TOML_STRING, &err); + if (err.code != TOML_OK) goto cleanup; + + value->value.string = result; + + cleanup: + toml_err_move(error, &err); + return value; +} + +TomlValue *toml_parse_multi_line_literal_string(TomlParser *self, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + TomlValue *value = NULL; + + TomlString *result = toml_string_new(&err); + if (err.code != TOML_OK) goto cleanup; + + if (*self->ptr == '\n') + { + toml_move_next(self); + } + + while (self->ptr + 3 <= self->end && strncmp(self->ptr, "\'\'\'", 3) != 0) + { + toml_string_append_char(result, *self->ptr, &err); + if (err.code != TOML_OK) goto cleanup; + toml_move_next(self); + } + + if (self->ptr + 3 > self->end || strncmp(self->ptr, "\'\'\'", 3) != 0) + { + toml_set_err(&err, TOML_ERR_SYNTAX, + "%s:%d:%d: unterminated multi-line literal string", + self->filename, self->lineno, self->colno); + goto cleanup; + } + + toml_next_n(self, 3); + + value = toml_value_new(TOML_STRING, &err); + if (err.code != TOML_OK) goto cleanup; + + value->value.string = result; + + cleanup: + toml_err_move(error, &err); + return value; +} + +TomlValue *toml_parse_datetime(TOML_CONST char *str, size_t len, TomlErr *error) +{ + (void) str; + (void) len; + return toml_value_new(TOML_DATETIME, error); +} + +TomlValue *toml_parse_int_or_float_or_time(TomlParser *self, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + TomlString *str = NULL; + TomlValue *result = NULL; + + char type = 'i'; + int base = 10; + + str = toml_string_new(&err); + if (err.code != TOML_OK) goto cleanup; + + // Determine nan and inf type as float, as we cannot determine by dot. + // But do not strip it because we will append it to the string later + if (self->ptr + 3 <= self->end && + (strncmp(self->ptr, "nan", 3) == 0 || strncmp(self->ptr, "inf", 3) == 0)) + { + type = 'f'; + } + + if (self->ptr + 4 <= self->end && + (strncmp(self->ptr, "+nan", 4) == 0 || + strncmp(self->ptr, "-nan", 4) == 0 || + strncmp(self->ptr, "+inf", 4) == 0 || + strncmp(self->ptr, "-inf", 4) == 0)) + { + type = 'f'; + } + + // If there is a base prefix, set the base and strip the prefix, + // because strtoll() do not recognize 0o and 0b + if (self->ptr + 2 <= self->end) + { + if (strncmp(self->ptr, "0x", 2) == 0) + { + base = 16; + toml_next_n(self, 2); + } + else if (strncmp(self->ptr, "0o", 2) == 0) + { + base = 8; + toml_next_n(self, 2); + } + else if (strncmp(self->ptr, "0b", 2) == 0) + { + base = 2; + toml_next_n(self, 2); + } + } + + char last_char = 0; + int has_exp = TOML_FALSE; + while (self->ptr < self->end) + { + if (*self->ptr == '+' || *self->ptr == '-') + { + if (last_char == 0 || ((last_char == 'e' || last_char == 'E') && !has_exp)) + { + if (last_char != 0) + { + type = 'f'; + has_exp = TOML_TRUE; + } + toml_string_append_char(str, *self->ptr, &err); + if (err.code != TOML_OK) goto cleanup; + } + else + { + break; + } + } + else if (isalnum(*self->ptr)) + { + if ((*self->ptr == 'e' || *self->ptr == 'E') && base == 10) + { + type = 'f'; + } + + toml_string_append_char(str, *self->ptr, &err); + if (err.code != TOML_OK) goto cleanup; + } + else if (*self->ptr == '.') + { + if (type == 'i') + { + type = 'f'; + toml_string_append_char(str, *self->ptr, &err); + if (err.code != TOML_OK) goto cleanup; + } + else + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: invalid float", + self->filename, self->lineno, self->colno); + goto cleanup; + } + } + else if (*self->ptr == '_') + { + if (type == 't') + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: invalid datetime", + self->filename, self->lineno, self->colno); + goto cleanup; + } + + if (!isalnum(last_char)) + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: invalid integer or float", + self->filename, self->lineno, self->colno); + goto cleanup; + } + } + else if (*self->ptr == '-') + { + type = 't'; + toml_string_append_char(str, *self->ptr, &err); + } + else + { + break; + } + + last_char = *self->ptr; + toml_move_next(self); + } + + if (last_char == '_') + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: invalid integer or float or datetime", + self->filename, self->lineno, self->colno); + goto cleanup; + } + + if (type == 'i') + { + char *end = NULL; + long long n = strtoll(str->str, &end, base); + if (end < str->str + str->len) + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: invalid integer", + self->filename, self->lineno, self->colno); + goto cleanup; + } + result = toml_value_new_integer(n, &err); + if (err.code != TOML_OK) goto cleanup; + } + else if (type == 'f') + { + char *end = NULL; + double n = strtod(str->str, &end); + if (end < str->str + str->len) + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: invalid float"); + goto cleanup; + } + result = toml_value_new_float(n, &err); + if (err.code != TOML_OK) goto cleanup; + } + else if (type == 't') + { + result = toml_parse_datetime(str->str, str->len, &err); + if (err.code != TOML_OK) goto cleanup; + } + + cleanup: + toml_string_free(str); + toml_err_move(error, &err); + return result; +} + +TomlValue *toml_parse_bool(TomlParser *self, TomlErr *error) +{ + if (self->ptr + 4 <= self->end && strncmp(self->ptr, "true", 4) == 0 && + (self->ptr + 4 == self->end || isspace(*(self->ptr + 4)))) + { + toml_next_n(self, 4); + return toml_value_new_boolean(TOML_TRUE, error); + } + + if (self->ptr + 5 <= self->end && strncmp(self->ptr, "false", 5) == 0 && + (self->ptr + 5 == self->end || isspace(*(self->ptr + 5)))) + { + toml_next_n(self, 5); + return toml_value_new_boolean(TOML_FALSE, error); + } + + return NULL; +} + +TomlValue *toml_parse_array(TomlParser *self, TomlErr *error); + +TomlValue *toml_parse_inline_table(TomlParser *self, TomlErr *error); + +TomlValue *toml_parse_value(TomlParser *self, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + TomlValue *value = NULL; + + char ch = *self->ptr; + + if (strncmp(self->ptr, "\"\"\"", 3) == 0) + { + toml_next_n(self, 3); + value = toml_parse_multi_line_basic_string(self, &err); + } + else if (strncmp(self->ptr, "\'\'\'", 3) == 0) + { + toml_next_n(self, 3); + value = toml_parse_multi_line_literal_string(self, &err); + } + else if (ch == '\"') + { + toml_move_next(self); + value = toml_parse_basic_string_value(self, &err); + } + else if (ch == '\'') + { + toml_move_next(self); + value = toml_parse_literal_string_value(self, &err); + } + else if (isdigit(ch) || ch == '+' || ch == '-' || ch == '.' || ch == 'n' || ch == 'i') + { + value = toml_parse_int_or_float_or_time(self, &err); + } + else if (ch == 't' || ch == 'f') + { + value = toml_parse_bool(self, &err); + } + else if (ch == '[') + { + toml_move_next(self); + value = toml_parse_array(self, &err); + } + else if (ch == '{') + { + toml_move_next(self); + value = toml_parse_inline_table(self, &err); + } + else + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: unexpected token", + self->filename, self->lineno, self->colno); + } + + toml_err_move(error, &err); + return value; +} + +void toml_parse_key_value(TomlParser *self, TomlTable *table, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + TomlString *key = NULL; + TomlValue *value = NULL; + + while (self->ptr < self->end) + { + char ch; + + ch = *self->ptr; + while (self->ptr < self->end && isspace(ch)) + { + toml_move_next(self); + ch = *self->ptr; + } + + if (self->ptr == self->end) break; + + if (isalnum(ch) || ch == '_') + { + key = toml_parse_bare_key(self, &err); + } + else if (ch == '\"') + { + toml_move_next(self); + key = toml_parse_basic_string(self, &err); + } + else if (ch == '\'') + { + toml_move_next(self); + key = toml_parse_literal_string(self, &err); + } + else if (ch == '[') + { + break; + } + else if (ch == '#') + { + do + { + toml_move_next(self); + ch = *self->ptr; + } while (self->ptr < self->end && ch != '\n'); + toml_move_next(self); + continue; + } + else + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: unexpected token", + self->filename, self->lineno, self->colno); + goto error; + } + + ch = *self->ptr; + while (self->ptr < self->end && (ch == ' ' || ch == '\t')) + { + toml_move_next(self); + ch = *self->ptr; + } + + if (self->ptr == self->end) + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: unterminated key value pair"); + goto error; + } + + if (ch != '=') + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: unexpected token", + self->filename, self->lineno, self->colno); + goto error; + } + + toml_move_next(self); + + ch = *self->ptr; + while (self->ptr < self->end && (ch == ' ' || ch == '\t')) + { + toml_move_next(self); + ch = *self->ptr; + } + + if (self->ptr == self->end) + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: unterminated key value pair"); + goto error; + } + + value = toml_parse_value(self, &err); + if (err.code != TOML_OK) goto error; + + toml_table_set_by_string(table, key, value, &err); + if (err.code != TOML_OK) goto error; + + key = NULL; + value = NULL; + + while (self->ptr < self->end && (*self->ptr == ' ' || *self->ptr == '\t')) + { + toml_move_next(self); + } + + if (self->ptr == self->end) + { + break; + } + + if (*self->ptr == '#') + { + do + { + toml_move_next(self); + } while (self->ptr < self->end && *self->ptr != '\n'); + } + + if (*self->ptr == '\r') + { + toml_move_next(self); + } + + if (*self->ptr == '\n') + { + toml_move_next(self); + } + else + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: new line expected", + self->filename, self->lineno, self->colno); + goto error; + } + } + + goto cleanup; + + error: + toml_value_free(value); + toml_string_free(key); + + cleanup: + toml_err_move(error, &err); +} + +TomlValue *toml_parse_array(TomlParser *self, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + TomlValue *array = NULL; + TomlValue *value = NULL; + + array = toml_value_new_array(&err); + if (err.code != TOML_OK) goto cleanup; + + while (self->ptr < self->end) + { + if (isspace(*self->ptr)) + { + while (self->ptr < self->end && isspace(*self->ptr)) + { + toml_move_next(self); + } + } + else if (*self->ptr == '#') + { + do + { + toml_move_next(self); + } while (self->ptr < self->end && *self->ptr != '\n'); + toml_move_next(self); + } + else if (*self->ptr == '\n') + { + toml_move_next(self); + } + else if (*self->ptr == ']') + { + toml_move_next(self); + break; + } + else + { + value = toml_parse_value(self, &err); + if (err.code != TOML_OK) goto error; + + toml_array_append(array->value.array, value, &err); + if (err.code != TOML_OK) goto error; + + value = NULL; + + while (self->ptr < self->end) + { + if (isspace(*self->ptr)) + { + do + { + toml_move_next(self); + } while (self->ptr < self->end && isspace(*self->ptr)); + } + else if (*self->ptr == '#') + { + do + { + toml_move_next(self); + } while (self->ptr < self->end && *self->ptr != '\n'); + } + else + { + break; + } + } + + if (self->ptr < self->end && *self->ptr == ',') + { + toml_move_next(self); + } + } + } + + goto cleanup; + + error: + toml_value_free(value); + + cleanup: + toml_err_move(error, &err); + return array; +} + +TomlValue *toml_parse_inline_table(TomlParser *self, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + TomlValue *table = NULL; + + table = toml_value_new_table(&err); + if (err.code != TOML_OK) goto cleanup; + + while (self->ptr < self->end) + { + TomlString *key = NULL; + TomlValue *value = NULL; + char ch; + + ch = *self->ptr; + while (self->ptr < self->end && (ch == ' ' || ch == '\t')) + { + toml_move_next(self); + ch = *self->ptr; + } + + if (isalnum(ch) || ch == '_') + { + key = toml_parse_bare_key(self, &err); + } + else if (ch == '\"') + { + toml_move_next(self); + key = toml_parse_basic_string(self, &err); + } + else if (ch == '\'') + { + toml_move_next(self); + key = toml_parse_literal_string(self, &err); + } + else if (ch == '}') + { + break; + } + else + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: unexpected token", + self->filename, self->lineno, self->colno); + goto cleanup; + } + + ch = *self->ptr; + while (self->ptr < self->end && (ch == ' ' || ch == '\t')) + { + toml_move_next(self); + ch = *self->ptr; + } + + if (self->ptr == self->end) + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: unterminated key value pair"); + goto cleanup; + } + + if (ch != '=') + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: unexpected token", + self->filename, self->lineno, self->colno); + goto cleanup; + } + + toml_move_next(self); + + ch = *self->ptr; + while (self->ptr < self->end && (ch == ' ' || ch == '\t')) + { + toml_move_next(self); + ch = *self->ptr; + } + + if (self->ptr == self->end) + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: unterminated key value pair"); + goto cleanup; + } + + value = toml_parse_value(self, &err); + if (err.code != TOML_OK) goto cleanup; + + toml_table_set_by_string(table->value.table, key, value, &err); + if (err.code != TOML_OK) goto cleanup; + + while (self->ptr < self->end && (*self->ptr == ' ' || *self->ptr == '\t')) + { + toml_move_next(self); + } + + if (*self->ptr == ',') + { + toml_move_next(self); + } + else if (*self->ptr == '}') + { + toml_move_next(self); + break; + } + } + + cleanup: + toml_err_move(error, &err); + return table; +} + +TomlTable *toml_walk_table_path(TomlParser *parser, TomlTable *table, + TomlArray *key_path, int is_array, + int create_if_not_exist, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + TomlTable *real_table = table; + TomlValue *new_table = NULL; + TomlValue *array = NULL; + TomlString *part_copy = NULL; + + if (is_array) + { + size_t i = 0; + for (; i < key_path->len - 1; i++) + { + TomlString *part = key_path->elements[i]->value.string; + TomlValue *t = toml_table_get_by_string(real_table, part); + if (t == NULL) + { + if (create_if_not_exist) + { + new_table = toml_value_new_table(&err); + if (err.code != TOML_OK) goto error; + + part_copy = toml_string_copy(part, &err); + if (err.code != TOML_OK) goto error; + + toml_table_set_by_string(real_table, part_copy, new_table, &err); + if (err.code != TOML_OK) goto error; + + real_table = new_table->value.table; + + part_copy = NULL; + new_table = NULL; + } + else + { + real_table = NULL; + break; + } + } + else + { + real_table = t->value.table; + } + } + + TomlString *part = key_path->elements[i]->value.string; + TomlValue *t = toml_table_get_by_string(real_table, part); + if (t == NULL) + { + if (create_if_not_exist) + { + array = toml_value_new_array(&err); + if (err.code != TOML_OK) goto error; + + new_table = toml_value_new_table(&err); + if (err.code != TOML_OK) goto error; + + toml_array_append(array->value.array, new_table, &err); + if (err.code != TOML_OK) goto error; + + part_copy = toml_string_copy(part, &err); + if (err.code != TOML_OK) goto error; + + toml_table_set_by_string(real_table, part_copy, array, &err); + if (err.code != TOML_OK) goto error; + + real_table = new_table->value.table; + + part_copy = NULL; + array = NULL; + new_table = NULL; + } + else + { + real_table = NULL; + } + } + else + { + if (t->type != TOML_ARRAY) + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: this key was not an array", + parser->filename, parser->lineno, parser->colno); + goto error; + } + + TomlValue *new_table = toml_value_new_table(&err); + if (err.code != TOML_OK) goto error; + + toml_array_append(t->value.array, new_table, &err); + if (err.code != TOML_OK) goto error; + + real_table = new_table->value.table; + } + } + else + { + for (size_t i = 0; i < key_path->len; i++) + { + TomlString *part = key_path->elements[i]->value.string; + TomlValue *t = toml_table_get_by_string(real_table, part); + if (t == NULL) + { + if (create_if_not_exist) + { + new_table = toml_value_new_table(&err); + if (err.code != TOML_OK) goto error; + + part_copy = toml_string_copy(part, &err); + if (err.code != TOML_OK) goto error; + + toml_table_set_by_string(real_table, part_copy, new_table, &err); + if (err.code != TOML_OK) goto error; + + real_table = new_table->value.table; + + part_copy = NULL; + new_table = NULL; + } + else + { + real_table = NULL; + break; + } + } + else + { + if (t->type == TOML_ARRAY) + { + real_table = t->value.array->elements[t->value.array->len - 1]->value.table; + } + else if (t->type == TOML_TABLE) + { + real_table = t->value.table; + } + } + } + } + + goto cleanup; + + error: + toml_string_free(part_copy); + toml_value_free(new_table); + toml_value_free(array); + + cleanup: + toml_err_move(error, &err); + return real_table; +} + +void toml_parse_table(TomlParser *self, TomlTable *table, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + TomlArray *key_path = NULL; + int is_array = TOML_FALSE; + TomlTable *real_table = table; + + key_path = toml_array_new(&err); + if (err.code != TOML_OK) goto cleanup; + + if (self->ptr < self->end && *self->ptr == '[') + { + is_array = TOML_TRUE; + toml_move_next(self); + } + + while (1) + { + if (*self->ptr == ' ' || *self->ptr == '\t') + { + do + { + toml_move_next(self); + } while (*self->ptr < *self->end && + (*self->ptr == ' ' || *self->ptr == '\t')); + } + else if (*self->ptr == ']') + { + if (is_array) + { + if (self->ptr + 2 <= self->end && strncmp(self->ptr, "]]", 2) == 0) + { + toml_next_n(self, 2); + break; + } + } + else + { + toml_move_next(self); + break; + } + } + else + { + TomlString *key_part = NULL; + TomlValue *key_part_value = NULL; + + if (isalnum(*self->ptr) || *self->ptr == '_') + { + key_part = toml_parse_bare_key(self, &err); + } + else if (*self->ptr == '\"') + { + toml_move_next(self); + key_part = toml_parse_basic_string(self, &err); + } + else if (*self->ptr == '\'') + { + toml_move_next(self); + key_part = toml_parse_literal_string(self, &err); + } + else + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: unexpected token", + self->filename, self->lineno, self->colno); + } + if (err.code != TOML_OK) goto cleanup; + + key_part_value = toml_value_new(TOML_STRING, &err); + if (err.code != TOML_OK) goto cleanup; + + key_part_value->value.string = key_part; + + toml_array_append(key_path, key_part_value, &err); + if (err.code != TOML_OK) goto cleanup; + + while (self->ptr < self->end && + (*self->ptr == ' ' || *self->ptr == '\t')) + { + toml_move_next(self); + } + + if (self->ptr < self->end && *self->ptr == '.') + { + toml_move_next(self); + } + } + } + + if (key_path->len == 0) + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: empty table name", + self->filename, self->lineno, self->colno); + goto cleanup; + } + + while (self->ptr < self->end && + (*self->ptr == ' ' || *self->ptr == '\t' || *self->ptr == '\r')) + { + toml_move_next(self); + } + + if (self->ptr < self->end && *self->ptr != '\n') + { + toml_set_err(&err, TOML_ERR_SYNTAX, "%s:%d:%d: new line expected", + self->filename, self->lineno, self->colno); + goto cleanup; + } + + real_table = toml_walk_table_path(self, table, key_path, is_array, TOML_TRUE, &err); + if (err.code != TOML_OK) goto cleanup; + + toml_parse_key_value(self, real_table, &err); + + cleanup: + toml_array_free(key_path); + toml_err_move(error, &err); +} + +TomlTable *toml_parse(TomlParser *self, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + + TomlTable *table = NULL; + + table = toml_table_new(&err); + if (err.code != TOML_OK) goto cleanup; + + while (self->ptr < self->end) + { + char ch = *self->ptr; + + while (self->ptr < self->end && isspace(ch)) + { + toml_move_next(self); + ch = *self->ptr; + } + + if (ch == '#') + { + do + { + toml_move_next(self); + ch = *self->ptr; + } while (self->ptr < self->end && ch != '\n'); + toml_move_next(self); + } + else if (ch == '[') + { + toml_move_next(self); + toml_parse_table(self, table, &err); + if (err.code != TOML_OK) goto cleanup; + } + else if (isalnum(ch) || ch == '_' || ch == '-') + { + toml_parse_key_value(self, table, &err); + if (err.code != TOML_OK) goto cleanup; + } + else if (ch == ' ' || ch == '\t' || ch == '\r') + { + do + { + toml_move_next(self); + ch = *self->ptr; + } while (ch == ' ' || ch == '\t' || ch == '\r'); + } + else if (ch == '\n') + { + toml_move_next(self); + } + } + + cleanup: + toml_err_move(error, &err); + return table; +} + +TomlTable *toml_load_nstring_filename(TOML_CONST char *str, size_t len, + TOML_CONST char *filename, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + TomlParser *parser = NULL; + TomlTable *table = NULL; + + parser = toml_parser_new(str, len, &err); + if (err.code != TOML_OK) goto cleanup; + + parser->filename = toml_strdup(filename); + + table = toml_parse(parser, &err); + if (err.code != TOML_OK) goto cleanup; + + cleanup: + toml_parser_free(parser); + toml_err_move(error, &err); + return table; +} + +TomlTable *toml_load_nstring(TOML_CONST char *str, size_t len, TomlErr *error) +{ + return toml_load_nstring_filename(str, len, "", error); +} + +TomlTable *toml_load_string(TOML_CONST char *str, TomlErr *error) +{ + return toml_load_nstring(str, sizeof(str), error); +} + +TomlTable *toml_load_file_filename(FILE *file, TOML_CONST char *filename, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + TomlTable *table = NULL; + TomlString *str = NULL; + + str = toml_string_new(&err); + if (err.code != TOML_OK) goto cleanup; + + toml_string_check_expand(str, 4095, &err); + if (err.code != TOML_OK) goto cleanup; + + size_t count; + size_t bytes_to_read; + do + { + bytes_to_read = str->_capacity - str->len - 1; + + count = fread(str->str, 1, bytes_to_read, file); + + if (str->len + 1 >= str->_capacity) + { + toml_string_check_expand(str, str->_capacity * 2, &err); + if (err.code != TOML_OK) goto cleanup; + } + + str->len += count; + } while (count == bytes_to_read); + + str->str[str->len] = 0; + + if (ferror(file)) + { + toml_set_err_literal(&err, TOML_ERR_IO, "error when reading file"); + goto cleanup; + } + + table = toml_load_nstring_filename(str->str, str->len, filename, &err); + + cleanup: + toml_string_free(str); + toml_err_move(error, &err); + return table; +} + +TomlTable *toml_load_file(FILE *file, TomlErr *error) +{ + return toml_load_file_filename(file, "", error); +} + +TomlTable *toml_load_filename(TOML_CONST char *filename, TomlErr *error) +{ + TomlErr err = TOML_ERR_INIT; + TomlTable *table = NULL; + FILE *f = NULL; + + f = fopen(filename, "r"); + if (f == NULL) + { + toml_set_err(&err, TOML_ERR_IO, "cannot open file: %s", filename); + goto cleanup; + } + + table = toml_load_file_filename(f, filename, &err); + + cleanup: + if (f != NULL) fclose(f); + toml_err_move(error, &err); + return table; +} diff --git a/src/utils/toml.h b/src/utils/toml.h new file mode 100644 index 000000000..06bb03f13 --- /dev/null +++ b/src/utils/toml.h @@ -0,0 +1,226 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __TOML_H__ +#define __TOML_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#define TOML_FALSE 0 +#define TOML_TRUE 1 + +#if !defined(_MSC_VER) || _MSC_VER >= 1800 +#define TOML_CONST const +#else +#define TOML_CONST +#endif + +enum +{ + TOML_OK, + TOML_ERR, + TOML_ERR_IO, + TOML_ERR_NOMEM, + TOML_ERR_SYNTAX, +}; + +typedef struct +{ + int code; + char *message; + int _is_literal; +} TomlErr; + +typedef struct +{ + char *str; + size_t len; + size_t _capacity; +} TomlString; + +typedef struct _TomlValue TomlValue; + +typedef struct +{ + TomlValue **elements; + size_t len; + size_t _capacity; +} TomlArray; + +typedef struct +{ + TomlString *key; + TomlValue *value; +} TomlKeyValue; + +typedef struct _TomlTable TomlTable; +typedef struct _TomlTableIter TomlTableIter; + +typedef struct +{ + int year; + int month; + int day; + int hour; + int minute; + double second; + int offset_hour; + int offset_minute; +} TomlDateTime; + +typedef enum +{ + TOML_TABLE, + TOML_ARRAY, + TOML_STRING, + TOML_INTEGER, + TOML_FLOAT, + TOML_DATETIME, + TOML_BOOLEAN, +} TomlType; + +struct _TomlValue +{ + TomlType type; + union + { + TomlTable *table; + TomlArray *array; + TomlString *string; +#if defined(_MSC_VER) + long long integer; +#else + long integer; +#endif + double float_; + TomlDateTime *datetime; + int boolean; + } value; +}; + +char *toml_strdup(TOML_CONST char *str); + +char *toml_strndup(TOML_CONST char *str, size_t n); + +int toml_vasprintf(char **str, TOML_CONST char *format, va_list args); + +int toml_asprintf(char **str, TOML_CONST char *format, ...); + +#define TOML_ERR_INIT {TOML_OK, NULL, TOML_FALSE} + +void toml_err_init(TomlErr *err); + +void toml_clear_err(TomlErr *err); + +void toml_err_move(TomlErr *to, TomlErr *from); + +void toml_set_err_v(TomlErr *err, int code, TOML_CONST char *format, va_list args); + +void toml_set_err(TomlErr *err, int code, TOML_CONST char *format, ...); + +void toml_set_err_literal(TomlErr *err, int code, TOML_CONST char *message); + +TomlString *toml_string_new(TomlErr *err); + +TomlString *toml_string_new_string(TOML_CONST char *str, TomlErr *err); + +TomlString *toml_string_new_nstring(TOML_CONST char *str, size_t len, TomlErr *err); + +void toml_string_append_char(TomlString *self, char ch, TomlErr *err); + +void toml_string_append_string(TomlString *self, TOML_CONST char *str, TomlErr *err); + +void toml_string_append_nstring(TomlString *self, TOML_CONST char *str, size_t len, TomlErr *err); + +TomlString *toml_string_copy(TOML_CONST TomlString *self, TomlErr *err); + +void toml_string_free(TomlString *self); + +int toml_string_equals(TOML_CONST TomlString *self, TOML_CONST TomlString *other); + +TomlTable *toml_table_new(TomlErr *err); + +void toml_table_free(TomlTable *self); + +void toml_table_set_by_string(TomlTable *self, TomlString *key, + TomlValue *value, TomlErr *err); + +TomlValue *toml_table_get_by_string(TOML_CONST TomlTable *self, TOML_CONST TomlString *key); + +void toml_table_set(TomlTable *self, TOML_CONST char *key, TomlValue *value, TomlErr *err); + +void toml_table_setn(TomlTable *self, TOML_CONST char *key, size_t key_len, + TomlValue *value, TomlErr *err); + +TomlValue *toml_table_get(TOML_CONST TomlTable *self, TOML_CONST char *key); + +TomlValue *toml_table_getn(TOML_CONST TomlTable *self, TOML_CONST char *key, size_t key_len); + +TomlTableIter *toml_table_iter_new(TomlTable *table, TomlErr *err); + +void toml_table_iter_free(TomlTableIter *self); + +TomlKeyValue *toml_table_iter_get(TomlTableIter *self); + +int toml_table_iter_has_next(TomlTableIter *self); + +void toml_table_iter_next(TomlTableIter *self); + +TomlArray *toml_array_new(TomlErr *err); + +void toml_array_free(TomlArray *self); + +void toml_array_append(TomlArray *self, TomlValue *value, TomlErr *err); + +TomlValue *toml_value_new(TomlType type, TomlErr *err); + +TomlValue *toml_value_new_string(TOML_CONST char *str, TomlErr *err); + +TomlValue *toml_value_new_nstring(TOML_CONST char *str, size_t len, TomlErr *err); + +TomlValue *toml_value_new_table(TomlErr *err); + +TomlValue *toml_value_new_array(TomlErr *err); + +#if defined(_MSC_VER) +TomlValue *toml_value_new_integer(long long integer, TomlErr *err); +#else + +TomlValue *toml_value_new_integer(long integer, TomlErr *err); + +#endif + +TomlValue *toml_value_new_float(double flt, TomlErr *err); + +TomlValue *toml_value_new_datetime(TomlErr *err); + +TomlValue *toml_value_new_boolean(int boolean, TomlErr *err); + +void toml_value_free(TomlValue *self); + +TomlTable *toml_load_string(TOML_CONST char *str, TomlErr *err); + +TomlTable *toml_load_nstring(TOML_CONST char *str, size_t len, TomlErr *err); + +TomlTable *toml_load_file(FILE *file, TomlErr *err); + +TomlTable *toml_load_filename(TOML_CONST char *filename, TomlErr *err); + +/* TODO: implement dump functions +char *toml_dump_string(TOML_CONST TomlTable *self, TomlErr *err); +TomlString *toml_dump_nstring(TOML_CONST TomlTable *self, TomlErr *err); +void toml_dump_file(TOML_CONST TomlTable *self, FILE *file, TomlErr *err); +*/ + +#ifdef __cplusplus +} +#endif + +#endif /* end of include guard: __TOML_H__ */