#define CBS_LIBRARY_PATH "./external/cbs.d/cbs.h"
#include CBS_LIBRARY_PATH
+// TODO: Support for Linux
+
#define CC "cc"
#define CFLAGS "-Wall", "-Wextra", "-Wpedantic", "-I./external/raylib/src", "-I./fonts"
#ifdef __MACH__
cbs_file_paths_build_file_ext(&src_files, "./src", ".c");
cbs_file_paths_for_each (src_file, src_files) {
const char *obj_file = cbs_string_build(cbs_strip_file_ext(src_file), ".o");
- if (cbs_needs_rebuild(obj_file, src_file))
+ if (cbs_needs_rebuild(obj_file, src_file, "./src/utils.h"))
cbs_run(CC, CFLAGS, "-c", "-o", obj_file, src_file);
}
#define DEFAULT_SCREEN_HEIGHT 600
#define FILE_PATH_CAP 2048
-// TODO: Improve error messages
-
-static Image image;
-static Texture2D texture;
-static bool have_texture;
-
-static void create_texture_from_xpm_file(const char *xpm_file_path) {
- if ((have_texture = parse_xpm_file(&image, xpm_file_path))) {
- UnloadTexture(texture);
- texture = LoadTextureFromImage(image);
- }
-}
-
-int main(int argc, char **argv) {
- char xpm_file_path[FILE_PATH_CAP] = {0};
-
- if (argc >= 2) {
- strncpy(xpm_file_path, argv[1], FILE_PATH_CAP);
- create_texture_from_xpm_file(xpm_file_path);
- }
-
+int main(void) {
SetTraceLogLevel(LOG_WARNING);
InitWindow(DEFAULT_SCREEN_WIDTH, DEFAULT_SCREEN_HEIGHT, "simplexpm");
SetWindowState(FLAG_WINDOW_RESIZABLE);
Font font = LoadSourceCodeProFont();
+ char xpm_file_path[FILE_PATH_CAP] = {0};
+ Image image;
+ Texture2D texture;
+ bool have_texture = false, startup = true;
while (!WindowShouldClose()) {
- if (IsFileDropped()) {
+ bool isFileDropped = IsFileDropped();
+ if (isFileDropped) {
FilePathList file_paths = LoadDroppedFiles();
strncpy(xpm_file_path, file_paths.paths[0], FILE_PATH_CAP);
UnloadDroppedFiles(file_paths);
- create_texture_from_xpm_file(xpm_file_path);
}
- if (IsKeyDown(KEY_R) && have_texture)
- create_texture_from_xpm_file(xpm_file_path);
- if (IsKeyDown(KEY_S) && have_texture) {
+ if ((IsKeyPressed(KEY_R) && !startup) || isFileDropped) {
+ if ((have_texture = parse_xpm_file(&image, xpm_file_path))) {
+ UnloadTexture(texture);
+ texture = LoadTextureFromImage(image);
+ }
+ startup = false;
+ }
+ if (IsKeyPressed(KEY_S) && have_texture) {
char png_file_path[FILE_PATH_CAP] = {0};
strncpy(png_file_path, xpm_file_path, strlen(xpm_file_path) - strlen(".xpm"));
strncat(png_file_path, ".png", strlen(".png"));
(screen_height - (texture.height * scale)) / 2};
DrawTextureEx(texture, position, 0, scale, WHITE);
} else {
- Vector2 message_dimensions = MeasureTextEx(font, error_message, FONT_SIZE, 0),
+ const char *message = startup ? "Drag and drop an XPM file here"
+ : "Unable to parse XPM file\n"
+ "(see console for detail)";
+ Vector2 message_dimensions = MeasureTextEx(font, message, FONT_SIZE, 0),
message_placement = {
.x = (screen_width - message_dimensions.x) / 2,
.y = (screen_height - message_dimensions.y) / 2,
};
- DrawTextEx(font, error_message, message_placement, FONT_SIZE, 0, BLACK);
+ DrawTextEx(font, message, message_placement, FONT_SIZE, 0, BLACK);
}
EndDrawing();
}
unsigned int *pixels;
bool parse_xpm_file(Image *image, const char *file_path) {
+ line_number = 0;
switch (sigsetjmp(env, 0)) {
case 1:
return false;
if ((file = fopen(file_path, "r")) == NULL)
SIMPLE_XPM_ERROR("Unable to open provided file");
- get_next_line(&line_buffer, file);
- char *line_buffer_p = line_buffer;
- check_next_token(&line_buffer_p, "/* XPM */");
+ check_xpm_header(file);
- get_next_line(&line_buffer, file);
- line_buffer_p = line_buffer;
+ get_next_line_check_eof(&line_buffer, file);
+ char *line_buffer_p = line_buffer;
check_next_token(&line_buffer_p, "static");
if (!isspace(*line_buffer_p))
SIMPLE_XPM_ERROR("Expected token \"static\"");
check_next_token(&line_buffer_p, "{");
// Parse values
- get_next_line(&line_buffer, file);
+ get_next_line_check_eof(&line_buffer, file);
line_buffer_p = line_buffer;
check_next_token(&line_buffer_p, "\"");
size_t width = convert_token_to_num(get_next_token(&line_buffer_p), 10),
SIMPLE_XPM_MALLOC(color_table, NUM_XPM_MODES * num_colors * sizeof(*color_table));
bool possible_modes[NUM_XPM_MODES] = {false};
for (size_t i = 0; i < num_colors; ++i) {
- get_next_line(&line_buffer, file);
+ get_next_line_check_eof(&line_buffer, file);
line_buffer_p = line_buffer;
check_next_token(&line_buffer_p, "\"");
char *key_buffer;
SIMPLE_XPM_MALLOC(key_buffer, (chars_per_pixel + 1) * sizeof(*key_buffer));
for (size_t i = 0; i < height; ++i) {
- get_next_line(&line_buffer, file);
+ get_next_line_check_eof(&line_buffer, file);
line_buffer_p = line_buffer;
check_next_token(&line_buffer_p, "\"");
if (strlen(line_buffer_p) < width * chars_per_pixel + strlen("\","))
printf("Parsing XPM extensions\n");
}
- get_next_line(&line_buffer, file);
+ get_next_line_check_eof(&line_buffer, file);
line_buffer_p = line_buffer;
check_next_token(&line_buffer_p, "}");
check_next_token(&line_buffer_p, ";");
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "utils.h"
-// TODO: Ignore comments
-int get_next_line(char **buffer, FILE *file) {
- static size_t line_cap = 0;
+static bool is_multiline;
+static unsigned int multiline_num;
+
+static bool line_is_comment(char *line) {
+
+ // Remove comments from line
+ char *multiline_start = NULL;
+ do {
+ if (is_multiline) {
+ char *multiline_end;
+ if (multiline_start) multiline_end = strstr(multiline_start + 2, "*/");
+ else multiline_end = strstr(line, "*/");
+ if (!multiline_end) {
+ if (!multiline_start) return true;
+ *multiline_start = '\0';
+ break;
+ }
+ is_multiline = false;
+ if (multiline_start) *(multiline_start++) = ' ';
+ else multiline_start = line;
+ multiline_end += strlen("*/");
+ memmove(multiline_start, multiline_end, strlen(multiline_end) + 1);
+ }
+ char *singleline_start = strstr(line, "//");
+ if (singleline_start)
+ *singleline_start = '\0';
+
+ multiline_start = strstr(line, "/*");
+ } while (multiline_start && (multiline_num = line_number, is_multiline = true));
+
+ // Return true if the line is only whitespace at this point
+ return *strstrip(&line) == '\0';
+}
+
+bool get_next_line(char **buffer, FILE *file) {
+ static size_t buffer_cap = 0;
errno = 0;
- if (getline(buffer, &line_cap, file) == -1) {
- if (errno != 0)
- SIMPLE_XPM_ERROR("Unable to parse provided file: expected a new line");
- return 0;
- }
- return 1;
+ do {
+ if (getline(buffer, &buffer_cap, file) == -1) {
+ if (errno != 0)
+ SIMPLE_XPM_ERROR("Error occured while getting line %d", line_number + 1);
+ if (is_multiline) {
+ is_multiline = false;
+ SIMPLE_XPM_ERROR("Failed to close multiline comment from line %d",
+ multiline_num);
+ }
+ return false;
+ }
+ ++line_number;
+ } while (line_is_comment(*buffer));
+ return true;
+}
+
+void get_next_line_check_eof(char **buffer, FILE *file) {
+ if (!get_next_line(buffer, file))
+ SIMPLE_XPM_ERROR("Expected another line after line %d", line_number);
}
void check_next_token(char **string, const char *token) {
size_t token_len = strlen(token);
if (strncmp(strstrip(string), token, token_len) != 0)
- SIMPLE_XPM_ERROR("Unable to parse provided file: expected token \"%s\"", token);
+ SIMPLE_XPM_ERROR("Expected \"%s\" at line %d", token, line_number);
*string += token_len;
}
+void check_xpm_header(FILE *file) {
+ char *buffer = NULL;
+ size_t buffer_cap;
+ if (getline(&buffer, &buffer_cap, file) == -1) {
+ if (errno != 0)
+ SIMPLE_XPM_ERROR("Error occured while getting line %d", line_number + 1);
+ SIMPLE_XPM_ERROR("The provided file is empty");
+ }
+ ++line_number;
+ check_next_token(&buffer, "/* XPM */");
+ if (*strstrip(&buffer) != '\0')
+ SIMPLE_XPM_ERROR("Trailing text after XPM file header; incorrect format");
+}
+
char *get_next_token(char **string) {
char *result;
do {
#include <stdbool.h>
#include <stdio.h>
-int get_next_line(char **buffer, FILE *file);
+bool get_next_line(char **buffer, FILE *file);
+void get_next_line_check_eof(char **buffer, FILE *file);
void check_next_token(char **string, const char *token);
+void check_xpm_header(FILE *file);
char *get_next_token(char **string);
bool get_terminal_token(char **string, char **token);
size_t convert_token_to_num(const char *token, int base);
#include "utils.h"
-char error_message[ERROR_MESSAGE_CAP] = "Drag and drop an XPM file here";
jmp_buf env;
+unsigned int line_number;
char *strstrip(char **string) {
for (; isspace(**string) && **string != '\0'; ++*string);
do { \
p = malloc(size); \
if (p == NULL) { \
- fprintf(stderr, "simplexpm: OUT OF MEMORY (buy more RAM?)"); \
+ fprintf(stderr, "simplexpm: OUT OF MEMORY: Terminating\n"); \
exit(EXIT_FAILURE); \
} \
memset(p, 0, size); \
p = NULL; \
} while (0)
-#define ERROR_MESSAGE_CAP 2048
-extern char error_message[ERROR_MESSAGE_CAP];
extern jmp_buf env;
+extern unsigned int line_number;
#define SIMPLE_XPM_ERROR(...) \
do { \
SIMPLE_XPM_FREE(keys); \
SIMPLE_XPM_FREE(color_table); \
SIMPLE_XPM_FREE(pixels); \
- snprintf(error_message, ERROR_MESSAGE_CAP, __VA_ARGS__); \
+ fprintf(stderr, "simplexpm: ERROR: "); \
+ fprintf(stderr, __VA_ARGS__); \
+ fprintf(stderr, "\n"); \
siglongjmp(env, 1); \
} while (0)