slbldtls/numbered.c

1840 lines
54 KiB
C
Raw Normal View History

// 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 <filename> [...args...]\n (runs a 'numbered' program)\n", argv[0]);
printf(" %s EDIT <filename>\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 <filename>\n (edits in simple command line mode)\n", argv[0]);
printf(" %s HIGH CONTRAST EDIT <filename>\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);
}