1840 lines
54 KiB
C
1840 lines
54 KiB
C
|
// 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);
|
||
|
}
|