diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cde4ac6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,10 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 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. + +For more information, please refer to diff --git a/audit.c b/audit.c new file mode 100644 index 0000000..c90c268 --- /dev/null +++ b/audit.c @@ -0,0 +1,196 @@ +// Copyright (c) 2025, Zak Fenton +// This "audit" program is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +#include +#include +#include + +#define AUDIT_TYPE_OLD 1 +#define AUDIT_TYPE_NEW 2 +#define AUDIT_TYPE_UNMARKED 3 + +typedef struct audit_info audit_info_t; +typedef struct audit_filelist audit_filelist_t; +struct audit_filelist { + const char* name; + audit_filelist_t* next; + int type; + int lines; +}; +struct audit_info { + int filecount; + int linecount; + audit_filelist_t* oldcode; + audit_filelist_t* newcode; + audit_filelist_t* unmarkedcode; +}; + +int audit_filelist_countfiles(audit_filelist_t* l) { + int total = 0; + while (l) { + total++; + l = l->next; + } + return total; +} + +int audit_filelist_countlines(audit_filelist_t* l) { + int total = 0; + while (l) { + total += l->lines; + l = l->next; + } + return total; +} + +int usage(int argc, char** argv, const char* error) { + FILE* out = error ? stderr : stdout; + fprintf(out, "ABOUT:\n"); + fprintf(out, "This is a simple code audit tool to note occurrences like OLD CODE & NEW CODE\n"); + fprintf(out, "The main purpose of this tool is to help gradually remove old/third-party code from a codebase,\n"); + fprintf(out, "but it could be extended later e.g. for checking each file has been manually audited at a recent date.\n"); + fprintf(out, "This tool isn't specific to a particular programming language and can be used for config files too.\n"); + fprintf(out, "\nUSAGE:\n"); + fprintf(out, "\t%s [source files...]\n", argv[0]); + if (error) { + fprintf(out, "ERROR: %s\n", error); + return -1; + } else { + return 0; + } +} + +int audit_check(const char* line, const char* check) { + char c; + while ((c = *line++) != 0) { + if (c == check[0]) { + const char* search = check+1; + while (*search && *search == *line) { + line++; + search++; + } + if (!*search) { + return 1; // Found! + } + } + } + return 0; // Not found +} + +void audit_line(audit_info_t* info, audit_filelist_t* file, const char* line) { + if (audit_check(line, "OLD CODE")) { + file->type = AUDIT_TYPE_OLD; + } else if (file->type == AUDIT_TYPE_UNMARKED && audit_check(line, "NEW CODE")) { + file->type = AUDIT_TYPE_NEW; + } + file->lines++; +} + +char linebuf[1000]; + +int audit_run(audit_info_t* info, const char* filename) { + FILE* f = fopen(filename, "r"); + if (!f) { + return -1; + } + audit_filelist_t* filelist = malloc(sizeof(audit_filelist_t)); + if (!filelist) { + fprintf(stderr, "ERROR: Failed to allocate memory\n"); + fclose(f); + return -1; + } + filelist->name = strdup(filename); + filelist->type = AUDIT_TYPE_UNMARKED; + filelist->lines = 0; + filelist->next = NULL; + + while (fgets(linebuf, 1000, f)) { + audit_line(info, filelist, linebuf); + } + + switch (filelist->type) { + case AUDIT_TYPE_NEW: + filelist->next = info->newcode; + info->newcode = filelist; + break; + case AUDIT_TYPE_OLD: + filelist->next = info->oldcode; + info->oldcode = filelist; + break; + default: + filelist->next = info->unmarkedcode; + info->unmarkedcode = filelist; + break; + } + info->linecount += filelist->lines; + + fclose(f); + return 0; +} + +void audit_subreport(audit_info_t* info, const char* startline, audit_filelist_t* files, FILE* out, int extrastats) { + int nfiles = audit_filelist_countfiles(files); + int nlines = audit_filelist_countlines(files); + int fpercent = (int) (((double) nfiles) / ((double) (info->filecount)) * 100); + int lpercent = (int) (((double) nlines) / ((double) (info->linecount)) * 100); + fprintf(out, "%s %d FILES (%d%%)\t%d LINES (%d%%)\n", startline, nfiles, fpercent, nlines, lpercent); + if (!files) { + return; + } + //fprintf(out, "%s IN ", startline); + audit_filelist_t* smallest = files; + audit_filelist_t* largest = files; + while (files) { + //fprintf(out, " %s", files->name); + if (files->lines < smallest->lines) { + smallest = files; + } else if (files->lines > largest->lines) { + largest = files; + } + files = files->next; + } + //fprintf(out, "\n"); + if (extrastats) { + fprintf(out, "%s SMALLEST %s (%d lines) LARGEST %s (%d lines)\n", startline, smallest->name, smallest->lines, largest->name, largest->lines); + } +} + +void audit_report(audit_info_t* info, FILE* out) { + fprintf(out, "THIS IS AN AUTOGENERATED REPORT\n"); + fprintf(out, "This report was created by the 'audit' program\n"); + fprintf(out, "TOTAL %d FILES %d LINES\n", info->filecount, info->linecount); + audit_subreport(info, "NEW CODE: ", info->newcode, out, 0); + audit_subreport(info, "OLD CODE: ", info->oldcode, out, 1); + audit_subreport(info, "UNMARKED CODE:", info->unmarkedcode, out, 1); +} + +int main(int argc, char** argv) { + audit_info_t info; + info.filecount = 0; + info.oldcode = NULL; + info.newcode = NULL; + info.unmarkedcode = NULL; + + for (int i = 1; i < argc; i++) { + if (audit_run(&info, argv[i]) != 0) { + fprintf(stderr, "ERROR: Failed to process file '%s'\n", argv[i]); + return -1; + } + info.filecount++; + } + + if (info.filecount < 1) { + return usage(argc, argv, "Expecting list of source files\n"); + } else { + audit_report(&info, stdout); + return 0; + } +} + diff --git a/numbered.c b/numbered.c new file mode 100644 index 0000000..b2b3c68 --- /dev/null +++ b/numbered.c @@ -0,0 +1,1839 @@ +// Numbered.codes +// Copyright (c) 2025, Zak Fenton +// Numbered.codes is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +#include "kernel/types.h" +#include "kernel/stat.h" +#include "kernel/fcntl.h" +#include "user/user.h" + +#ifndef NULL +#define NULL ((void*)0ULL) +#endif + +// These are added to different base numbers to obtain the standard terminal colour codes +#define NC_COLOUR_BLACK 0 +#define NC_COLOUR_RED 1 +#define NC_COLOUR_GREEN 2 +#define NC_COLOUR_YELLOW 3 +#define NC_COLOUR_BLUE 4 +#define NC_COLOUR_MAGENTA 5 +#define NC_COLOUR_CYAN 6 +#define NC_COLOUR_LIGHTGREY 7 +#define NC_COLOUR_DARKGREY 60 +#define NC_COLOUR_LIGHTRED 61 +#define NC_COLOUR_LIGHTGREEN 62 +#define NC_COLOUR_LIGHTYELLOW 63 +#define NC_COLOUR_LIGHTBLUE 64 +#define NC_COLOUR_LIGHTMAGENTA 65 +#define NC_COLOUR_LIGHTCYAN 66 +#define NC_COLOUR_WHITE 67 + +#define NC_TERMBASE_FG 30 +#define NC_TERMBASE_BG 40 + +#define NC_INVALIDLINENUMBER ((nc_uword_t)0xFFFFFFFFFFFFFFFFULL) + +// Ideally the editor should detect whether it should be using +// plaintext mode or a programming mode. +#define NC_MODE_NONE 0 +#define NC_MODE_TEXT 1 +#define NC_MODE_AUTOCODE 2 + +typedef signed char nc_char_t; +typedef long long nc_word_t; +typedef unsigned long long nc_uword_t; + +typedef struct nc_line nc_line_t; +typedef struct nc_type nc_type_t; +typedef struct nc_var nc_var_t; +typedef struct nc_expr nc_expr_t; +typedef struct nc_handler nc_handler_t; +typedef struct nc_syntax nc_syntax_t; +typedef struct nc_stmtpart nc_stmtpart_t; +typedef struct nc_stmt nc_stmt_t; +typedef struct nc_edit nc_edit_t; +typedef struct nc_state nc_state_t; +typedef nc_state_t* (*nc_parsestep_t) (nc_state_t*, nc_line_t*); +typedef nc_state_t* (*nc_compiledstep_t) (nc_state_t*); + +struct nc_line { + nc_uword_t number; + int mode; + int flags; + char* src; + nc_stmt_t* parsed; // The parsed statement, not necessarily present for non-code lines but currently always present at least as an empty structure + nc_line_t* next; +}; + +// An operation adding, removing, editing or renumbering a line will be +// encoded as an nc_edit_t. +struct nc_edit { + //nc_uword_t typeflags; // no flags yet, only 1 line edit + nc_uword_t oldline; + nc_uword_t newline; + char* oldsrc; + char* newsrc; + nc_edit_t* next; +}; + +#define NC_TYPE_LOGIC 1 +#define NC_TYPE_INT 2 +#define NC_TYPE_FLOAT 3 +#define NC_TYPE_STRING 4 +#define NC_TYPE_ARRAY 5 +#define NC_TYPE_IO 6 + +struct nc_type { + int typekind; + int option; + nc_type_t* inner; +}; + +struct nc_var { + nc_line_t* firstuse; // The first noticed use + char* name; + nc_uword_t type; + nc_var_t* next; +}; + +// The operators are numbered 900-9xx so that any mixup with int values is noticeable +#define NC_OP_ADD 900 +#define NC_OP_SUB 901 +#define NC_OP_MUL 902 +#define NC_OP_DIV 903 +#define NC_OP_MOD 904 + +#define NC_OP_BITSEQ 950 +#define NC_OP_BITSNE 951 +#define NC_OP_ISEQ 952 +#define NC_OP_ISNE 953 +#define NC_OP_ISLE 954 +#define NC_OP_ISGE 955 +#define NC_OP_ISLT 956 +#define NC_OP_ISGT 957 +#define NC_OP_LOGAND 958 +#define NC_OP_LOGOR 959 + +#define NC_OP_STRING 997 +#define NC_OP_FUNC 998 +#define NC_OP_VAR 999 + +// Every expression is encoded like a binary expression, simple int constants +// are just the int as the opdata with a NULL left & right. +struct nc_expr { + nc_type_t* type; + nc_expr_t* left; + char* strdata; // Only used for strings and names of functions & variables + nc_word_t opdata; // The operator or value + nc_expr_t* right; +}; + +#define NC_EXPR_ISVALUE(x) (((x)->left == NULL) && ((x)->right == NULL)) +#define NC_EXPR_ISBINARY(x) (((x)->left != NULL) && ((x)->right != NULL)) +#define NC_EXPR_ISUNARY(x) (((x)->left == NULL) && ((x)->right != NULL)) +#define NC_EXPR_ISVALID(x) ((x = NULL) && (NC_EXPR_ISVALUE(x) || NC_EXPR_ISBINARY(x) || NC_EXPR_ISUNARY(x))) + +struct nc_handler { + char* name; + nc_syntax_t* syntax; + char* help; + nc_parsestep_t parsestep; + nc_handler_t* next; +}; + +#define NC_SYNTAX_KEYWORD 1 +#define NC_SYNTAX_OPERATOR 2 +#define NC_SYNTAX_VARIABLE 3 +#define NC_SYNTAX_MATH 4 +#define NC_SYNTAX_LOGIC 5 +#define NC_SYNTAX_LINE 6 +#define NC_SYNTAX_NAME 7 +#define NC_SYNTAX_TYPE 8 + +#define NC_SYNTAX_OPTIONAL (1<<8) + +// A piece of syntax +struct nc_syntax { + nc_word_t type; + char* string; + nc_syntax_t* next; +}; + +#define NC_STMTPART_PRESENT 1 +#define NC_STMTPART_SKIPPED 2 +#define NC_STMTPART_ERROR 3 + +// A parameter or keyword in a parsed statement +struct nc_stmtpart { + nc_uword_t parttype; + nc_expr_t* expr; + char* srcpart; + char* strvalue; + nc_stmtpart_t* next; +}; + +// A parsed/compiled statement. +// This will only exist for runnable code, not plaintext lines. +struct nc_stmt { + nc_handler_t* head; // The handler or first part of the statement + nc_stmtpart_t* tail; // Any additional parsed components + nc_uword_t abiinfo; // ABI information if applicable + nc_compiledstep_t compiled; // The compiled step, if applicable +}; + +struct nc_state { + nc_line_t* instruction; + nc_line_t* stepinstruction; // Only for step by step debugging + nc_line_t* errorinstruction; + char* inputlinebuffer; + char* errormessage; + char* prompt; + + nc_handler_t* currentmode; // The current mode, usually CODE or TEXT + + nc_handler_t* modes; // Mode handlers (can be thought of as generic statements, e.g. data text or code passed on elsewhere) + nc_handler_t* stmthandlers; // Statement handlers + + nc_var_t* variables; + + nc_edit_t* undostack; + nc_edit_t* redostack; + + nc_line_t* firstline; + nc_line_t tmpline; // May be used to run a line directly + int stopped; + int errorcode; + int exited; + int exitvalue; + int editormode; + int visualimpairment; // Controls colours, -1 for text<->speech users or plaintext terminal for commands and 1 for high contrast mode, 0 will run in default mode +}; + +nc_expr_t* nc_expr_alloc(nc_state_t* state, nc_type_t* type, nc_expr_t* left, char* strdata, nc_word_t opdata, nc_expr_t* right); +void nc_expr_free(nc_state_t* state, nc_expr_t* expr); +nc_expr_t* nc_expr_parse(nc_state_t* state, char** strvar); +nc_expr_t* nc_expr_parselogic(nc_state_t* state, char** strvar); +nc_type_t* nc_expr_eval(nc_state_t* state, nc_expr_t* expr, nc_word_t* resultvar); + +nc_line_t* nc_lookupline(nc_state_t* state, nc_uword_t number); +char* nc_copystr(char* str, int n); +void nc_resetvars(nc_state_t* state); + +#define NC_ISSPACE(ch) (ch == ' ' || ch == '\n' || ch == '\t') +#define NC_ISOP(ch) (ch == '=' || ch == '<' || ch == '>' || ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '%') +#define NC_ISBR(ch) (ch == '(' || ch == ')' || ch == '[' || ch == ']' || ch == '{' || ch == '}' || ch == '/') + +void nc_setfg(nc_state_t* state, int colour) { + if (state->visualimpairment >= 0) { + printf("\033[%dm", NC_TERMBASE_FG + colour); + } +} +void nc_setbg(nc_state_t* state, int colour) { + if (state->visualimpairment >= 0) { + printf("\033[%dm", NC_TERMBASE_BG + colour); + } +} +void nc_resetcolour(nc_state_t* state) { + if (state->visualimpairment >= 0) { + printf("\033[0m"); + } +} + +void nc_skipspace(char** strvar) { + char* s = *strvar; + while (NC_ISSPACE(*s)) { + s++; + } + *strvar = s; +} + +nc_uword_t nc_scandec(char** strvar) { + nc_skipspace(strvar); + nc_uword_t val = 0; + char* s = *strvar; + char c; + while ((c = *s) >= '0' && c <= '9') { + nc_uword_t oldval = val; + val = val * 10 + c - '0'; + if (val / 10 != oldval) { + printf("Failed to scan number, too big.\n"); + return 0; + } + s++; + } + + *strvar = s; + return val; +} + +// Scans a string returning the number of characters (and advancing strvar to the first) +// or -1 if not a string, -2 for a malformed string +int nc_scanstring(char** strvar) { + nc_skipspace(strvar); + char* s = *strvar; + char quote = 0; + int index = 0; + // Return -1 if it's not a string. + if (*s != '\'' && *s != '"') { + return -1; + } + quote = *s; + s++; + while (s[index] != quote) { + if (s[index] == 0 || s[index] == '\n') { + return -2; + } + index++; + } + *strvar = s; + return index; +} + +#define NC_UPPERCASE(ch) (((ch >= 'a') && (ch <= 'z')) ? ch - 'a' + 'A' : ch) + +int nc_scankeyword(char** strvar, char* k) { + if (k == NULL) { // Ignore dummy calls from macros + return 0; + } + nc_skipspace(strvar); + char* s = *strvar; + //printf("Checking '%s' against '%s'\n", s, k); + while (*s && *k) { + if (NC_UPPERCASE(*s) != NC_UPPERCASE(*k)) { + return 0; + } + s++; + k++; + } + if (!*k) { // Did we reach the end of a matching token? + *strvar = s; + return 1; + } else { + return 0; + } +} + +#define NC_ISVALIDNAMESTART(c) ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '_')) +#define NC_ISVALIDNAMEEND(c) (NC_ISVALIDNAMESTART(c) || (c >= '0' && c <= '9') || c == '$') + +// Scans a name returning the number of characters (and advancing strvar to the first) +// or -1 if not a name +int nc_scanname(char** strvar) { + nc_skipspace(strvar); + char* s = *strvar; + int index = 0; + if (!NC_ISVALIDNAMESTART(*s)) { + return -1; + } + while (NC_ISVALIDNAMEEND(s[index])) { + index++; + } + *strvar = s; + return index; +} + +int nc_scaneol(char** strvar) { + nc_skipspace(strvar); + return **strvar == 0 ? 1 : 0; +} + +nc_uword_t nc_strlen(char* s) { + if (!s) { + return 0; + } + nc_uword_t l = 0; + while (*s++) { + l++; + } + return l; +} + +nc_uword_t nc_strchars(char* src) { + char* s = src; + if (!s) { + return 0; + } + nc_uword_t l = 0; + while (*s) { + if (*s & 0x80) { + l++; + s++; + while (*s & 0x80 && !(*s & 0x40)) { + s++; + } + } else { + s++; + l++; + } + } + //printf("Length of '%s' is %d characters or %d bytes\n", src, l, nc_strlen(src)); + return l; +} + +char* nc_strdup(char* s) { + if (!s) { + return NULL; + } + nc_uword_t l = nc_strlen(s); + char* result = malloc(l+1); + if (result) { + for (nc_uword_t i = 0; i < l; i++) { + result[i] = s[i]; + } + result[l] = 0; + } + return result; +} + +// Returns a matching type object, could be allocated from a pool but +// for now just allocates a type. +nc_type_t* nc_type_ref(nc_state_t* state, int typekind, int option, nc_type_t* inner) { + nc_type_t* result = malloc(sizeof(nc_type_t)); + if (result) { + result->typekind = typekind; + result->option = option; + result->inner = inner; + } + return result; +} + +nc_type_t* nc_type_copyref(nc_state_t* state, nc_type_t* type) { + if (type == NULL) { + return NULL; + } + return nc_type_ref(state, type->typekind, type->option, type->inner); +} + +// Dereferences/frees a type structure. +void nc_type_deref(nc_state_t* state, nc_type_t* type); +void nc_type_deref(nc_state_t* state, nc_type_t* type) { + if (type->inner) { + nc_type_deref(state, type->inner); + } + free(type); +} + +nc_type_t* nc_scantype(nc_state_t* state, char** strvar) { + if (nc_scankeyword(strvar, "INT") || nc_scankeyword(strvar, "INTEGER") || nc_scankeyword(strvar, "SIGNED")) { + return nc_type_ref(state, NC_TYPE_INT, -64 /* 0-sizeof(nc_word_t)*8 */, NULL); + } else if (nc_scankeyword(strvar, "STRING")) { + return nc_type_ref(state, NC_TYPE_STRING, -1, NULL); + } else if (nc_scankeyword(strvar, "IO")) { + return nc_type_ref(state, NC_TYPE_STRING, -1, NULL); + } else { + return 0; + } +} + +nc_expr_t* nc_expr_alloc(nc_state_t* state, nc_type_t* type, nc_expr_t* left, char* strdata, nc_word_t opdata, nc_expr_t* right) { + nc_expr_t* result = malloc(sizeof(nc_expr_t)); + if (result) { + result->type = type; + result->left = left; + result->strdata = strdata; + result->opdata = opdata; + result->right = right; + } + return result; +} + +void nc_expr_free(nc_state_t* state, nc_expr_t* expr) { + if (expr) { + if (expr->type && expr->type->typekind == NC_TYPE_STRING && NC_EXPR_ISVALUE(expr)) { + free((void*)(expr->opdata)); + } + nc_expr_free(state, expr->left); + nc_expr_free(state, expr->right); + nc_type_deref(state, expr->type); + } +} + +nc_stmt_t* nc_stmt_alloc(nc_state_t* state) { + nc_stmt_t* result = malloc(sizeof(nc_stmt_t)); + if (result) { + result->head = NULL; + result->tail = NULL; + result->abiinfo = 0; + result->compiled = NULL; + } + return result; +} + +void nc_stmtpart_free(nc_state_t* state, nc_stmtpart_t* list) { + while (list) { + if (list->strvalue) { + free(list->strvalue); + } + if (list->expr) { + //TODO nc_expr_free(state, list->expr); + } + nc_stmtpart_t* oldlist = list; + list = list->next; + free(oldlist); + } +} + +void nc_stmt_reset(nc_state_t* state, nc_stmt_t* stmt) { + if (stmt) { + stmt->head = NULL; + nc_stmtpart_free(state, stmt->tail); + stmt->tail = NULL; + stmt->abiinfo = 0; + stmt->compiled = NULL; + } +} + +void nc_stmt_free(nc_state_t* state, nc_stmt_t* stmt) { + if (stmt) { + nc_stmt_reset(state, stmt); + free(stmt); + } +} + +// Parses a single syntax element (e.g. a keyword or expression) into a preallocated stmtpart structure. +int nc_stmt_parsepart(nc_state_t* state, char** strvar, nc_syntax_t* syntax, nc_stmtpart_t* part) { + nc_skipspace(strvar); + part->srcpart = *strvar; // Record the start of this part for syntax hilighting/other purposes + part->strvalue = NULL; + //printf("Parsing part '%s'\n", part->srcpart); + nc_word_t n; + switch (syntax->type & 0xFF) { + case NC_SYNTAX_VARIABLE: // TODO, check syntax more precisely: For now, variable locations just allow any "MATH" expression + case NC_SYNTAX_MATH: + part->expr = nc_expr_parse(state, strvar); + if (part->expr) { + part->parttype = NC_STMTPART_PRESENT; + return 1; + } else { + goto notpresent; + } + case NC_SYNTAX_LOGIC: + part->expr = nc_expr_parselogic(state, strvar); + if (part->expr) { + part->parttype = NC_STMTPART_PRESENT; + return 1; + } else { + goto notpresent; + } + case NC_SYNTAX_KEYWORD: + case NC_SYNTAX_OPERATOR: + if (nc_scankeyword(strvar, syntax->string)) { + part->parttype = NC_STMTPART_PRESENT; + return 1; + } else { + goto notpresent; + } + case NC_SYNTAX_NAME: + n = nc_scanname(strvar); + if (n > 0) { + part->parttype = NC_STMTPART_PRESENT; + part->strvalue = nc_copystr(*strvar, n); + return 1; + } else { + goto notpresent; + } + default: + part->parttype = NC_STMTPART_ERROR; + return 0; + } + notpresent: + if (syntax->type & NC_SYNTAX_OPTIONAL) { + part->parttype = NC_STMTPART_SKIPPED; + return 1; + } else { + part->parttype = NC_STMTPART_ERROR; + return 0; + } +} + +// Parses an entire statement line based on the syntax specified in it's handler (without invoking the handler!) +// This should be invoked before the handler is invoked. +nc_stmt_t* nc_stmt_parse(nc_state_t* state, nc_line_t* line, nc_handler_t* handler) { + if (!line->parsed) { + line->parsed = nc_stmt_alloc(state); + } + char* s = line->src; + nc_syntax_t* syntax = handler->syntax; + nc_stmtpart_t* prev = NULL; + while (syntax) { + nc_stmtpart_t* part = malloc(sizeof(nc_stmtpart_t)); + if (!part) { + printf("Failed to allocate memory during parsing\n"); + return NULL; + } + part->expr = NULL; + part->next = NULL; + part->parttype = 0; + part->strvalue = NULL; + if (prev == NULL) { + line->parsed->tail = part; + } else { + prev->next = part; + } + if (!nc_stmt_parsepart(state, &s, syntax, part)) { + part->parttype = NC_STMTPART_ERROR; + return line->parsed; + } + prev = part; + syntax = syntax->next; + } + return line->parsed; +} + +#define NC_WARN(str) printf("WARNING: %s\n", str) + +char* nc_copystr(char* str, int n) { + char* result = malloc(n+1); + if (result) { + for (int i = 0; i < n; i++) { + result[i] = str[i]; + } + result[n] = 0; + } + return result; +} + +nc_expr_t* nc_expr_parsesimple(nc_state_t* state, char** strvar) { + char* s = *strvar; + nc_expr_t* result = NULL; + + nc_skipspace(&s); + + if (*s >= '0' && *s <= '9') { + nc_uword_t n = nc_scandec(&s); + nc_word_t signedn = (nc_word_t) n; + if (signedn >= 0) { + result = nc_expr_alloc(state, nc_type_ref(state, NC_TYPE_INT, -64, NULL), NULL, NULL, signedn, NULL); + } else { + NC_WARN("integer value was parsed (in unsigned mode) but does not fit in signed integer"); + } + } else if (*s == '\'' || *s == '"') { + int n = nc_scanstring(&s); + if (n >= 0) { + result = nc_expr_alloc(state, nc_type_ref(state, NC_TYPE_STRING, -1, NULL), NULL, nc_copystr(s, n), NC_OP_STRING, NULL); + s += n+1; + } else { + NC_WARN("bad string"); + } + } else if (NC_ISVALIDNAMESTART(*s)) { + int n = nc_scanname(&s); + if (n >= 0) { + result = nc_expr_alloc(state, NULL, NULL, nc_copystr(s, n), NC_OP_STRING, NULL); + s += n; + } else { + NC_WARN("bad name"); + } + } else if (*s == '(') { + nc_expr_t* inner = nc_expr_parse(state, &s); + if (inner) { + nc_skipspace(&s); + if (*s == ')') { + s++; + result = inner; + } else { + NC_WARN("expected closing bracket at the end of bracketed expression"); + } + } else { + NC_WARN("expected expression after opening bracket"); + } + } + + if (result) { + *strvar = s; + } + return result; +} + +#define NC_EXPR_PARSEBINOPLEVEL(innerlevel, at, as, bt, bs, ct, cs, dt, ds, et, es, ft, fs) \ + nc_expr_t* left = innerlevel(state, strvar); \ + if (left) { \ + nc_word_t op = 0; \ + do { \ + nc_skipspace(strvar); \ + if (nc_scankeyword(strvar, as)) { \ + op = at; \ + } else if (nc_scankeyword(strvar, bs)) { \ + op = bt; \ + } else if (nc_scankeyword(strvar, cs)) { \ + op = ct; \ + } else if (nc_scankeyword(strvar, ds)) { \ + op = dt; \ + } else if (nc_scankeyword(strvar, es)) { \ + op = et; \ + } else if (nc_scankeyword(strvar, fs)) { \ + op = ft; \ + } \ + if (op) { \ + nc_expr_t* right = innerlevel(state, strvar); \ + if (!right) { \ + NC_WARN("failed to parse right hand side of binary expression"); \ + } \ + left = nc_expr_alloc(state, nc_type_copyref(state, left->type), left, NULL, op, right); \ + } \ + } while (op != 0); \ + } \ + return left; + +nc_expr_t* nc_expr_addoporinner(nc_state_t* state, char** strvar) { + NC_EXPR_PARSEBINOPLEVEL(nc_expr_parsesimple, NC_OP_ADD, "+", NC_OP_SUB, "-", 0, NULL, 0, NULL, 0, NULL, 0, NULL); +} + +nc_expr_t* nc_expr_muloporinner(nc_state_t* state, char** strvar) { + NC_EXPR_PARSEBINOPLEVEL(nc_expr_addoporinner, NC_OP_MUL, "*", NC_OP_DIV, "/", NC_OP_MOD, "%", 0, NULL, 0, NULL, 0, NULL); +} + +nc_expr_t* nc_expr_parse(nc_state_t* state, char** strvar) { + return nc_expr_muloporinner(state, strvar); +} + +nc_expr_t* nc_expr_logicinner(nc_state_t* state, char** strvar) { + NC_EXPR_PARSEBINOPLEVEL(nc_expr_muloporinner, NC_OP_ISEQ, "=", NC_OP_ISNE, "<>", NC_OP_ISLT, "<", NC_OP_ISGT, ">", NC_OP_ISLE, "<=", NC_OP_ISGE, ">="); +} + +nc_expr_t* nc_expr_parselogic(nc_state_t* state, char** strvar) { + NC_EXPR_PARSEBINOPLEVEL(nc_expr_logicinner, NC_OP_LOGOR, "OR", NC_OP_LOGAND, "AND", 0, NULL, 0, NULL, 0, NULL, 0, NULL); +} + +nc_syntax_t* nc_syntax_parse(nc_state_t* state, char** strval) { + char* s = *strval; + nc_syntax_t* first = NULL; + nc_syntax_t* last = NULL; + while (!nc_scaneol(&s)) { + nc_syntax_t* curr = malloc(sizeof(nc_syntax_t)); + if (!curr) { + printf("Out of memory during setup?\n"); + return NULL; + } + curr->type = 0; + curr->string = NULL; + curr->next = NULL; + if (*s == '{') { + s++; + if (nc_scankeyword(&s, "MATH")) { + curr->type = NC_SYNTAX_MATH; + } else if (nc_scankeyword(&s, "LOGIC")) { + curr->type = NC_SYNTAX_LOGIC; + } else if (nc_scankeyword(&s, "LINE")) { + curr->type = NC_SYNTAX_LINE; + } else if (nc_scankeyword(&s, "VARIABLE")) { + curr->type = NC_SYNTAX_VARIABLE; + } else if (nc_scankeyword(&s, "NAME")) { + curr->type = NC_SYNTAX_NAME; + } else if (nc_scankeyword(&s, "TYPE")) { + curr->type = NC_SYNTAX_TYPE; + } else { + printf("Failed to parse syntax at '%s', expected an expression type MATH, LOGIC, LINE, VARIABLE, NAME or TYPE\n", s); + return NULL; + } + nc_skipspace(&s); + if (*s != '}') { + printf("Failed to parse syntax at '%s', expeced closing '}' symbol\n", s); + return NULL; + } + s++; // Skip closing '}' + } else if (*s == '=') { + curr->type = NC_SYNTAX_OPERATOR; + curr->string = nc_copystr(s, 1); + s++; + } else { + int kwlen = nc_scanname(&s); + if (kwlen <= 0) { + printf("Failed to parse syntax at '%s', expected keyword name or some {SPECIAL_ELEMENT}\n", s); + return NULL; + } + curr->type = NC_SYNTAX_KEYWORD; + curr->string = nc_copystr(s, kwlen); + s += kwlen; + } + nc_skipspace(&s); + if (*s == '?') { + curr->type |= NC_SYNTAX_OPTIONAL; + s++; + } + if (!first) { + first = curr; + last = curr; + } else { + last->next = curr; + } + } + *strval = s; + return first; +} + +nc_type_t* nc_expr_binop(nc_state_t* state, nc_word_t lhsval, nc_type_t* lhstype, nc_word_t op, nc_word_t rhsval, nc_type_t* rhstype, nc_word_t* resultvar) { + if (!lhstype || !rhstype) { + printf("Type was NULL!\n"); + return NULL; + } + if (lhstype->typekind == NC_TYPE_INT && rhstype->typekind == NC_TYPE_INT) { + nc_word_t result; + switch (op) { + case NC_OP_ADD: + result = lhsval + rhsval; + break; + case NC_OP_SUB: + result = lhsval - rhsval; + break; + case NC_OP_MUL: + result = lhsval * rhsval; + break; + case NC_OP_DIV: + result = lhsval / rhsval; + break; + case NC_OP_MOD: + result = lhsval % rhsval; + break; + default: + printf("Bad operator!\n"); + return NULL; + } + *resultvar = result; + return nc_type_copyref(state, lhstype); + } + return NULL; +} + +nc_type_t* nc_expr_eval(nc_state_t* state, nc_expr_t* expr, nc_word_t* resultvar) { + if (NC_EXPR_ISVALUE(expr)) { + *resultvar = expr->strdata ? ((nc_word_t) (expr->strdata)) : expr->opdata; + printf("Evaluating expression of type kind %d\n", expr->type->typekind); + return nc_type_copyref(state, expr->type); + } else if (NC_EXPR_ISBINARY(expr)) { + nc_word_t lhsval; + nc_type_t* lhstype = nc_expr_eval(state, expr->left, &lhsval); + if (!lhstype) { + return NULL; + } + nc_word_t rhsval; + nc_type_t* rhstype = nc_expr_eval(state, expr->right, &rhsval); + if (!rhstype) { + nc_type_deref(state, lhstype); + return NULL; + } + + nc_type_t* resulttype = nc_expr_binop(state, lhsval, lhstype, expr->opdata, rhsval, rhstype, resultvar); + nc_type_deref(state, lhstype); + nc_type_deref(state, rhstype); + return resulttype; + } else if (NC_EXPR_ISUNARY(expr)) { + return NULL; + } else { + return NULL; + } +} + +nc_handler_t* nc_mode_add(nc_state_t* state, char* name, nc_parsestep_t parser, char* helpstr) { + nc_handler_t* result = malloc(sizeof(nc_handler_t)); + if (result) { + result->name = nc_strdup(name); + result->help = nc_strdup(helpstr); + result->parsestep = parser; + result->next = state->modes; + state->modes = result; + } + return result; +} + +nc_handler_t* nc_stmt_add(nc_state_t* state, char* syntax, nc_parsestep_t parser, char* helpstr) { + nc_handler_t* result = malloc(sizeof(nc_handler_t)); + if (result) { + //result->name = nc_strdup(syntax); + char* s = syntax; + result->syntax = nc_syntax_parse(state, &s); + if (result->syntax == NULL || result->syntax->string == NULL) { + free(result); // TODO: Cleanup syntax in obscure cases. + return NULL; + } + result->name = nc_strdup(result->syntax->string); // The first syntax item will be the name of the command + //printf("Added command '%s'\n", result->name); + result->help = nc_strdup(helpstr); + result->parsestep = parser; + result->next = state->stmthandlers; + state->stmthandlers = result; + } + return result; +} + +nc_stmtpart_t* nc_line_parsed(nc_state_t* state, nc_line_t* line, int partnum) { + if (!line->parsed || !line->parsed->tail) { + return NULL; + } + nc_stmtpart_t* curr = line->parsed->tail; + for (int i = 0; i < partnum; i++) { + curr = curr->next; + if (curr == NULL) { + return NULL; + } + } + return curr; +} + + +#define NC_ERROR_BAD_ARGUMENT 100 +#define NC_ERROR_BAD_STATEMENT 101 +#define NC_ERROR_BAD_MODE 102 +#define NC_ERROR_INTERNAL_TODO 199 + +nc_state_t* nc_errorstate(nc_state_t* state, int errorcode, char* errormessage) { + state->errorinstruction = state->instruction; + if (state->errormessage) { + free(state->errormessage); + } + state->errormessage = nc_strdup(errormessage); + state->errorinstruction = state->instruction; + state->stepinstruction = state->instruction; + state->instruction = NULL; + state->errorcode = errorcode; + return state; +} + +// These macros are designed to report errors within functions accepting/returning a "state" value +#define NC_ERROR(errorcode, msg) \ + return nc_errorstate(state, errorcode, msg) +#define NC_BAD_ARGUMENT(msg) \ + NC_ERROR(NC_ERROR_BAD_ARGUMENT, msg) +#define NC_BAD_STATEMENT(msg) \ + NC_ERROR(NC_ERROR_BAD_STATEMENT, msg) +#define NC_BAD_MODE(msg) \ + NC_ERROR(NC_ERROR_BAD_STATEMENT, msg) + +#define NC_NEXTINSTRUCTION() \ + do { \ + state->instruction = state->instruction ? state->instruction->next : NULL; \ + return state; \ + } while(0) + +#define NC_ISRUNNING() (line->number != NC_INVALIDLINENUMBER) +#define NC_ISEDITING() (line->number == NC_INVALIDLINENUMBER) +#define NC_RUNONLY() \ + do { \ + if (!NC_ISRUNNING()) { \ + NC_BAD_MODE("This statement only works in run mode"); \ + } \ + } while(0) + +#define NC_EDITONLY() \ + do { \ + if (!NC_ISEDITING()) { \ + NC_BAD_MODE("This statement only works in edit mode"); \ + } \ + } while(0) + +nc_state_t* nc_step_exit0(nc_state_t* state) { + state->instruction = NULL; + state->exitvalue = 0; + state->exited = 1; + return state; +} + +nc_state_t* nc_step_help0(nc_state_t* state) { + printf("OLD HELP. Type a command and press the enter key, try HELP with a keyword following for details [TODO...].\n"); + printf("Type a line number followed by some command or text to store in the list.\n"); + printf("Type EXIT to exit.\n"); + printf("Type LIST to list program or text file.\n"); + return state; +} + +nc_state_t* nc_command_help(nc_state_t* state, nc_line_t* line) { + printf("Type a command and press the enter key, try HELP with a keyword following for details [TODO...].\n"); + printf("Type a line number followed by some command or text to store in the list.\n"); + printf("Available commands:"); + int n = 0; + nc_handler_t* h = state->stmthandlers; + while (h) { + if (n % 8 == 0) { + printf("\n"); + } + printf("%s ", h->name); + for (int i = nc_strchars(h->name)+1; i < 20; i++) { + printf(" "); + } + n++; + h = h->next; + } + printf("\n"); + NC_NEXTINSTRUCTION(); +} + +nc_state_t* nc_command_jump(nc_state_t* state, nc_line_t* line) { + char* src = line->src; + nc_skipspace(&src); + if (!(nc_scankeyword(&src, "J") || nc_scankeyword(&src, "JUMP"))) { + NC_BAD_STATEMENT("Jump command invoked but this is not a J or JUMP instruction"); + } + nc_skipspace(&src); + char* argstart = src; + nc_uword_t target = nc_scandec(&src); + if (src == argstart) { + NC_BAD_ARGUMENT("expected a line number to jump to"); + } + if (!nc_scaneol(&src)) { + NC_BAD_ARGUMENT("JUMP doesn't accept any arguments following the jump label"); + } + nc_line_t* targetline = nc_lookupline(state, target); + if (targetline == NULL) { + NC_BAD_ARGUMENT("line number is beyond the program"); + } + state->instruction = targetline; + return state; +} + +nc_state_t* nc_command_run(nc_state_t* state, nc_line_t* line) { + char* src = line->src; + NC_EDITONLY(); + nc_skipspace(&src); + if (!(nc_scankeyword(&src, "RUN"))) { + NC_BAD_STATEMENT("RUN command invoked but this is not a RUN instruction"); + } + if (!nc_scaneol(&src)) { + NC_BAD_ARGUMENT("RUN doesn't accept any arguments yet, it will run from first line"); + } + nc_resetvars(state); + state->instruction = state->firstline; + return state; +} + +nc_state_t* nc_command_print(nc_state_t* state, nc_line_t* line) { + char* src = line->src; + nc_skipspace(&src); + if (!(nc_scankeyword(&src, "PRINT"))) { + NC_BAD_STATEMENT("PRINT command invoked but this is not a PRINT"); + } + nc_skipspace(&src); + int strlen = nc_scanstring(&src); + if (strlen < 0) { + NC_BAD_ARGUMENT("PRINT expects a string for now"); + } + write(1, src, strlen); + write(1, "\n", 1); + src += strlen+1; + if (!nc_scaneol(&src)) { + NC_BAD_ARGUMENT("PRINT only prints a single value at a time for now"); + } + NC_NEXTINSTRUCTION(); +} + +void nc_syntax_colour(nc_state_t* state, nc_syntax_t* syntax) { + if (!syntax) { + nc_setfg(state, NC_COLOUR_RED); + nc_setbg(state, NC_COLOUR_BLACK); + } else { + switch (syntax->type) { + case NC_SYNTAX_KEYWORD: + nc_setfg(state, NC_COLOUR_GREEN); + break; + case NC_SYNTAX_LINE: + nc_setfg(state, NC_COLOUR_CYAN); + break; + default: + nc_setfg(state, NC_COLOUR_YELLOW); + } + } +} + +nc_state_t* nc_command_list(nc_state_t* state, nc_line_t* line) { + nc_line_t* l = state->firstline; + while (l) { + //printf("Attempting to list %p\n", l); + nc_setfg(state, NC_COLOUR_BLUE); + printf("%d ", (int) (l->number)); + nc_resetcolour(state); + if (l->parsed && l->parsed->tail) { + char* s = l->src; + nc_syntax_t* syn = l->parsed->head->syntax; + nc_syntax_colour(state, syn); + nc_stmtpart_t* p = l->parsed->tail; + while (*s) { + if (p->next && s >= p->next->srcpart) { + p = p->next; + syn = syn ? syn->next : NULL; + nc_syntax_colour(state, syn); + } + char x[2]; + x[0] = *s++; + x[1] = 0; + printf("%s", x); + } + printf("\n"); + } else { + printf("%s\n", l->src); + } + l = l->next; + } + return state; +} + +nc_state_t* nc_command_exit(nc_state_t* state, nc_line_t* line) { + state->instruction = NULL; + state->exitvalue = 0; + state->exited = 1; + return state; +} + +nc_state_t* nc_command_let(nc_state_t* state, nc_line_t* line) { + char* src = line->src; + nc_skipspace(&src); + if (!(nc_scankeyword(&src, "LET") || nc_scankeyword(&src, "ΕΣΤΩ"))) { + NC_BAD_STATEMENT("LET command invoked but this is not a LET"); + } + nc_expr_t* lhs = nc_expr_parse(state, &src); + if (!lhs) { + NC_BAD_ARGUMENT("LET expects a name or array index expression"); + } + printf("lhs name='%s'\n", lhs->strdata); + if (!nc_scankeyword(&src, "=")) { + printf("at remaining line '%s':\n", src); + NC_BAD_ARGUMENT("LET expects an = symbol"); + } + + nc_expr_t* rhs = nc_expr_parse(state, &src); + if (!rhs) { + NC_BAD_ARGUMENT("LET expects an expression on the right hand side of the = symbol"); + } + if (!nc_scaneol(&src)) { + NC_BAD_ARGUMENT("LET only prints a single assignment at a time for now"); + } + NC_NEXTINSTRUCTION(); +} + +nc_state_t* nc_command_if(nc_state_t* state, nc_line_t* line) { + char* src = line->src; + nc_skipspace(&src); + if (!(nc_scankeyword(&src, "IF"))) { + NC_BAD_STATEMENT("IF command invoked but this is not an IF"); + } + nc_expr_t* condexpr = nc_expr_parse(state, &src); + if (!condexpr) { + NC_BAD_ARGUMENT("IF expects a condition expression"); + } + printf("cond name='%s'\n", condexpr->strdata); + + if (!nc_scankeyword(&src, "THEN")) { + printf("at remaining line '%s':\n", src); + NC_BAD_ARGUMENT("IF exxpects a THEN keyword"); + } + + nc_skipspace(&src); + if (!(nc_scankeyword(&src, "J") || nc_scankeyword(&src, "JUMP"))) { + NC_BAD_STATEMENT("IF statement is expected to end with an embedded JUMP statement"); + } + nc_skipspace(&src); + char* argstart = src; + nc_uword_t target = nc_scandec(&src); + if (src == argstart) { + NC_BAD_ARGUMENT("expected a line number to jump to"); + } + if (!nc_scaneol(&src)) { + NC_BAD_ARGUMENT("IF/JUMP doesn't accept any arguments following the jump label"); + } + nc_line_t* targetline = nc_lookupline(state, target); + if (targetline == NULL) { + NC_BAD_ARGUMENT("line number is beyond the program"); + } + state->instruction = targetline; + return state; + + NC_NEXTINSTRUCTION(); +} + +void nc_strremovetail(char* str) { + int i = nc_strlen(str); + while (i > 0) { + i--; + if (str[i] == '\n' || str[i] == '\r') { + str[i] = 0; + } else { + return; + } + } +} + +nc_line_t* nc_command_processloadline(nc_state_t* state, char* src, nc_uword_t size, int linenumbers, nc_line_t* prev) { + //printf("Attempting to load line '%s'\n", src); + char* s = src; + nc_line_t* nline = malloc(sizeof(nc_line_t)); + if (!nline) { + printf("Couldn't allocate line!\n"); + return NULL; + } + if (linenumbers && *s >= '0' && *s <= '9') { + nline->number = nc_scandec(&s); + if (prev && nline->number <= prev->number) { + printf("Unordered program lines in file input\n"); + free(nline); + return NULL; + } + if (*s == ' ') { + s++; // Skip any space that only exists to separate the number, but allow indentation after the first space + } + } else { + if (prev == NULL) { + nline->number = 1; + } else { + nline->number = prev->number + 1; + } + } + nline->src = nc_strdup(s); + nc_strremovetail(nline->src); + nline->parsed = nc_stmt_alloc(state); + if (!nline->parsed) { + printf("Couldn't allocate parse structure\n"); + free(nline->src); + free(nline); + return NULL; + } + nline->next = NULL; + if (prev) { + prev->next = nline; + } else { + state->firstline = nline; + printf("Set firstline to '%s'\n", state->firstline->src); + } + return nline; +} + +int nc_command_processloaddata(nc_state_t* state, char* data, nc_uword_t size, int linenumbers) { + char* s = data; + char* e = data+size; + char* linestart = s; + nc_line_t* line = NULL; + while (s < e) { + if (*s == '\n') { + nc_word_t diff = s - linestart; + char* copied = nc_copystr(linestart, diff); + if (!copied) { + printf("Failed to copy string\n"); + return 0; + } + line = nc_command_processloadline(state, copied, diff, linenumbers, line); + if (!line) { + printf("Failed to load a particular line '%s'\n", copied); + free(copied); + return 0; + } + free(copied); + + s++; + linestart = s; + } else { + s++; + } + } + if (s != linestart) { + nc_word_t diff = s - linestart; + char* copied = nc_copystr(linestart, diff); + if (!copied) { + printf("Failed to copy string\n"); + return 0; + } + line = nc_command_processloadline(state, copied, diff, linenumbers, line); + if (!line) { + printf("Failed to load a particular line '%s'\n", copied); + free(copied); + return 0; + } + free(copied); + } + return 1; +} + +int nc_command_loadinner(nc_state_t* state, char* fname, int numbered) { + printf("Attempting to stat/open '%s'\n", fname); + struct stat info; + int fd = open(fname, O_RDONLY); + if (fd < 0) { + return 0; + } + if (fstat(fd, &info) < 0) { + printf("Failed to stat file!\n"); + close(fd); + return 0; + } + char* buffer = malloc(info.size + 1); // Reserve extra "zero" byte + if (buffer == NULL) { + close(fd); + return 0; + } + buffer[info.size] = 0; // Add terminating zero + /*int n; + while ((n = read(fd, buffer, n)) > 0) { + if (!nc_command_processloaddata(state, buffer, n)) { + printf("Failed to process data!\n"); + close(fd); + return 0; + } + }*/ + if (read(fd, buffer, info.size) != info.size) { + printf("Failed to load file!\n"); + close(fd); + return 0; + } + if (!nc_command_processloaddata(state, buffer, info.size, numbered)) { + printf("Failed to process data!\n"); + close(fd); + return 0; + } + free(buffer); + close(fd); + return 1; +} + +int nc_command_saveinnerline(nc_state_t* state, int fd, int numbered, nc_line_t* l) { + if (numbered) { + char buffer[50]; + int i = 49; + buffer[i] = 0; + nc_uword_t x = l->number; + if (x == 0) { + buffer[--i] = '0'; // Will not work in the loop! + } + while (x) { + i--; + buffer[i] = (char) ('0'+(x%10)); + x /= 10; + } + int n = 49-i; + if (write(fd, buffer+i, n) != n) { + goto wrerr; + } + if (write(fd, " ", 1) != 1) { + goto wrerr; + } + } + int srclen = nc_strlen(l->src); + if (write(fd, l->src, srclen) != srclen) { + goto wrerr; + } + if (write(fd, "\n", 1) != 1) { + goto wrerr; + } + return 1; + +wrerr: + printf("Error while writing"); + return 0; +} + +int nc_command_saveinner(nc_state_t* state, char* fname, int numbered) { + int fd = open(fname, O_WRONLY | O_CREATE | O_TRUNC); + if (fd < 0) { + return 0; + } + nc_line_t* l = state->firstline; + while (l) { + if (!nc_command_saveinnerline(state, fd, numbered, l)) { + printf("Failed to write line to output\n"); + close(fd); + return 0; + } + l = l->next; + } + close(fd); + return 1; +} + +// Returns nonzero if the filename matches the extension or 0 otherwise. +// Extension string must include the dot, but is case insensitive. +int nc_matchfileext(char* fname, char* ext) { + int l = nc_strlen(fname); + do { + l--; + } while (l > 0 && fname[l] != '.'); + char* s = fname + l; + return nc_scankeyword(&s, ext); +} + +int nc_fileext_numbered(nc_state_t* state, char* fname) { + if (nc_matchfileext(fname, ".NUM") || nc_matchfileext(fname, ".NUMBERED")) { + return 1; + } else { + return 0; + } +} + +nc_state_t* nc_command_load(nc_state_t* state, nc_line_t* line) { + NC_EDITONLY(); + if (state->firstline || state->undostack || state->redostack) { + NC_BAD_MODE("LOAD cannot overwrite your edits, it expects to be run in a fresh editor"); + } + nc_stmtpart_t* fnameexpr = nc_line_parsed(state, line, 1); + if (!fnameexpr || fnameexpr->parttype != NC_STMTPART_PRESENT) { + NC_BAD_ARGUMENT("LOAD expects filename expression"); + } + nc_word_t fnameval; + nc_type_t* t = nc_expr_eval(state, fnameexpr->expr, &fnameval); + if (!t || t->typekind != NC_TYPE_STRING) { + NC_BAD_ARGUMENT("filename expression must evaluate to a string"); + } + char* fname = (void*) fnameval; + printf("Attempting to open '%s'\n", fname); + if (!nc_command_loadinner(state, fname, nc_fileext_numbered(state, fname))) { + NC_BAD_ARGUMENT("Couldn't load file"); + } + NC_NEXTINSTRUCTION(); +} + +nc_state_t* nc_command_save(nc_state_t* state, nc_line_t* line) { + NC_EDITONLY(); + nc_stmtpart_t* fnameexpr = nc_line_parsed(state, line, 1); + if (!fnameexpr || fnameexpr->parttype != NC_STMTPART_PRESENT) { + NC_BAD_ARGUMENT("SAVE expects filename expression"); + } + nc_word_t fnameval; + nc_type_t* t = nc_expr_eval(state, fnameexpr->expr, &fnameval); + if (!t || t->typekind != NC_TYPE_STRING) { + NC_BAD_ARGUMENT("filename expression must evaluate to a string"); + } + char* fname = (void*) fnameval; + printf("Attempting to open '%s'\n", fname); + if (!nc_command_saveinner(state, fname, nc_fileext_numbered(state, fname))) { + NC_BAD_ARGUMENT("Couldn't save file"); + } + NC_NEXTINSTRUCTION(); +} + +nc_state_t* nc_step_list0(nc_state_t* state) { + nc_line_t* l = state->firstline; + while (l) { + printf("%d %s #[compiled=%p]\n", (int) (l->number), l->src, l->parsed->compiled); + l = l->next; + } + return state; +} + +// Looks up the line at or after the given number +nc_line_t* nc_lookupline(nc_state_t* state, nc_uword_t number) { + nc_line_t* l = state->firstline; + while (l && l->number < number) { + l = l->next; + } + return l; +} + +void nc_resetvars(nc_state_t* state) { + while (state->variables) { + nc_var_t* var = state->variables; + free(var->name); + state->variables = var->next; + free(var); + } +} + +/** Returns the mode if the line starts with a mode string or NULL otherwise. Sets lineendvar to the end of this mode section. */ +nc_handler_t* nc_explicitmode(nc_state_t* state, nc_line_t* line, nc_uword_t* lineendvar) { + nc_handler_t* h = state->modes; + while (h) { + char* s = line->src; + if (nc_scankeyword(&s, h->name)) { + if (nc_scankeyword(&s, "UNTIL")) { + nc_scankeyword(&s, "LINE"); // Optional. + nc_uword_t l = nc_scandec(&s); + if (lineendvar) { + *lineendvar = l; + } + } else { + if (lineendvar) { + *lineendvar = line->number+1; + } + } + return h; + } + h = h->next; + } + return NULL; +} + +// Rescans the mode of each line, e.g. to identify TEXT blocks in a program +nc_state_t* nc_rescan(nc_state_t* state) { + nc_line_t* l = state->firstline; + //nc_handler_t* currentmode = NULL; + //TODO... nc_uword_t nextendvar = 0; + + while (l) { + l = l->next; + } + + return state; +} + +nc_state_t* nc_step(nc_state_t* state, nc_line_t* line) { + if (!line->parsed->compiled) { + char* src = line->src; + if (!line->parsed || !line->parsed->head) { + nc_handler_t* handler = state->stmthandlers; + while (handler && (!line->parsed || !line->parsed->head)) { + if (nc_scankeyword(&src, handler->name)) { + if (!nc_stmt_parse(state, line, handler)) { + } + nc_parsestep_t parsestep = handler->parsestep; + line->parsed->head = handler; // This can be reset to NULL if the parser only overloads a statement in certain cases + state = parsestep(state, line); + if (line->parsed->head) { + return state; + } + } + handler = handler->next; + } + } + if (line->parsed->head) { + nc_parsestep_t parsestep = line->parsed->head->parsestep; + state = parsestep(state, line); + return state; + } + + if (nc_scankeyword(&src, "STEP")) { + NC_EDITONLY(); + nc_skipspace(&src); + if (!nc_scaneol(&src)) { + NC_BAD_ARGUMENT("STEP doesn't accept any arguments yet, it will run from first line if not already paused"); + } + if (state->stepinstruction == NULL) { + state->stepinstruction = state->firstline; + } + state = nc_step(state, state->stepinstruction); + state->stepinstruction = state->instruction; + state->instruction = NULL; + return state; + } else if (nc_scankeyword(&src, "CONTINUE")) { + NC_EDITONLY(); + nc_skipspace(&src); + if (!nc_scaneol(&src)) { + NC_BAD_ARGUMENT("CONTINUE doesn't accept any arguments yet, it will run from the last debugging step"); + } + state->instruction = state->stepinstruction; + return state; + } else if (nc_scankeyword(&src, "RUN")) { + NC_EDITONLY(); + nc_skipspace(&src); + if (!nc_scaneol(&src)) { + NC_BAD_ARGUMENT("RUN doesn't accept any arguments yet, it will run from first line"); + } + nc_resetvars(state); + state->instruction = state->firstline; + return state; + } else if (nc_scankeyword(&src, "EDIT")) { + NC_RUNONLY(); + nc_skipspace(&src); + if (!nc_scaneol(&src)) { + NC_BAD_ARGUMENT("EDIT doesn't accept any arguments yet, it will run from first line"); + } + state->stepinstruction = state->instruction; + state->instruction = NULL; + return state; + } else if (nc_scankeyword(&src, "J") || nc_scankeyword(&src, "JUMP")) { + nc_skipspace(&src); + char* argstart = src; + nc_uword_t target = nc_scandec(&src); + if (src == argstart) { + NC_BAD_ARGUMENT("expected a line number to jump to"); + } + nc_line_t* targetline = nc_lookupline(state, target); + if (targetline == NULL) { + NC_BAD_ARGUMENT("line number is beyond the program"); + } + state->instruction = targetline; + return state; + } else if (nc_scankeyword(&src, "PRINT")) { + int strlen = nc_scanstring(&src); + if (strlen < 0) { + NC_BAD_ARGUMENT("PRINT expects a string for now"); + } + write(1, src, strlen); + write(1, "\n", 1); + src += strlen+1; + NC_NEXTINSTRUCTION(); + } else if (nc_scankeyword(&src, "EXIT")) { + line->parsed->compiled = &nc_step_exit0; + } else if (nc_scankeyword(&src, "HELP")) { + NC_EDITONLY(); + line->parsed->compiled = &nc_step_help0; + } else if (nc_scankeyword(&src, "LIST")) { + NC_EDITONLY(); + line->parsed->compiled = &nc_step_list0; + } else { + NC_BAD_STATEMENT("Unrecognised command"); + } + } + nc_compiledstep_t func = line->parsed->compiled; + if (func) { + state = func(state); + } + NC_NEXTINSTRUCTION(); +} + +nc_state_t* nc_run(nc_state_t* state) { + while (state && state->instruction) { + state = nc_step(state, state->instruction); + } + return state; +} + +nc_edit_t* nc_undoable(nc_state_t* state, nc_uword_t oldline, nc_uword_t newline, char* oldsrc, char* newsrc) { + nc_edit_t* e = malloc(sizeof(nc_edit_t)); + + if (e) { + e->oldline = oldline; + e->newline = newline; + e->oldsrc = nc_strdup(oldsrc); + e->newsrc = nc_strdup(newsrc); + e->next = state->undostack; + state->undostack = e; + } + + return e; +} + +// Called to run a line from the user +nc_state_t* nc_autocode(nc_state_t* state, char* ln) { + if (!ln || !*ln) { + return state; + } + char* s = ln; // Keep a working variable of where we're up to in the line + // If the line starts with a number, it's stored in the list + if (*s >= '0' && *s <= '9') { + nc_uword_t num = nc_scandec(&s); // Scan the digits and advance the variable + // Create a new line with a copy of our thing + nc_line_t* l = malloc(sizeof(nc_line_t)); + if (!l) { + return 0; + } + l->number = num; + l->src = nc_strdup(s); + nc_strremovetail(l->src); + l->parsed = nc_stmt_alloc(state); // TODO: This should not actually always be needed. + if (!l->parsed) { + free(l); + return 0; + } + l->parsed->head = NULL; // This effectively signals that it isn't parsed (if parsed or head is NULL) + l->parsed->compiled = NULL; // This is currently needed though! + if (!state->firstline || state->firstline->number > num) { + // If this is the first line, insert it at the head of the list + + l->next = state->firstline; + state->firstline = l; + } else { + nc_line_t* curr = state->firstline; + nc_line_t* prev = NULL; + while (curr && curr->number < num) { + prev = curr; + curr = curr->next; + } + if (curr && curr->number == num) { + // If the line number already exists, consume the existing structure + nc_undoable(state, num, num, curr->src, l->src); + free(curr->src); + nc_stmt_free(state, l->parsed); + curr->src = l->src; //strdup(s); + curr->parsed->compiled = NULL; + free(l); + } else { + // If the line number doesn't exist, insert the new structure after prev + // and make the edit undoable + nc_undoable(state, NC_INVALIDLINENUMBER, num, NULL, l->src); + l->next = curr; + prev->next = l; + } + } + return state; + } else { // If the line doesn't start with a number, just do it and print result + //printf("Not a number...\n"); + state->tmpline.number = NC_INVALIDLINENUMBER; + state->tmpline.src = ln; + //state->tmpline.parsed->compiled = NULL; + if (state->tmpline.parsed) { + nc_stmt_reset(state, state->tmpline.parsed); + } + state->tmpline.next = NULL; + //printf("Performing step '%s'\n", state->tmpline.src); + state = nc_step(state, &state->tmpline); + if (state->instruction) { + state = nc_run(state); + } + if (state->errorcode) { + printf("ERROR %d: %s\n", state->errorcode, state->errormessage); + if (state->errorinstruction) { + printf("AT LINE %d\n", (int) (state->errorinstruction->number)); + } + state->errorcode = 0; + free(state->errormessage); + state->errormessage = NULL; + state->errorinstruction = NULL; + } + return state; + } +} + +char* nc_inputline(nc_state_t* state) { + if (state->inputlinebuffer == NULL) { + state->inputlinebuffer = malloc(1024); + } + if (state->prompt) { + printf("%s", state->prompt); + } + gets(state->inputlinebuffer, 1024); + if (state->inputlinebuffer[0]) { + //nc_strremovetail(state->inputlinebuffer); + return state->inputlinebuffer; + } else { + return NULL; + } +} + +int nc_repl(nc_state_t* state) { + char* line; + int nc_firstrun = 1; + replstart: + state->stopped = 0; + if (nc_firstrun) { + printf("READY. For manual type HELP.\n"); + nc_firstrun = 0; + } else { + printf("READY.\n"); + } + while (!state->exited && (line = nc_inputline(state)) != NULL) { + nc_autocode(state, line); + if (state->stopped && !state->exited) { + goto replstart; + } + } + return state->exitvalue; +} + +int nc_streq(char* a, char* b) { + while (*a && *b) { + if (*a != *b) { + return 0; + } + a++; + b++; + } + if (*a == 0 && *b == 0) { + return 1; + } else { + return 0; + } +} + +int nc_cmdeq(char* a, char* b) { // like nc_streq but internally does case-insensitive comparison + return nc_scankeyword(&a, b); +} + +void usage (int argc, char** argv) { + printf("ERROR: Expected one of the following patterns of arguments: "); + printf(" %s RUN [...args...]\n (runs a 'numbered' program)\n", argv[0]); + printf(" %s EDIT \n (edits a 'numbered' program or text file)\n", argv[0]); + printf(" %s COMMAND\n (opens the editor shell without a file in simple command line mode)\n", argv[0]); + printf(" %s HIGH CONTRAST\n (opens the editor shell without a file in high contrast mode)\n", argv[0]); + printf(" %s COMMAND EDIT \n (edits in simple command line mode)\n", argv[0]); + printf(" %s HIGH CONTRAST EDIT \n (edits in high contrast mode)\n", argv[0]); + printf("NOTE: COMMAND mode will avoid any tricks like coloured text which may not work on some terminals.\n"); + printf("This program is intended to be highly usable and reliable as a command line, text editor and simple scripting engine.\n"); + exit(-1); +} + +int main(int argc, char** argv) { + char* fname = NULL; + int runargstart = -1; // -1 if not running, otherwise the index of the first arg + int visualimpairment = 0; // -1 for text<->speech users or plaintext terminal for commands and 1 for high contrast mode, 0 will run in default mode + if (argc >= 2 && nc_cmdeq(argv[1], "RUN")) { + if (argc < 3) { + printf("ERROR: Expected filename of program following RUN!\n"); + exit(-1); + } + fname = argv[2]; + runargstart = 3; + } else if (argc >= 2 && nc_cmdeq(argv[1], "EDIT")) { + fname = argv[2]; + } else if (argc >= 2 && nc_cmdeq(argv[1], "COMMAND") && nc_cmdeq(argv[2], "EDIT")) { + fname = argv[3]; + visualimpairment = -1; + } else if (argc >= 2 && nc_cmdeq(argv[1], "HIGH") && nc_cmdeq(argv[2], "CONTRAST") && nc_cmdeq(argv[2], "EDIT")) { + fname = argv[4]; + visualimpairment = 1; + } else if (argc >= 2 && nc_cmdeq(argv[1], "COMMAND")) { + visualimpairment = -1; + } else if (argc >= 2 && nc_cmdeq(argv[1], "HIGH") && nc_cmdeq(argv[2], "CONTRAST")) { + fname = argv[4]; + visualimpairment = 1; + } else if (argc >= 2) { + usage(argc, argv); + } + fname = fname; // TODO, this is just to avoid GCC error + + nc_state_t* state = malloc(sizeof(nc_state_t)); + if (!state) { + printf("ERROR: FAILED TO ALLOCATE STATE"); + return 1; + } + state->visualimpairment = visualimpairment; // Important for determining whether colours are activated in the header. + if (runargstart < 0) { + nc_setfg(state, NC_COLOUR_CYAN); + printf(" **************** "); + nc_setfg(state, NC_COLOUR_GREEN); + printf("NUMBERED.CODES"); + nc_setfg(state, NC_COLOUR_CYAN); + printf(" ****************\n"); + printf(" * "); + nc_setfg(state, state->visualimpairment > 0 ? NC_COLOUR_RED : NC_COLOUR_LIGHTGREY); + nc_setbg(state, NC_COLOUR_BLACK); + printf("Simple shell, editor & programming language."); + nc_resetcolour(state); + nc_setfg(state, NC_COLOUR_CYAN); + printf(" *\n"); + printf(" ************************************************\n"); + nc_resetcolour(state); + } + state->instruction = NULL; + state->stepinstruction = NULL; + state->errorinstruction = NULL; + state->errorcode = 0; + state->errormessage = NULL; + state->variables = NULL; + state->prompt = NULL; + + state->firstline = NULL; + + state->exited = 0; + state->exitvalue = 0; + + state->undostack = NULL; + state->redostack = NULL; + + state->modes = NULL; + state->stmthandlers = NULL; + state->tmpline.parsed = nc_stmt_alloc(state); // This should not always be necessary, but for now should be preallocated + if (!state->tmpline.parsed) { + printf("ERROR: FAILED TO ALLOCATE STATEMENT"); + return 1; + } + + state->currentmode = nc_mode_add(state, "CODE", NULL, "Program code (built-in syntax)"); + nc_mode_add(state, "TEXT", NULL, "Plain text (ASCII or UTF-8)"); + + nc_stmt_add(state, "HELP {NAME}?", &nc_command_help, "Displays information about statements and syntax"); + nc_stmt_add(state, "JUMP {LINE}", &nc_command_jump, "Causes the program to jump to a different line number"); + nc_stmt_add(state, "RUN", &nc_command_run, "Runs the program from the first line, resetting variables"); + nc_stmt_add(state, "PRINT {MATH}", &nc_command_print, "Prints the result of an expression to the screen or other output"); + nc_stmt_add(state, "LIST", &nc_command_list, "Lists the currently loaded program or file line-by-line"); + nc_stmt_add(state, "LOAD {MATH}", &nc_command_load, "Loads a text file from disk (line numbering depends on mode/detection)"); + nc_stmt_add(state, "SAVE {MATH}", &nc_command_save, "Saves a text file to disk (line numbering depends on mode/detection)"); + //nc_stmt_add(state, "LOAD CODE FROM? {MATH}", &nc_command_loadcode, "Loads a code file from disk (preserving line numbers)"); + //nc_stmt_add(state, "SAVE CODE TO? {MATH}", &nc_command_savecode, "Saves a code file to disk (preserving line numbers)"); + nc_stmt_add(state, "EXIT {MATH}?", &nc_command_exit, "Exits the program/editor with an optional result value"); + nc_stmt_add(state, "LET {VARIABLE} = {MATH}", &nc_command_let, "Assigns a value to a variable"); + // TODO, unicode is not fully supported: nc_stmt_add(state, "ΕΣΤΩ", &nc_command_let, "Alias for LET (testing...)"); + nc_stmt_add(state, "IF {LOGIC} THEN? JUMP {LINE}", &nc_command_if, "Checks if a condition is true and jumps to a different line number if so"); + //nc_stmt_add(state, "IF {LOGIC} THEN? BEGIN ... END IF", &nc_command_if, "Checks if a condition is true and if so executes the code lines until END IF"); + + return nc_repl(state); +} diff --git a/res2c.c b/res2c.c new file mode 100644 index 0000000..52b6761 --- /dev/null +++ b/res2c.c @@ -0,0 +1,185 @@ +#include +#include +#include + +int usage(int argc, char** argv, int argerr, const char* error) { + FILE* o = error ? stderr : stdout; + char* progname = argv[0]; + fprintf(o, "This program '%s' loads arbitrary files and outputs them as C code which loads them in the style of 'program resources'.\n", progname); + fprintf(o, "It is particularly designed for loading one or more ramdisks, init programs or other arbitrary 'blobs' in kernel boot images.\n\n"); + fprintf(o, "By default it will produce a function which calls another function to register each resource.\n"); + fprintf(o, "Using the --nofunctions option and setting the name with --arrayname you can also get it to just produce arrays similar to a simple 'bin2c' program.\n\n"); + fprintf(o, "USAGE:\n"); + fprintf(o, " %s [--output ] [--allowempty] [--nofunctions] [--defaulttype ] [--loadfunction ] [--registerfunction ] [[--type ] [--arrayname ] [--filename ] resource1 [[--type ] [--arrayname ] [--filename ] resource2 [...]]]\n\n", progname); + fprintf(o, "EXAMPLES:\n"); + fprintf(o, " %s --output gamemods_autogenerated.c --loadfunction mods_init --allowempty --defaulttype GAME_MOD *.mod\n\n", progname); + fprintf(o, " %s --output mybootscripts.c --type SCRIPT --name bootscript startup.script --type RAMDISK --name initramdisk startup.image\n\n", progname); + fprintf(o, " %s --allowempty\n\n", progname); + return error == NULL ? 0 : -1; +} + +#define MAX_RESOURCES 100 +FILE* files[MAX_RESOURCES]; +char* types[MAX_RESOURCES]; +char* arraynames[MAX_RESOURCES]; +char* filenames[MAX_RESOURCES]; +unsigned long sizes[MAX_RESOURCES]; + + unsigned char buffer[256]; + +int main(int argc, char** argv) { + char* defaulttype = "RESOURCE"; + char* nexttype = NULL; + char* nextarrayname = NULL; + char* nextfilename = NULL; + int nresources = 0; + int allowempty = 0; + int nofunctions = 0; + char* loadfunction = "resources_init"; + char* registerfunction = "resources_register"; + char* progname = argv[0]; + int bytesperline = 16; + + char* outputname = NULL; + + char simplenamebuff[6]; + simplenamebuff[0] = '_'; + simplenamebuff[1] = 'r'; + simplenamebuff[2] = 'e'; + simplenamebuff[3] = 's'; + simplenamebuff[5] = 0; + + int argi = 1; + while (argi < argc) { + if (!strcmp(argv[argi], "--usage")) { + usage(argc, argv, argi, NULL); + return 0; + } else if (!strcmp(argv[argi], "--type")) { + if (argi + 1 >= argc) { + return usage(argc, argv, argi, "Missing argument after --type"); + } + argi++; + nexttype = argv[argi]; + } else if (!strcmp(argv[argi], "--arrayname")) { + if (argi + 1 >= argc) { + return usage(argc, argv, argi, "Missing argument after --arrayname"); + } + argi++; + nextarrayname = argv[argi]; + } else if (!strcmp(argv[argi], "--filename")) { + if (argi + 1 >= argc) { + return usage(argc, argv, argi, "Missing argument after --filename"); + } + argi++; + nextfilename = argv[argi]; + } else if (!strcmp(argv[argi], "--output") || (argv[argi][0] == '-' && argv[argi][1] == 'o' && argv[argi][2] == 0)) { + if (outputname != NULL) { + usage(argc, argv, argi, "Can't specify output more than once"); + return -1; + } + if (argi + 1 >= argc) { + usage(argc, argv, argi, "Missing argument after --output"); + return -1; + } + argi++; + outputname = argv[argi]; + } else if (argv[argi][0] == '-' && argv[argi][1] == 'o') { + if (outputname != NULL) { + usage(argc, argv, argi, "Can't specify output more than once"); + return -1; + } + outputname = argv[argi]+2; + } else if (!strcmp(argv[argi], "--allowempty")) { + allowempty = 1; + } else if (!strcmp(argv[argi], "--nofunctions")) { + nofunctions = 1; + } else { + if (nresources + 1 >= MAX_RESOURCES) { + return usage(argc, argv, argi, "Too many resources"); + } + files[nresources] = fopen(argv[argi], "rb"); + if (nexttype) { + types[nresources] = nexttype; + nexttype = NULL; + } else { + types[nresources] = defaulttype; + } + if (nextarrayname) { + arraynames[nresources] = nextarrayname; + nextarrayname = NULL; + } else { + simplenamebuff[4] = (char)('A' + nresources); + arraynames[nresources] = strdup(simplenamebuff); + } + if (nextfilename) { + filenames[nresources] = nextfilename; + nextfilename = NULL; + } else { + char* lastsegment = argv[argi]; + char* s = lastsegment; + while (*s) { + if (*s == '/' || *s == '\\' || *s == ':') { + lastsegment = s+1; + } + s++; + } + filenames[nresources] = lastsegment; + } + nresources++; + } + argi++; + } + + if (nresources < 1 && !allowempty) { + return usage(argc, argv, argi, "Expected some resources to add or --allowempty to allow an empty resource structure"); + } + + FILE* output = stdout; + + if (outputname != NULL) { + output = fopen(outputname, "w"); + if (output == NULL) { + fprintf(stderr, "ERROR: Failed to open output file '%s'\n", outputname); + return -1; + } + } + + fprintf(output, "// This file is autogenerated by '%s'\n\n", progname); + char* hex = "0123456789ABCDEF"; + for (int resi = 0; resi < nresources; resi++) { + FILE* f = files[resi]; + unsigned long sz = 0; + size_t nread; + fprintf(output, "unsigned char %s[] = {\n", arraynames[resi]); + while ((nread = fread(buffer, 1, bytesperline, f)) > 0) { + if (sz > 0) { + fprintf(output, ",\n"); + } + sz += (unsigned long) nread; + for (size_t bytei = 0; bytei < nread; bytei++) { + int b = ((int) buffer[bytei]) & 0xFF; + fprintf(output, "%s0x%c%c", bytei > 0 ? ", " : " ", hex[b>>4], hex[b&0xF]); + } + } + fprintf(output, "\n}; // end of %s\n\n", arraynames[resi]); + + sizes[resi] = sz; + fclose(f); + } + + if (!nofunctions) { + fprintf(output, "// This function should be implemented in the program code, the name can be changed with using --registerfunction when generating this code\n"); + fprintf(output, "void %s(const char* type, const char* name, unsigned char* data, unsigned long size);\n\n", registerfunction); + + fprintf(output, "// This function should be invoked from the program code, the name can be changed with using --loadfunction when generating this code\n"); + fprintf(output, "void %s() {\n", loadfunction); + for (int resi = 0; resi < nresources; resi++) { + fprintf(output, " %s(\"%s\", \"%s\", %s, %lu);\n", registerfunction, types[resi], filenames[resi], arraynames[resi], sizes[resi]); + } + fprintf(output, "}\n\n"); + } + + if (outputname != NULL) { + fclose(output); + } +}