diff --git a/examples/libc/exit.c b/examples/libc/exit.c new file mode 100644 index 0000000..b3d47b7 --- /dev/null +++ b/examples/libc/exit.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include +#include + +void raylib_js_set_entry(void (*entry)(void)); + +const double time_per_calc = 1; // in s +double time_since_last_calc = 0; +char buf[BUFSIZ]; +int counter = 3; + +void GameFrame() +{ + BeginDrawing(); + + ClearBackground((Color){20, 20, 20, 255}); + + if (counter <= 0) + { + exit(0); + } + + const size_t w = GetScreenWidth(); + const size_t h = GetScreenHeight(); + const size_t font_size = 72; + + const int ch = h / 2 - (font_size / 2); + + sprintf(buf, "%d%c", counter, '\0'); + size_t text_size = MeasureText(buf, font_size); + int cw = w / 2 - (text_size / 2); + DrawText(buf, cw, ch, font_size, RED); + + if (time_since_last_calc >= time_per_calc) + { + counter--; + time_since_last_calc = 0; + } + else + { + time_since_last_calc += GetFrameTime(); + } + + EndDrawing(); +} + +int main() +{ + InitWindow(800, 600, "Hello, with math.h"); + SetTargetFPS(60); + +#ifdef PLATFORM_WEB + raylib_js_set_entry(GameFrame); +#else + while (!WindowShouldClose()) + { + GameFrame(); + } + CloseWindow(); +#endif + return 0; +} diff --git a/examples/libc/file.c b/examples/libc/file.c new file mode 100644 index 0000000..74d3c18 --- /dev/null +++ b/examples/libc/file.c @@ -0,0 +1,84 @@ +#include +#include +#include +#include + +void raylib_js_set_entry(void (*entry)(void)); + +bool once = true; +FILE *read_me = NULL; + +char buf[BUFSIZ]; +const double time_per_calc = 2; // in s +double time_since_last_calc = 0; + +void GameFrame() +{ + BeginDrawing(); + + ClearBackground((Color){20, 20, 20, 255}); + + const size_t w = GetScreenWidth(); + const size_t h = GetScreenHeight(); + const size_t font_size = 48; + + if (time_since_last_calc >= time_per_calc) + { + // Get new line + if (is_file_ready(read_me)) + { + if (feof(read_me)) + { + rewind(read_me); + } + + fgets(buf, BUFSIZ, read_me); + } + + time_since_last_calc = 0; + } + else + { + time_since_last_calc += GetFrameTime(); + } + + // display text somewhat sensibly + size_t current_font_size = font_size; + size_t text_size = MeasureText(buf, font_size); + while (text_size > w && current_font_size > font_size / 2) + { + text_size = MeasureText(buf, --current_font_size); + } + if (text_size <= w) + { + DrawText(buf, w / 2 - (text_size / 2), h / 2 - (current_font_size / 2), current_font_size, RED); + } + else + { + DrawText(buf, 10, h / 2 - (current_font_size / 2), current_font_size, RED); + } + + EndDrawing(); +} + +int main() +{ + InitWindow(800, 600, "Hello, with loaded README.md"); + SetTargetFPS(60); + + read_me = fopen("README.md", "rb"); + + int w = GetScreenWidth(); + int h = GetScreenHeight(); + +#ifdef PLATFORM_WEB + raylib_js_set_entry(GameFrame); +#else + while (!WindowShouldClose()) + { + GameFrame(); + } + CloseWindow(); +#endif + return 0; +} diff --git a/examples/libc/malloc.c b/examples/libc/malloc.c new file mode 100644 index 0000000..786fac1 --- /dev/null +++ b/examples/libc/malloc.c @@ -0,0 +1,79 @@ +#include +#include +#include +#include +#include +#include +#include + +void raylib_js_set_entry(void (*entry)(void)); + +const double time_per_calc = 1; // in s +double time_since_last_calc = 0; + +char buf[128]; + +char *buf_dym = NULL; +int counter = 1; + +void GameFrame() +{ + BeginDrawing(); + + ClearBackground((Color){20, 20, 20, 255}); + + const size_t w = GetScreenWidth(); + const size_t h = GetScreenHeight(); + const size_t font_size = 72; + + const int ch = h / 2 - (font_size / 2); + + buf_dym = realloc(buf_dym, counter + 1); + + memset(buf_dym, 'a', counter); + buf_dym[counter] = '\0'; + size_t text_size = MeasureText(buf_dym, font_size); + int cw = w / 2 - (text_size / 2); + DrawText(buf_dym, cw, ch - font_size, font_size, RED); + + sprintf(buf, "malloc size: %d%c", counter + 1, '\0'); + size_t m_text_size = MeasureText(buf, font_size); + cw = w / 2 - (m_text_size / 2); + DrawText(buf, cw, ch + font_size, font_size, RED); + + if (text_size > w) + { + counter = 1; + free(buf_dym); + buf_dym = NULL; + } + + if (time_since_last_calc >= time_per_calc) + { + counter++; + time_since_last_calc = 0; + } + else + { + time_since_last_calc += GetFrameTime(); + } + + EndDrawing(); +} + +int main() +{ + InitWindow(800, 600, "Hello, with malloc"); + SetTargetFPS(60); + +#ifdef PLATFORM_WEB + raylib_js_set_entry(GameFrame); +#else + while (!WindowShouldClose()) + { + GameFrame(); + } + CloseWindow(); +#endif + return 0; +} diff --git a/examples/libc/math.c b/examples/libc/math.c new file mode 100644 index 0000000..be69906 --- /dev/null +++ b/examples/libc/math.c @@ -0,0 +1,69 @@ +#include +#include +#include +#include +#include +#include + +void raylib_js_set_entry(void (*entry)(void)); + +const double time_per_calc = 2; // in s +double time_since_last_calc = 0; +char buf[BUFSIZ]; +double random = 0; +void GameFrame() +{ + BeginDrawing(); + + ClearBackground((Color){20, 20, 20, 255}); + + const size_t w = GetScreenWidth(); + const size_t h = GetScreenHeight(); + const size_t font_size = 48; + + const int ch = h / 2 - (font_size / 2); + + sprintf(buf, "cos(%.2g) = %.2g%c", random, cos(random), '\0'); + size_t text_size = MeasureText(buf, font_size); + int cw = w / 2 - (text_size / 2); + DrawText(buf, cw, ch - font_size, font_size, RED); + + sprintf(buf, "sin(%.2g) = %.2g%c", random, sin(random), '\0'); + text_size = MeasureText(buf, font_size); + cw = w / 2 - (text_size / 2); + DrawText(buf, cw, ch + font_size, font_size, RED); + + if (time_since_last_calc >= time_per_calc) + { + const int random_int = rand(); + random = ((random_int / (double)RAND_MAX) - 0.5) * 2 * 4; + + time_since_last_calc = 0; + } + else + { + time_since_last_calc += GetFrameTime(); + } + + EndDrawing(); +} + +int main() +{ + InitWindow(800, 600, "Hello, with math.h"); + SetTargetFPS(60); + + int w = GetScreenWidth(); + int h = GetScreenHeight(); + +#ifdef PLATFORM_WEB + raylib_js_set_entry(GameFrame); +#else + while (!WindowShouldClose()) + { + GameFrame(); + } + CloseWindow(); +#endif + return 0; +} diff --git a/examples/libc/time.c b/examples/libc/time.c new file mode 100644 index 0000000..c6aeff1 --- /dev/null +++ b/examples/libc/time.c @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include + +void raylib_js_set_entry(void (*entry)(void)); + +char buf[BUFSIZ]; + +void GameFrame() +{ + BeginDrawing(); + + ClearBackground((Color){20, 20, 20, 255}); + + const size_t w = GetScreenWidth(); + const size_t h = GetScreenHeight(); + const size_t font_size = 48; + + const int ch = h / 2 - (font_size / 2); + + time_t t = time(NULL); + struct tm *ts = localtime(&t); + + char *time_text = asctime(ts); + sprintf(buf, "%s%c", time_text, '\0'); + + size_t text_size = MeasureText(buf, font_size); + int cw = w / 2 - (text_size / 2); + DrawText(buf, cw, ch, font_size, RED); + + EndDrawing(); +} + +int main() +{ + InitWindow(800, 600, "Hello, with math.h"); + SetTargetFPS(60); + +#ifdef PLATFORM_WEB + raylib_js_set_entry(GameFrame); +#else + while (!WindowShouldClose()) + { + GameFrame(); + } + CloseWindow(); +#endif + return 0; +} \ No newline at end of file diff --git a/examples/tsoding_snake/tsoding_snake.c b/examples/tsoding_snake/tsoding_snake.c index 32cbdf5..ca307c1 100644 --- a/examples/tsoding_snake/tsoding_snake.c +++ b/examples/tsoding_snake/tsoding_snake.c @@ -1,25 +1,27 @@ #include "./tsoding_snake.h" #include +#include +#include +#include // #define FEATURE_DYNAMIC_CAMERA // #define FEATURE_DEV -#define STB_SPRINTF_IMPLEMENTATION -#include "stb_sprintf.h" - #define TRUE 1 #define FALSE 0 static char logf_buf[4096] = {0}; -#define LOGF(...) \ - do { \ - stbsp_snprintf(logf_buf, sizeof(logf_buf), __VA_ARGS__); \ - platform_log(logf_buf); \ - } while(0) +#define LOGF(...) \ + do \ + { \ + snprintf(logf_buf, sizeof(logf_buf), __VA_ARGS__); \ + platform_log(logf_buf); \ + } while (0) static void platform_assert(const char *file, i32 line, b32 cond, const char *message) { - if (!cond) { + if (!cond) + { TraceLog(LOG_FATAL, "%s:%d: GAME ASSERTION FAILED: %s\n", file, line, message); } } @@ -48,7 +50,8 @@ static void platform_assert(const char *file, i32 line, b32 cond, const char *me #define RAND_A 6364136223846793005ULL #define RAND_C 1442695040888963407ULL -typedef enum { +typedef enum +{ ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER, @@ -57,10 +60,16 @@ typedef enum { static void fill_text_aligned(i32 x, i32 y, const char *text, u32 size, u32 color, Align align) { u32 width = platform_text_width(text, size); - switch (align) { - case ALIGN_LEFT: break; - case ALIGN_CENTER: x -= width/2; break; - case ALIGN_RIGHT: x -= width; break; + switch (align) + { + case ALIGN_LEFT: + break; + case ALIGN_CENTER: + x -= width / 2; + break; + case ALIGN_RIGHT: + x -= width; + break; } platform_fill_text(x, y, text, size, color); } @@ -68,19 +77,12 @@ static void fill_text_aligned(i32 x, i32 y, const char *text, u32 size, u32 colo static u32 my_rand(void) { static u64 rand_state = 0; - rand_state = rand_state*RAND_A + RAND_C; - return (rand_state >> 32)&0xFFFFFFFF; + rand_state = rand_state * RAND_A + RAND_C; + return (rand_state >> 32) & 0xFFFFFFFF; } -static void *memset(void *mem, u32 c, u32 n) +typedef enum { - void *result = mem; - u8 *bytes = mem; - while (n-- > 0) *bytes++ = c; - return result; -} - -typedef enum { DIR_RIGHT = 0, DIR_UP, DIR_LEFT, @@ -91,14 +93,16 @@ typedef enum { static Dir dir_opposite(Dir dir) { ASSERT(0 <= dir && dir < COUNT_DIRS, "Invalid direction"); - return (dir + 2)%COUNT_DIRS; + return (dir + 2) % COUNT_DIRS; } -typedef struct { +typedef struct +{ f32 x, y, w, h; } Rect; -typedef struct { +typedef struct +{ f32 lens[COUNT_DIRS]; } Sides; @@ -106,12 +110,11 @@ static Sides rect_sides(Rect rect) { Sides sides = { .lens = { - [DIR_LEFT] = rect.x, + [DIR_LEFT] = rect.x, [DIR_RIGHT] = rect.x + rect.w, - [DIR_UP] = rect.y, - [DIR_DOWN] = rect.y + rect.h, - } - }; + [DIR_UP] = rect.y, + [DIR_DOWN] = rect.y + rect.h, + }}; return sides; } @@ -126,42 +129,49 @@ static Rect sides_rect(Sides sides) return rect; } -typedef struct { +typedef struct +{ i32 x, y; } Cell; -typedef struct { +typedef struct +{ f32 x, y; } Vec; -#define SNAKE_CAP (ROWS*COLS) -typedef struct { +#define SNAKE_CAP (ROWS * COLS) +typedef struct +{ Cell items[SNAKE_CAP]; u32 begin; u32 size; } Snake; -typedef struct { +typedef struct +{ Rect items[SNAKE_CAP]; Vec vels[SNAKE_CAP]; u8 masks[SNAKE_CAP]; u32 size; } Dead_Snake; -typedef enum { +typedef enum +{ STATE_GAMEPLAY = 0, STATE_PAUSE, STATE_GAMEOVER, } State; #define DIR_QUEUE_CAP 3 -typedef struct { +typedef struct +{ u32 begin; u32 size; Dir items[DIR_QUEUE_CAP]; } Dir_Queue; -typedef struct { +typedef struct +{ u32 width; u32 height; @@ -195,8 +205,8 @@ static Game game = {0}; static Rect cell_rect(Cell cell) { Rect result = { - .x = cell.x*CELL_SIZE, - .y = cell.y*CELL_SIZE, + .x = cell.x * CELL_SIZE, + .y = cell.y * CELL_SIZE, .w = CELL_SIZE, .h = CELL_SIZE, }; @@ -205,43 +215,49 @@ static Rect cell_rect(Cell cell) #define ring_empty(ring) ((ring)->size == 0) -#define ring_cap(ring) (sizeof((ring)->items)/sizeof((ring)->items[0])) +#define ring_cap(ring) (sizeof((ring)->items) / sizeof((ring)->items[0])) -#define ring_push_back(ring, item) \ - do { \ +#define ring_push_back(ring, item) \ + do \ + { \ ASSERT((ring)->size < ring_cap(ring), "Ring buffer overflow"); \ - u32 index = ((ring)->begin + (ring)->size)%ring_cap(ring); \ - (ring)->items[index] = (item); \ - (ring)->size += 1; \ + u32 index = ((ring)->begin + (ring)->size) % ring_cap(ring); \ + (ring)->items[index] = (item); \ + (ring)->size += 1; \ } while (0) -#define ring_displace_back(ring, item) \ - do { \ - u32 index = ((ring)->begin + (ring)->size)%ring_cap(ring); \ - (ring)->items[index] = (item); \ - if ((ring)->size < ring_cap(ring)) { \ - (ring)->size += 1; \ - } else { \ - (ring)->begin = ((ring)->begin + 1)%ring_cap(ring); \ - } \ +#define ring_displace_back(ring, item) \ + do \ + { \ + u32 index = ((ring)->begin + (ring)->size) % ring_cap(ring); \ + (ring)->items[index] = (item); \ + if ((ring)->size < ring_cap(ring)) \ + { \ + (ring)->size += 1; \ + } \ + else \ + { \ + (ring)->begin = ((ring)->begin + 1) % ring_cap(ring); \ + } \ } while (0) -#define ring_pop_front(ring) \ - do { \ - ASSERT((ring)->size > 0, "Ring buffer underflow"); \ - (ring)->begin = ((ring)->begin + 1)%ring_cap(ring); \ - (ring)->size -= 1; \ +#define ring_pop_front(ring) \ + do \ + { \ + ASSERT((ring)->size > 0, "Ring buffer underflow"); \ + (ring)->begin = ((ring)->begin + 1) % ring_cap(ring); \ + (ring)->size -= 1; \ } while (0) -#define ring_back(ring) \ +#define ring_back(ring) \ (ASSERT((ring)->size > 0, "Ring buffer is empty"), \ - &(ring)->items[((ring)->begin + (ring)->size - 1)%ring_cap(ring)]) -#define ring_front(ring) \ + &(ring)->items[((ring)->begin + (ring)->size - 1) % ring_cap(ring)]) +#define ring_front(ring) \ (ASSERT((ring)->size > 0, "Ring buffer is empty"), \ &(ring)->items[(ring)->begin]) -#define ring_get(ring, index) \ +#define ring_get(ring, index) \ (ASSERT((ring)->size > 0, "Ring buffer is empty"), \ - &(ring)->items[((ring)->begin + (index))%ring_cap(ring)]) + &(ring)->items[((ring)->begin + (index)) % ring_cap(ring)]) static b32 cell_eq(Cell a, Cell b) { @@ -251,8 +267,10 @@ static b32 cell_eq(Cell a, Cell b) static i32 is_cell_snake_body(Cell cell) { // TODO: ignoring the tail feel hacky @tail-ignore - for (u32 index = 1; index < game.snake.size; ++index) { - if (cell_eq(*ring_get(&game.snake, index), cell)) { + for (u32 index = 1; index < game.snake.size; ++index) + { + if (cell_eq(*ring_get(&game.snake, index), cell)) + { return index; } } @@ -261,7 +279,7 @@ static i32 is_cell_snake_body(Cell cell) static i32 emod(i32 a, i32 b) { - return (a%b + b)%b; + return (a % b + b) % b; } static Cell cell_wrap(Cell cell) @@ -272,10 +290,10 @@ static Cell cell_wrap(Cell cell) } static Cell dir_cell_data[COUNT_DIRS] = { - [DIR_LEFT] = {.x = -1}, - [DIR_RIGHT] = {.x = 1}, - [DIR_UP] = {.y = -1}, - [DIR_DOWN] = {.y = 1}, + [DIR_LEFT] = {.x = -1}, + [DIR_RIGHT] = {.x = 1}, + [DIR_UP] = {.y = -1}, + [DIR_DOWN] = {.y = 1}, }; static Cell cell_add(Cell a, Cell b) @@ -285,19 +303,22 @@ static Cell cell_add(Cell a, Cell b) return a; } -#define dir_cell(dir) (ASSERT((u32) dir < COUNT_DIRS, "Invalid direction"), dir_cell_data[dir]) +#define dir_cell(dir) (ASSERT((u32)dir < COUNT_DIRS, "Invalid direction"), dir_cell_data[dir]) #define dir_vec(dir) cell_vec(dir_cell(dir)) static Cell step_cell(Cell head, Dir dir) { - if (game.infinite_field) { + if (game.infinite_field) + { return cell_add(head, dir_cell(dir)); - } else { + } + else + { return cell_wrap(cell_add(head, dir_cell(dir))); } } -#define SNAKE_INIT_ROW (ROWS/2) +#define SNAKE_INIT_ROW (ROWS / 2) static void random_egg(b32 first) { @@ -307,18 +328,20 @@ static void random_egg(b32 first) i32 row2 = ROWS - 1; // TODO: make a single formula that works for any mode - if (game.infinite_field) { - col1 = (i32)(game.camera_pos.x - game.width*0.5f + CELL_SIZE)/CELL_SIZE; - col2 = (i32)(game.camera_pos.x + game.width*0.5f - CELL_SIZE)/CELL_SIZE; - row1 = (i32)(game.camera_pos.y - game.height*0.5f + CELL_SIZE)/CELL_SIZE; - row2 = (i32)(game.camera_pos.y + game.height*0.5f - CELL_SIZE)/CELL_SIZE; + if (game.infinite_field) + { + col1 = (i32)(game.camera_pos.x - game.width * 0.5f + CELL_SIZE) / CELL_SIZE; + col2 = (i32)(game.camera_pos.x + game.width * 0.5f - CELL_SIZE) / CELL_SIZE; + row1 = (i32)(game.camera_pos.y - game.height * 0.5f + CELL_SIZE) / CELL_SIZE; + row2 = (i32)(game.camera_pos.y + game.height * 0.5f - CELL_SIZE) / CELL_SIZE; } #define RANDOM_EGG_MAX_ATTEMPTS 1000 u32 attempt = 0; - do { - game.egg.x = my_rand()%(col2 - col1 + 1) + col1; - game.egg.y = my_rand()%(row2 - row1 + 1) + row1; + do + { + game.egg.x = my_rand() % (col2 - col1 + 1) + col1; + game.egg.y = my_rand() % (row2 - row1 + 1) + row1; attempt += 1; } while ((is_cell_snake_body(game.egg) >= 0 || (first && game.egg.y == SNAKE_INIT_ROW)) && attempt < RANDOM_EGG_MAX_ATTEMPTS); @@ -334,37 +357,38 @@ static void game_restart(u32 width, u32 height) game.dt_scale = 1.0f; #endif - game.width = width; - game.height = height; - game.camera_pos.x = width/2; - game.camera_pos.y = height/2; + game.width = width; + game.height = height; + game.camera_pos.x = width / 2; + game.camera_pos.y = height / 2; - for (u32 i = 0; i < SNAKE_INIT_SIZE; ++i) { + for (u32 i = 0; i < SNAKE_INIT_SIZE; ++i) + { Cell head = {.x = i, .y = SNAKE_INIT_ROW}; ring_push_back(&game.snake, head); } random_egg(TRUE); game.dir = DIR_RIGHT; // TODO: Using snprintf to render Score is an overkill - // I believe snprintf should be only used for LOGF and in the "release" build stbsp_snprintf should not be included at all - stbsp_snprintf(game.score_buffer, sizeof(game.score_buffer), "Score: %u", game.score); + // I believe snprintf should be only used for LOGF and in the "release" build snprintf should not be included at all + snprintf(game.score_buffer, sizeof(game.score_buffer), "Score: %u", game.score); } static f32 lerpf(f32 a, f32 b, f32 t) { - return (b - a)*t + a; + return (b - a) * t + a; } static f32 ilerpf(f32 a, f32 b, f32 v) { - return (v - a)/(b - a); + return (v - a) / (b - a); } static void fill_rect(Rect rect, u32 color) { platform_fill_rect( - rect.x - game.camera_pos.x + game.width/2, - rect.y - game.camera_pos.y + game.height/2, + rect.x - game.camera_pos.x + game.width / 2, + rect.y - game.camera_pos.y + game.height / 2, rect.w, rect.h, color); } @@ -372,16 +396,16 @@ static void fill_rect(Rect rect, u32 color) static void stroke_rect(Rect rect, u32 color) { platform_stroke_rect( - rect.x - game.camera_pos.x + game.width/2, - rect.y - game.camera_pos.y + game.height/2, + rect.x - game.camera_pos.x + game.width / 2, + rect.y - game.camera_pos.y + game.height / 2, rect.w, rect.h, color); } #endif static Rect scale_rect(Rect r, float a) { - r.x = lerpf(r.x, r.x + r.w*0.5f, 1.0f - a); - r.y = lerpf(r.y, r.y + r.h*0.5f, 1.0f - a); + r.x = lerpf(r.x, r.x + r.w * 0.5f, 1.0f - a); + r.y = lerpf(r.y, r.y + r.h * 0.5f, 1.0f - a); r.w = lerpf(0.0f, r.w, a); r.h = lerpf(0.0f, r.h, a); return r; @@ -399,8 +423,10 @@ static void fill_sides(Sides sides, u32 color) static Dir cells_dir(Cell a, Cell b) { - for (Dir dir = 0; dir < COUNT_DIRS; ++dir) { - if (cell_eq(step_cell(a, dir), b)) return dir; + for (Dir dir = 0; dir < COUNT_DIRS; ++dir) + { + if (cell_eq(step_cell(a, dir), b)) + return dir; } UNREACHABLE(); return 0; @@ -408,58 +434,60 @@ static Dir cells_dir(Cell a, Cell b) static Vec cell_center(Cell a) { - return (Vec) { - .x = a.x*CELL_SIZE + CELL_SIZE/2, - .y = a.y*CELL_SIZE + CELL_SIZE/2, + return (Vec){ + .x = a.x * CELL_SIZE + CELL_SIZE / 2, + .y = a.y * CELL_SIZE + CELL_SIZE / 2, }; } static Sides slide_sides(Sides sides, Dir dir, f32 a) { f32 d = sides.lens[dir] - sides.lens[dir_opposite(dir)]; - sides.lens[dir] += lerpf(0, d, a); + sides.lens[dir] += lerpf(0, d, a); sides.lens[dir_opposite(dir)] += lerpf(0, d, a); return sides; } Vec sides_center(Sides sides) { - return (Vec) { - .x = sides.lens[DIR_LEFT] + (sides.lens[DIR_RIGHT] - sides.lens[DIR_LEFT])*0.5f, - .y = sides.lens[DIR_UP] + (sides.lens[DIR_DOWN] - sides.lens[DIR_UP])*0.5f, + return (Vec){ + .x = sides.lens[DIR_LEFT] + (sides.lens[DIR_RIGHT] - sides.lens[DIR_LEFT]) * 0.5f, + .y = sides.lens[DIR_UP] + (sides.lens[DIR_DOWN] - sides.lens[DIR_UP]) * 0.5f, }; } static void fill_spine(Vec center, Dir dir, float len) { - f32 thicc = CELL_SIZE*SNAKE_SPINE_THICCNESS_PERCENT; + f32 thicc = CELL_SIZE * SNAKE_SPINE_THICCNESS_PERCENT; Sides sides = { .lens = { - [DIR_LEFT] = center.x - thicc, - [DIR_RIGHT] = center.x + thicc, - [DIR_UP] = center.y - thicc, - [DIR_DOWN] = center.y + thicc, - } - }; - if (dir == DIR_RIGHT || dir == DIR_DOWN) sides.lens[dir] += len; - if (dir == DIR_LEFT || dir == DIR_UP) sides.lens[dir] -= len; + [DIR_LEFT] = center.x - thicc, + [DIR_RIGHT] = center.x + thicc, + [DIR_UP] = center.y - thicc, + [DIR_DOWN] = center.y + thicc, + }}; + if (dir == DIR_RIGHT || dir == DIR_DOWN) + sides.lens[dir] += len; + if (dir == DIR_LEFT || dir == DIR_UP) + sides.lens[dir] -= len; fill_sides(sides, SNAKE_SPINE_COLOR); } static void fill_fractured_spine(Sides sides, u8 mask) { - f32 thicc = CELL_SIZE*SNAKE_SPINE_THICCNESS_PERCENT; + f32 thicc = CELL_SIZE * SNAKE_SPINE_THICCNESS_PERCENT; Vec center = sides_center(sides); - for (Dir dir = 0; dir < COUNT_DIRS; ++dir) { - if (mask&(1< 1e-6; ++i) { - x -= (x*x - a)/(2*x); - } - return x; -} - static f32 vec_len(Vec a) { - return sqrtf(a.x*a.x + a.y*a.y); + return sqrtf(a.x * a.x + a.y * a.y); } void game_resize(u32 width, u32 height) @@ -665,56 +696,72 @@ void game_update(f32 dt) #ifdef FEATURE_DEV dt *= game.dt_scale; - #define DEV_DT_SCALE_STEP 0.05f - if (IsKeyPressed(KEY_Z)) { +#define DEV_DT_SCALE_STEP 0.05f + if (IsKeyPressed(KEY_Z)) + { game.dt_scale -= DEV_DT_SCALE_STEP; - if (game.dt_scale < 0.0f) game.dt_scale = 0.0f; + if (game.dt_scale < 0.0f) + game.dt_scale = 0.0f; LOGF("dt scale = %f", game.dt_scale); } - if (IsKeyPressed(KEY_X)) { + if (IsKeyPressed(KEY_X)) + { game.dt_scale += DEV_DT_SCALE_STEP; LOGF("dt scale = %f", game.dt_scale); } - if (IsKeyPressed(KEY_C)) { + if (IsKeyPressed(KEY_C)) + { game.dt_scale = 1.0f; LOGF("dt scale = %f", game.dt_scale); } #endif #define CAMERA_VELOCITY_FACTOR 0.80f - if (game.infinite_field) { - game.camera_pos.x += game.camera_vel.x*CAMERA_VELOCITY_FACTOR*dt; - game.camera_pos.y += game.camera_vel.y*CAMERA_VELOCITY_FACTOR*dt; + if (game.infinite_field) + { + game.camera_pos.x += game.camera_vel.x * CAMERA_VELOCITY_FACTOR * dt; + game.camera_pos.y += game.camera_vel.y * CAMERA_VELOCITY_FACTOR * dt; game.camera_vel = vec_sub( - cell_center(*ring_back(&game.snake)), - game.camera_pos); + cell_center(*ring_back(&game.snake)), + game.camera_pos); } - switch (game.state) { - case STATE_GAMEPLAY: { - if (IsKeyPressed(KEY_W)) { + switch (game.state) + { + case STATE_GAMEPLAY: + { + if (IsKeyPressed(KEY_W)) + { ring_displace_back(&game.next_dirs, DIR_UP); } - if (IsKeyPressed(KEY_S)) { + if (IsKeyPressed(KEY_S)) + { ring_displace_back(&game.next_dirs, DIR_DOWN); } - if (IsKeyPressed(KEY_A)) { + if (IsKeyPressed(KEY_A)) + { ring_displace_back(&game.next_dirs, DIR_LEFT); } - if (IsKeyPressed(KEY_D)) { + if (IsKeyPressed(KEY_D)) + { ring_displace_back(&game.next_dirs, DIR_RIGHT); } - if (IsKeyPressed(KEY_SPACE)) { + if (IsKeyPressed(KEY_SPACE)) + { game.state = STATE_PAUSE; } - if (IsKeyPressed(KEY_R)) { + if (IsKeyPressed(KEY_R)) + { game_restart(game.width, game.height); } game.step_cooldown -= dt; - if (game.step_cooldown <= 0.0f) { - if (!ring_empty(&game.next_dirs)) { - if (dir_opposite(game.dir) != *ring_front(&game.next_dirs)) { + if (game.step_cooldown <= 0.0f) + { + if (!ring_empty(&game.next_dirs)) + { + if (dir_opposite(game.dir) != *ring_front(&game.next_dirs)) + { game.dir = *ring_front(&game.next_dirs); } ring_pop_front(&game.next_dirs); @@ -722,7 +769,8 @@ void game_update(f32 dt) Cell next_head = step_cell(*ring_back(&game.snake), game.dir); - if (cell_eq(game.egg, next_head)) { + if (cell_eq(game.egg, next_head)) + { ring_push_back(&game.snake, next_head); random_egg(FALSE); game.eating_egg = TRUE; @@ -730,10 +778,13 @@ void game_update(f32 dt) game.infinite_field = TRUE; #endif game.score += 1; - stbsp_snprintf(game.score_buffer, sizeof(game.score_buffer), "Score: %u", game.score); - } else { + snprintf(game.score_buffer, sizeof(game.score_buffer), "Score: %u", game.score); + } + else + { i32 next_head_index = is_cell_snake_body(next_head); - if (next_head_index >= 0) { + if (next_head_index >= 0) + { // NOTE: reseting step_cooldown to 0 is important bcause the whole smooth movement is based on it. // Without this reset the head of the snake "detaches" from the snake on the Game Over, when // step_cooldown < 0.0f @@ -742,49 +793,60 @@ void game_update(f32 dt) game.dead_snake.size = game.snake.size; Vec head_center = cell_center(next_head); - for (u32 i = 0; i < game.snake.size; ++i) { + for (u32 i = 0; i < game.snake.size; ++i) + { #define GAMEOVER_EXPLOSION_RADIUS 1000.0f #define GAMEOVER_EXPLOSION_MAX_VEL 200.0f Cell cell = *ring_get(&game.snake, i); game.dead_snake.items[i] = cell_rect(cell); - if (!cell_eq(cell, next_head)) { + if (!cell_eq(cell, next_head)) + { Vec vel_vec = vec_sub(cell_center(cell), head_center); f32 vel_len = vec_len(vel_vec); f32 t = ilerpf(0.0f, GAMEOVER_EXPLOSION_RADIUS, vel_len); - if (t > 1.0f) t = 1.0f; + if (t > 1.0f) + t = 1.0f; t = 1.0f - t; - f32 noise_x = (my_rand()%1000)*0.01; - f32 noise_y = (my_rand()%1000)*0.01; - vel_vec.x = vel_vec.x/vel_len*GAMEOVER_EXPLOSION_MAX_VEL*t + noise_x; - vel_vec.y = vel_vec.y/vel_len*GAMEOVER_EXPLOSION_MAX_VEL*t + noise_y; + f32 noise_x = (my_rand() % 1000) * 0.01; + f32 noise_y = (my_rand() % 1000) * 0.01; + vel_vec.x = vel_vec.x / vel_len * GAMEOVER_EXPLOSION_MAX_VEL * t + noise_x; + vel_vec.y = vel_vec.y / vel_len * GAMEOVER_EXPLOSION_MAX_VEL * t + noise_y; game.dead_snake.vels[i] = vel_vec; // TODO: additional velocities along the body of the dead snake - } else { + } + else + { game.dead_snake.vels[i].x = 0; game.dead_snake.vels[i].y = 0; } // @tail-ignore - if (i > 0) { + if (i > 0) + { game.dead_snake.masks[i] = 0; - if (i > 1) { + if (i > 1) + { game.dead_snake.masks[i] |= 1 << cells_dir(cell, *ring_get(&game.snake, i - 1)); } - if (i < game.snake.size - 1) { + if (i < game.snake.size - 1) + { game.dead_snake.masks[i] |= 1 << cells_dir(cell, *ring_get(&game.snake, i + 1)); } } - if (i == game.snake.size - 1) { + if (i == game.snake.size - 1) + { game.dead_snake.masks[i] |= 1 << game.dir; } } game.dead_snake.masks[next_head_index] |= 1 << cells_dir( - *ring_get(&game.snake, next_head_index), - *ring_get(&game.snake, game.snake.size - 1)); + *ring_get(&game.snake, next_head_index), + *ring_get(&game.snake, game.snake.size - 1)); return; - } else { + } + else + { ring_push_back(&game.snake, next_head); ring_pop_front(&game.snake); game.eating_egg = FALSE; @@ -796,50 +858,58 @@ void game_update(f32 dt) } break; - case STATE_PAUSE: { - if (IsKeyPressed(KEY_SPACE)) { + case STATE_PAUSE: + { + if (IsKeyPressed(KEY_SPACE)) + { game.state = STATE_GAMEPLAY; } - if (IsKeyPressed(KEY_R)) { + if (IsKeyPressed(KEY_R)) + { game_restart(game.width, game.height); } - } break; + } + break; - case STATE_GAMEOVER: { - if (IsKeyPressed(KEY_A) || IsKeyPressed(KEY_S) || IsKeyPressed(KEY_D) || IsKeyPressed(KEY_W) || IsKeyPressed(KEY_SPACE)) { + case STATE_GAMEOVER: + { + if (IsKeyPressed(KEY_A) || IsKeyPressed(KEY_S) || IsKeyPressed(KEY_D) || IsKeyPressed(KEY_W) || IsKeyPressed(KEY_SPACE)) + { game_restart(game.width, game.height); } // @tail-ignore - for (u32 i = 1; i < game.dead_snake.size; ++i) { + for (u32 i = 1; i < game.dead_snake.size; ++i) + { game.dead_snake.vels[i].x *= 0.99f; game.dead_snake.vels[i].y *= 0.99f; - game.dead_snake.items[i].x += game.dead_snake.vels[i].x*dt; - game.dead_snake.items[i].y += game.dead_snake.vels[i].y*dt; + game.dead_snake.items[i].x += game.dead_snake.vels[i].x * dt; + game.dead_snake.items[i].y += game.dead_snake.vels[i].y * dt; } } break; - default: { + default: + { UNREACHABLE(); } } } #define FACTOR 100 -#define WIDTH (16*FACTOR) -#define HEIGHT (9*FACTOR) +#define WIDTH (16 * FACTOR) +#define HEIGHT (9 * FACTOR) static Font font = {0}; void platform_fill_rect(i32 x, i32 y, i32 w, i32 h, u32 color) { - DrawRectangle(x, y, w, h, *(Color*)&color); + DrawRectangle(x, y, w, h, *(Color *)&color); } void platform_stroke_rect(i32 x, i32 y, i32 w, i32 h, u32 color) { - DrawRectangleLines(x, y, w, h, *(Color*)&color); + DrawRectangleLines(x, y, w, h, *(Color *)&color); } u32 platform_text_width(const char *text, u32 size) @@ -851,7 +921,7 @@ void platform_fill_text(i32 x, i32 y, const char *text, u32 fontSize, u32 color) { Vector2 size = MeasureTextEx(font, text, fontSize, 0); Vector2 position = {.x = x, .y = y - size.y}; - DrawTextEx(font, text, position, fontSize, 0.0, *(Color*)&color); + DrawTextEx(font, text, position, fontSize, 0.0, *(Color *)&color); } void platform_log(const char *message) @@ -881,7 +951,8 @@ int main(void) #ifdef PLATFORM_WEB raylib_js_set_entry(GameFrame); #else - while (!WindowShouldClose()) { + while (!WindowShouldClose()) + { GameFrame(); } diff --git a/include/math.h b/include/math.h deleted file mode 100644 index 3d32b52..0000000 --- a/include/math.h +++ /dev/null @@ -1,17 +0,0 @@ -// Phony math.h. Since we are compiling with --no-standard-libraries raymath.h can't find math.h. -// But it only needs it for few function definitions. So we've put those definitions here. -#ifndef MATH_H_ -#define MATH_H_ -float floorf(float); -float fabsf(float); -double fabs(double); -float fmaxf(float, float); -float fminf(float, float); -float sqrtf(float); -float atan2f(float, float); -float cosf(float); -float sinf(float); -float acosf(float); -float asinf(float); -double tan(double); -#endif // MATH_H_ diff --git a/index.html b/index.html index 960a2cd..f195eef 100644 --- a/index.html +++ b/index.html @@ -57,6 +57,12 @@ src: url(fonts/acme_7_wide_xtnd.woff); } + + + + + + @@ -72,6 +78,7 @@ "shapes": ["shapes_colors_palette"], "text": ["text_writing_anim"], "textures": ["textures_logo_raylib"], + "libc": ["libc_math", "libc_exit", "libc_malloc", "libc_file", "libc_time"], } const defaultWasm = Object.values(wasmPaths)[0][0]; diff --git a/libc/include/assert.h b/libc/include/assert.h new file mode 100644 index 0000000..a70bdb4 --- /dev/null +++ b/libc/include/assert.h @@ -0,0 +1,31 @@ +#ifndef _INC_ASSERT +#define _INC_ASSERT + +#ifdef NDEBUG + +#define assert(expression) ((void)0) + +#else + +#ifndef NO_ASSERT_INCLUDE +void _assert(const char *message, const char *file, unsigned line); +#endif + +#ifdef ASSERT_IMPL + +#define assert(expression) (void)((!!(expression)) || \ + (_assert(#expression, __FILE__, (unsigned)(__LINE__)), 0)) + +// Cannot import stdlib or stdio as it is imported by them +void exit(int); +int printf(const char *, ...); +void _assert(const char *message, const char *file, unsigned line) +{ + printf("Assertion failed at %s : %i\n\t%s", file, line, message); + exit(1); +} +#endif + +#endif + +#endif // _INC_ASSERT \ No newline at end of file diff --git a/libc/include/errno.h b/libc/include/errno.h new file mode 100644 index 0000000..4269d41 --- /dev/null +++ b/libc/include/errno.h @@ -0,0 +1,106 @@ +#ifndef _INC_ERRNO +#define _INC_ERRNO + +#define EPERM 1 +#define ENOENT 2 +#define ESRCH 3 +#define EINTR 4 +#define EIO 5 +#define ENXIO 6 +#define E2BIG 7 +#define ENOEXEC 8 +#define EBADF 9 +#define ECHILD 10 +#define EAGAIN 11 +#define ENOMEM 12 +#define EACCES 13 +#define EFAULT 14 +#define EBUSY 16 +#define EEXIST 17 +#define EXDEV 18 +#define ENODEV 19 +#define ENOTDIR 20 +#define EISDIR 21 +#define ENFILE 23 +#define EMFILE 24 +#define ENOTTY 25 +#define EFBIG 27 +#define ENOSPC 28 +#define ESPIPE 29 +#define EROFS 30 +#define EMLINK 31 +#define EPIPE 32 +#define EDOM 33 +#define EDEADLK 36 +#define ENAMETOOLONG 38 +#define ENOLCK 39 +#define ENOSYS 40 +#define ENOTEMPTY 41 + +// Error codes used in the Secure CRT functions +#define EINVAL 22 +#define ERANGE 34 +#define EILSEQ 42 +#define STRUNCATE 80 + +// POSIX +#define EADDRINUSE 100 +#define EADDRNOTAVAIL 101 +#define EAFNOSUPPORT 102 +#define EALREADY 103 +#define EBADMSG 104 +#define ECANCELED 105 +#define ECONNABORTED 106 +#define ECONNREFUSED 107 +#define ECONNRESET 108 +#define EDESTADDRREQ 109 +#define EHOSTUNREACH 110 +#define EIDRM 111 +#define EINPROGRESS 112 +#define EISCONN 113 +#define ELOOP 114 +#define EMSGSIZE 115 +#define ENETDOWN 116 +#define ENETRESET 117 +#define ENETUNREACH 118 +#define ENOBUFS 119 +#define ENODATA 120 +#define ENOLINK 121 +#define ENOMSG 122 +#define ENOPROTOOPT 123 +#define ENOSR 124 +#define ENOSTR 125 +#define ENOTCONN 126 +#define ENOTRECOVERABLE 127 +#define ENOTSOCK 128 +#define ENOTSUP 129 +#define EOPNOTSUPP 130 +#define EOTHER 131 +#define EOVERFLOW 132 +#define EOWNERDEAD 133 +#define EPROTO 134 +#define EPROTONOSUPPORT 135 +#define EPROTOTYPE 136 +#define ETIME 137 +#define ETIMEDOUT 138 +#define ETXTBSY 139 +#define EWOULDBLOCK 140 + +#define errno __errno_impl +int __errno_impl; + +void _set_errno(int error_code); +void _add_errno(int error_code); + +#ifdef ERRNO_IMPL +int __errno_impl = 0; + +void _set_errno(int error_code) { + errno = error_code; +} +void _add_errno(int error_code) { + errno |= error_code; +} +#endif + +#endif \ No newline at end of file diff --git a/libc/include/fenv.h b/libc/include/fenv.h new file mode 100644 index 0000000..10b9a03 --- /dev/null +++ b/libc/include/fenv.h @@ -0,0 +1,140 @@ +#ifndef _INC_FENV +#define _INC_FENV + +// DEFINES + +#define FE_DOWNWARD 0 +#define FE_TONEAREST 1 +#define FE_TOWARDZERO 2 +#define FE_UPWARD 3 + +#define FE_DIVBYZERO 0x01 // Pole error: division by zero, or some other asymptotically infinite result (from finite arguments). +#define FE_INEXACT 0x02 // Inexact: the result is not exact. +#define FE_INVALID 0x04 // Domain error: At least one of the arguments is a value for which the function is not defined. +#define FE_OVERFLOW 0x08 // Overflow range error: The result is too large in magnitude to be represented as a value of the return type. +#define FE_UNDERFLOW 0x10 // Underflow range error: The result is too small in magnitude to be represented as a value of the return type. +#define FE_ALL_EXCEPT 0x1F // All exceptions (selects all of the exceptions supported by the implementation). + +#ifndef NO_FENV_INCLUDE + +// TYPES +typedef int fexcept_t; +typedef struct fenv_t fenv_t; + +// DEFINITIONS +fenv_t *_create_default_env(); +#define FE_DFL_ENV _create_default_env() + +// EXCEPTIONS +int feclearexcept(int excepts); +int feraiseexcept(int excepts); +int fegetexceptflag(fexcept_t *flagp, int excepts); +int fesetexceptflag(const fexcept_t *flagp, int excepts); +int fetestexcept(int excepts); + +// ENV +int fegetenv(fenv_t *envp); +int fesetenv(const fenv_t *envp); +int feholdexcept(fenv_t *envp); +int feupdateenv(const fenv_t *envp); + +// ROUNDING +int fegetround(void); +int fesetround(int mode); + +#endif + +// IMPLEMENTATION +#ifdef FENV_IMPL + +#ifdef NO_FENV_INCLUDE +typedef int fexcept_t; +#endif +struct fenv_t +{ + int exception_flags; + int rounding_mode; +}; + +// +// NOT PER THREAD +int __fe_exception_flag = 0; +int __fe_rounding_mode = 0; + +struct fenv_t __fe_env; +struct fenv_t *_create_default_env() +{ + __fe_env.exception_flags = 0; + __fe_env.rounding_mode = 0; + return &__fe_env; +} + +// EXCEPTIONS +int feclearexcept(int excepts) +{ + __fe_exception_flag = __fe_exception_flag & ~excepts; + return 0; +} +int feraiseexcept(int excepts) +{ + __fe_exception_flag = __fe_exception_flag | excepts; + return 0; +} +int fegetexceptflag(fexcept_t *flagp, int excepts) +{ + *flagp = excepts; + return 0; +} +int fesetexceptflag(const fexcept_t *flagp, int excepts) +{ + __fe_exception_flag = __fe_exception_flag | (*flagp) & excepts; + return 0; +} +int fetestexcept(int excepts) +{ + return __fe_exception_flag & excepts; +} + +// ENV +int fegetenv(struct fenv_t *envp) +{ + envp->exception_flags = __fe_exception_flag; + envp->rounding_mode = __fe_rounding_mode; + return 0; +} +int fesetenv(const struct fenv_t *envp) +{ + __fe_exception_flag = envp->exception_flags; + __fe_rounding_mode = envp->rounding_mode; + return 0; +} +int feholdexcept(struct fenv_t *envp) +{ + fegetenv(envp); + + __fe_exception_flag = 0; + __fe_rounding_mode = 0; + + return 0; +} +int feupdateenv(const struct fenv_t *envp) +{ + fesetenv(envp); + // raise the exceptions ? + return 0; +} + +// ROUNDING +int fegetround(void) +{ + return __fe_rounding_mode; +} +void _js_fesetround(int mode); +int fesetround(int mode) +{ + __fe_rounding_mode = mode; + _js_fesetround(mode); + return 0; +} +#endif +#endif \ No newline at end of file diff --git a/libc/include/math.h b/libc/include/math.h new file mode 100644 index 0000000..acb86e7 --- /dev/null +++ b/libc/include/math.h @@ -0,0 +1,239 @@ +#ifndef _INC_MATH +#define _INC_MATH + +#include // wasm needs the export + +#define INFINITY (1.0 / 0.0) +#define NAN (0.0 / 0.0) +#define HUGE_VAL (double)INFINITY +#define HUGE_VALF INFINITY +#define HUGE_VALL (long double)INFINITY + +#define FP_ILOGB0 -2147483648 +#define FP_ILOGBNAN -2147483648 + +#define FP_FAST_FMA 1 +#define FP_FAST_FMAF 1 +#define FP_FAST_FMAL 1 + +#define FP_INFINITE 0x01 +#define FP_NAN 0x02 +#define FP_NORMAL 0x04 +#define FP_SUBNORMAL 0x08 // https://stackoverflow.com/questions/8341395/what-is-a-subnormal-floating-point-number +#define FP_ZERO 0x10 + +// CLASSIFICATION +#define fpclassify(arg) \ + (arg == 0 ? FP_ZERO : (isnan(arg) ? FP_NAN : (isinf(arg) ? FP_INFINITE : ((arg > 0 ? arg : -arg) < 1 * 2 ^ (-126)) ? FP_SUBNORMAL \ + : FP_NORMAL))) +#define isinf(arg) ((float)arg == INFINITY) +#define isfinite(arg) !isinf(arg) +#define isnan(arg) (arg != arg) +#define isnormal(arg) (!isnan(arg) && isfinite(arg) && (arg > 0 ? arg : -arg) >= 1 * 2 ^ (-126)) +#define signbit(arg) (isnan(arg) ? 1 : arg < 0) + +// COMPARISON +#define isgreater(x, y) (x > y) +#define isgreaterequal(x, y) (x >= y) +#define isless(x, y) (x < y) +#define islessequal(x, y) (x <= y) +#define islessgreater(x, y) (x < y || x > y) +#define isunordered(x, y) (isnan(x) || isnan(y)) + +// FUNCTIONS +double atan(double); +double cos(double); +double sin(double); +double tan(double); +double tanh(double); +double frexp(double, int *); +double modf(double, double *); +double ceil(double); +double fabs(double); +double floor(double); + +double acos(double); +double asin(double); +double atan2(double, double); +double cosh(double); +double sinh(double); +double exp(double); +double ldexp(double, int); +double log(double); +double log10(double); +double pow(double, double); +double sqrt(double); +double fmod(double, double); + +double infinity(void); +double nan(const char *); +double copysign(double, double); +double logb(double); +int ilogb(double); + +double asinh(double); +double cbrt(double); +double nextafter(double, double); +double rint(double); +double scalbn(double, int); + +double exp2(double); +double scalbln(double, long int); +double tgamma(double); +double nearbyint(double); +long int lrint(double); +long long int llrint(double); +double round(double); +long int lround(double); +long long int llround(double); +double trunc(double); +double remquo(double, double, int *); +double fdim(double, double); +double fmax(double, double); +double fmin(double, double); +double fma(double, double, double); + +double log1p(double); +double expm1(double); + +double acosh(double); +double atanh(double); +double remainder(double, double); +double gamma(double); +double lgamma(double); +double erf(double); +double erfc(double); +double log2(double); + +double hypot(double, double); + +// Single Precision +float atanf(float); +float cosf(float); +float sinf(float); +float tanf(float); +float tanhf(float); +float frexpf(float, int *); +float modff(float, float *); +float ceilf(float); +float fabsf(float); +float floorf(float); + +float acosf(float); +float asinf(float); +float atan2f(float, float); +float coshf(float); +float sinhf(float); +float expf(float); +float ldexpf(float, int); +float logf(float); +float log10f(float); +float powf(float, float); +float sqrtf(float); +float fmodf(float, float); + +float exp2f(float); +float scalblnf(float, long int); +float tgammaf(float); +float nearbyintf(float); +long int lrintf(float); +long long int llrintf(float); +float roundf(float); +long int lroundf(float); +long long int llroundf(float); +float truncf(float); +float remquof(float, float, int *); +float fdimf(float, float); +float fmaxf(float, float); +float fminf(float, float); +float fmaf(float, float, float); + +float infinityf(void); +float nanf(const char *); +float copysignf(float, float); +float logbf(float); +int ilogbf(float); + +float asinhf(float); +float cbrtf(float); +float nextafterf(float, float); +float rintf(float); +float scalbnf(float, int); +float log1pf(float); +float expm1f(float); + +float acoshf(float); +float atanhf(float); +float remainderf(float, float); +float gammaf(float); +float lgammaf(float); +float erff(float); +float erfcf(float); +float log2f(float); +float hypotf(float, float); + +// long double (js has no support for this but is like a define over the double variant) +long double atanl(long double); +long double cosl(long double); +long double sinl(long double); +long double tanl(long double); +long double tanhl(long double); +long double frexpl(long double, int *); +long double modfl(long double, long double *); +long double ceill(long double); +long double fabsl(long double); +long double floorl(long double); +long double log1pl(long double); +long double expm1l(long double); + +long double acosl(long double); +long double asinl(long double); +long double atan2l(long double, long double); +long double coshl(long double); +long double sinhl(long double); +long double expl(long double); +long double ldexpl(long double, int); +long double logl(long double); +long double log10l(long double); +long double powl(long double, long double); +long double sqrtl(long double); +long double fmodl(long double, long double); +long double hypotl(long double, long double); + +long double copysignl(long double, long double); +long double nanl(const char *); +int ilogbl(long double); +long double asinhl(long double); +long double cbrtl(long double); +long double nextafterl(long double, long double); +float nexttowardf(float, long double); +double nexttoward(double, long double); +long double nexttowardl(long double, long double); +long double logbl(long double); +long double log2l(long double); +long double rintl(long double); +long double scalbnl(long double, int); +long double exp2l(long double); +long double scalblnl(long double, long); +long double tgammal(long double); +long double nearbyintl(long double); +long int lrintl(long double); +long long int llrintl(long double); +long double roundl(long double); +long lroundl(long double); +long long int llroundl(long double); +long double truncl(long double); +long double remquol(long double, long double, int *); +long double fdiml(long double, long double); +long double fmaxl(long double, long double); +long double fminl(long double, long double); +long double fmal(long double, long double, long double); + +long double acoshl(long double); +long double atanhl(long double); +long double remainderl(long double, long double); +long double lgammal(long double); +long double erfl(long double); +long double erfcl(long double); + +#endif // _INC_MATH diff --git a/examples/tsoding_snake/stb_sprintf.h b/libc/include/stb_sprintf.h similarity index 99% rename from examples/tsoding_snake/stb_sprintf.h rename to libc/include/stb_sprintf.h index ca432a6..c96bc40 100644 --- a/examples/tsoding_snake/stb_sprintf.h +++ b/libc/include/stb_sprintf.h @@ -206,9 +206,9 @@ typedef char *STBSP_SPRINTFCB(const char *buf, void *user, int len); #endif STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va); -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsnprintf)(char *buf, int count, char const *fmt, va_list va); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsnprintf)(char *buf, size_t count, char const *fmt, va_list va); STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(2,3); -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(3,4); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(snprintf)(char *buf, size_t count, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(3,4); STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va); STBSP__PUBLICDEC void STB_SPRINTF_DECORATE(set_separators)(char comma, char period); @@ -1427,7 +1427,7 @@ static char * stbsp__count_clamp_callback( const char * buf, void * user, int le return c->tmp; // go direct into buffer if you can } -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE( vsnprintf )( char * buf, int count, char const * fmt, va_list va ) +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE( vsnprintf )( char * buf, size_t count, char const * fmt, va_list va ) { stbsp__context c; @@ -1457,7 +1457,7 @@ STBSP__PUBLICDEF int STB_SPRINTF_DECORATE( vsnprintf )( char * buf, int count, c return c.length; } -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(snprintf)(char *buf, size_t count, char const *fmt, ...) { int result; va_list va; @@ -1903,4 +1903,4 @@ AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ -*/ +*/ \ No newline at end of file diff --git a/libc/include/stdio.h b/libc/include/stdio.h new file mode 100644 index 0000000..51d8ce4 --- /dev/null +++ b/libc/include/stdio.h @@ -0,0 +1,775 @@ +#ifndef INC_STDIO +#define INC_STDIO + +#ifndef NO_STDIO_INCLUDE + +// IMPORTED FNS +#define SIMPLE_PRINT +void _print_string(const char *); + +// DEFINES +#include +#include +#include +#define BUFSIZ 512 +#define EOF (-1) +#define FILENAME_MAX 128 +#define FOPEN_MAX 16 +#define L_tmpnam 0 +#define TMP_MAX 0 + +#define _IOFBF 0 +#define _IOLBF 1 +#define _IONBF 2 + +#define SEEK_CUR 0 +#define SEEK_END 1 +#define SEEK_SET 2 + +// DECLARATIONS +typedef struct FILE FILE; +typedef size_t fpos_t; + +// STRING PRINT +#define STB_SPRINTF_DECORATE(name) name +#include "stb_sprintf.h" +#undef STB_SPRINTF_DECORATE + +// PRINT +int fprintf(FILE *, const char *, ...); +int printf(const char *, ...); +int vfprintf(FILE *, const char *, va_list); +int vprintf(const char *, va_list); + +// SCAN +int fscanf(FILE *, const char *, ...); +int scanf(const char *, ...); +int vfscanf(FILE *, const char *, va_list); +int vscanf(const char *, va_list); +int sscanf(const char *, const char *, ...); +int vsscanf(const char *, const char *, va_list); + +// OPEN/CLOSE +int remove(const char *filename); +int rename(const char *oldname, const char *newname); +FILE *tmpfile(void); +char *tmpnam(char *); +FILE *fopen(const char *name, const char *mode); +FILE *freopen(const char *name, const char *mode, FILE *stream); +int fclose(FILE *stream); + +// BUFFER FNS +int fflush(FILE *stream); +void setbuf(FILE *stream, char *buffer); +void setvbuf(FILE *stream, char *buffer, int mode, size_t size); + +// CHARACTER INPUT/OUTPUT +int fgetc(FILE *stream); +char *fgets(char *str, int max_size, FILE *stream); +int fputc(int character, FILE *stream); +int fputs(const char *str, FILE *stream); +int getc(FILE *stream); +int getchar(); +char *gets(char *str); +int putc(int character, FILE *stream); +int putchar(int character); +int puts(const char *str, FILE *stream); +int ungetc(int character, FILE *stream); + +// DIRECT INPUT/OUTPUT +size_t fread(void *buffer, size_t size, size_t count, FILE *stream); +size_t fwrite(const void *buffer, size_t size, size_t count, FILE *stream); + +// FILE POSITIONING +int fgetpos(FILE *stream, fpos_t *pos); +int fseek(FILE *stream, long int offset, int origin); +int fsetpos(FILE *stream, const fpos_t *pos); +long int ftell(FILE *stream); +void rewind(FILE *stream); + +// ERROR HANDLING +void clearerr(FILE *stream); +int feof(FILE *stream); +int ferror(FILE *stream); +void perror(const char *buf); + +#endif + +#define STDIO_IMPL +#ifdef STDIO_IMPL + +#include +#include +#include +#include +#include +#include +#include +#include + +// TYPES + +struct FILE +{ + bool used; + bool error; + bool eof; + + bool ready; // true: files was fetched or is std stream + bool is_writable; + bool is_readable; + + size_t buf_index; + char *buf; // if file is not stdout or stderr buf is assumed to contain the whole file + size_t buf_size; + unsigned char mode; + bool own_buf; +}; + +bool __files_were_init = false; +FILE __files[FOPEN_MAX]; + +// STD FILES +char __stderr_buf[BUFSIZ + 1]; +FILE __stderr_hold = { + .used = true, + .error = false, + .eof = false, + .ready = true, + .is_writable = true, + .is_readable = false, + .buf_index = 0, + .buf = __stderr_buf, + .buf_size = BUFSIZ, + .mode = _IOFBF, + .own_buf = false, +}; +FILE *stderr = &__stderr_hold; + +char __stdout_buf[BUFSIZ + 1]; +FILE __stdout_hold = { + .used = true, + .error = false, + .eof = false, + .ready = true, + .is_writable = true, + .is_readable = false, + .buf_index = 0, + .buf = __stdout_buf, + .buf_size = BUFSIZ, + .mode = _IOFBF, + .own_buf = false, +}; +FILE *stdout = &__stdout_hold; + +char __stdin_buf[BUFSIZ + 1]; +FILE __stdin_hold = { + .used = true, + .error = true, + .eof = true, + .ready = false, + .is_writable = false, + .is_readable = false, + .buf_index = 0, + .buf = NULL, + .buf_size = BUFSIZ, + .mode = _IOFBF, + .own_buf = false, +}; +FILE *stdin = &__stdin_hold; + +// BASE FNS +FILE *_init_file(FILE *init) +{ + init->used = true; + init->error = false; + init->eof = false; + + init->ready = false; + init->is_writable = false; + init->is_readable = false; + + init->buf_index = 0; + init->buf = (char *)malloc(BUFSIZ + 1); + init->buf_size = BUFSIZ; + init->mode = _IOFBF; + init->own_buf = true; + + return init; +} +FILE *_create_file() +{ + if (!__files_were_init) + { + for (size_t i = 0; i < FOPEN_MAX; i++) + { + __files[i].used = false; + } + __files_were_init = true; + } + + // Find first free file + size_t found_free = 0; + while (__files[found_free].used && found_free < FOPEN_MAX) + { + found_free++; + } + + if (found_free >= FOPEN_MAX) + { + // Reached max possible file count + return NULL; + } + + return _init_file(__files + found_free); +} +void _set_file_ready(FILE *stream) +{ + stream->ready = true; +} + +// STRING PRINT +#define STB_SPRINTF_IMPLEMENTATION +#define STB_SPRINTF_DECORATE(name) name +#include "stb_sprintf.h" + +// PRINT +char __print_buf[BUFSIZ * 8]; // good enough for everyone ? +int fprintf(FILE *stream, const char *format, ...) +{ + va_list argptr; + va_start(argptr, format); + int res = vsprintf(__print_buf, format, argptr); + va_end(argptr); + + fputs(__print_buf, stream); + + return res; +} +int printf(const char *format, ...) +{ + va_list argptr; + va_start(argptr, format); + int res = vprintf(format, argptr); + va_end(argptr); + + return res; +} +int vfprintf(FILE *stream, const char *format, va_list list) +{ + int res = vsprintf(__print_buf, format, list); + va_end(list); // need? + + fputs(__print_buf, stream); + + return res; +} +int vprintf(const char *format, va_list list) +{ + return vfprintf(stdout, format, list); +} + +// SCAN +int fscanf(FILE *stream, const char *format, ...) +{ + printf("scanf is not implemented"); + return -1; +} +int scanf(const char *format, ...) +{ + printf("scanf is not implemented"); + return -1; +} +int vfscanf(FILE *stream, const char *format, va_list args) +{ + printf("scanf is not implemented"); + return -1; +} +int vscanf(const char *format, va_list args) +{ + printf("scanf is not implemented"); + return -1; +} +int sscanf(const char *src, const char *format, ...) +{ + printf("scanf is not implemented"); + return -1; +} +int vsscanf(const char *src, const char *format, va_list args) +{ + printf("scanf is not implemented"); + return -1; +} + +// OPEN/CLOSE +int remove(const char *filename) +{ + printf("ERROR: Cannot Remove files on server"); + return 0; +} +int rename(const char *oldname, const char *newname) +{ + printf("ERROR: Cannot Rename files on server"); + return 0; +} +FILE *tmpfile(void) +{ + printf("ERROR: Cannot Create a files on server"); + return 0; +} +char *tmpnam(char *filename_buf) +{ + printf("ERROR: Creating temporary filename is not implemented"); + return NULL; +} +size_t open_files_count = 0; +FILE *_append_fetch_promise(const char *name, FILE *optional_file); +FILE *fopen(const char *name, const char *mode) +{ + if (open_files_count >= FOPEN_MAX) + { + // MAX FILES OPEN NUMBER REACHED + return NULL; + } + else if (strcmp(mode, "rb") != 0) + { + printf("ERROR: Only supports mode 'rb'"); + return NULL; + } + + FILE *f = _append_fetch_promise(name, NULL); + f->is_readable = true; + return f; +} +FILE *freopen(const char *name, const char *mode, FILE *stream) +{ + + if (strcmp(mode, "rb") != 0) + { + printf("ERROR: Only supports mode 'rb'"); + return NULL; + } + + _init_file(stream); + + FILE *f = _append_fetch_promise(name, stream); + f->is_readable = true; + return f; +} +int fclose(FILE *stream) +{ + + fflush(stream); + stream->used = false; + stream->ready = false; + + if (stream->own_buf) + { + free(stream->buf); + } + + if (stream == stderr) + { + stderr = NULL; + } + else if (stream == stdout) + { + stdout = NULL; + } + else if (stream == stdin) + { + stdin = NULL; + } + + return 0; +} + +// BUFFER FNS +int fflush(FILE *stream) +{ + + if (stream == NULL) + { + // FLUSH ALL streams with output + fflush(stderr); + fflush(stdout); + + // flush others + for (size_t i = 0; i < sizeof(__files) / sizeof(FILE); i++) + { + if (__files[i].ready) + { + fflush(__files + i); + } + } + } + else + { + stream->buf[stream->buf_index] = '\0'; + _print_string(stream->buf); + stream->buf_index = 0; + } + + return 0; +} +void setbuf(FILE *stream, char *buffer) +{ + + free(stream->buf); + stream->buf_size = BUFSIZ; + stream->buf = buffer; + stream->own_buf = false; +} +void setvbuf(FILE *stream, char *buffer, int mode, size_t size) +{ + + if (stream->own_buf) + { + free(stream->buf); + } + + switch (mode) + { + case _IOFBF: + case _IOLBF: + case _IONBF: + stream->mode = mode; + break; + + default: + assert(0 && "Unknown mode given"); + return; + } + + if (buffer == NULL) + { + if (size <= BUFSIZ) + { + if (stream == stderr) + { + stream->buf = __stderr_buf; + stream->own_buf = false; + } + else if (stream == stdout) + { + stream->buf = __stdout_buf; + stream->own_buf = false; + } + else if (stream == stdin) + { + stream->buf = __stdin_buf; + stream->own_buf = false; + } + else + { + stream->buf = (char *)malloc(size + 1); + stream->own_buf = true; + } + } + else + { + stream->buf = (char *)malloc(size + 1); + stream->own_buf = true; + } + } + else + { + stream->buf = buffer; + stream->own_buf = false; + } + stream->buf_size = size; +} + +// CHARACTER INPUT/OUTPUT +int fgetc(FILE *stream) +{ + + if (stream == stderr || stream == stdout) + { + // Cannot read from stderr or stdout + stream->eof = true; + return EOF; + } + else if (stream == stdin) + { + // How read from stdin? + return EOF; + } + else if (stream->is_readable == true) + { + if (stream->buf_index < stream->buf_size) + { + return stream->buf[stream->buf_index++]; + } + else + { + stream->eof = true; + return EOF; + } + } + + return EOF; +} +char *fgets(char *str, int max_size, FILE *stream) +{ + if (max_size > 0) + { + size_t i = 0; + int c = fgetc(stream); + while (c != EOF && c != '\n' && i < max_size) + { + str[i++] = c; + c = fgetc(stream); + } + str[i] = '\0'; + } + + return str; +} +int fputc(int character, FILE *stream) +{ + + if (stream->is_writable) + { + if (stream->buf_index >= stream->buf_size) + { + fflush(stream); + } + + stream->buf[stream->buf_index++] = (char)character; + + if (character == '\n') + { + fflush(stream); + } + + return character; + } + else + { + printf("ERROR: Cannot put char in read only stream"); + return EOF; + } +} +int fputs(const char *str, FILE *stream) +{ + const size_t len_str = strlen(str); + + for (size_t i = 0; i < len_str; i++) + { + if (fputc(str[i], stream) == EOF) + { + return EOF; + } + } + + return 0; +} +int getc(FILE *stream) +{ + return fgetc(stream); +} +int getchar() +{ + return getc(stdin); +} +char *gets(char *str) +{ + return fgets(str, INT_MAX, stdin); +} +int putc(int character, FILE *stream) +{ + return fputc(character, stream); +} +int putchar(int character) +{ + return fputc(character, stdout); +} +int puts(const char *str, FILE *stream) +{ + return fputs(str, stream); +} +int ungetc(int character, FILE *stream) +{ + printf("ERROR: ungetc not supported"); + return EOF; +} + +// DIRECT INPUT/OUTPUT +size_t fread(void *buffer, size_t size, size_t count, FILE *stream) +{ + unsigned char *buf = (unsigned char *)buffer; + + if (size == 0 || count == 0) + return 0; + + for (size_t i = 0; i < count; i++) + { + for (size_t j = 0; i < size; j++) + { + int res = fgetc(stream); + + if (res == EOF) + return i; + + buf[i + j] = (unsigned char)res; + } + } + + return count; +} +size_t fwrite(const void *buffer, size_t size, size_t count, FILE *stream) +{ + unsigned char *buf = (unsigned char *)buffer; + + if (size == 0 || count == 0) + return 0; + + for (size_t i = 0; i < count; i++) + { + for (size_t j = 0; i < size; j++) + { + int res = fputc((int)buf[i + j], stream); + + if (res == EOF) + return i; + } + } + + return count; +} + +// FILE POSITIONING +int fgetpos(FILE *stream, fpos_t *pos) +{ + + if (stream->is_readable) + { + const long res = ftell(stream); + if (res >= 0) + { + *pos = res; + return 0; + } + return res; + } + else + { + printf("ERROR: fgetpos is not implemented for writable files"); + return -1; + } +} +int fseek(FILE *stream, long int offset, int origin) +{ + + if (stream->is_readable) + { + switch (origin) + { + case SEEK_CUR: + { + if (stream->buf_index + offset > 0 && stream->buf_index + offset < stream->buf_size) + { + stream->buf_index += offset; + } + else + { + return EOF; + } + } + break; + + case SEEK_SET: + { + if (offset > 0 && offset < stream->buf_size) + { + stream->buf_index = offset; + } + else + { + return EOF; + } + } + break; + + case SEEK_END: + return EOF; + + default: + break; + } + return 0; + } + else + { + printf("ERROR: fseek is not implemented for writable files"); + return -1L; + } +} +int fsetpos(FILE *stream, const fpos_t *pos) +{ + + if (stream->is_readable) + { + return fseek(stream, *pos, SEEK_SET); + } + else + { + printf("ERROR: fsetpos is not implemented for writable files"); + return -1; + } +} +long int ftell(FILE *stream) +{ + + if (stream->is_readable) + { + return stream->buf_index; + } + else + { + printf("ERROR: ftell is not implemented for writable files"); + return -1L; + } +} +void rewind(FILE *stream) +{ + + stream->eof = false; + stream->error = false; + + if (stream->is_readable) + { + stream->buf_index = 0; + } + else + { + printf("ERROR: rewind is not implemented for writable files"); + } +} + +// ERROR HANDLING +void clearerr(FILE *stream) +{ + stream->eof = false; + stream->error = false; +} +int feof(FILE *stream) +{ + return stream->eof; +} +int ferror(FILE *stream) +{ + return stream->error; +} +void perror(const char *buf) +{ + if (buf != NULL) + { + fprintf(stderr, "%s: ", buf); + } + fprintf(stderr, "%s\n", strerror(errno)); +} + +#endif +#endif \ No newline at end of file diff --git a/libc/include/stdlib.h b/libc/include/stdlib.h new file mode 100644 index 0000000..5ec528f --- /dev/null +++ b/libc/include/stdlib.h @@ -0,0 +1,485 @@ +#ifndef _INC_STDLIB +#define _INC_STDLIB + +#include +#include +#include + +#ifndef NO_STDLIB_INCLUDE + +// DEFINES +#define EXIT_FAILURE 1 +#define EXIT_SUCCESS 0 +#define MB_CUR_MAX 4 +#define RAND_MAX 2147483647 + +// #define SIMPLE_PRINT +#ifdef SIMPLE_PRINT +void _print_string(const char *); +#endif + +// DECLARATIONS + +// STRING TO NUMBER +double atof(const char *); +int atoi(const char *); +long atol(const char *); +long long atoll(const char *); + +double strtod(const char *, char **); +float strtof(const char *, char **); +long double strtold(const char *, char **); +long strtol(const char *, char **, int); +long long strtoll(const char *, char **, int); +unsigned long strtoul(const char *, char **, int); +unsigned long long strtoull(const char *, char **, int); + +// RANDOM +int rand(void); +void srand(unsigned int); + +// MEMORY +void *malloc(size_t byte_count); +void *calloc(size_t size, size_t element_size); +void free(void *ptr); +void *realloc(void *ptr, size_t new_byte_count); + +// ENVIRONMENT +void abort(void); +void exit(int status); +void quick_exit(int); +int atexit(void (*)(void)); +int at_quick_exit(void (*)(void)); + +// ALGORITHMS +void *bsearch(const void *key, const void *base, size_t num, size_t size, int (*compar)(const void *, const void *)); +void qsort(void *base, size_t num, size_t size, int (*compar)(const void *, const void *)); + +// INTEGER ARITHMETICS +int abs(int); +long labs(long); +long long llabs(long long); + +typedef struct div_t div_t; +typedef struct ldiv_t ldiv_t; +typedef struct lldiv_t lldiv_t; + +div_t div(int a, int b); +ldiv_t ldiv(long a, long b); +lldiv_t lldiv(long long a, long long b); + +// MULTIBYTE CHARACTERS +int mblen(const char *, size_t); +int mbtowc(wchar_t *, const char *, size_t); +int wctomb(char *, wchar_t); + +// MULTIBYTE STRING +size_t mbstowcs(wchar_t *dest, const char *src, size_t max); +size_t wcstombs(char *dest, const wchar_t *src, size_t max); + +#endif + +// IMPLEMENTATION +#ifdef STDLIB_IMPL + +// MEMORY ALLOCATOR +#define MAX_PAGE_COUNT 16 +#define PAGE_SIZE (64 * 1024) + +#define BLOCKS_PER_PAGE 32 +#define MAX_BLOCK_COUNT (MAX_PAGE_COUNT * BLOCKS_PER_PAGE) +#define DEFAULT_SIZE_OF_BLOCK (PAGE_SIZE / BLOCKS_PER_PAGE) + +#define BLOCK_NUM_IN_PTR_TYPE size_t +#define BLOCK_NUM_IN_PTR_SIZE sizeof(BLOCK_NUM_IN_PTR_TYPE) + +unsigned char *_heap_start(); +unsigned char *_grow_memory(unsigned size); + +unsigned __page_block_count[MAX_PAGE_COUNT]; +size_t __used_page_count = 0; +unsigned char *__last_page_start = 0; +unsigned char *__last_page_end = 0; + +size_t __used_block_start = 0; +size_t __block_size[MAX_BLOCK_COUNT]; +unsigned char *__block_start[MAX_BLOCK_COUNT]; +size_t __block_page_i[MAX_BLOCK_COUNT]; +bool __block_used[MAX_BLOCK_COUNT]; + +size_t _next_unused_block(size_t start) +{ + for (unsigned i = start; i < MAX_BLOCK_COUNT; i++) + { + if (!__block_used[i]) + return i; + } + + return MAX_BLOCK_COUNT; +} +void _add_pages_for(size_t byte_count) +{ + unsigned page_count; + unsigned char *page_start; + size_t pages_size; + + if (__used_page_count == 0) + { + page_start = _heap_start(); + const size_t heap_start_num = (size_t)page_start; + const size_t page_size_left = PAGE_SIZE - (heap_start_num % PAGE_SIZE); + page_count = page_size_left < byte_count ? ceil((double)(byte_count - page_size_left) / PAGE_SIZE) : 0; + if (page_count != 0) + { + _grow_memory(page_count); + } + pages_size = page_size_left + page_count * PAGE_SIZE; + page_count++; + } + else + { + page_count = ceil((double)byte_count / PAGE_SIZE); + page_start = _grow_memory(page_count); + pages_size = page_count * PAGE_SIZE; + } + + for (unsigned i = 0; i < page_count * BLOCKS_PER_PAGE; i++) + { + __block_start[__used_block_start + i] = page_start + i * DEFAULT_SIZE_OF_BLOCK; + __block_size[__used_block_start + i] = DEFAULT_SIZE_OF_BLOCK; + __block_page_i[__used_block_start + i] = i / BLOCKS_PER_PAGE; + __block_used[__used_page_count + i] = false; + } + __used_block_start += page_count * BLOCKS_PER_PAGE; + + for (unsigned i = 0; i < page_count; i++) + { + __page_block_count[__used_page_count + i] = BLOCKS_PER_PAGE; + } + __used_page_count += page_count; + + __last_page_start = page_start; + __last_page_end = page_start + pages_size; +} +size_t _find_block(size_t start, size_t byte_count, long long end) +{ + // end == -1 : do not add new page & just end with return MAX_BLOCK_COUNT + // end <= -2 : use default end of MAX_BLOCK_COUNT + size_t MAX = end > MAX_BLOCK_COUNT ? end : MAX_BLOCK_COUNT; + + size_t unused = _next_unused_block(start); + if (unused >= MAX) + { + if (end != -1) + { + _add_pages_for(byte_count); + unused = _next_unused_block(start); + } + } + while (unused < MAX) + { + unsigned int bs = __block_size[unused]; + + // block does not fit + if (bs < byte_count) + { + // Try to create block with large enough size + unsigned size = bs; + unsigned i = 1; + while (!__block_used[unused + i] && unused + i < MAX && size < byte_count) + { + size += __block_size[unused + i]; + i++; + } + + if (size >= byte_count) + { + __block_size[unused] = size; + + for (unsigned j = 1; j < i; j++) + { + __block_size[unused + j] = 0; + __block_start[unused + j] = 0; + __page_block_count[__block_page_i[unused + j]]--; + } + bs = size; + } + } + + // Found fitting block || new blocks were added + if (bs >= byte_count) + { + if (unused > 0 && !__block_used[unused - 1] && __block_size[unused - 1] == 0) // block unused & not assigned + { + unsigned split_i = unused - 1; + __block_start[split_i] = __block_start[unused]; + __block_size[split_i] = byte_count; + __block_used[split_i] = true; + + __block_start[unused] += byte_count; + __block_size[unused] -= byte_count; + + return split_i; + } + else if (unused + 1 < MAX && !__block_used[unused + 1] && __block_size[unused + 1] == 0) + { + unsigned split_i = unused + 1; + __block_start[split_i] = __block_start[unused] + byte_count; + __block_size[split_i] = __block_size[unused]; + + __block_size[unused] = byte_count; + + return unused; + } + return unused; // cannot split blocks + } + + unused = _next_unused_block(start); + if (unused >= MAX) + { + if (end != -1) + { + _add_pages_for(byte_count); + unused = _next_unused_block(start); + } + } + } + + return MAX_BLOCK_COUNT; +} + +void *malloc(size_t byte_count) +{ + if (__used_page_count == 0) + { + _add_pages_for(byte_count); + } + + size_t block = _find_block(0, byte_count + BLOCK_NUM_IN_PTR_SIZE, -2); + + if (block < MAX_BLOCK_COUNT) + { + // Store block number at start + ((BLOCK_NUM_IN_PTR_TYPE *)(__block_start[block]))[0] = block; + __block_used[block] = true; + return __block_start[block] + BLOCK_NUM_IN_PTR_SIZE; // give ptr to 4 byte after start (after block num) + } + else + { + return (void *)0; + } +} +void *calloc(size_t size, size_t element_size) +{ + return malloc(size * element_size); +} +void free(void *ptr) +{ + unsigned block = ((unsigned *)ptr)[-1]; // get block number from before ptr + + if (0 <= block && block < MAX_BLOCK_COUNT) + { + if (__block_start[block] == (unsigned char *)ptr - 4) + { + __block_used[block] = false; + } + else + { +#ifdef SIMPLE_PRINT + _print_string("ERROR: Segmentation fault: Invalid ptr given to free"); +#endif + } + } + else + { +#ifdef SIMPLE_PRINT + _print_string("ERROR: Index out of bounds: Invalid ptr given to free"); +#endif + } +} + +void *memcpy(void *, const void *, size_t); +void *memmove(void *, const void *, size_t); + +void *realloc(void *ptr, size_t new_byte_count) +{ + if (ptr == NULL) + { + return malloc(new_byte_count); + } + + unsigned block = ((unsigned *)ptr)[-1]; // get block number from before ptr + + if (0 <= block && block < MAX_BLOCK_COUNT) + { + if (__block_start[block] != (unsigned char *)ptr - 4) + { +#ifdef SIMPLE_PRINT + _print_string("ERROR: Segmentation fault: Invalid ptr given to realloc"); +#endif + return (void *)0; + } + } + else + { +#ifdef SIMPLE_PRINT + _print_string("ERROR: Index out of bounds: Invalid ptr given to realloc"); +#endif + return (void *)0; + } + + size_t old___block_size = __block_size[block]; + unsigned char *old___block_start = __block_start[block]; + + if (__block_size[block] < new_byte_count + BLOCK_NUM_IN_PTR_SIZE) + { + __block_used[block] = false; // mark temporary as unused + + size_t new_block = _find_block(block, new_byte_count + BLOCK_NUM_IN_PTR_SIZE, -1); + if (new_block >= MAX_BLOCK_COUNT) + { + new_block = _find_block(0, new_byte_count + BLOCK_NUM_IN_PTR_SIZE, block); + } + if (new_block >= MAX_BLOCK_COUNT) + { + new_block = _find_block(block, new_byte_count + BLOCK_NUM_IN_PTR_SIZE, -2); + } + + if (block < new_block || (block > new_block && __block_size[block] != 0)) + { + // block is before new block + // or old block was after but was not overwritten + memcpy(__block_start[new_block], old___block_start, old___block_size); + __block_used[new_block] = true; + ((BLOCK_NUM_IN_PTR_TYPE *)(__block_start[block]))[0] = new_block; + return __block_start[new_block] + BLOCK_NUM_IN_PTR_SIZE; + } + else if (block > new_block) + { + // old block is in new block + memmove(__block_start[new_block], old___block_start, old___block_size); + __block_used[new_block] = true; + ((BLOCK_NUM_IN_PTR_TYPE *)(__block_start[block]))[0] = new_block; + return __block_start[new_block] + BLOCK_NUM_IN_PTR_SIZE; + } + else // == + { + __block_used[block] = true; + return __block_start[block] + BLOCK_NUM_IN_PTR_SIZE; + } + } + else if (__block_size[block] > new_byte_count + BLOCK_NUM_IN_PTR_SIZE) + { + return __block_start[block] + BLOCK_NUM_IN_PTR_SIZE; + } + else + { + return __block_start[block] + BLOCK_NUM_IN_PTR_SIZE; + } +} + +// ALGORITHMS +// https://cplusplus.com/reference/cstdlib/bsearch/ +void *bsearch(const void *key, const void *base, size_t num, size_t size, int (*compar)(const void *, const void *)) +{ + size_t start = 0; + size_t end = num; + size_t middle = num / 2; + const unsigned char *ptr = (const unsigned char *)base + (middle * size); + int cmp = compar(key, ptr); + while (cmp != 0) + { + if (cmp < 0) + { + end = middle - 1; + } + else + { + start = middle + 1; + } + + middle = start + (end - start) / 2; + ptr = (const unsigned char *)base + (middle * size); + cmp = compar(key, ptr); + } + + return (void *)ptr; +} +// https://cplusplus.com/reference/cstdlib/qsort/ +void qsort(void *base, size_t num, size_t size, int (*compar)(const void *, const void *)) +{ + unsigned char *tmp = (unsigned char *)malloc(size); + unsigned char *base_c = (unsigned char *)base; + + for (size_t i = 1; i < num; i++) + { + size_t j = 0; + + // while want to insert is larger + int cmp = compar(base_c + i * size, base_c + j * size); + while (cmp >= 0) + { + j++; + cmp = compar(base_c + i * size, base_c + j * size); + } + + // swap + for (size_t k = 0; k < size; k++) + { + tmp[k] = base_c[i + k]; + base_c[i + k] = base_c[j + k]; + base_c[j + k] = tmp[k]; + } + break; + } + + free(tmp); +} + +// INTEGER ARITHMETICS +struct div_t +{ + int quot; + int rem; +}; +struct ldiv_t +{ + long quot; + long rem; +}; +struct lldiv_t +{ + long long quot; + long long rem; +}; + +struct div_t div(int a, int b) +{ + const int quot = a / b; + const int rem = a - (quot * b); + struct div_t res; + res.quot = quot; + res.rem = rem; + return res; +}; +struct ldiv_t ldiv(long a, long b) +{ + const long quot = a / b; + const long rem = a - (quot * b); + struct ldiv_t res; + res.quot = quot; + res.rem = rem; + return res; +} +struct lldiv_t lldiv(long long a, long long b) +{ + const long long quot = a / b; + const long long rem = a - (quot * b); + struct lldiv_t res; + res.quot = quot; + res.rem = rem; + return res; +} + +#endif +#endif \ No newline at end of file diff --git a/libc/include/string.h b/libc/include/string.h new file mode 100644 index 0000000..1eef6ed --- /dev/null +++ b/libc/include/string.h @@ -0,0 +1,548 @@ +#ifndef _INC_STRING +#define _INC_STRING + +#include +#include + +#ifndef NO_STRING_INCLUDE + +// DEFINES +#include + +#ifdef SIMPLE_PRINT +void _print_string(const char *); +#endif + +// INCLUDES + +// MEMORY +void *memchr(const void *ptr, int value, size_t num); +int memcmp(const void *ptr1, const void *ptr2, size_t num); +void *memcpy(void *dest, const void *src, size_t size); +void *memmove(void *dest, const void *src, size_t num); +void *memset(void *ptr, int value, size_t num); + +// STRING +size_t strlen(const char *str); +char *strcpy(char *dest, const char *src); +char *strcat(char *dest, const char *src); +char *strchr(const char *str, int character); +int strcmp(const char *str1, const char *str2); +int strcoll(const char *str1, const char *str2); +size_t strcspn(const char *str1, const char *str2); +char *strerror(int errnum); +char *strncat(char *dest, const char *src, size_t num); +int strncmp(const char *str1, const char *str2, size_t num); +char *strncpy(char *dest, const char *src, size_t num); +char *strpbrk(const char *str1, const char *str2); +char *strrchr(const char *str, int character); +size_t strspn(const char *str1, const char *str2); +char *strstr(const char *str1, const char *str2); +char *strtok(char *str, const char *delimiters); +size_t strxfrm(char *dest, const char *src, size_t num); + +#endif + +// IMPLEMENTATIONS +#ifdef STRING_IMPL + +// MEMORY +void *memchr(const void *ptr, int value, size_t num) +{ + unsigned char *dest_cast = (unsigned char *)ptr; + for (size_t i = 0; i < num; i++) + { + if (dest_cast[i] == (unsigned char)value) + { + return (unsigned char *)ptr + i; + } + } + + return (void *)0; +} +int memcmp(const void *ptr1, const void *ptr2, size_t num) +{ + unsigned char *ptr1_cast = (unsigned char *)ptr1; + unsigned char *ptr2_cast = (unsigned char *)ptr2; + for (size_t i = 0; i < num; i++) + { + if (ptr1_cast[i] < ptr2_cast[i]) + { + return -1; + } + else if (ptr1_cast[i] > ptr2_cast[i]) + { + return 1; + } + } + + return 0; +} +void *memcpy(void *dest, const void *src, size_t size) +{ + unsigned char div8_leftover = size % sizeof(unsigned long long); + unsigned long size64 = (size - div8_leftover) / sizeof(unsigned long long); + + unsigned long long *dest_cast_8 = (unsigned long long *)dest; + unsigned long long *src_cast_8 = (unsigned long long *)src; + for (size_t i = 0; i < size64; i++) + { + dest_cast_8[i] = src_cast_8[i]; + } + + unsigned char *dest_cast = (unsigned char *)dest; + unsigned char *src_cast = (unsigned char *)src; + for (unsigned char i = 0; i < div8_leftover; i++) + { + dest_cast[i] = src_cast[i]; + } + + return dest; +} +void *memmove(void *dest, const void *src, size_t num) +{ + unsigned char *src_buff = (unsigned char *)malloc(num); + memcpy(src_buff, (void *)src, num); + + unsigned char *dest_cast = (unsigned char *)dest; + for (unsigned char i = 0; i < num; i++) + { + dest_cast[i] = src_buff[i]; + } + + free(src_buff); + return dest; +} +void *memset(void *ptr, int value, size_t num) +{ + unsigned char *dest_cast = (unsigned char *)ptr; + for (size_t i = 0; i < num; i++) + { + dest_cast[i] = (unsigned char)value; + } + + return ptr; +} + +// STRING +size_t strlen(const char *str) +{ + size_t size = 0; + while (str[size] != '\0') + { + size++; + } + return size; +} +char *strcpy(char *dest, const char *src) +{ + size_t len_src = strlen(src); + memcpy(dest, (void *)src, len_src); + return dest; +} +char *strcat(char *dest, const char *src) +{ + size_t len_dest = strlen(dest); + strcpy(dest + len_dest, src); + return dest; +} +char *strchr(const char *str, int character) +{ + size_t len_str = strlen(str); + + if (character == '\0') + return (char *)str + len_str; + + return (char *)memchr((void *)str, character, len_str); +} +int strcmp(const char *str1, const char *str2) +{ + size_t len_str1 = strlen(str1); + size_t len_str2 = strlen(str2); + + size_t lower_len = len_str1 < len_str2 ? len_str1 : len_str2; + return memcmp((void *)str1, (void *)str2, lower_len); +} +int strcoll(const char *str1, const char *str2) +{ +// TODO: Implement with c locale +#ifdef SIMPLE_PRINT + _print_string("strcoll not yet implemented"); +#endif + return 0; +} +size_t strcspn(const char *str1, const char *str2) +{ + size_t len_str2 = strlen(str2); + + size_t index = 0; + while (str1[index] != 0) + { + char st1_char = str1[index]; + for (size_t i = 0; i < len_str2 + 1; i++) + { + if (st1_char == str2[i]) + { + return index; + } + } + } + + return strlen(str1); // unreachable +} +char *strerror(int errnum) +{ + switch (errnum) + { + case EAFNOSUPPORT: + return (char *)"Address family is not supported"; + case EADDRINUSE: + return (char *)"Address already in use"; + case EADDRNOTAVAIL: + return (char *)"Address not available"; + case EISCONN: + return (char *)"Already connected"; + case E2BIG: + return (char *)"Argument list too long"; + case EDOM: + return (char *)"Argument out of domain"; + case EFAULT: + return (char *)"Bad Address"; + case EBADF: + return (char *)"Bad filter descriptor"; + case EBADMSG: + return (char *)"Bad message"; + case EPIPE: + return (char *)"Broken pipe"; + case ECONNABORTED: + return (char *)"Connection aborted"; + case EALREADY: + return (char *)"Connection already in progress"; + case ECONNREFUSED: + return (char *)"Connection refused"; + case ECONNRESET: + return (char *)"Connection reset"; + case EXDEV: + return (char *)"Cross device link"; + case EDESTADDRREQ: + return (char *)"Destination address required"; + case EBUSY: + return (char *)"Devicec or resource busy"; + case ENOTEMPTY: + return (char *)"Directory not empty"; + case ENOEXEC: + return (char *)"Executable format error"; + case EEXIST: + return (char *)"File already exists"; + case EFBIG: + return (char *)"File too large"; + case ENAMETOOLONG: + return (char *)"Filename too long"; + case ENOSYS: + return (char *)"Function not supported"; + case EHOSTUNREACH: + return (char *)"Host is unreachable"; + case EIDRM: + return (char *)"Identifier removed"; + case EILSEQ: + return (char *)"Illegal byte sequence"; + case ENOTTY: + return (char *)"Inappropriate IO control operation"; + case EINTR: + return (char *)"Interrupted"; + case EINVAL: + return (char *)"Invalid argument"; + case ESPIPE: + return (char *)"Invalid seek"; + case EIO: + return (char *)"IO error"; + case EISDIR: + return (char *)"Is a directory"; + case EMSGSIZE: + return (char *)"Message size"; + case ENETDOWN: + return (char *)"Network down"; + case ENETRESET: + return (char *)"Network reset"; + case ENETUNREACH: + return (char *)"Network unreachable"; + case ENOBUFS: + return (char *)"No buffer space"; + case ECHILD: + return (char *)"No child process"; + case ENOLINK: + return (char *)"No link"; + // case ENOLOCK: + // return (char *)"No lock available"; + case ENOMSG: + return (char *)"No message"; + case ENODATA: + return (char *)"No message available"; + case ENOPROTOOPT: + return (char *)"No protocol option"; + case ENOSPC: + return (char *)"No space on device"; + case ENOSR: + return (char *)"No stream resources"; + case ENODEV: + return (char *)"No such device"; + case ENXIO: + return (char *)"No such device or address"; + case ENOENT: + return (char *)"No such file or directory"; + case ESRCH: + return (char *)"No such proccess"; + case ENOTDIR: + return (char *)"Not a directory"; + case ENOTSOCK: + return (char *)"Not a socket"; + case ENOSTR: + return (char *)"Not a stream"; + case ENOTCONN: + return (char *)"Not connected"; + case ENOMEM: + return (char *)"Not enough memory"; + case ENOTSUP: + return (char *)"Not supported"; + case ECANCELED: + return (char *)"Operation canceled"; + case EINPROGRESS: + return (char *)"Operation in progress"; + case EPERM: + return (char *)"Operation was not permitted"; + case EOPNOTSUPP: + return (char *)"Operation not supported"; + case EWOULDBLOCK: + return (char *)"Operation would block"; + case EOWNERDEAD: + return (char *)"Owner dead"; + case EACCES: + return (char *)"Permission denied"; + case EPROTO: + return (char *)"Protocol error"; + case EPROTONOSUPPORT: + return (char *)"Protocol not supported"; + case EROFS: + return (char *)"Read only file system"; + case EDEADLK: + return (char *)"Resource deadlock would occur"; + case EAGAIN: + return (char *)"Resource unavailable try again"; + case ERANGE: + return (char *)"Result out of range"; + case ENOTRECOVERABLE: + return (char *)"State not recoverable"; + case ETIME: + return (char *)"Stream timeout"; + case ETXTBSY: + return (char *)"Text file busy"; + case ETIMEDOUT: + return (char *)"Timed out"; + case EMFILE: + return (char *)"Too many files open"; + case ENFILE: + return (char *)"Too many files open in system"; + case EMLINK: + return (char *)"Too many links"; + case ELOOP: + return (char *)"Too many symbolic link levels"; + case EOVERFLOW: + return (char *)"Value too large"; + case EPROTOTYPE: + return (char *)"Wrong protocol type"; + + default: + return (char *)"UNKNOWN ERROR CODE"; + } +} +char *strncat(char *dest, const char *src, size_t num) +{ + size_t len_dest = strlen(dest); + size_t len_src = strlen(src); + if (len_src > num) + { + len_src = num; + } + + memcpy(dest + len_dest, (void *)src, len_src); + dest[len_src] = '\0'; + + return dest; +} +int strncmp(const char *str1, const char *str2, size_t num) +{ + const size_t len_str1 = strlen(str1); + const size_t len_str2 = strlen(str2); + size_t len = len_str1 < len_str2 ? len_str1 : len_str2; + len = len < num ? len : num; + + return memcmp(str1, str2, len); +} +char *strncpy(char *dest, const char *src, size_t num) +{ + const size_t len_src = strlen(src); + const size_t len = len_src < num ? len_src : num; + + memcpy(dest, src, len); + + if (len_src < num) + { + memset(dest + len, 0, num - len_src); + } + + return dest; +} +char *strpbrk(const char *str1, const char *str2) +{ + const size_t len_str2 = strlen(str2); + + size_t i = 0; + while (str1[i] != '\0') + { + for (size_t j = 0; j < len_str2; j++) + { + if (str1[i] == str2[j]) + { + return (char *)(str1 + i); + } + } + i++; + } + + return (char *)NULL; +} +char *strrchr(const char *str, int character) +{ + if (character == 0) + { + return (char *)(str + strlen(str)); + } + else + { + size_t last_index = 0; + bool found = false; + + size_t i = 0; + while (str[i] != '\0') + { + if (str[i] == (char)character) + { + found = true; + last_index = i; + } + i++; + } + + if (found) + { + return (char *)(str + last_index); + } + } + + return (char *)NULL; +} +size_t strspn(const char *str1, const char *str2) +{ + const size_t len_str2 = strlen(str2); + + size_t i = 0; + while (str1[i] != '\0') + { + bool not_found = true; + for (size_t j = 0; j < len_str2; j++) + { + if (str1[i] == str2[j]) + { + not_found = false; + break; + } + } + + if (not_found) + { + return i; + } + i++; + } + + return i; +} +char *strstr(const char *str1, const char *str2) +{ + if (str2[0] == '\0') + { + return (char *)str1; + } + + const size_t len_str1 = strlen(str1); + const size_t len_str2 = strlen(str2); + + if (len_str2 > len_str1) + { + return (char *)NULL; + } + + for (size_t i = 0; i < len_str1 + 1 - len_str2; i++) + { + bool found = true; + for (size_t j = 0; j < len_str2; j++) + { + if (str1[i + j] != str2[j]) + { + found = false; + } + } + + if (found) + { + return (char *)(str1 + i); + } + } + + return (char *)NULL; +} +char *strtok(char *str, const char *delimiters) +{ + static char *token_string_start = (char *)NULL; + static bool end_found = true; + + if (end_found) + { + return (char *)NULL; + } + + if (str != (char *)NULL) + { + token_string_start = str; + end_found = false; + } + + size_t span_with_delimiters = strspn(token_string_start, delimiters); + token_string_start += span_with_delimiters; + + char *first_occurrence = strpbrk(token_string_start, delimiters); + + if (first_occurrence != (char *)NULL) + { + first_occurrence[0] = '\0'; + return token_string_start; + } + else + { + token_string_start = (char *)NULL; + end_found = true; + return (char *)NULL; + } +} +size_t strxfrm(char *dest, const char *src, size_t num) +{ + if (num != 0 && dest != (char *)NULL) + { + // Transform src according to c locale (no mention how) + strncpy(dest, src, num); + } + return strlen(src); +} +#endif + +#endif \ No newline at end of file diff --git a/libc/include/time.h b/libc/include/time.h new file mode 100644 index 0000000..4e75444 --- /dev/null +++ b/libc/include/time.h @@ -0,0 +1,435 @@ +#ifndef _INC_TIME +#define _INC_TIME + +#include + +#ifndef NO_TIME_INCLUDE + +#define CLOCKS_PER_SEC 1000 + +typedef long long clock_t; +typedef long long time_t; +typedef struct tm tm; + +// DECLARATIONS + +// TIME MANIPULATION +clock_t clock(); +time_t time(time_t *timer); +double difftime(time_t end, time_t start); +time_t mktime(tm *time_ptr); + +// CONVERSION +char *asctime(const tm *time_ptr); +char *ctime(const time_t *timer); +struct tm *gmtime(const time_t *timer); +struct tm *localtime(const time_t *timer); +size_t strftime(char *ptr, size_t maxsize, const char *format, const struct tm *time_ptr); + +#endif + +// IMPLEMENTATION +#ifdef TIME_IMPL + +#include +#include + +#ifdef NO_TIME_INCLUDE +typedef unsigned int clock_t; +typedef unsigned int time_t; +#endif +struct tm +{ + int tm_sec; // seconds after the minute + int tm_min; // minutes after the hour + int tm_hour; // hours since midnight + int tm_mon; // months since january + int tm_year; // years since 1900 + int tm_wday; // days since sunday + int tm_mday; // day of the month + int tm_yday; // days since january 1 + int tm_isdst; // daylight saving flag +}; + +#define __FIRST_LEAP_YEAR_AFTER_START 2 // 1972 + +// TIME MANIPULATION +double difftime(time_t end, time_t start) +{ + time_t diff = end - start; + return (double)diff / 1000.0; +} +time_t mktime(struct tm *time_ptr) +{ + const int ys1970 = time_ptr->tm_year - 70; + + time_t t = 0; + t += ys1970 * 356; + t += (__FIRST_LEAP_YEAR_AFTER_START + ys1970) / 4; + t *= 24; // go from days to hours + t += time_ptr->tm_hour + (time_ptr->tm_isdst > 0 ? 1 : 0); + t *= 60; // from hours to minutes + t += time_ptr->tm_min; + t *= 60; // from minutes to seconds + t += time_ptr->tm_sec; + t += 10; // 10 leap secs in ~1970 (cannot account for leap secs after) + + return t; +} + +static const char week_days[][4] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; +static const char week_days_long[][10] = { + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; +static const char months[][4] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; +static const char months_long[][10] = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December"}; +static const unsigned char month_lengths[] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + +// CONVERSION +char *asctime(const struct tm *time_ptr) +{ + + static char str[3 + 1 + 3 + 2 + 2 + 1 + 2 + 1 + 2 + 1 + 4 + 1]; + + int res = sprintf(str, "%.3s %.3s %.2d %.2d:%.2d:%.2d %.4d\n%c", + week_days[time_ptr->tm_wday], + months[time_ptr->tm_mon], + time_ptr->tm_mday, + time_ptr->tm_hour + (time_ptr->tm_isdst > 0 ? 1 : 0), + time_ptr->tm_min, + time_ptr->tm_sec, + 1900 + time_ptr->tm_year, + '\0'); + + if (res != 26) + return (char *)NULL; + + return str; +} +char __ctime_buf[25]; +char *ctime(const time_t *timer) +{ + return asctime(localtime(timer)); +} +struct tm __gm_time_tm = {0}; +int _get_year(time_t); +int _get_month(time_t); +int _get_day(time_t); +int _get_day_of_month(time_t); +int _get_days_since_year(time_t); +int _get_hours(time_t); +int _get_minutes(time_t); +int _get_seconds(time_t); +struct tm *gmtime(const time_t *timer) +{ + time_t timer_cpy = *timer; + + __gm_time_tm.tm_year = _get_year(timer_cpy) - 1900; + __gm_time_tm.tm_mon = _get_month(timer_cpy); + __gm_time_tm.tm_yday = _get_days_since_year(timer_cpy); + __gm_time_tm.tm_mday = _get_day_of_month(timer_cpy); + __gm_time_tm.tm_wday = _get_day(timer_cpy); + __gm_time_tm.tm_hour = _get_hours(timer_cpy); + __gm_time_tm.tm_min = _get_minutes(timer_cpy); + __gm_time_tm.tm_sec = _get_seconds(timer_cpy); + __gm_time_tm.tm_isdst = -1; // don't care about Daylight Saving Time + + return &__gm_time_tm; +} +int _get_local_year(time_t); +int _get_local_month(time_t); +int _get_local_day(time_t); +int _get_local_day_of_month(time_t); +int _get_local_days_since_year(time_t); +int _get_local_hours(time_t); +int _get_local_minutes(time_t); +int _get_local_seconds(time_t); +struct tm *localtime(const time_t *timer) +{ + time_t timer_cpy = *timer; + + __gm_time_tm.tm_year = _get_local_year(timer_cpy) - 1900; + __gm_time_tm.tm_mon = _get_local_month(timer_cpy); + __gm_time_tm.tm_yday = _get_local_days_since_year(timer_cpy); + __gm_time_tm.tm_mday = _get_local_day_of_month(timer_cpy); + __gm_time_tm.tm_wday = _get_local_day(timer_cpy); + __gm_time_tm.tm_hour = _get_local_hours(timer_cpy); + __gm_time_tm.tm_min = _get_local_minutes(timer_cpy); + __gm_time_tm.tm_sec = _get_local_seconds(timer_cpy); + __gm_time_tm.tm_isdst = -1; // don't care about Daylight Saving Time + + return &__gm_time_tm; +} +int _get_weeks_in_year(int); +int _get_timezone_offset(time_t); +// https://cplusplus.com/reference/ctime/strftime/ +size_t strftime(char *ptr, size_t maxsize, const char *format, const struct tm *time_ptr) +{ + size_t fi = 0; + size_t wi = 0; + while (format[fi] != '\0' && wi < maxsize) + { + + if (format[fi] == '%') + { + fi++; + + switch (format[fi]) + { + case '%': + ptr[wi++] = '%'; + break; + + case 'a': + memcpy(ptr + wi, week_days[time_ptr->tm_wday], 3); + wi += 3; + break; + case 'A': + strcpy(ptr + wi, week_days_long[time_ptr->tm_wday]); + wi += strlen(week_days_long[time_ptr->tm_wday]); + break; + + case 'b': + case 'h': + memcpy(ptr + wi, months[time_ptr->tm_wday], 3); + wi += 3; + break; + case 'B': + strcpy(ptr + wi, months_long[time_ptr->tm_wday]); + wi += strlen(months_long[time_ptr->tm_wday]); + break; + + case 'c': + + case 'C': + sprintf(ptr + wi, "%.2d", time_ptr->tm_year / 20); + wi += 2; + break; + + case 'd': + sprintf(ptr + wi, "%.2d", time_ptr->tm_mday); + wi += 2; + break; + + case 'D': + sprintf(ptr + wi, "%.2d/%.2d/%.2d", time_ptr->tm_mon + 1, time_ptr->tm_mday, time_ptr->tm_year % 100); + wi += 2 + 1 + 2 + 1 + 2; + break; + + case 'e': + if (time_ptr->tm_mday < 10) + { + sprintf(ptr + wi, " %.1d", time_ptr->tm_mday); + } + else + { + sprintf(ptr + wi, "%.2d", time_ptr->tm_mday); + } + wi += 2; + break; + + case 'F': + sprintf(ptr + wi, "%.4d-%.2d-%.2d", time_ptr->tm_year, time_ptr->tm_mon + 1, time_ptr->tm_mday); + wi += 4 + 1 + 2 + 1 + 2; + break; + + // diff to y,Y ? + case 'g': + sprintf(ptr + wi, "%.2d", time_ptr->tm_year % 100); + wi += 2; + break; + case 'G': + sprintf(ptr + wi, "%.4d", time_ptr->tm_year); + wi += 4; + break; + + case 'H': + sprintf(ptr + wi, "%.2d", time_ptr->tm_hour); + wi += 2; + break; + + case 'I': + { + const int mod_12 = time_ptr->tm_hour % 12; + sprintf(ptr + wi, "%.2d", mod_12 == 0 ? 12 : mod_12); + wi += 2; + } + break; + + case 'j': + sprintf(ptr + wi, "%.3d", time_ptr->tm_yday); + wi += 3; + break; + + case 'm': + sprintf(ptr + wi, "%.2d", time_ptr->tm_mon + 1); + wi += 2; + break; + + case 'M': + sprintf(ptr + wi, "%.2d", time_ptr->tm_min); + wi += 2; + break; + + case 'n': + sprintf(ptr + wi, "\n"); + wi += 1; + break; + + case 'p': + if (time_ptr->tm_hour < 12) + { + sprintf(ptr + wi, "AM"); + } + else + { + sprintf(ptr + wi, "PM"); + } + wi += 2; + break; + + case 'r': + { + int mod_12 = time_ptr->tm_hour % 12; + sprintf(ptr + wi, "%.2d:%.2d:%.2d %s", mod_12 == 0 ? 12 : mod_12, time_ptr->tm_min, time_ptr->tm_sec, time_ptr->tm_hour < 12 ? "am" : "pm"); + wi += 2 + 1 + 2 + 1 + 2 + 1 + 2; + } + break; + case 'R': + sprintf(ptr + wi, "%.2d:%.2d", time_ptr->tm_hour, time_ptr->tm_min); + wi += 2 + 1 + 2; + break; + + case 'S': + sprintf(ptr + wi, "%.2d", time_ptr->tm_sec); + wi += 2; + break; + + case 't': + sprintf(ptr + wi, "\t"); + wi += 1; + break; + + case 'T': + sprintf(ptr + wi, "%.2d:%.2d:%.2d", time_ptr->tm_hour, time_ptr->tm_min, time_ptr->tm_sec); + wi += 2 + 1 + 2 + 1 + 2; + break; + + case 'u': + sprintf(ptr + wi, "%.1d", time_ptr->tm_wday); + wi += 1; + break; + + case 'U': + { + int last_sun = time_ptr->tm_yday - time_ptr->tm_wday; + sprintf(ptr + wi, "%.2d", last_sun / 7); + wi += 2; + } + break; + + case 'V': + { + int last_sun = 10 + time_ptr->tm_yday - time_ptr->tm_wday; + int w = last_sun / 7; + int woy; + if (w < 1) + { + woy = _get_weeks_in_year(time_ptr->tm_year - 1); + } + else if (w > _get_weeks_in_year(time_ptr->tm_year)) + { + woy = 1; + } + else + { + woy = w; + } + sprintf(ptr + wi, "%.2d", woy); + wi += 2; + } + break; + + case 'w': + sprintf(ptr + wi, "%.1d", time_ptr->tm_wday); + wi += 1; + break; + + case 'W': + { + int last_mon = time_ptr->tm_yday - (time_ptr->tm_wday == 1 ? 6 : time_ptr->tm_wday - 1); + sprintf(ptr + wi, "%.2d", last_mon / 7); + wi += 2; + } + break; + + case 'x': + sprintf(ptr + wi, "%.2d/%.2d/%.2d", time_ptr->tm_mon + 1, time_ptr->tm_mday, time_ptr->tm_year % 100); + wi += 2 + 1 + 2 + 1 + 2; + break; + case 'X': + sprintf(ptr + wi, "%.2d:%.2d:%.2d", time_ptr->tm_hour, time_ptr->tm_min, time_ptr->tm_sec); + wi += 2 + 1 + 2 + 1 + 2; + break; + + case 'y': + sprintf(ptr + wi, "%.2d", time_ptr->tm_year % 100); + wi += 2; + break; + case 'Y': + sprintf(ptr + wi, "%.4d", time_ptr->tm_year); + wi += 4; + break; + + case 'z': + { + tm tmp; + memcpy(&tmp, time_ptr, sizeof(tm)); + const int offset = _get_timezone_offset(mktime(&tmp)); // - if ahead + if behind + const int min = (offset < 0 ? -offset : offset) % 60; + const int hours = (offset < 0 ? -offset : offset) / 60; + sprintf(ptr + wi, "%c%.d", (offset < 0 ? '+' : '-'), (hours * 100) + min); + wi += 1 + (int)log10(hours) + (int)log10(min); + } + break; + + case 'Z': + // TODO: No support for printing time zone abbreviation + wi += 1; + break; + + case 'E': + case 'O': + wi += 2; + // TODO: No support for local alternative representation + break; + + default: + break; + } + } + else + { + ptr[wi++] = format[fi]; + } + } + + if (wi < maxsize) + { + ptr[wi] = '\0'; + return wi; + } + else + { + return 0; + } +} + +#endif + +#endif \ No newline at end of file diff --git a/libc/js/fenv.js b/libc/js/fenv.js new file mode 100644 index 0000000..e179376 --- /dev/null +++ b/libc/js/fenv.js @@ -0,0 +1,38 @@ +const FeRoundingMode = { + FE_TONEAREST: 0, + FE_DOWNWARD: 1, + FE_UPWARD: 2, + FE_TOWARDZERO: 3, +}; + +const fe_exception_flag = { + FE_DIVBYZERO: 0x01, // Pole error: division by zero, or some other asymptotically infinite result (from finite arguments). + FE_INEXACT: 0x02, // Inexact: the result is not exact. + FE_INVALID: 0x04, // Domain error: At least one of the arguments is a value for which the function is not defined. + FE_OVERFLOW: 0x08, // Overflow range error: The result is too large in magnitude to be represented as a value of the return type. + FE_UNDERFLOW: 0x10, // Underflow range error: The result is too small in magnitude to be represented as a value of the return type. + FE_ALL_EXCEPT: 0x1f, // All exceptions (selects all of the exceptions supported by the implementation). +}; + +// https://en.cppreference.com/w/c/numeric/fenv +class FeEnvJs { + #reset() { + this.__memory = undefined; + + this.__double_view = new Float64Array(1); + this.__float_view = new Float32Array(this.__double_view.buffer); + + this.___fe_rounding_mode_default = FeRoundingMode.FE_DOWNWARD; + this.___fe_rounding_mode = this.___fe_rounding_mode_default; + + this; + } + + constructor() { + this.#reset(); + } + + _js_fesetround(round) { + this.___fe_rounding_mode = round; + } +} diff --git a/libc/js/math.js b/libc/js/math.js new file mode 100644 index 0000000..ff43de2 --- /dev/null +++ b/libc/js/math.js @@ -0,0 +1,568 @@ +// https=//www.gnu.org/software/libc/manual/html_node/Floating-Point-Parameters.html + +// DEFINES +const FLT_RADIX = 2; // assume to always be 2 (is not for IBM 360 or derivatives) +const FP_ILOGB0 = 1 << 31; // -2147483648 +const FP_ILOGBNAN = 1 << 31; // -2147483648 +const INT_MAX = 2147483647; +const UINT_MAX = -1 >>> 0; +const INT_MIN = 1 << 31; +const UINT_MIN = 0; +const SMALLEST_DENORM = Math.pow(2, -1074); + +// ERRNO +const ERROR_CODES = { + EPERM: 1, + ENOENT: 2, + ESRCH: 3, + EINTR: 4, + EIO: 5, + ENXIO: 6, + E2BIG: 7, + ENOEXEC: 8, + EBADF: 9, + ECHILD: 10, + EAGAIN: 11, + ENOMEM: 12, + EACCES: 13, + EFAULT: 14, + EBUSY: 16, + EEXIST: 17, + EXDEV: 18, + ENODEV: 19, + ENOTDIR: 20, + EISDIR: 21, + ENFILE: 23, + EMFILE: 24, + ENOTTY: 25, + EFBIG: 27, + ENOSPC: 28, + ESPIPE: 29, + EROFS: 30, + EMLINK: 31, + EPIPE: 32, + EDOM: 33, + EDEADLK: 36, + ENAMETOOLONG: 38, + ENOLCK: 39, + ENOSYS: 40, + ENOTEMPTY: 41, + + // Error codes used in the Secure CRT functions + EINVAL: 22, + ERANGE: 34, + EILSEQ: 42, + STRUNCATE: 80, + + // POSIX + EADDRINUSE: 100, + EADDRNOTAVAIL: 101, + EAFNOSUPPORT: 102, + EALREADY: 103, + EBADMSG: 104, + ECANCELED: 105, + ECONNABORTED: 106, + ECONNREFUSED: 107, + ECONNRESET: 108, + EDESTADDRREQ: 109, + EHOSTUNREACH: 110, + EIDRM: 111, + EINPROGRESS: 112, + EISCONN: 113, + ELOOP: 114, + EMSGSIZE: 115, + ENETDOWN: 116, + ENETRESET: 117, + ENETUNREACH: 118, + ENOBUFS: 119, + ENODATA: 120, + ENOLINK: 121, + ENOMSG: 122, + ENOPROTOOPT: 123, + ENOSR: 124, + ENOSTR: 125, + ENOTCONN: 126, + ENOTRECOVERABLE: 127, + ENOTSOCK: 128, + ENOTSUP: 129, + EOPNOTSUPP: 130, + EOTHER: 131, + EOVERFLOW: 132, + EOWNERDEAD: 133, + EPROTO: 134, + EPROTONOSUPPORT: 135, + EPROTOTYPE: 136, + ETIME: 137, + ETIMEDOUT: 138, + ETXTBSY: 139, + EWOULDBLOCK: 140, +}; + +// ENDIANNESS +function find_endianness() { + var double_view = new Float64Array(1); + var int_view = new Uint32Array(double_view.buffer); + double_view[0] = -0; + + if (int_view[0] === 0) return 'little'; + else return 'big'; +} +const ENDIANNESS = find_endianness(); + +// https=//blog.codefrau.net/2014/08/deconstructing-floats-frexp-and-ldexp.html +function frexp(value) { + if (value === 0) return [value, 0]; + let data = new DataView(new ArrayBuffer(8)); + data.setFloat64(0, value); + let bits = (data.getUint32(0) >>> 20) & 0x7ff; + if (bits === 0) { + // denormal + data.setFloat64(0, value * Math.pow(2, 64)); // exp + 64 + bits = ((data.getUint32(0) >>> 20) & 0x7ff) - 64; + } + let exponent = bits - 1022; + let mantissa = ldexp(value, -exponent); + return [mantissa, exponent]; +} +function ldexp(mantissa, exponent) { + let steps = Math.min(3, Math.ceil(Math.abs(exponent) / 1023)); + let result = mantissa; + for (let i = 0; i < steps; i++) + result *= Math.pow(2, Math.floor((exponent + i) / steps)); + return result; +} + +// Implementation of https://de.wikipedia.org/wiki/Fehlerfunktion +function erf(x) { + const c = [ + 1.26551223, 1.00002368, 0.37409196, 0.09678418, 0.18628806, 0.27886807, + 1.13520398, 1.48851587, 0.82215223, 0.17087277, + ]; + + const tau = (x) => { + const t = 1 / (1 + 0.5 * Math.abs(x)); + + exp_inner = 0; + exp_inner += -c[0]; + t_mul = t; + exp_inner += c[1] * t_mul; + t_mul *= t; + exp_inner += c[2] * t_mul; + t_mul *= t; + exp_inner += c[3] * t_mul; + t_mul *= t; + exp_inner += -c[4] * t_mul; + t_mul *= t; + exp_inner += c[5] * t_mul; + t_mul *= t; + exp_inner += -c[6] * t_mul; + t_mul *= t; + exp_inner += c[7] * t_mul; + t_mul *= t; + exp_inner += -c[8] * t_mul; + t_mul *= t; + exp_inner += c[9] * t_mul; + + return t * Math.exp(-(x * x) + exp_inner); + }; + + if (x >= 0) { + return 1 - tau(x); + } else { + return tau(-x) - 1; + } +} + +class MathJs { + #reset() { + this.__wasm = undefined; + this.__memory = undefined; + this.__fenv = new FeEnvJs(); + this.__stdlib = new StdlibJs(); + } + + constructor() { + this.#reset(); + } + + init(wasm) { + this.__wasm = wasm; + this.__memory = wasm.instance.exports.memory; + } + + // Double precision + + // misc + fabs = Math.abs; + fmod = (x, y) => x % y; + remainder = (x, y) => { + // https://en.cppreference.com/w/c/numeric/math/remainder + if (isNaN(x) || isNaN(y)) return NaN; + if (!isFinite(x) || y === 0) { + this.__wasm.instance.exports.feraiseexcept(fe_exception_flag.FE_INVALID); + this.__wasm.instance.exports._add_errno(ERROR_CODES.EDOM); + return NaN; + } + + return x % y; + }; + remquo = (x, y, quo_ptr) => { + // https://en.cppreference.com/w/c/numeric/math/remquo + if (isNaN(x) || isNaN(y)) return NaN; + if (!isFinite(x) || y === 0) { + this.__wasm.instance.exports.feraiseexcept(fe_exception_flag.FE_INVALID); + this.__wasm.instance.exports._add_errno(ERROR_CODES.EDOM); + return NaN; + } + + let remainder = x % y; + + let sign = 0; + if (remainder === 0) { + if (x !== 0) sign = Math.sign(x); + else if (x === -0) sign = -1; + else sign = 1; + } else if (remainder < 0) sign = -1; + else sign = 1; + + if (x === 0 && y === 0) { + this.__wasm.instance.exports.feraiseexcept(fe_exception_flag.FE_INVALID); + this.__wasm.instance.exports._add_errno(ERROR_CODES.EDOM); + } + if (y === 0) { + this.__wasm.instance.exports.feraiseexcept( + fe_exception_flag.FE_DIVBYZERO + ); + this.__wasm.instance.exports._add_errno(ERROR_CODES.ERANGE); + } + + let quo = Math.floor(x / y); + quo &= 0x7fffffff; + + let buf = new Uint32Array(this.__memory.buffer, quo_ptr, 1); + buf.set([sign * quo]); + return remainder; + }; + fma = (x, y, z) => x * y + z; + fmax = Math.max; + fmin = Math.min; + fdim = (x, y) => (x > y ? x - y : 0); // https://en.cppreference.com/w/c/numeric/math/fdim + infinity = () => Infinity; + nan = (content_ptr) => { + const content_str = cstr_by_ptr(this.__memory.buffer, content_ptr); + const num = parseInt(content_str); + + if (num <= 0 || num > 0xffffffff || num === NaN) { + return NaN; + } else { + let nan = new Uint32Array(2); + let buf_double = new Float64Array(nan.buffer, 0, 1); + + if (ENDIANNESS === 'big') { + nan[0] = 0x80000000; + nan[0] |= 0x7ff00000; + // nan[0] |= 0x000fffff & num; + nan[1] |= num; + } else if (ENDIANNESS === 'little') { + nan[1] = 0x80000000; + nan[1] |= 0x7ff00000; + // nan[1] |= 0x000fffff & num; + nan[0] |= num; + } else { + throw new Error( + 'Could not determine if numbers are little or big ENDIANNESS' + ); + } + return buf_double[0]; + } + }; + + // exponential + exp = Math.exp; + exp2 = (x) => Math.pow(2, x); + expm1 = Math.expm1; + log = Math.log; + log2 = Math.log2 || ((x) => Math.log(x) * Math.LOG2E); + log10 = Math.log10; + log1p = Math.log1p; + ilogb = (x) => { + // https://en.cppreference.com/w/c/numeric/math/ilogb + if (x === 0) return FP_ILOGB0; + else if (!isFinite(x)) return INT_MAX; + else if (isNaN(x)) return FP_ILOGBNAN; + + return Math.floor(this.logb(x)); + }; + logb = (x) => Math.log(x) / Math.log(FLT_RADIX); + + // power + pow = Math.pow; + sqrt = (x) => { + if (x < 0) { + this.__wasm.instance.exports.feraiseexcept(fe_exception_flag.FE_INVALID); + this.__wasm.instance.exports._add_errno(ERROR_CODES.EDOM); + } + return Math.sqrt(x); + }; + cbrt = Math.cbrt; + hypot = Math.hypot; + + //trigonometric + sin = Math.sin; + cos = Math.cos; + tan = Math.tan; + asin = Math.asin; + acos = Math.acos; + atan = Math.atan; + atan2 = Math.atan2; + + // hyperbolic + sinh = Math.sinh; + cosh = Math.cosh; + tanh = Math.tanh; + asinh = Math.asinh; + acosh = Math.acosh; + atanh = Math.atanh; + + // error/gamma + erf = erf; + erfc = (x) => 1 - this.erf(x); + lgamma = (x) => { + throw new ERROR('MATH GAMMA functions not implemented.'); + }; + tgamma = (x) => { + throw new ERROR('MATH GAMMA functions not implemented.'); + }; + + // floating point to int + ceil = Math.ceil; + floor = Math.floor; + trunc = Math.trunc; + round = Math.round; + lround = (x) => { + if (!isFinite(x)) { + this.__wasm.instance.exports.feraiseexcept(fe_exception_flag.FE_INVALID); + this.__wasm.instance.exports._add_errno(ERROR_CODES.EDOM); + return Infinity; + } else if (isNaN(x)) { + this.__wasm.instance.exports.feraiseexcept(fe_exception_flag.FE_INVALID); + this.__wasm.instance.exports._add_errno(ERROR_CODES.EDOM); + return NaN; + } + return Math.round(x); + }; + llround = (x) => { + if (!isFinite(x)) { + this.__wasm.instance.exports.feraiseexcept(fe_exception_flag.FE_INVALID); + this.__wasm.instance.exports._add_errno(ERROR_CODES.EDOM); + return Infinity; + } else if (isNaN(x)) { + this.__wasm.instance.exports.feraiseexcept(fe_exception_flag.FE_INVALID); + this.__wasm.instance.exports._add_errno(ERROR_CODES.EDOM); + return NaN; + } + return BigInt(Math.round(x)); + }; + nearbyint = (x) => { + let rounding_mode_before = this.__fenv.___fe_rounding_mode; + this.__fenv.___fe_rounding_mode = FeRoundingModes.FE_TONEAREST; + const res = this.rint(x); + this.__fenv.___fe_rounding_mode = rounding_mode_before; + return res; + }; + rint = (x) => { + switch (this.__fenv.___fe_rounding_mode) { + case FeRoundingModes.FE_DOWNWARD: + return Math.floor(x); + case FeRoundingModes.FE_UPWARD: + return Math.ceil(x); + case FeRoundingModes.FE_TOWARDZERO: + return x >= 0 ? Math.floor(x) : Math.ceil(x); + case FeRoundingModes.FE_TONEAREST: + return Math.floor(x) + 0.5 <= x ? Math.ceil(x) : Math.floor(x); + } + }; + lrint = this.rint; + llrint = (x) => BigInt(this.rint(x)); + + // floating point manipulation + frexp = (x, exponent_ptr) => { + const [mantissa, exponent] = frexp(x); + let buf = new Uint32Array(this.__memory.buffer, exponent_ptr, 1); + buf.set([exponent]); + return mantissa; + }; + ldexp = (x, y) => x * Math.pow(2, y); + modf = (x, int_part_ptr) => { + // TODO: This implementation is right for small values but is not C compliant + const int_part = Math.floor(x); + let buf = new Uint32Array(this.__memory.buffer, int_part_ptr, 1); + buf.set([int_part]); + this.__wasm.instance.exports.feraiseexcept(fe_exception_flag.FE_INEXACT); + return x - int_part; + }; + scalbln = (x, y) => x * Math.pow(FLT_RADIX, y); + scalbn = this.scalbln; + nextafter = (from, to) => { + // https://github.com/scijs/nextafter (MIT) + // https://www.npmjs.com/package/double-bits (MIT) + if (isNaN(from) || isNaN(to)) return NaN; + else if (from === to) return from; + else if (from === 0) return to < 0 ? -SMALLEST_DENORM : SMALLEST_DENORM; + + double_view[0] = from; + + var lo = ENDIANNESS === 'little' ? float_view[0] : float_view[1]; + var hi = ENDIANNESS === 'little' ? float_view[1] : float_view[0]; + + if (y > x === x > 0) { + if (lo === UINT_MAX) { + hi += 1; + lo = 0; + } else { + lo += 1; + } + } else { + if (lo === 0) { + lo = UINT_MAX; + hi -= 1; + } else { + lo -= 1; + } + } + + // TODO: raise fe exceptions + + return double_view[0]; + }; + nexttoward = this.nextafter; + copysign = (x, y) => Math.abs(x) * Math.sign(y); + + // Single precision + atanf = this.atan; + cosf = this.cos; + sinf = this.sin; + tanf = this.tan; + tanhf = this.tanh; + frexpf = this.frexp; + modff = this.modf; + ceilf = this.ceil; + fabsf = this.fabs; + floorf = this.floor; + + acosf = this.acos; + atan2f = this.atan2; + coshf = this.cosh; + sinhf = this.sinh; + expf = this.exp; + ldexpf = this.ldexp; + logf = this.log; + log10f = this.log10; + powf = this.pow; + sqrtf = this.sqrt; + fmodf = this.fmod; + + exp2f = this.exp2; + scalblnf = this.scalbln; + tgammaf = this.tgamma; + nearbyintf = this.nearbyint; + lrintf = this.lrint; + llrintf = this.llrint; + roundf = this.round; + lroundf = this.lround; + llroundf = this.llround; + truncf = this.trunc; + remquof = this.remquo; + fdimf = this.fdim; + fmaxf = this.fmax; + fminf = this.fmin; + fmaf = this.fma; + + infinityf = this.infinity; + nanf = this.nan; + copysignf = this.copysign; + logbf = this.logb; + ilogbf = this.ilogb; + + asinhf = this.asinh; + cbrtf = this.cbrt; + nextafterf = this.nextafter; + rintf = this.rint; + scalbnf = this.scalbn; + log1pf = this.log1p; + expm1f = this.expm1; + + acoshf = this.acosh; + atanhf = this.atanh; + remainderf = this.remainder; + lgammaf = this.lgamma; + erff = this.erf; + erfcf = this.erfc; + log2f = this.log2; + hypotf = this.hypot; + + // long double precision + // (WARNING: only for completness, is not more preccise) + + atanl = this.atan; + cosl = this.cos; + sinl = this.sin; + tanl = this.tan; + tanhl = this.tanh; + frexpl = this.frexp; + modfl = this.modf; + ceill = this.ceil; + fabsl = this.fabs; + floorl = this.floor; + + acosl = this.acos; + atan2l = this.atan2; + coshl = this.cosh; + sinhl = this.sinh; + expl = this.exp; + ldexpl = this.ldexp; + logl = this.log; + log10l = this.log10; + powl = this.pow; + sqrtl = this.sqrt; + fmodl = this.fmod; + + exp2l = this.exp2; + scalblnl = this.scalbln; + tgammal = this.tgamma; + nearbyintl = this.nearbyint; + lrintl = this.lrint; + llrintl = this.llrint; + roundl = this.round; + lroundl = this.lround; + llroundl = this.llround; + truncl = this.trunc; + remquol = this.remquo; + fdiml = this.fdim; + fmaxl = this.fmax; + fminl = this.fmin; + fmal = this.fma; + + infinityl = this.infinity; + nanl = this.nan; + copysignl = this.copysign; + logbl = this.logb; + ilogbl = this.ilogb; + + asinhl = this.asinh; + cbrtl = this.cbrt; + nextafterl = this.nextafter; + rintl = this.rint; + scalbnl = this.scalbn; + log1pl = this.log1p; + expm1l = this.expm1; + + acoshl = this.acosh; + atanhl = this.atanh; + remainderl = this.remainder; + lgammal = this.lgamma; + erfl = this.erf; + erfcl = this.erfc; + log2l = this.log2; + hypotl = this.hypot; +} diff --git a/libc/js/stdio.js b/libc/js/stdio.js new file mode 100644 index 0000000..470f8b4 --- /dev/null +++ b/libc/js/stdio.js @@ -0,0 +1,81 @@ +const BUFSIZ = 512; + +class StdioJs { + #reset() { + this.__wasm = undefined; + this.__memory = undefined; + + this.__file_promises = []; + } + + constructor() { + this.#reset(); + } + + init(wasm) { + this.__wasm = wasm; + this.__memory = wasm.instance.exports.memory; + + this.__file_promises = []; + } + + _print_string = (str_ptr) => { + const str = cstr_by_ptr(this.__memory.buffer, str_ptr); + console.log(str); + }; + + // Fetching a file and giving it to the c instance is difficult, as fetch is async and one + // cannot await the fetch while in fn called from c (while loop blocks fetch). + + // An option is to fopen() in one frame and have the FILE ready in next frame, so js can wait for fetch in between, + // but this would make fopen inconsistent between native and web. + + // to solve this, introduced is_file_ready() that is always true on native and true in the next frame on web. + // This needs a ready field in FILE & a way to set it, eg. _set_file_ready. + // Also need malloc & setvbuf from c to allocate a new buffer for the content and set it for the FILE ptr. + // The _append_fetch_promise is the fn being called from c. + // It creates a file with the exported fn _create_file and appends the fetch + // for the file to a promise list that is awaited all with wait_open_files between frames. + + // Just read that async was implemented so can fix that with another worker? + + _fetch_file = async (file_ptr, path_ptr) => { + const path = cstr_by_ptr(this.__memory.buffer, path_ptr); + const res = await fetch(path); + + const data_buf = await res.arrayBuffer(); + + const buf_start = this.__wasm.instance.exports.malloc( + data_buf.byteLength + 1 + ); + let buf = new Int8Array( + this.__memory.buffer, + buf_start, + data_buf.byteLength + 1 + ); + buf.set(new Int8Array(data_buf, 0, data_buf.byteLength)); + this.__wasm.instance.exports.setvbuf( + file_ptr, + buf_start, + 0, + data_buf.byteLength + ); + buf.set([0], data_buf.byteLength); + this.__wasm.instance.exports._set_file_ready(file_ptr); + }; + _append_fetch_promise = (path_ptr, optional_file_ptr) => { + let file_ptr = + optional_file_ptr == 0 + ? this.__wasm.instance.exports._create_file() + : optional_file_ptr; + + let prom = this._fetch_file(file_ptr, path_ptr); + this.__file_promises.push(prom); + + return file_ptr; + }; + wait_open_files() { + Promise.all(this.__file_promises); + this.__file_promises.length = 0; + } +} diff --git a/libc/js/stdlib.js b/libc/js/stdlib.js new file mode 100644 index 0000000..d0dacf4 --- /dev/null +++ b/libc/js/stdlib.js @@ -0,0 +1,499 @@ +const RAND_MAX = INT_MAX; + +// https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript +function splitmix32(a) { + return function () { + a |= 0; + a = (a + 0x9e3779b9) | 0; + var t = a ^ (a >>> 16); + t = Math.imul(t, 0x21f0aaad); + t = t ^ (t >>> 15); + t = Math.imul(t, 0x735a2d97); + return ((t = t ^ (t >>> 15)) >>> 0) / 4294967296; + }; +} + +function cstr_by_ptr_len(mem_buffer, ptr, str_len) { + const bytes = new Uint8Array(mem_buffer, ptr, str_len); + return new TextDecoder().decode(bytes); +} + +class ExitWasmException extends Error { + constructor() { + super('This is a exception thrown to exist wasm'); + this.name = this.constructor.name; + } +} + +class StdlibJs { + #reset() { + this.__memory = undefined; + this.__fn_table = undefined; + + // STRING TO NUMBER + this.__had_dot = undefined; + this.__had_e = undefined; + this.__had_x = undefined; + + this.___had_helpers = {}; + this.___had_helpers.is_second_dot = (x) => { + if (str[i] === '.') { + if (!this.__had_dot) { + this.__had_dot = true; + return false; + } else { + return true; + } + } + return false; + }; + this.___had_helpers.is_second_e = (x) => { + if (str[i] === 'e' || str[i] === 'E') { + if (!this.__had_e) { + this.__had_e = true; + return false; + } else { + return true; + } + } + return false; + }; + this.___had_helpers.is_second_x = (x) => { + if (str[i] === 'x' || str[i] === 'X') { + if (!this.__had_x) { + this.__had_x = true; + return false; + } else { + return true; + } + } + return false; + }; + + // RANDOM + this.__current_random_generator = splitmix32(1); + + // ENVIRONMENT + this.__ctx = undefined; + this.__change_running_fn = undefined; + this.__wasm = undefined; + this.__at_exit_fns = []; + this.__at_quick_exit_fns = []; + } + + constructor() { + this.#reset(); + } + + init(ctx, wasm, change_running_fn) { + this.__ctx = ctx; + this.__wasm = wasm; + this.__memory = wasm.instance.exports.memory; + this.__fn_table = wasm.instance.exports.__indirect_function_table; + this.__change_running_fn = change_running_fn; + } + handle_exit() { + for (let i = 0; i < this.__at_exit_fns.length; i++) { + this.__at_exit_fns[this.__at_exit_fns.length - 1 - i](); + } + } + + // STRING TO NUMBER + atof = (str_ptr) => { + var str = cstr_by_ptr(this.__memory.buffer, str_ptr); + return parseFloat(str); + }; + atoi = (str_ptr) => { + var str = cstr_by_ptr(this.__memory.buffer, str_ptr); + return parseInt(str); + }; + atol = this.atoi; + atoll = this.atoi; + + _next_is_num(i, str) { + return ( + (str[i] >= '0' && str[i] <= '9') || + (str[i] === '-' && + str[i + 1] !== undefined && + !isNaN(parseInt(str[i + 1], 10))) + ); + } + _parse_num(start_i, str) { + let i = start_i; + + // eat until first non number char + this.__had_dot = false; + this.__had_e = false; + while ( + str[i] !== undefined && + (!isNaN(parseInt(str[i], 10)) || + str[i] === '-' || + str[i] === '.' || + str[i].toLowerCase() === 'e') && + !this.___had_helpers.is_second_dot(str[i]) && + !this.___had_helpers.is_second_e(str[i]) + ) { + i++; + } + + return [parseFloat(str), i]; + } + + _next_is_nan(i, str) { + return ( + (str[i] === '-' && + str[i + 1] !== undefined && + str[i + 1].toLowerCase() === 'n') || + str[i].toLowerCase() === 'n' + ); + } + _parse_nan(start_i, str, is_float) { + let i = start_i; + + let is_negative = false; + if (str[i] === '-') { + is_negative = true; + i++; + } + + i++; + const valid2 = str[i] === 'A' || str[i] === 'a'; + i++; + const valid3 = str[i] === 'N' || str[i] === 'n'; + i++; + + if (!valid2 || !valid3) { + i -= 3; + return [0, i]; + } else if (str[i] === '(') { + i++; + const substr = str.substring(i); + const num = parseInt(substr); + + // eat until first non number char + while (!isNaN(parseInt(str[i], 10))) { + i++; + } + + if (str[i] === ')') { + i++; + if (num <= 0 || num > 0xffffffff) { + return [is_negative ? -NaN : NaN, i]; + } else { + if (is_float) { + let nan = new Uint32Array(1); + let buf_float = new Float32Array(nan.buffer, 0, 1); + + nan[0] = 0x7fc00000; + nan[0] |= 0x3fffff & num; + + return [is_negative ? -buf_float[0] : buf_float[0], i]; + } else { + let nan = new Uint32Array(2); + let buf_double = new Float64Array(nan.buffer, 0, 1); + + if (ENDIANNESS === 'big') { + nan[0] = 0x7ff80000; + // nan[0] |= 0x000fffff & num; + nan[1] |= num; + } else if (ENDIANNESS === 'little') { + nan[1] = 0x7ff80000; + // nan[1] |= 0x000fffff & num; + nan[0] |= num; + } + return [is_negative ? -buf_double[0] : buf_double[0], i]; + } + } + } else { + return [0, start_i]; + } + } else { + return [is_negative ? -NaN : NaN, i]; + } + } + + _next_is_inf(i, str) { + return ( + (str[i] === '-' && + str[i + 1] !== undefined && + str[i + 1].toLowerCase() === 'i') || + str[i].toLowerCase() === 'i' + ); + } + _parse_inf(start_i, str) { + let i = start_i; + + let is_negative = false; + if (str[i] === '-') { + is_negative = true; + i++; + } + + i++; + const find = 'infinity'; + let j = 1; + while ( + i < str.length && + j < find.length && + str[i].toLowerCase() === find[j].toLowerCase() + ) { + i++; + j++; + } + + if (j >= 3 && j <= 7) { + i -= j - 3; + return [is_negative ? -Infinity : Infinity, i]; + } else if (j === 8) { + return [is_negative ? -Infinity : Infinity, i]; + } else { + i -= j; + return [0, i]; + } + } + + _strtodf(str_ptr, end_ptr, is_float) { + let str = cstr_by_ptr(this.__memory.buffer, str_ptr); + let i = 0; + + // eat whitespace + while (str.length < i && ' \t\n\r\v'.indexOf(str[i]) > -1) { + i++; + } + + let res; + if (str[i] === undefined) { + res = 0; + } else if (this._next_is_num(i, str)) { + [res, i] = this._parse_num(i, str); + } else if (this._next_is_nan(i, str)) { + [res, i] = this._parse_nan(i, str, is_float); + } else if (this._next_is_inf(i, str)) { + [res, i] = this._parse_inf(i, str); + } else { + res = 0; + } + + // eat whitespace + while (str.length < i && ' \t\n\r\v'.indexOf(str[i]) > -1) { + i++; + } + + var buf = new Uint32Array(this.__memory.buffer, end_ptr); + buf.set([str_ptr + i]); // do not find + return res; + } + + strtod = (str_ptr, end_ptr) => this._strtodf(str_ptr, end_ptr, false); + strtof = (str_ptr, end_ptr) => this._strtodf(str_ptr, end_ptr, true); + strtol = (str_ptr, end_ptr, base) => { + var str = cstr_by_ptr(this.__memory.buffer, str_ptr); + + var i = 0; + + // eat whitespace + while (str.length < i && ' \t\n\r\v'.indexOf(str[i]) > -1) { + i++; + } + + // eat until first non number char + this.__had_x = false; + while ( + !isNaN(parseInt(str[i], base)) && + !this.___had_helpers.is_second_x(str[i]) // TODO: x is accepted for bases other than 0 and 16 + ) { + i++; + } + + var buf = Uint32Array(this.__memory.buffer, end_ptr); + buf.set([str_ptr + i]); // do not find + return parseInt(str, base); + }; + strtoll = this.strtol; + strtoul = this.strtol; + strtoull = this.strtol; + + // RANDOM + rand = () => { + return this.__current_random_generator() * RAND_MAX; + }; + srand = (seed) => { + this.__current_random_generator = splitmix32(seed); + }; + + // MEMORY ALLOCATOR + _heap_start = () => { + return this.__wasm.instance.exports.__heap_base; + }; + _grow_memory = (page_size) => { + var first_free_mem_index = this.__memory.buffer.byteLength; + this.__memory.grow(page_size); + return first_free_mem_index; + }; + + // ENVIRONMENT + abort = () => { + this.__change_running_fn(() => { + const w = this.__ctx.canvas.width; + const h = this.__ctx.canvas.height; + const old_style = this.__ctx.fillStyle; + + this.__ctx.fillStyle = '#fa4141'; + this.__ctx.fillRect(0, 0, w, h); + + const fontSize = 20; + const text = `Program aborted`; + this.__ctx.font = `${fontSize}px grixel`; + this.__ctx.fillStyle = 'black'; + const textWidth = this.__ctx.measureText(text); + this.__ctx.fillText(text, w / 2 - textWidth.width / 2, h / 2 + fontSize); + + this.__ctx.fillStyle = old_style; + }); + throw new ExitWasmException(); // exit wasm + }; + exit = (status) => { + for (let i = 0; i < this.__at_exit_fns.length; i++) { + this.__at_exit_fns[this.__at_exit_fns.length - 1 - i](); + } + + this.__change_running_fn(() => { + const w = this.__ctx.canvas.width; + const h = this.__ctx.canvas.height; + const old_style = this.__ctx.fillStyle; + + this.__ctx.fillStyle = status == 0 ? '#37bd3b' : '#fa4141'; + this.__ctx.fillRect(0, 0, w, h); + + const fontSize = 20; + const text = `Exited with status ${status}`; + this.__ctx.font = `${fontSize}px grixel`; + this.__ctx.fillStyle = 'black'; + const textWidth = this.__ctx.measureText(text); + this.__ctx.fillText(text, w / 2 - textWidth.width / 2, h / 2 + fontSize); + + this.__ctx.fillStyle = old_style; + }); + throw new ExitWasmException(); // exit wasm + }; + quick_exit = (status) => { + for (let i = 0; i < this.__at_quick_exit_fn.length; i++) { + this.__at_quick_exit_fn[this.__at_quick_exit_fn.length - 1 - i](); + } + + this.__change_running_fn(() => { + const w = this.__ctx.canvas.width; + const h = this.__ctx.canvas.height; + const old_style = this.__ctx.fillStyle; + + this.__ctx.fillStyle = status == 0 ? '#37bd3b' : '#fa4141'; + this.__ctx.fillRect(0, 0, w, h); + + const fontSize = 20; + const text = `Exited with status ${status}`; + this.__ctx.font = `${fontSize}px grixel`; + this.__ctx.fillStyle = 'black'; + const textWidth = this.__ctx.measureText(text); + this.__ctx.fillText(text, w / 2 - textWidth.width / 2, h / 2 + fontSize); + + this.__ctx.fillStyle = old_style; + }); + throw new ExitWasmException(); // exit wasm + }; + atexit = (fn_ptr) => { + try { + this.__at_exit_fns.push(this.__fn_table.get(fn_ptr)); + } catch (e) { + return 1; + } + return 0; + }; + at_quick_exit = (fn_ptr) => { + try { + this.__at_quick_exit_fns.push(this.__fn_table.get(fn_ptr)); + } catch (e) { + return 1; + } + return 0; + }; + + // INTEGER ARITHMETICS + abs = Math.abs; + labs = this.abs; + llabs = (x) => { + return x > 0 ? x : -x; + }; + + // MULTIBYTE CHARACTERS (UTF-ONLY (LC_CTYPE=UTF-8)) + mblen = (mbc_ptr, max) => { + if (mbc_ptr == 0) return 0; // not state dependant + + let str = cstr_by_ptr_len(mbc_ptr, max); + let first_char = str[0]; + return new Blob([first_char]).size; + }; + mbtowc = (wc_ptr, mbc_ptr, max) => { + if (mbc_ptr == 0) return 0; // not state dependant + + let str = cstr_by_ptr_len(mbc_ptr, max); + + if (wc_ptr != 0) { + // assume sizeof(wchar_t) == 4 + let buf = new Int32Array(this.__memory.buffer, wc_ptr, max); + buf[0] = str.codePointAt(0); + } + + return new Blob([str[0]]).size; + }; + wctomb = (mbc_ptr, wc) => { + if (mbc_ptr == 0) return 0; // not state dependant + + let mbc; + try { + mbc = String.fromCodePoint(wc); + } catch (e) { + return -1; // handles invalid code points + } + + let encoded = new TextEncoder().encode(mbc); + let buf = new Int8Array(this.__memory.buffer, mbc_ptr, encoded.byteLength); + + for (i = 0; i < encoded.byteLength; i++) { + buf[i] = encoded[i]; + } + + return encoded.length; + }; + + mbstowcs = (dest_ptr, src_ptr, max) => { + const src_buf = new Int8Array(this.__memory.buffer, src_ptr, max); + const str = new TextDecoder('utf-8').decode(src_buf); + + let dest_buf = new Int32Array(this.__memory.buffer, dest_ptr, max); + + for (let i = 0; i < Math.min(str.length, max); i++) { + dest_buf[i] = str.codePointAt(i); + } + if (str.length < max) { + dest_buf[str.length] = 0; // add '\0'; + } + + return Math.min(str.length, max); + }; + wcstombs = (dest_ptr, src_ptr, max) => { + const src_buf = new Int32Array(this.__memory.buffer, src_ptr, max); + const str = new TextDecoder('utf-8').decode(src_buf); + + let cur_len = 0; + + let last = src_buf[0]; + for (let i = 0; i < max && last != 0; i++) { + last = String.fromCodePoint(src_buf[i]); + let len = this.wctomb(dest_ptr + cur_len); + if (len < 0) return -1; + cur_len += len; + } + + return cur_len; + }; +} diff --git a/libc/js/time.js b/libc/js/time.js new file mode 100644 index 0000000..b118866 --- /dev/null +++ b/libc/js/time.js @@ -0,0 +1,109 @@ +class TimeJs { + #reset() { + this.__memory = undefined; + } + + constructor() { + this.#reset(); + } + + init(wasm) { + this.__memory = wasm.instance.exports.memory; + } + + clock = () => BigInt(Date.now()); + time = (timer_ptr) => { + if (timer_ptr != 0) { + var buf = new BigInt64Array(this.__memory.buffer, timer_ptr, 1); + buf[0] = BigInt(Date.now()); + } + + return BigInt(Date.now()); + }; + + create_date(time_num) { + let num = Number(time_num); + return new Date(num); + } + + // Get UTC time + _get_year = (time_num) => { + return this.create_date(time_num).getUTCFullYear(); + }; + _get_month = (time_num) => { + return this.create_date(time_num).getUTCMonth(); + }; + _get_day = (time_num) => { + return this.create_date(time_num).getUTCDay(); + }; + _get_day_of_month = (time_num) => { + return this.create_date(time_num).getUTCDate(); + }; + _get_days_since_year = (time_num) => { + const date = this.create_date(time_num); + return ( + (Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()) - + Date.UTC(date.getUTCFullYear(), 0, 0)) / + 24 / + 60 / + 60 / + 1000 + ); + }; + _get_hours = (time_num) => { + return this.create_date(time_num).getUTCHours(); + }; + _get_minutes = (time_num) => { + return this.create_date(time_num).getUTCMinutes(); + }; + _get_seconds = (time_num) => { + return this.create_date(time_num).getUTCSeconds(); + }; + + // Get Local time + _get_local_year = (time_num) => { + return this.create_date(time_num).getFullYear(); + }; + _get_local_month = (time_num) => { + return this.create_date(time_num).getMonth(); + }; + _get_local_day = (time_num) => { + return this.create_date(time_num).getDay(); + }; + _get_local_day_of_month = (time_num) => { + return this.create_date(time_num).getDate(); + }; + _get_local_days_since_year = (time_num) => { + const date = this.create_date(time_num); + return ( + (Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()) - + Date.UTC(date.getFullYear(), 0, 0)) / + 24 / + 60 / + 60 / + 1000 + ); + }; + _get_local_hours = (time_num) => { + return this.create_date(time_num).getHours(); + }; + _get_local_minutes = (time_num) => { + return this.create_date(time_num).getMinutes(); + }; + _get_local_seconds = (time_num) => { + return this.create_date(time_num).getSeconds(); + }; + + _get_weeks_in_year = (year) => { + let last_date_of_year = new Date(year, 11, 31); + let d = new Date(+last_date_of_year); + d.setHours(0, 0, 0, 0); + d.setDate(d.getDate() + 4 - (d.getDay() || 7)); + let yearStart = new Date(d.getFullYear(), 0, 1); + let weekNo = Math.ceil(((d - yearStart) / 86400000 + 1) / 7); + return weekNo == 1 ? 52 : 53; + }; + _get_timezone_offset = (time_num) => { + return this.create_date(time_num).getTimezoneOffset(); + }; +} diff --git a/libc/libc.h b/libc/libc.h new file mode 100644 index 0000000..94e9c8a --- /dev/null +++ b/libc/libc.h @@ -0,0 +1,30 @@ +#ifdef PLATFORM_WEB + +#define ERRNO_IMPL +#include "errno.h" + +#define ASSERT_IMPL +#include "assert.h" + +#define FENV_IMPL +#include "fenv.h" + +#define STDLIB_IMPL +#include "stdlib.h" + +#define STRING_IMPL +#include "string.h" + +#define TIME_IMPL +#include "time.h" + +#define STDIO_IMPL +#include "stdio.h" + +#include +bool is_file_ready(FILE *stream) { return stream->ready; } +#else +#include +#include +bool is_file_ready(FILE *stream) { return true; } +#endif \ No newline at end of file diff --git a/nob.c b/nob.c index 15ef36d..a217795 100644 --- a/nob.c +++ b/nob.c @@ -48,6 +48,31 @@ Example examples[] = { .bin_path = "./build/text_writing_anim", .wasm_path = "./wasm/text_writing_anim.wasm", }, + { + .src_path = "./examples/libc/math.c", + .bin_path = "./build/libc_math", + .wasm_path = "./wasm/libc_math.wasm", + }, + { + .src_path = "./examples/libc/exit.c", + .bin_path = "./build/libc_exit", + .wasm_path = "./wasm/libc_exit.wasm", + }, + { + .src_path = "./examples/libc/malloc.c", + .bin_path = "./build/libc_malloc", + .wasm_path = "./wasm/libc_malloc.wasm", + }, + { + .src_path = "./examples/libc/time.c", + .bin_path = "./build/libc_time", + .wasm_path = "./wasm/libc_time.wasm", + }, + { + .src_path = "./examples/libc/file.c", + .bin_path = "./build/libc_file", + .wasm_path = "./wasm/libc_file.wasm", + }, }; bool build_native(void) @@ -56,6 +81,7 @@ bool build_native(void) for (size_t i = 0; i < NOB_ARRAY_LEN(examples); ++i) { cmd.count = 0; nob_cmd_append(&cmd, "clang", "-I./include/"); + nob_cmd_append(&cmd, "-includelibc/libc.h"); // for stdio compatibility nob_cmd_append(&cmd, "-o", examples[i].bin_path, examples[i].src_path); nob_cmd_append(&cmd, "-L./lib/", "-lraylib", "-lm"); if (!nob_cmd_run_sync(cmd)) return 1; @@ -70,11 +96,17 @@ bool build_wasm(void) nob_cmd_append(&cmd, "clang"); nob_cmd_append(&cmd, "--target=wasm32"); nob_cmd_append(&cmd, "-I./include"); + nob_cmd_append(&cmd, "-I./libc/include"); + nob_cmd_append(&cmd, "-includelibc/libc.h"); nob_cmd_append(&cmd, "--no-standard-libraries"); nob_cmd_append(&cmd, "-Wl,--export-table"); nob_cmd_append(&cmd, "-Wl,--no-entry"); nob_cmd_append(&cmd, "-Wl,--allow-undefined"); nob_cmd_append(&cmd, "-Wl,--export=main"); + nob_cmd_append(&cmd, "-Wl,--export=__heap_base", "-Wl,--export=feraiseexcept"); // libc + nob_cmd_append(&cmd, "-Wl,--export=malloc"); // libc + nob_cmd_append(&cmd, "-Wl,--export=_create_file", "-Wl,--export=setvbuf", "-Wl,--export=_set_file_ready"); // libc + nob_cmd_append(&cmd, "-Wl,--export=_add_errno", "-Wl,--export=_set_errno"); // libc nob_cmd_append(&cmd, "-o"); nob_cmd_append(&cmd, examples[i].wasm_path); nob_cmd_append(&cmd, examples[i].src_path); diff --git a/raylib.js b/raylib.js index a6a0154..ee8ff9f 100644 --- a/raylib.js +++ b/raylib.js @@ -1,9 +1,14 @@ -function make_environment(env) { +function make_environment(env, ...envs) { return new Proxy(env, { get(target, prop, receiver) { if (env[prop] !== undefined) { return env[prop].bind(env); } + for (var i = 0; i < envs.length; i++) { + if (envs[i][prop] !== undefined) { + return envs[i][prop].bind(envs[i]); + } + } return (...args) => { throw new Error(`NOT IMPLEMENTED: ${prop} ${args}`); } @@ -44,6 +49,12 @@ class RaylibJs { this.currentMousePosition = {x: 0, y: 0}; this.images = []; this.quit = false; + + //libc + this.math = new MathJs(); + this.stdlib = new StdlibJs(); + this.stdio = new StdioJs(); + this.time_js = new TimeJs(); } constructor() { @@ -67,9 +78,17 @@ class RaylibJs { } this.wasm = await WebAssembly.instantiateStreaming(fetch(wasmPath), { - env: make_environment(this) + env: make_environment(this, this.math, this.stdlib, this.stdio, this.time_js) }); + // Init libc + this.math.init(this.wasm); + this.stdlib.init(this.ctx, this.wasm, (js_fn) => { + this.entryFunction = js_fn; + }); + this.stdio.init(this.wasm); + this.time_js.init(this.wasm); + const keyDown = (e) => { this.currentPressedKeyState.add(glfwKeyMapping[e.code]); }; @@ -87,9 +106,17 @@ class RaylibJs { window.addEventListener("wheel", wheelMove); window.addEventListener("mousemove", mouseMove); - this.wasm.instance.exports.main(); + try { + this.wasm.instance.exports.main(); + } catch (e) { + if (!(e instanceof ExitWasmException)) { + throw e; + } + } + const next = (timestamp) => { if (this.quit) { + this.stdlib.handle_exit(); this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); window.removeEventListener("keydown", keyDown); this.#reset() @@ -97,7 +124,16 @@ class RaylibJs { } this.dt = (timestamp - this.previous)/1000.0; this.previous = timestamp; - this.entryFunction(); + + try { + this.stdio.wait_open_files(); + this.entryFunction(); + } catch (e) { + if (!(e instanceof ExitWasmException)) { + throw e; + } + } + window.requestAnimationFrame(next); }; window.requestAnimationFrame((timestamp) => { diff --git a/wasm/file_example.wasm b/wasm/file_example.wasm new file mode 100644 index 0000000..2219de6 Binary files /dev/null and b/wasm/file_example.wasm differ diff --git a/wasm/libc_exit.wasm b/wasm/libc_exit.wasm new file mode 100644 index 0000000..e69cb15 Binary files /dev/null and b/wasm/libc_exit.wasm differ diff --git a/wasm/libc_file.wasm b/wasm/libc_file.wasm new file mode 100644 index 0000000..865f9ce Binary files /dev/null and b/wasm/libc_file.wasm differ diff --git a/wasm/libc_malloc.wasm b/wasm/libc_malloc.wasm new file mode 100644 index 0000000..b373d03 Binary files /dev/null and b/wasm/libc_malloc.wasm differ diff --git a/wasm/libc_math.wasm b/wasm/libc_math.wasm new file mode 100644 index 0000000..6c38db4 Binary files /dev/null and b/wasm/libc_math.wasm differ diff --git a/wasm/libc_time.wasm b/wasm/libc_time.wasm new file mode 100644 index 0000000..c0ebc0f Binary files /dev/null and b/wasm/libc_time.wasm differ diff --git a/wasm/tsoding_snake.wasm b/wasm/tsoding_snake.wasm index aced634..b6eaf75 100755 Binary files a/wasm/tsoding_snake.wasm and b/wasm/tsoding_snake.wasm differ