// 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); }