From 99dac61bde76dfa2d11da9e37d76994dd84eda0d Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Fri, 6 Nov 2020 21:31:30 +0000 Subject: [PATCH 01/41] Introduce a process library --- c/optionals/math.c | 2 - c/optionals/math.h | 1 + c/optionals/optionals.c | 11 +-- c/optionals/optionals.h | 1 + c/optionals/process.c | 145 ++++++++++++++++++++++++++++++++++++++++ c/optionals/process.h | 9 +++ c/optionals/random.c | 3 +- 7 files changed, 163 insertions(+), 9 deletions(-) create mode 100644 c/optionals/process.c create mode 100644 c/optionals/process.h diff --git a/c/optionals/math.c b/c/optionals/math.c index 468f4ae0..973d36cd 100644 --- a/c/optionals/math.c +++ b/c/optionals/math.c @@ -1,6 +1,4 @@ #include "math.h" -#include "../vm.h" -#include static Value averageNative(VM *vm, int argCount, Value *args) { double average = 0; diff --git a/c/optionals/math.h b/c/optionals/math.h index 0c7dc08a..4fe310a5 100644 --- a/c/optionals/math.h +++ b/c/optionals/math.h @@ -2,6 +2,7 @@ #define dictu_math_h #include +#include #include "optionals.h" #include "../vm.h" diff --git a/c/optionals/optionals.c b/c/optionals/optionals.c index 7c152ba6..b82c4ed0 100644 --- a/c/optionals/optionals.c +++ b/c/optionals/optionals.c @@ -1,16 +1,17 @@ #include "optionals.h" BuiltinModules modules[] = { - {"Math", &createMathsModule}, - {"Env", &createEnvModule}, {"JSON", &createJSONModule}, {"Path", &createPathModule}, - {"Datetime", &createDatetimeModule}, - {"Socket", &createSocketModule}, - {"Random", &createRandomModule}, #ifndef DISABLE_HTTP {"HTTP", &createHTTPModule}, #endif + {"Datetime", &createDatetimeModule}, + {"Random", &createRandomModule}, + {"Env", &createEnvModule}, + {"Socket", &createSocketModule}, + {"Process", &createProcessModule}, + {"Math", &createMathsModule}, {NULL, NULL} }; diff --git a/c/optionals/optionals.h b/c/optionals/optionals.h index 9975ef2d..f0907309 100644 --- a/c/optionals/optionals.h +++ b/c/optionals/optionals.h @@ -12,6 +12,7 @@ #include "datetime.h" #include "socket.h" #include "random.h" +#include "process.h" #define GET_SELF_CLASS \ AS_MODULE(args[-1]) diff --git a/c/optionals/process.c b/c/optionals/process.c new file mode 100644 index 00000000..4b821e36 --- /dev/null +++ b/c/optionals/process.c @@ -0,0 +1,145 @@ +#include "process.h" + +static Value execute(VM *vm, ObjList *argList, bool wait) { + char **arguments = ALLOCATE(vm, char *, argList->values.count + 1); + for (int i = 0; i < argList->values.count; ++i) { + arguments[i] = AS_CSTRING(argList->values.values[i]); + } + + arguments[argList->values.count] = NULL; + pid_t pid = fork(); + if (pid == 0) { + execvp(arguments[0], arguments); + exit(errno); + } + + FREE_ARRAY(vm, char *, arguments, argList->values.count + 1); + + if (wait) { + int status = 0; + waitpid(pid, &status, 0); + + if (WIFEXITED(status)) { + status = WEXITSTATUS(status); + } + + return NUMBER_VAL(status); + } + + return NIL_VAL; +} + +static Value executeReturnOutput(VM *vm, ObjList *argList) { + char **arguments = ALLOCATE(vm, char *, argList->values.count + 1); + for (int i = 0; i < argList->values.count; ++i) { + arguments[i] = AS_CSTRING(argList->values.values[i]); + } + + arguments[argList->values.count] = NULL; + + int fd[2]; + pipe(fd); + pid_t pid = fork(); + if (pid == 0) { + close(fd[0]); + dup2(fd[1], 1); + dup2(fd[1], 2); + close(fd[1]); + + execvp(arguments[0], arguments); + exit(errno); + } + + FREE_ARRAY(vm, char *, arguments, argList->values.count + 1); + + close(fd[1]); + + int size = 1024; + char *output = ALLOCATE(vm, char, size); + char buffer[1024]; + int total = 0; + int numRead; + + while ((numRead = read(fd[0], buffer, 1024)) != 0) { + if (total >= size) { + output = GROW_ARRAY(vm, output, char, size, size * 3); + size *= 3; + } + + memcpy(output + total, buffer, numRead); + total += numRead; + } + + output[total] = '\0'; + vm->bytesAllocated -= size - total - 1; + + return OBJ_VAL(takeString(vm, output, total)); +} + +static Value execNative(VM *vm, int argCount, Value *args) { + if (argCount != 1) { + runtimeError(vm, "exec() takes 1 argument (%d given).", argCount); + return EMPTY_VAL; + } + + if (!IS_LIST(args[0])) { + runtimeError(vm, "Argument passed to exec() must be a string"); + return EMPTY_VAL; + } + + ObjList *argList = AS_LIST(args[0]); + return execute(vm, argList, false); +} + +static Value runNative(VM *vm, int argCount, Value *args) { + if (argCount != 1 && argCount != 2) { + runtimeError(vm, "run() takes 1 or 2 arguments (%d given)", argCount); + return EMPTY_VAL; + } + + if (!IS_LIST(args[0])) { + runtimeError(vm, "Argument passed to run() must be a string"); + return EMPTY_VAL; + } + + bool getOutput = false; + + if (argCount == 2) { + if (!IS_BOOL(args[1])) { + runtimeError(vm, "Optional argument passed to run() must be a boolean"); + return EMPTY_VAL; + } + + getOutput = AS_BOOL(args[1]); + } + + ObjList *argList = AS_LIST(args[0]); + + if (getOutput) { + return executeReturnOutput(vm, argList); + } + + return execute(vm, argList, true); +} + +ObjModule *createProcessModule(VM *vm) { + ObjString *name = copyString(vm, "Process", 7); + push(vm, OBJ_VAL(name)); + ObjModule *module = newModule(vm, name); + push(vm, OBJ_VAL(module)); + + /** + * Define process methods + */ + defineNative(vm, &module->values, "exec", execNative); + defineNative(vm, &module->values, "run", runNative); + + /** + * Define process properties + */ + + pop(vm); + pop(vm); + + return module; +} diff --git a/c/optionals/process.h b/c/optionals/process.h new file mode 100644 index 00000000..b7c4f526 --- /dev/null +++ b/c/optionals/process.h @@ -0,0 +1,9 @@ +#ifndef dictu_process_h +#define dictu_process_h + +#include "optionals.h" +#include "../vm.h" + +ObjModule *createProcessModule(VM *vm); + +#endif //dictu_process_h diff --git a/c/optionals/random.c b/c/optionals/random.c index 0d79ffcb..74550665 100644 --- a/c/optionals/random.c +++ b/c/optionals/random.c @@ -67,8 +67,7 @@ static Value randomSelect(VM *vm, int argCount, Value *args) return args[index]; } -ObjModule *createRandomModule(VM *vm) -{ +ObjModule *createRandomModule(VM *vm) { ObjString *name = copyString(vm, "Random", 6); push(vm, OBJ_VAL(name)); ObjModule *module = newModule(vm, name); From 6eacef651477c5c038946676034a92f41d660c17 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Thu, 24 Dec 2020 01:33:10 +0000 Subject: [PATCH 02/41] Add socket.connect --- docs/docs/standard-lib/sockets.md | 15 +++++++++++- src/optionals/socket.c | 39 ++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/docs/docs/standard-lib/sockets.md b/docs/docs/standard-lib/sockets.md index 8a6f8c2f..5c10fd43 100644 --- a/docs/docs/standard-lib/sockets.md +++ b/docs/docs/standard-lib/sockets.md @@ -48,7 +48,20 @@ This will bind a given socket object to an IP and port number. Returns a Result type and on success will unwrap to nil. ```cs -var result = socket.bind("host", 10); +var result = socket.bind("127.0.0.1", 1000); +if (!result.success()) { + print(result.unwrapError()); + // ... +} +``` + +### socket.connect(string, number) + +This will connect to a socket on a given host and IP. +Returns a Result type and on success will unwrap to nil. + +```cs +var result = socket.connect("127.0.0.1", 1000); if (!result.success()) { print(result.unwrapError()); // ... diff --git a/src/optionals/socket.c b/src/optionals/socket.c index 53290c1d..2917cfd0 100644 --- a/src/optionals/socket.c +++ b/src/optionals/socket.c @@ -205,13 +205,49 @@ static Value recvSocket(DictuVM *vm, int argCount, Value *args) { ERROR_RESULT; } + // Resize string + if (readSize != bufferSize) { + buffer = SHRINK_ARRAY(vm, buffer, char, bufferSize, readSize + 1); + } + + buffer[readSize] = '\0'; ObjString *rString = takeString(vm, buffer, readSize); return newResultSuccess(vm, OBJ_VAL(rString)); } +static Value connectSocket(DictuVM *vm, int argCount, Value *args) { + if (argCount != 2) { + runtimeError(vm, "connect() takes two arguments (%d given)", argCount); + return EMPTY_VAL; + } + + if (!IS_STRING(args[1])) { + runtimeError(vm, "host passed to bind() must be a string"); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[2])) { + runtimeError(vm, "port passed to bind() must be a number"); + return EMPTY_VAL; + } + + SocketData *sock = AS_SOCKET(args[0]); + + struct sockaddr_in server; + + server.sin_family = sock->socketFamily; + server.sin_addr.s_addr = inet_addr(AS_CSTRING(args[1])); + server.sin_port = htons(AS_NUMBER(args[2])); + + if (connect(sock->socket, (struct sockaddr *)&server, sizeof(server)) < 0) { + ERROR_RESULT; + } + + return newResultSuccess(vm, NIL_VAL); +} + static Value closeSocket(DictuVM *vm, int argCount, Value *args) { - UNUSED(vm); if (argCount != 0) { runtimeError(vm, "close() takes no arguments (%d given)", argCount); return EMPTY_VAL; @@ -269,6 +305,7 @@ ObjAbstract *newSocket(DictuVM *vm, int sock, int socketFamily, int socketType, defineNative(vm, &abstract->values, "accept", acceptSocket); defineNative(vm, &abstract->values, "write", writeSocket); defineNative(vm, &abstract->values, "recv", recvSocket); + defineNative(vm, &abstract->values, "connect", connectSocket); defineNative(vm, &abstract->values, "close", closeSocket); defineNative(vm, &abstract->values, "setsockopt", setSocketOpt); pop(vm); From b858f858de80b786dd9f2a4e3b3c06497ca53627 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Thu, 24 Dec 2020 01:49:25 +0000 Subject: [PATCH 03/41] Ensure there's enough space for the null terminator --- src/optionals/socket.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/optionals/socket.c b/src/optionals/socket.c index 2917cfd0..2ca163da 100644 --- a/src/optionals/socket.c +++ b/src/optionals/socket.c @@ -197,7 +197,7 @@ static Value recvSocket(DictuVM *vm, int argCount, Value *args) { return EMPTY_VAL; } - char *buffer = ALLOCATE(vm, char, bufferSize); + char *buffer = ALLOCATE(vm, char, bufferSize + 1); int readSize = recv(sock->socket, buffer, bufferSize, 0); if (readSize == -1) { From 37c63c75daf5b227afc47f382a81c722090c4f25 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Sat, 26 Dec 2020 15:04:36 +0000 Subject: [PATCH 04/41] Correct socket documentation and fix memory reporting for sock recv --- docs/docs/standard-lib/sockets.md | 8 +++++++- src/optionals/socket.c | 6 +++--- src/vm/util.c | 5 ++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/docs/docs/standard-lib/sockets.md b/docs/docs/standard-lib/sockets.md index 5c10fd43..7cf58a9d 100644 --- a/docs/docs/standard-lib/sockets.md +++ b/docs/docs/standard-lib/sockets.md @@ -39,7 +39,13 @@ Create a new socket object given a socket type and socket family. This will return a Result and unwrap to a new socket object in which the rest of the methods are ran on. ```cs -var socket = Socket.create(Socket.AF_INET, Sockket.SOCK_STREAM).unwrap(); +var result = Socket.create(Socket.AF_INET, Socket.SOCK_STREAM); +if (!result.success()) { + print(result.unwrapError()); + // ... +} + +var socket = result.unwrap(); ``` ### socket.bind(string, number) diff --git a/src/optionals/socket.c b/src/optionals/socket.c index 2ca163da..9e904ba9 100644 --- a/src/optionals/socket.c +++ b/src/optionals/socket.c @@ -190,15 +190,15 @@ static Value recvSocket(DictuVM *vm, int argCount, Value *args) { } SocketData *sock = AS_SOCKET(args[0]); - int bufferSize = AS_NUMBER(args[1]); + int bufferSize = AS_NUMBER(args[1]) + 1; if (bufferSize < 1) { runtimeError(vm, "recv() argument must be greater than 1"); return EMPTY_VAL; } - char *buffer = ALLOCATE(vm, char, bufferSize + 1); - int readSize = recv(sock->socket, buffer, bufferSize, 0); + char *buffer = ALLOCATE(vm, char, bufferSize); + int readSize = recv(sock->socket, buffer, bufferSize - 1, 0); if (readSize == -1) { FREE_ARRAY(vm, char, buffer, bufferSize); diff --git a/src/vm/util.c b/src/vm/util.c index 33c9dd97..5d7a1edf 100644 --- a/src/vm/util.c +++ b/src/vm/util.c @@ -72,8 +72,11 @@ ObjString *dirname(DictuVM *vm, char *path, int len) { bool resolvePath(char *directory, char *path, char *ret) { char buf[PATH_MAX]; + if (*path == DIR_SEPARATOR) + snprintf (buf, PATH_MAX, "%s", path); + else + snprintf(buf, PATH_MAX, "%s%c%s", directory, DIR_SEPARATOR, path); - snprintf(buf, PATH_MAX, "%s%c%s", directory, DIR_SEPARATOR, path); #ifdef _WIN32 _fullpath(ret, buf, PATH_MAX); #else From 2cdd4616f00d7f5a9e65f6e2fb7b8c870797f81e Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Sat, 26 Dec 2020 22:19:35 +0000 Subject: [PATCH 05/41] Handle empty lists when using .join() --- docs/docs/collections/lists.md | 4 +++- src/vm/datatypes/lists/lists.c | 5 +++++ tests/lists/join.du | 2 ++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/docs/collections/lists.md b/docs/docs/collections/lists.md index f082680c..d6a95f68 100644 --- a/docs/docs/collections/lists.md +++ b/docs/docs/collections/lists.md @@ -138,13 +138,15 @@ myList.contains(10); // false ### list.join(string: delimiter -> optional) To convert a list of elements to a string use `.join()` to concatenate elements together by a given delimiter. -If a delimiter is not supplied `", "` is the default. +If a delimiter is not supplied `", "` is the default. Attempting to join an empty list will return an empty string. ```cs var myList = [1, 2, 3]; print(myList.join()); // "1, 2, 3" print(myList.join("")); // "123" print(myList.join("-")); // "1-2-3" + +print([].join("delimiter")); // "" ``` ### list.remove(value) diff --git a/src/vm/datatypes/lists/lists.c b/src/vm/datatypes/lists/lists.c index 915d60b3..e94d4378 100644 --- a/src/vm/datatypes/lists/lists.c +++ b/src/vm/datatypes/lists/lists.c @@ -220,6 +220,11 @@ static Value joinListItem(DictuVM *vm, int argCount, Value *args) { } ObjList *list = AS_LIST(args[0]); + + if (list->values.count == 0) { + return OBJ_VAL(copyString(vm, "", 0)); + } + char *delimiter = ", "; if (argCount == 1) { diff --git a/tests/lists/join.du b/tests/lists/join.du index f6de14cc..428afa37 100644 --- a/tests/lists/join.du +++ b/tests/lists/join.du @@ -6,6 +6,8 @@ * .join() will return a string of joined list elements by a given delimiter */ +assert([].join() == ""); + var x = [1, 2, 3, 4, 5, 6]; assert(x.join() == "1, 2, 3, 4, 5, 6"); From 8996ec6bed5d487c98960857b803985120258937 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Sat, 26 Dec 2020 23:19:06 +0000 Subject: [PATCH 06/41] Enable FKs by default for SQLite --- docs/docs/standard-lib/sqlite.md | 6 ++++-- src/optionals/sqlite.c | 13 ++++++++++++- tests/sqlite/delete.du | 2 +- tests/sqlite/foreignKeys.du | 18 ++++++++++++++++++ tests/sqlite/import.du | 1 + 5 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 tests/sqlite/foreignKeys.du diff --git a/docs/docs/standard-lib/sqlite.md b/docs/docs/standard-lib/sqlite.md index a04123f6..2ef61aaa 100644 --- a/docs/docs/standard-lib/sqlite.md +++ b/docs/docs/standard-lib/sqlite.md @@ -19,6 +19,8 @@ parent: Standard Library ## Sqlite To make use of the Sqlite module an import is required. +Note: Unlike SQLite and most other libraries, foreign keys **are** enabled by default when a connection is opened. + ```js import Sqlite; ``` @@ -30,8 +32,8 @@ This opens a connection to a SQLite database. Returns a Result type and on succe Note: You can pass ":memory:" to open the SQLite database in memory rather than a file. ```cs -Sqlite.connect(":memory:"); -Sqlite.connect("my/database/file.db"); +Sqlite.connect(":memory:").unwrap(); +Sqlite.connect("my/database/file.db").unwrap(); ``` ### sqlite.execute(string: query, list: arguments -> optional) diff --git a/src/optionals/sqlite.c b/src/optionals/sqlite.c index 8657b93e..7bf97d21 100644 --- a/src/optionals/sqlite.c +++ b/src/optionals/sqlite.c @@ -55,7 +55,7 @@ static Value execute(DictuVM *vm, int argCount, Value *args) { Database *db = AS_SQLITE_DATABASE(args[0]); char *sql = AS_CSTRING(args[1]); ObjList *list = NULL; - int parameterCount = countParameters(sql);; + int parameterCount = countParameters(sql); int argumentCount = 0; if (argCount == 2) { @@ -105,6 +105,7 @@ static Value execute(DictuVM *vm, int argCount, Value *args) { sqlite3_finalize(result.stmt); char *error = (char *)sqlite3_errmsg(db->db); + pop(vm); return newResultError(vm, error); } @@ -186,6 +187,16 @@ static Value connectSqlite(DictuVM *vm, int argCount, Value *args) { return newResultError(vm, error); } + sqlite3_stmt *res; + err = sqlite3_prepare_v2(db->db, "PRAGMA foreign_keys = ON;", -1, &res, 0); + + if (err) { + char *error = (char *)sqlite3_errmsg(db->db); + return newResultError(vm, error); + } + + sqlite3_finalize(res); + return newResultSuccess(vm, OBJ_VAL(abstract)); } diff --git a/tests/sqlite/delete.du b/tests/sqlite/delete.du index c2f05ca6..2e04ae65 100644 --- a/tests/sqlite/delete.du +++ b/tests/sqlite/delete.du @@ -1,5 +1,5 @@ /** - * update.du + * delete.du * * Testing Sqlite.connect() and Sqlite.execute() * diff --git a/tests/sqlite/foreignKeys.du b/tests/sqlite/foreignKeys.du new file mode 100644 index 00000000..42de38cd --- /dev/null +++ b/tests/sqlite/foreignKeys.du @@ -0,0 +1,18 @@ +/** + * foreignKeys.du + * + * Testing that FKs are enabled by default + */ + +import Sqlite; + +var con = Sqlite.connect(":memory:").unwrap(); + +con.execute("CREATE TABLE test (col UNIQUE)").unwrap(); +con.execute("CREATE TABLE test1 (col, FOREIGN KEY(col) REFERENCES test(col))").unwrap(); + +assert(con.execute("INSERT INTO test1(col) VALUES (1)").success() == false); +assert(con.execute("INSERT INTO test1(col) VALUES (1)").unwrapError() == "FOREIGN KEY constraint failed"); + +assert(con.execute("INSERT INTO test(col) VALUES (1)").success() == true); +assert(con.execute("INSERT INTO test1(col) VALUES (1)").success() == true); \ No newline at end of file diff --git a/tests/sqlite/import.du b/tests/sqlite/import.du index 44bb30a7..c719472a 100644 --- a/tests/sqlite/import.du +++ b/tests/sqlite/import.du @@ -9,3 +9,4 @@ import "insert.du"; import "select.du"; import "update.du"; import "delete.du"; +import "foreignKeys.du"; From bde189ea3d83c543e23298ac6073e68492982989 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Sat, 26 Dec 2020 23:29:10 +0000 Subject: [PATCH 07/41] Update process to use correct VM variable and be located in correct directory --- src/optionals/optionals.c | 1 + src/optionals/optionals.h | 1 + {c => src}/optionals/process.c | 12 ++++++------ {c => src}/optionals/process.h | 3 +-- 4 files changed, 9 insertions(+), 8 deletions(-) rename {c => src}/optionals/process.c (89%) rename {c => src}/optionals/process.h (62%) diff --git a/src/optionals/optionals.c b/src/optionals/optionals.c index 64fb5a82..1e28bb8e 100644 --- a/src/optionals/optionals.c +++ b/src/optionals/optionals.c @@ -11,6 +11,7 @@ BuiltinModules modules[] = { {"Base64", &createBase64Module}, {"Hashlib", &createHashlibModule}, {"Sqlite", &createSqliteModule}, + {"Process", &createProcessModule}, #ifndef DISABLE_HTTP {"HTTP", &createHTTPModule}, #endif diff --git a/src/optionals/optionals.h b/src/optionals/optionals.h index a1056c46..2f7d0f05 100644 --- a/src/optionals/optionals.h +++ b/src/optionals/optionals.h @@ -15,6 +15,7 @@ #include "base64.h" #include "hashlib.h" #include "sqlite.h" +#include "process.h" typedef ObjModule *(*BuiltinModule)(DictuVM *vm); diff --git a/c/optionals/process.c b/src/optionals/process.c similarity index 89% rename from c/optionals/process.c rename to src/optionals/process.c index 4b821e36..a85cd0a4 100644 --- a/c/optionals/process.c +++ b/src/optionals/process.c @@ -1,6 +1,6 @@ #include "process.h" -static Value execute(VM *vm, ObjList *argList, bool wait) { +static Value execute(DictuVM *vm, ObjList *argList, bool wait) { char **arguments = ALLOCATE(vm, char *, argList->values.count + 1); for (int i = 0; i < argList->values.count; ++i) { arguments[i] = AS_CSTRING(argList->values.values[i]); @@ -29,7 +29,7 @@ static Value execute(VM *vm, ObjList *argList, bool wait) { return NIL_VAL; } -static Value executeReturnOutput(VM *vm, ObjList *argList) { +static Value executeReturnOutput(DictuVM *vm, ObjList *argList) { char **arguments = ALLOCATE(vm, char *, argList->values.count + 1); for (int i = 0; i < argList->values.count; ++i) { arguments[i] = AS_CSTRING(argList->values.values[i]); @@ -76,7 +76,7 @@ static Value executeReturnOutput(VM *vm, ObjList *argList) { return OBJ_VAL(takeString(vm, output, total)); } -static Value execNative(VM *vm, int argCount, Value *args) { +static Value execNative(DictuVM *vm, int argCount, Value *args) { if (argCount != 1) { runtimeError(vm, "exec() takes 1 argument (%d given).", argCount); return EMPTY_VAL; @@ -91,14 +91,14 @@ static Value execNative(VM *vm, int argCount, Value *args) { return execute(vm, argList, false); } -static Value runNative(VM *vm, int argCount, Value *args) { +static Value runNative(DictuVM *vm, int argCount, Value *args) { if (argCount != 1 && argCount != 2) { runtimeError(vm, "run() takes 1 or 2 arguments (%d given)", argCount); return EMPTY_VAL; } if (!IS_LIST(args[0])) { - runtimeError(vm, "Argument passed to run() must be a string"); + runtimeError(vm, "Argument passed to run() must be a list"); return EMPTY_VAL; } @@ -122,7 +122,7 @@ static Value runNative(VM *vm, int argCount, Value *args) { return execute(vm, argList, true); } -ObjModule *createProcessModule(VM *vm) { +ObjModule *createProcessModule(DictuVM *vm) { ObjString *name = copyString(vm, "Process", 7); push(vm, OBJ_VAL(name)); ObjModule *module = newModule(vm, name); diff --git a/c/optionals/process.h b/src/optionals/process.h similarity index 62% rename from c/optionals/process.h rename to src/optionals/process.h index b7c4f526..8300f901 100644 --- a/c/optionals/process.h +++ b/src/optionals/process.h @@ -2,8 +2,7 @@ #define dictu_process_h #include "optionals.h" -#include "../vm.h" -ObjModule *createProcessModule(VM *vm); +ObjModule *createProcessModule(DictuVM *vm); #endif //dictu_process_h From 4c038ad1f3d53431515947d3284c6ecc08003542 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Sun, 27 Dec 2020 16:26:58 +0000 Subject: [PATCH 08/41] Use new result types for process lib and correctly resize buffer --- src/optionals/process.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/optionals/process.c b/src/optionals/process.c index a85cd0a4..62cd7c5a 100644 --- a/src/optionals/process.c +++ b/src/optionals/process.c @@ -19,14 +19,12 @@ static Value execute(DictuVM *vm, ObjList *argList, bool wait) { int status = 0; waitpid(pid, &status, 0); - if (WIFEXITED(status)) { - status = WEXITSTATUS(status); + if (WIFEXITED(status) && (status = WEXITSTATUS(status)) != 0) { + ERROR_RESULT; } - - return NUMBER_VAL(status); } - return NIL_VAL; + return newResultSuccess(vm, NIL_VAL); } static Value executeReturnOutput(DictuVM *vm, ObjList *argList) { @@ -70,10 +68,10 @@ static Value executeReturnOutput(DictuVM *vm, ObjList *argList) { total += numRead; } + output = SHRINK_ARRAY(vm, output, char, size, total + 1); output[total] = '\0'; - vm->bytesAllocated -= size - total - 1; - return OBJ_VAL(takeString(vm, output, total)); + return newResultSuccess(vm, OBJ_VAL(takeString(vm, output, total))); } static Value execNative(DictuVM *vm, int argCount, Value *args) { From 316a62ed2ed6d8fbb1b3be425669a359f5bfcd6a Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Sun, 27 Dec 2020 23:27:15 +0000 Subject: [PATCH 09/41] Write process documentation --- docs/docs/standard-lib/process.md | 52 +++++++++++++++++++++++++++++++ docs/docs/standard-lib/sqlite.md | 2 +- src/optionals/process.c | 3 +- 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 docs/docs/standard-lib/process.md diff --git a/docs/docs/standard-lib/process.md b/docs/docs/standard-lib/process.md new file mode 100644 index 00000000..717f7b14 --- /dev/null +++ b/docs/docs/standard-lib/process.md @@ -0,0 +1,52 @@ +--- +layout: default +title: Process +nav_order: 12 +parent: Standard Library +--- + +# Process +{: .no_toc } + +## Table of contents +{: .no_toc .text-delta } + +1. TOC +{:toc} + +--- + +## Process + +To make use of the Process module an import is required. + +```cs +import Process; +``` + +### Process.exec(list) + +Executing an external process can be done via `.exec`. Unlike `.run()` exec does not wait for the process +to finish executing, so it is only useful for circumstances where you wish to "fire and forget". + +`.exec()` expects a list as a parameter which is the command and any arguments for the command as individual list elements, +and it will return `nil`. + +```cs +Process.exec(["ls", "-la"]); +``` + +### Process.run(list, boolean: captureOutput -> optional) + +Similar to `.run()` except this **will** wait for the external process to finish executing. + +`.run()` expects a list as a parameter which is the command and any arguments for the command as individual list elements, +and it will return a Result that unwraps to `nil` on success. + +If the external process writes to stdout and you wish to capture the output you can pass an optional boolean argument to +`.run()`, this will instead make the Result unwrap to a string of the captured output. + +```cs +Process.run(["ls", "-la"]).unwrap(); +print(Process.run(["echo", "test"], true).unwrap()); // 'test' +``` \ No newline at end of file diff --git a/docs/docs/standard-lib/sqlite.md b/docs/docs/standard-lib/sqlite.md index a04123f6..08c03360 100644 --- a/docs/docs/standard-lib/sqlite.md +++ b/docs/docs/standard-lib/sqlite.md @@ -1,7 +1,7 @@ --- layout: default title: Sqlite -nav_order: 12 +nav_order: 13 parent: Standard Library --- diff --git a/src/optionals/process.c b/src/optionals/process.c index 62cd7c5a..d1b9865c 100644 --- a/src/optionals/process.c +++ b/src/optionals/process.c @@ -86,7 +86,8 @@ static Value execNative(DictuVM *vm, int argCount, Value *args) { } ObjList *argList = AS_LIST(args[0]); - return execute(vm, argList, false); + execute(vm, argList, false); + return NIL_VAL; } static Value runNative(DictuVM *vm, int argCount, Value *args) { From fe819cba6a3b730b6970293659c04cabdd9b4364 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Sun, 27 Dec 2020 23:57:08 +0000 Subject: [PATCH 10/41] Correctly track sqlite database status Previously it was attempting to close sqlite connections no matter if they had previously been free'd --- src/optionals/sqlite.c | 18 ++++++++++++++++-- tests/sqlite/create.du | 6 +++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/optionals/sqlite.c b/src/optionals/sqlite.c index 8657b93e..c8c83a28 100644 --- a/src/optionals/sqlite.c +++ b/src/optionals/sqlite.c @@ -2,6 +2,7 @@ typedef struct { sqlite3 *db; + bool open; } Database; typedef struct { @@ -53,6 +54,11 @@ static Value execute(DictuVM *vm, int argCount, Value *args) { } Database *db = AS_SQLITE_DATABASE(args[0]); + + if (!db->open) { + return newResultError(vm, "Database connection is closed"); + } + char *sql = AS_CSTRING(args[1]); ObjList *list = NULL; int parameterCount = countParameters(sql);; @@ -158,7 +164,11 @@ static Value closeConnection(DictuVM *vm, int argCount, Value *args) { } Database *db = AS_SQLITE_DATABASE(args[0]); - sqlite3_close(db->db); + + if (db->open) { + sqlite3_close(db->db); + db->open = false; + } return NIL_VAL; } @@ -191,7 +201,10 @@ static Value connectSqlite(DictuVM *vm, int argCount, Value *args) { void freeSqlite(DictuVM *vm, ObjAbstract *abstract) { Database *db = (Database*)abstract->data; - sqlite3_close(db->db); + if (db->open) { + sqlite3_close(db->db); + db->open = false; + } FREE(vm, Database, abstract->data); } @@ -200,6 +213,7 @@ ObjAbstract *newSqlite(DictuVM *vm) { push(vm, OBJ_VAL(abstract)); Database *db = ALLOCATE(vm, Database, 1); + db->open = true; /** * Setup Sqlite object methods diff --git a/tests/sqlite/create.du b/tests/sqlite/create.du index d73f56ba..fb001a57 100644 --- a/tests/sqlite/create.du +++ b/tests/sqlite/create.du @@ -19,4 +19,8 @@ var resp = connection.execute("CREATE TABLE test (x int)"); assert(resp.success() == false); assert(resp.unwrapError() == "table test already exists"); -connection.close(); \ No newline at end of file +connection.close(); + +// Assert operations on a closed connection fail correctly +assert(connection.execute("CREATE TABLE test1 (x int)").success() == false); +assert(connection.execute("CREATE TABLE test1 (x int)").unwrapError() == "Database connection is closed"); \ No newline at end of file From a7a492b5a4086c8926d0fb48b9416652965e834f Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Tue, 29 Dec 2020 17:53:20 +0000 Subject: [PATCH 11/41] Correctly handle string resizing when dealing with escape chars --- src/vm/compiler.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/vm/compiler.c b/src/vm/compiler.c index 4d2a6a92..67e7380c 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -932,15 +932,16 @@ static void string(Compiler *compiler, bool canAssign) { UNUSED(canAssign); Parser *parser = compiler->parser; + int stringLength = parser->previous.length - 2; - char *string = ALLOCATE(parser->vm, char, parser->previous.length - 1); + char *string = ALLOCATE(parser->vm, char, stringLength + 1); - memcpy(string, parser->previous.start + 1, parser->previous.length - 2); - int length = parseString(string, parser->previous.length - 2); + memcpy(string, parser->previous.start + 1, stringLength); + int length = parseString(string, stringLength); // If there were escape chars and the string shrank, resize the buffer - if (length != parser->previous.length - 1) { - string = SHRINK_ARRAY(parser->vm, string, char, parser->previous.length - 1, length + 1); + if (length != stringLength) { + string = SHRINK_ARRAY(parser->vm, string, char, stringLength + 1, length + 1); } string[length] = '\0'; From 423afcb628ff2d8d21ea5f03a19c040b501a9a9a Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Sat, 2 Jan 2021 21:08:05 +0000 Subject: [PATCH 12/41] Implement process on windows --- src/optionals/process.c | 174 +++++++++++++++++++++++++++++++++++----- 1 file changed, 156 insertions(+), 18 deletions(-) diff --git a/src/optionals/process.c b/src/optionals/process.c index d1b9865c..a91ffbf6 100644 --- a/src/optionals/process.c +++ b/src/optionals/process.c @@ -1,8 +1,142 @@ #include "process.h" -static Value execute(DictuVM *vm, ObjList *argList, bool wait) { - char **arguments = ALLOCATE(vm, char *, argList->values.count + 1); +#ifdef _WIN32 +static char* buildArgs(DictuVM *vm, ObjList* list, int *size) { + // 3 for 1st arg escape + null terminator + int length = 3; + + for (int i = 0; i < list->values.count; i++) { + if (!IS_STRING(list->values.values[i])) { + return NULL; + } + + // + 1 for space + length += AS_STRING(list->values.values[i])->length + 1; + } + + int len = AS_STRING(list->values.values[0])->length; + + char* string = ALLOCATE(vm, char, length); + memcpy(string, "\"", 1); + memcpy(string + 1, AS_CSTRING(list->values.values[0]), len); + memcpy(string + 1 + len, "\"", 1); + memcpy(string + 2 + len, " ", 1); + + int pointer = 3 + len; + for (int i = 1; i < list->values.count; i++) { + len = AS_STRING(list->values.values[i])->length; + memcpy(string + pointer, AS_CSTRING(list->values.values[i]), len); + pointer += len; + memcpy(string + pointer, " ", 1); + pointer += 1; + } + string[pointer] = '\0'; + + *size = pointer; + return string; +} + +static Value execute(DictuVM* vm, ObjList* argList, bool wait) { + PROCESS_INFORMATION ProcessInfo; + + STARTUPINFO StartupInfo; + + ZeroMemory(&StartupInfo, sizeof(StartupInfo)); + StartupInfo.cb = sizeof StartupInfo; + + int len; + char* args = buildArgs(vm, argList, &len); + + if (CreateProcess(NULL, args, + NULL, NULL, TRUE, 0, NULL, + NULL, &StartupInfo, &ProcessInfo)) + { + if (wait) { + WaitForSingleObject(ProcessInfo.hProcess, INFINITE); + } + CloseHandle(ProcessInfo.hThread); + CloseHandle(ProcessInfo.hProcess); + + FREE_ARRAY(vm, char, args, len); + return newResultSuccess(vm, NIL_VAL); + } + + return newResultError(vm, "Unable to start process"); +} + +static Value executeReturnOutput(DictuVM* vm, ObjList* argList) { + PROCESS_INFORMATION ProcessInfo; + STARTUPINFO StartupInfo; + + HANDLE childOutRead = NULL; + HANDLE childOutWrite = NULL; + SECURITY_ATTRIBUTES saAttr; + + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = true; + saAttr.lpSecurityDescriptor = NULL; + + if (!CreatePipe(&childOutRead, &childOutWrite, &saAttr, 0)) { + return newResultError(vm, "Unable to start process"); + } + + ZeroMemory(&StartupInfo, sizeof(StartupInfo)); + StartupInfo.cb = sizeof StartupInfo; + StartupInfo.hStdError = childOutWrite; + StartupInfo.hStdOutput = childOutWrite; + StartupInfo.dwFlags |= STARTF_USESTDHANDLES; + + int len; + char* args = buildArgs(vm, argList, &len); + + if (!CreateProcess(NULL, args, + NULL, NULL, TRUE, 0, NULL, + NULL, &StartupInfo, &ProcessInfo)) + { + FREE_ARRAY(vm, char, args, len); + return newResultError(vm, "Unable to start process2"); + } + + WaitForSingleObject(ProcessInfo.hProcess, INFINITE); + CloseHandle(ProcessInfo.hThread); + CloseHandle(ProcessInfo.hProcess); + CloseHandle(childOutWrite); + FREE_ARRAY(vm, char, args, len); + + int dwRead; + int size = 1024; + char* output = ALLOCATE(vm, char, size); + char buffer[1024]; + int total = 0; + + for (;;) { + bool ret = ReadFile(childOutRead, buffer, 1024, &dwRead, NULL); + + if (!ret || dwRead == 0) + break; + + if (total >= size) { + output = GROW_ARRAY(vm, output, char, size, size * 3); + size *= 3; + } + + memcpy(output + total, buffer, dwRead); + total += dwRead; + } + + output = SHRINK_ARRAY(vm, output, char, size, total + 1); + output[total] = '\0'; + + return newResultSuccess(vm, OBJ_VAL(takeString(vm, output, total))); +} +#else +static Value execute(DictuVM* vm, ObjList* argList, bool wait) { + char** arguments = ALLOCATE(vm, char*, argList->values.count + 1); for (int i = 0; i < argList->values.count; ++i) { + if (!IS_STRING(argList->values.values[i])) { + return newResultError(vm, "Arguments passed must all be strings"); + } + arguments[i] = AS_CSTRING(argList->values.values[i]); } @@ -13,7 +147,7 @@ static Value execute(DictuVM *vm, ObjList *argList, bool wait) { exit(errno); } - FREE_ARRAY(vm, char *, arguments, argList->values.count + 1); + FREE_ARRAY(vm, char*, arguments, argList->values.count + 1); if (wait) { int status = 0; @@ -27,9 +161,13 @@ static Value execute(DictuVM *vm, ObjList *argList, bool wait) { return newResultSuccess(vm, NIL_VAL); } -static Value executeReturnOutput(DictuVM *vm, ObjList *argList) { - char **arguments = ALLOCATE(vm, char *, argList->values.count + 1); +static Value executeReturnOutput(DictuVM* vm, ObjList* argList) { + char** arguments = ALLOCATE(vm, char*, argList->values.count + 1); for (int i = 0; i < argList->values.count; ++i) { + if (!IS_STRING(argList->values.values[i])) { + return newResultError(vm, "Arguments passed must all be strings"); + } + arguments[i] = AS_CSTRING(argList->values.values[i]); } @@ -48,12 +186,12 @@ static Value executeReturnOutput(DictuVM *vm, ObjList *argList) { exit(errno); } - FREE_ARRAY(vm, char *, arguments, argList->values.count + 1); + FREE_ARRAY(vm, char*, arguments, argList->values.count + 1); close(fd[1]); int size = 1024; - char *output = ALLOCATE(vm, char, size); + char* output = ALLOCATE(vm, char, size); char buffer[1024]; int total = 0; int numRead; @@ -73,24 +211,24 @@ static Value executeReturnOutput(DictuVM *vm, ObjList *argList) { return newResultSuccess(vm, OBJ_VAL(takeString(vm, output, total))); } +#endif -static Value execNative(DictuVM *vm, int argCount, Value *args) { +static Value execNative(DictuVM* vm, int argCount, Value* args) { if (argCount != 1) { runtimeError(vm, "exec() takes 1 argument (%d given).", argCount); return EMPTY_VAL; } if (!IS_LIST(args[0])) { - runtimeError(vm, "Argument passed to exec() must be a string"); + runtimeError(vm, "Argument passed to exec() must be a list"); return EMPTY_VAL; } - ObjList *argList = AS_LIST(args[0]); - execute(vm, argList, false); - return NIL_VAL; + ObjList* argList = AS_LIST(args[0]); + return execute(vm, argList, false); } -static Value runNative(DictuVM *vm, int argCount, Value *args) { +static Value runNative(DictuVM* vm, int argCount, Value* args) { if (argCount != 1 && argCount != 2) { runtimeError(vm, "run() takes 1 or 2 arguments (%d given)", argCount); return EMPTY_VAL; @@ -112,7 +250,7 @@ static Value runNative(DictuVM *vm, int argCount, Value *args) { getOutput = AS_BOOL(args[1]); } - ObjList *argList = AS_LIST(args[0]); + ObjList* argList = AS_LIST(args[0]); if (getOutput) { return executeReturnOutput(vm, argList); @@ -121,10 +259,10 @@ static Value runNative(DictuVM *vm, int argCount, Value *args) { return execute(vm, argList, true); } -ObjModule *createProcessModule(DictuVM *vm) { - ObjString *name = copyString(vm, "Process", 7); +ObjModule* createProcessModule(DictuVM* vm) { + ObjString* name = copyString(vm, "Process", 7); push(vm, OBJ_VAL(name)); - ObjModule *module = newModule(vm, name); + ObjModule* module = newModule(vm, name); push(vm, OBJ_VAL(module)); /** @@ -141,4 +279,4 @@ ObjModule *createProcessModule(DictuVM *vm) { pop(vm); return module; -} +} \ No newline at end of file From eb0f4013d9f5ee0e388f20ec1af82b3cd0651564 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Sat, 2 Jan 2021 21:23:47 +0000 Subject: [PATCH 13/41] Add some tests for Process and ensure we wait for the process to finish --- docs/docs/standard-lib/process.md | 4 ++-- src/optionals/process.c | 7 +++++++ tests/process/exec.du | 15 +++++++++++++++ tests/process/import.du | 8 ++++++++ tests/process/run.du | 28 ++++++++++++++++++++++++++++ tests/runTests.du | 1 + 6 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 tests/process/exec.du create mode 100644 tests/process/import.du create mode 100644 tests/process/run.du diff --git a/docs/docs/standard-lib/process.md b/docs/docs/standard-lib/process.md index 717f7b14..ad381df7 100644 --- a/docs/docs/standard-lib/process.md +++ b/docs/docs/standard-lib/process.md @@ -29,8 +29,8 @@ import Process; Executing an external process can be done via `.exec`. Unlike `.run()` exec does not wait for the process to finish executing, so it is only useful for circumstances where you wish to "fire and forget". -`.exec()` expects a list as a parameter which is the command and any arguments for the command as individual list elements, -and it will return `nil`. +`.exec()` expects a list as a parameter which is the command and any arguments for the command as individual list elements, which all must be strings. +It will return a Result that unwraps to `nil` on success. ```cs Process.exec(["ls", "-la"]); diff --git a/src/optionals/process.c b/src/optionals/process.c index a91ffbf6..8307b7a8 100644 --- a/src/optionals/process.c +++ b/src/optionals/process.c @@ -190,6 +190,13 @@ static Value executeReturnOutput(DictuVM* vm, ObjList* argList) { close(fd[1]); + int status = 0; + waitpid(pid, &status, 0); + + if (WIFEXITED(status) && (status = WEXITSTATUS(status)) != 0) { + ERROR_RESULT; + } + int size = 1024; char* output = ALLOCATE(vm, char, size); char buffer[1024]; diff --git a/tests/process/exec.du b/tests/process/exec.du new file mode 100644 index 00000000..cb4b4f3e --- /dev/null +++ b/tests/process/exec.du @@ -0,0 +1,15 @@ +/** + * exec.du + * + * Testing the Process.exec() function + * + * exec() executes a new process and does not wait for a return. + */ +import Process; + +if (System.platform == "windows") { + assert(Process.exec(["cmd.exe", "/c", "dir"]).success()); +} else { + assert(Process.exec(["ls"]).success()); + assert(Process.exec(["ls", "-la"]).success()); +} \ No newline at end of file diff --git a/tests/process/import.du b/tests/process/import.du new file mode 100644 index 00000000..894e9ea7 --- /dev/null +++ b/tests/process/import.du @@ -0,0 +1,8 @@ +/** + * import.du + * + * General import file for all the Process methods + */ + +import "exec.du"; +import "run.du"; \ No newline at end of file diff --git a/tests/process/run.du b/tests/process/run.du new file mode 100644 index 00000000..060bd7a4 --- /dev/null +++ b/tests/process/run.du @@ -0,0 +1,28 @@ +/** + * run.du + * + * Testing the Process.run() function + * + * run() executes a new process and does wait for a return. + */ +import Process; + +if (System.platform == "windows") { + assert(Process.run(["cmd.exe", "/c", "dir"]).success()); + + var output = Process.run(["cmd.exe", "/c", "dir"], true); + + assert(output.success()); + assert(type(output.unwrap()) == "string"); + assert(output.unwrap().len() > 0); +} else { + assert(Process.run(["ls"]).success()); + assert(Process.run(["ls", "-la"]).success()); + + var output = Process.run(["ls", "-la"], true); + + print(output.unwrap()); + assert(output.success()); + assert(type(output.unwrap()) == "string"); + assert(output.unwrap().len() > 0); +} \ No newline at end of file diff --git a/tests/runTests.du b/tests/runTests.du index 4f66dc04..8aa59d9f 100644 --- a/tests/runTests.du +++ b/tests/runTests.du @@ -21,6 +21,7 @@ import "path/import.du"; import "sockets/import.du"; import "base64/import.du"; import "sqlite/import.du"; +import "process/import.du"; if (isDefined("HTTP")) { import "http/import.du"; From abe411aee22870047352042d82c215cc553ffcd2 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Sat, 2 Jan 2021 21:45:16 +0000 Subject: [PATCH 14/41] Include wait.h --- src/optionals/process.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/optionals/process.h b/src/optionals/process.h index 8300f901..119c390b 100644 --- a/src/optionals/process.h +++ b/src/optionals/process.h @@ -1,6 +1,8 @@ #ifndef dictu_process_h #define dictu_process_h +#include + #include "optionals.h" ObjModule *createProcessModule(DictuVM *vm); From 9a065017b26c156cc4d58dd62d5ed21157edd864 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Sun, 3 Jan 2021 18:52:22 +0000 Subject: [PATCH 15/41] Add argument parsing for CLI --- src/cli/argparse.c | 384 +++++++++++++++++++++++++++++++++++++++++ src/cli/argparse.h | 130 ++++++++++++++ src/cli/main.c | 64 +++++-- src/optionals/system.c | 2 +- 4 files changed, 561 insertions(+), 19 deletions(-) create mode 100644 src/cli/argparse.c create mode 100644 src/cli/argparse.h diff --git a/src/cli/argparse.c b/src/cli/argparse.c new file mode 100644 index 00000000..148f17d9 --- /dev/null +++ b/src/cli/argparse.c @@ -0,0 +1,384 @@ +/** + * Copyright (C) 2012-2015 Yecheng Fu + * All rights reserved. + * + * Use of this source code is governed by a MIT-style license that can be found + * in the LICENSE file. + */ +#include +#include +#include +#include +#include +#include "argparse.h" + +#define OPT_UNSET 1 +#define OPT_LONG (1 << 1) + +static const char * +prefix_skip(const char *str, const char *prefix) +{ + size_t len = strlen(prefix); + return strncmp(str, prefix, len) ? NULL : str + len; +} + +static int +prefix_cmp(const char *str, const char *prefix) +{ + for (;; str++, prefix++) + if (!*prefix) { + return 0; + } else if (*str != *prefix) { + return (unsigned char)*prefix - (unsigned char)*str; + } +} + +static void +argparse_error(struct argparse *self, const struct argparse_option *opt, + const char *reason, int flags) +{ + (void)self; + if (flags & OPT_LONG) { + fprintf(stderr, "error: option `--%s` %s\n", opt->long_name, reason); + } else { + fprintf(stderr, "error: option `-%c` %s\n", opt->short_name, reason); + } + exit(1); +} + +static int +argparse_getvalue(struct argparse *self, const struct argparse_option *opt, + int flags) +{ + const char *s = NULL; + if (!opt->value) + goto skipped; + switch (opt->type) { + case ARGPARSE_OPT_BOOLEAN: + if (flags & OPT_UNSET) { + *(int *)opt->value = *(int *)opt->value - 1; + } else { + *(int *)opt->value = *(int *)opt->value + 1; + } + if (*(int *)opt->value < 0) { + *(int *)opt->value = 0; + } + break; + case ARGPARSE_OPT_BIT: + if (flags & OPT_UNSET) { + *(int *)opt->value &= ~opt->data; + } else { + *(int *)opt->value |= opt->data; + } + break; + case ARGPARSE_OPT_STRING: + if (self->optvalue) { + *(const char **)opt->value = self->optvalue; + self->optvalue = NULL; + } else if (self->argc > 1) { + self->argc--; + *(const char **)opt->value = *++self->argv; + } else { + argparse_error(self, opt, "requires a value", flags); + } + break; + case ARGPARSE_OPT_INTEGER: + errno = 0; + if (self->optvalue) { + *(int *)opt->value = strtol(self->optvalue, (char **)&s, 0); + self->optvalue = NULL; + } else if (self->argc > 1) { + self->argc--; + *(int *)opt->value = strtol(*++self->argv, (char **)&s, 0); + } else { + argparse_error(self, opt, "requires a value", flags); + } + if (errno) + argparse_error(self, opt, strerror(errno), flags); + if (s[0] != '\0') + argparse_error(self, opt, "expects an integer value", flags); + break; + case ARGPARSE_OPT_FLOAT: + errno = 0; + if (self->optvalue) { + *(float *)opt->value = strtof(self->optvalue, (char **)&s); + self->optvalue = NULL; + } else if (self->argc > 1) { + self->argc--; + *(float *)opt->value = strtof(*++self->argv, (char **)&s); + } else { + argparse_error(self, opt, "requires a value", flags); + } + if (errno) + argparse_error(self, opt, strerror(errno), flags); + if (s[0] != '\0') + argparse_error(self, opt, "expects a numerical value", flags); + break; + default: + assert(0); + } + + skipped: + if (opt->callback) { + return opt->callback(self, opt); + } + + return 0; +} + +static void +argparse_options_check(const struct argparse_option *options) +{ + for (; options->type != ARGPARSE_OPT_END; options++) { + switch (options->type) { + case ARGPARSE_OPT_END: + case ARGPARSE_OPT_BOOLEAN: + case ARGPARSE_OPT_BIT: + case ARGPARSE_OPT_INTEGER: + case ARGPARSE_OPT_FLOAT: + case ARGPARSE_OPT_STRING: + case ARGPARSE_OPT_GROUP: + continue; + default: + fprintf(stderr, "wrong option type: %d", options->type); + break; + } + } +} + +static int +argparse_short_opt(struct argparse *self, const struct argparse_option *options) +{ + for (; options->type != ARGPARSE_OPT_END; options++) { + if (options->short_name == *self->optvalue) { + self->optvalue = self->optvalue[1] ? self->optvalue + 1 : NULL; + return argparse_getvalue(self, options, 0); + } + } + return -2; +} + +static int +argparse_long_opt(struct argparse *self, const struct argparse_option *options) +{ + for (; options->type != ARGPARSE_OPT_END; options++) { + const char *rest; + int opt_flags = 0; + if (!options->long_name) + continue; + + rest = prefix_skip(self->argv[0] + 2, options->long_name); + if (!rest) { + // negation disabled? + if (options->flags & OPT_NONEG) { + continue; + } + // only OPT_BOOLEAN/OPT_BIT supports negation + if (options->type != ARGPARSE_OPT_BOOLEAN && options->type != + ARGPARSE_OPT_BIT) { + continue; + } + + if (prefix_cmp(self->argv[0] + 2, "no-")) { + continue; + } + rest = prefix_skip(self->argv[0] + 2 + 3, options->long_name); + if (!rest) + continue; + opt_flags |= OPT_UNSET; + } + if (*rest) { + if (*rest != '=') + continue; + self->optvalue = rest + 1; + } + return argparse_getvalue(self, options, opt_flags | OPT_LONG); + } + return -2; +} + +int +argparse_init(struct argparse *self, struct argparse_option *options, + const char *const *usages, int flags) +{ + memset(self, 0, sizeof(*self)); + self->options = options; + self->usages = usages; + self->flags = flags; + self->description = NULL; + self->epilog = NULL; + return 0; +} + +void +argparse_describe(struct argparse *self, const char *description, + const char *epilog) +{ + self->description = description; + self->epilog = epilog; +} + +int +argparse_parse(struct argparse *self, int argc, const char **argv) +{ + self->argc = argc - 1; + self->argv = argv + 1; + self->out = argv; + + argparse_options_check(self->options); + + for (; self->argc; self->argc--, self->argv++) { + const char *arg = self->argv[0]; + if (arg[0] != '-' || !arg[1]) { + if (self->flags & ARGPARSE_STOP_AT_NON_OPTION) { + goto end; + } + // if it's not option or is a single char '-', copy verbatim + self->out[self->cpidx++] = self->argv[0]; + continue; + } + // short option + if (arg[1] != '-') { + self->optvalue = arg + 1; + switch (argparse_short_opt(self, self->options)) { + case -1: + break; + case -2: + goto unknown; + } + while (self->optvalue) { + switch (argparse_short_opt(self, self->options)) { + case -1: + break; + case -2: + goto unknown; + } + } + continue; + } + // if '--' presents + if (!arg[2]) { + self->argc--; + self->argv++; + break; + } + // long option + switch (argparse_long_opt(self, self->options)) { + case -1: + break; + case -2: + goto unknown; + } + continue; + + unknown: + fprintf(stderr, "error: unknown option `%s`\n", self->argv[0]); + argparse_usage(self); + exit(1); + } + + end: + memmove(self->out + self->cpidx, self->argv, + self->argc * sizeof(*self->out)); + self->out[self->cpidx + self->argc] = NULL; + + return self->cpidx + self->argc; +} + +void +argparse_usage(struct argparse *self) +{ + if (self->usages) { + fprintf(stdout, "Usage: %s\n", *self->usages++); + while (*self->usages && **self->usages) + fprintf(stdout, " or: %s\n", *self->usages++); + } else { + fprintf(stdout, "Usage:\n"); + } + + // print description + if (self->description) + fprintf(stdout, "%s\n", self->description); + + fputc('\n', stdout); + + const struct argparse_option *options; + + // figure out best width + size_t usage_opts_width = 0; + size_t len; + options = self->options; + for (; options->type != ARGPARSE_OPT_END; options++) { + len = 0; + if ((options)->short_name) { + len += 2; + } + if ((options)->short_name && (options)->long_name) { + len += 2; // separator ", " + } + if ((options)->long_name) { + len += strlen((options)->long_name) + 2; + } + if (options->type == ARGPARSE_OPT_INTEGER) { + len += strlen("="); + } + if (options->type == ARGPARSE_OPT_FLOAT) { + len += strlen("="); + } else if (options->type == ARGPARSE_OPT_STRING) { + len += strlen("="); + } + len = (len + 3) - ((len + 3) & 3); + if (usage_opts_width < len) { + usage_opts_width = len; + } + } + usage_opts_width += 4; // 4 spaces prefix + + options = self->options; + for (; options->type != ARGPARSE_OPT_END; options++) { + size_t pos = 0; + int pad = 0; + if (options->type == ARGPARSE_OPT_GROUP) { + fputc('\n', stdout); + fprintf(stdout, "%s", options->help); + fputc('\n', stdout); + continue; + } + pos = fprintf(stdout, " "); + if (options->short_name) { + pos += fprintf(stdout, "-%c", options->short_name); + } + if (options->long_name && options->short_name) { + pos += fprintf(stdout, ", "); + } + if (options->long_name) { + pos += fprintf(stdout, "--%s", options->long_name); + } + if (options->type == ARGPARSE_OPT_INTEGER) { + pos += fprintf(stdout, "="); + } else if (options->type == ARGPARSE_OPT_FLOAT) { + pos += fprintf(stdout, "="); + } else if (options->type == ARGPARSE_OPT_STRING) { + pos += fprintf(stdout, "="); + } + if (pos <= usage_opts_width) { + pad = usage_opts_width - pos; + } else { + fputc('\n', stdout); + pad = usage_opts_width; + } + fprintf(stdout, "%*s%s\n", pad + 2, "", options->help); + } + + // print epilog + if (self->epilog) + fprintf(stdout, "%s\n", self->epilog); +} + +int +argparse_help_cb(struct argparse *self, const struct argparse_option *option) +{ + (void)option; + argparse_usage(self); + exit(0); +} \ No newline at end of file diff --git a/src/cli/argparse.h b/src/cli/argparse.h new file mode 100644 index 00000000..b3721783 --- /dev/null +++ b/src/cli/argparse.h @@ -0,0 +1,130 @@ +/** + * Copyright (C) 2012-2015 Yecheng Fu + * All rights reserved. + * + * Use of this source code is governed by a MIT-style license that can be found + * in the LICENSE file. + */ +#ifndef ARGPARSE_H +#define ARGPARSE_H + +/* For c++ compatibility */ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct argparse; +struct argparse_option; + +typedef int argparse_callback (struct argparse *self, + const struct argparse_option *option); + +enum argparse_flag { + ARGPARSE_STOP_AT_NON_OPTION = 1, +}; + +enum argparse_option_type { + /* special */ + ARGPARSE_OPT_END, + ARGPARSE_OPT_GROUP, + /* options with no arguments */ + ARGPARSE_OPT_BOOLEAN, + ARGPARSE_OPT_BIT, + /* options with arguments (optional or required) */ + ARGPARSE_OPT_INTEGER, + ARGPARSE_OPT_FLOAT, + ARGPARSE_OPT_STRING, +}; + +enum argparse_option_flags { + OPT_NONEG = 1, /* disable negation */ +}; + +/** + * argparse option + * + * `type`: + * holds the type of the option, you must have an ARGPARSE_OPT_END last in your + * array. + * + * `short_name`: + * the character to use as a short option name, '\0' if none. + * + * `long_name`: + * the long option name, without the leading dash, NULL if none. + * + * `value`: + * stores pointer to the value to be filled. + * + * `help`: + * the short help message associated to what the option does. + * Must never be NULL (except for ARGPARSE_OPT_END). + * + * `callback`: + * function is called when corresponding argument is parsed. + * + * `data`: + * associated data. Callbacks can use it like they want. + * + * `flags`: + * option flags. + */ +struct argparse_option { + enum argparse_option_type type; + const char short_name; + const char *long_name; + void *value; + const char *help; + argparse_callback *callback; + intptr_t data; + int flags; +}; + +/** + * argpparse + */ +struct argparse { + // user supplied + const struct argparse_option *options; + const char *const *usages; + int flags; + const char *description; // a description after usage + const char *epilog; // a description at the end + // internal context + int argc; + const char **argv; + const char **out; + int cpidx; + const char *optvalue; // current option value +}; + +// built-in callbacks +int argparse_help_cb(struct argparse *self, + const struct argparse_option *option); + +// built-in option macros +#define OPT_END() { ARGPARSE_OPT_END, 0, NULL, NULL, 0, NULL, 0, 0 } +#define OPT_BOOLEAN(...) { ARGPARSE_OPT_BOOLEAN, __VA_ARGS__ } +#define OPT_BIT(...) { ARGPARSE_OPT_BIT, __VA_ARGS__ } +#define OPT_INTEGER(...) { ARGPARSE_OPT_INTEGER, __VA_ARGS__ } +#define OPT_FLOAT(...) { ARGPARSE_OPT_FLOAT, __VA_ARGS__ } +#define OPT_STRING(...) { ARGPARSE_OPT_STRING, __VA_ARGS__ } +#define OPT_GROUP(h) { ARGPARSE_OPT_GROUP, 0, NULL, NULL, h, NULL, 0, 0 } +#define OPT_HELP() OPT_BOOLEAN('h', "help", NULL, \ + "Show this help message and exit", \ + argparse_help_cb, 0, OPT_NONEG) + +int argparse_init(struct argparse *self, struct argparse_option *options, + const char *const *usages, int flags); +void argparse_describe(struct argparse *self, const char *description, + const char *epilog); +int argparse_parse(struct argparse *self, int argc, const char **argv); +void argparse_usage(struct argparse *self); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/cli/main.c b/src/cli/main.c index d7cbf072..a11f7e70 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -9,6 +9,7 @@ #endif #include "../include/dictu_include.h" +#include "argparse.h" #ifndef DISABLE_LINENOISE #include "linenoise.h" @@ -63,9 +64,7 @@ static bool replCountQuotes(char *line) { } #endif -static void repl(DictuVM *vm, int argc, char *argv[]) { - UNUSED(argc); UNUSED(argv); - +static void repl(DictuVM *vm) { printf(DICTU_STRING_VERSION); char *line; @@ -177,35 +176,64 @@ static char *readFile(const char *path) { return buffer; } -static void runFile(DictuVM *vm, int argc, char *argv[]) { - UNUSED(argc); - - char *source = readFile(argv[1]); +static void runFile(DictuVM *vm, char *filename) { + char *source = readFile(filename); if (source == NULL) { - fprintf(stderr, "Could not open file \"%s\".\n", argv[1]); + fprintf(stderr, "Could not open file \"%s\".\n", filename); exit(74); } - DictuInterpretResult result = dictuInterpret(vm, argv[1], source); + DictuInterpretResult result = dictuInterpret(vm, filename, source); free(source); if (result == INTERPRET_COMPILE_ERROR) exit(65); if (result == INTERPRET_RUNTIME_ERROR) exit(70); } +static const char *const usage[] = { + "dictu [options] [[--] args]", + "dictu [options]", + NULL, +}; + int main(int argc, char *argv[]) { - DictuVM *vm = dictuInitVM(argc == 1, argc, argv); - - if (argc == 1) { - repl(vm, argc, argv); - } else if (argc >= 2) { - runFile(vm, argc, argv); - } else { - fprintf(stderr, "Usage: dictu [path] [args]\n"); - exit(64); + int version; + char *cmd = NULL; + + struct argparse_option options[] = { + OPT_HELP(), + OPT_BOOLEAN('v', "version", &version, "Display Dictu version"), + OPT_STRING('c', "cmd", &cmd, "Run program passed in as string"), + OPT_END(), + }; + + struct argparse argparse; + argparse_init(&argparse, options, usage, 0); + argc = argparse_parse(&argparse, argc, (const char **)argv); + + if (version) { + printf(DICTU_STRING_VERSION); + return 0; + } + + DictuVM *vm = dictuInitVM(argc == 0, argc, argv); + + if (cmd != NULL) { + DictuInterpretResult result = dictuInterpret(vm, "repl", cmd); + if (result == INTERPRET_COMPILE_ERROR) exit(65); + if (result == INTERPRET_RUNTIME_ERROR) exit(70); + dictuFreeVM(vm); + return 0; + } + + if (argc == 0) { + repl(vm); + dictuFreeVM(vm); + return 0; } + runFile(vm, argv[0]); dictuFreeVM(vm); return 0; } diff --git a/src/optionals/system.c b/src/optionals/system.c index a368d468..109d3378 100644 --- a/src/optionals/system.c +++ b/src/optionals/system.c @@ -275,7 +275,7 @@ void initArgv(DictuVM *vm, Table *table, int argc, char *argv[]) { ObjList *list = newList(vm); push(vm, OBJ_VAL(list)); - for (int i = 1; i < argc; i++) { + for (int i = 0; i < argc; i++) { Value arg = OBJ_VAL(copyString(vm, argv[i], strlen(argv[i]))); push(vm, arg); writeValueArray(vm, &list->values, arg); From db3774fe1499e6f4ce867aa363c5c51acf6f1d17 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Sun, 3 Jan 2021 19:01:57 +0000 Subject: [PATCH 16/41] Don't include wait.h on windows systems --- src/optionals/process.c | 2 +- src/optionals/process.h | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/optionals/process.c b/src/optionals/process.c index 8307b7a8..dd0ab789 100644 --- a/src/optionals/process.c +++ b/src/optionals/process.c @@ -32,7 +32,7 @@ static char* buildArgs(DictuVM *vm, ObjList* list, int *size) { } string[pointer] = '\0'; - *size = pointer; + *size = length; return string; } diff --git a/src/optionals/process.h b/src/optionals/process.h index 119c390b..b6f09891 100644 --- a/src/optionals/process.h +++ b/src/optionals/process.h @@ -1,7 +1,9 @@ #ifndef dictu_process_h #define dictu_process_h -#include +#ifndef _WIN32 +#include +#endif // !_WIN32 #include "optionals.h" From 43949c383e09fb5f85d7ee5d97a00669dcad3ba0 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Sun, 3 Jan 2021 19:23:06 +0000 Subject: [PATCH 17/41] Allow argparse to compile and function on windows --- src/cli/argparse.c | 6 +++--- src/cli/main.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cli/argparse.c b/src/cli/argparse.c index 148f17d9..0f551a8b 100644 --- a/src/cli/argparse.c +++ b/src/cli/argparse.c @@ -95,7 +95,7 @@ argparse_getvalue(struct argparse *self, const struct argparse_option *opt, } if (errno) argparse_error(self, opt, strerror(errno), flags); - if (s[0] != '\0') + if (s != NULL && s[0] != '\0') argparse_error(self, opt, "expects an integer value", flags); break; case ARGPARSE_OPT_FLOAT: @@ -111,7 +111,7 @@ argparse_getvalue(struct argparse *self, const struct argparse_option *opt, } if (errno) argparse_error(self, opt, strerror(errno), flags); - if (s[0] != '\0') + if (s != NULL && s[0] != '\0') argparse_error(self, opt, "expects a numerical value", flags); break; default: @@ -278,7 +278,7 @@ argparse_parse(struct argparse *self, int argc, const char **argv) } end: - memmove(self->out + self->cpidx, self->argv, + memmove((void *)(self->out + self->cpidx), self->argv, self->argc * sizeof(*self->out)); self->out[self->cpidx + self->argc] = NULL; diff --git a/src/cli/main.c b/src/cli/main.c index a11f7e70..13228c4c 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -198,7 +198,7 @@ static const char *const usage[] = { }; int main(int argc, char *argv[]) { - int version; + int version = 0; char *cmd = NULL; struct argparse_option options[] = { From ff911fc25442956f726c48639395ade226eef30d Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Sun, 3 Jan 2021 22:27:20 +0000 Subject: [PATCH 18/41] Get windows CI/CD working again --- .github/workflows/main.yml | 31 +++++++++++++------------------ src/CMakeLists.txt | 5 ++++- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 277303e2..a60608bf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -53,22 +53,17 @@ jobs: cmake -DCMAKE_BUILD_TYPE=Debug -B ./build cmake --build ./build ./dictu tests/runTests.du | tee /dev/stderr | grep -q 'Total memory usage: 0' + test-windows-cmake: + name: Test on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-2019, windows-2016] -## Seems the MSVC compiler was updated on Actions and we are getting loads of compilation -## issues with what seems like standard library header files. While investigating windows -## compilation will be removed from Actions -# -# test-windows-cmake: -# name: Test on ${{ matrix.os }} -# runs-on: ${{ matrix.os }} -# strategy: -# matrix: -# os: [windows-2019, windows-latest] -# -# steps: -# - uses: actions/checkout@v2 -# - name: Make dictu and run tests (No HTTP No Linenoise) -# run: | -# cmake -DCMAKE_BUILD_TYPE=Debug -DDISABLE_HTTP=1 -DDISABLE_LINENOISE=1 -B build -# cmake --build build -# Debug\dictu.exe tests/runTests.du \ No newline at end of file + steps: + - uses: actions/checkout@v2 + - name: Make dictu and run tests (No HTTP No Linenoise) + run: | + cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_SYSTEM_VERSION="10.0.18362.0" -DCICD -DDISABLE_HTTP=1 -DDISABLE_LINENOISE=1 -B build + cmake --build build + Debug\dictu.exe tests/runTests.du \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b1d42c0c..dc6270d5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -40,7 +40,10 @@ endif() if(MSVC) add_compile_definitions(_CRT_SECURE_NO_WARNINGS) - set(CMAKE_C_FLAGS "/WX /W1") + # An issue on CI/CD with winbase.h producing C5105 warnings + if(NOT CICD) + set(CMAKE_C_FLAGS "/WX /W1") + endif() set(CMAKE_C_FLAGS_DEBUG "/Zi") set(CMAKE_C_FLAGS_RELEASE "/O2") else() From 4bbc0d46b0713d19ca168fa36cb764aa49747cc8 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Sun, 3 Jan 2021 22:30:54 +0000 Subject: [PATCH 19/41] Add missing value on cmake flag --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a60608bf..25af0e73 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -64,6 +64,6 @@ jobs: - uses: actions/checkout@v2 - name: Make dictu and run tests (No HTTP No Linenoise) run: | - cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_SYSTEM_VERSION="10.0.18362.0" -DCICD -DDISABLE_HTTP=1 -DDISABLE_LINENOISE=1 -B build + cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_SYSTEM_VERSION="10.0.18362.0" -DCICD=1 -DDISABLE_HTTP=1 -DDISABLE_LINENOISE=1 -B build cmake --build build Debug\dictu.exe tests/runTests.du \ No newline at end of file From 94601e7a3681f4bbb1f87953010390499ae2b5d2 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Thu, 7 Jan 2021 00:08:41 +0000 Subject: [PATCH 20/41] Add utf-8 support for linenoise --- src/cli/encodings/utf8.c | 535 ++++++++++++++++++++++++++++++++ src/cli/encodings/utf8.h | 55 ++++ src/cli/linenoise.c | 639 +++++++++++++++++++++++---------------- src/cli/linenoise.h | 17 +- src/cli/main.c | 7 + 5 files changed, 990 insertions(+), 263 deletions(-) create mode 100755 src/cli/encodings/utf8.c create mode 100755 src/cli/encodings/utf8.h mode change 100644 => 100755 src/cli/linenoise.c mode change 100644 => 100755 src/cli/linenoise.h diff --git a/src/cli/encodings/utf8.c b/src/cli/encodings/utf8.c new file mode 100755 index 00000000..adda52d7 --- /dev/null +++ b/src/cli/encodings/utf8.c @@ -0,0 +1,535 @@ +/* encoding/utf8.c -- VERSION 1.0 + * + * Guerrilla line editing library against the idea that a line editing lib + * needs to be 20,000 lines of C code. + * + * You can find the latest source code at: + * + * http://github.com/antirez/linenoise + * + * Does a number of crazy assumptions that happen to be true in 99.9999% of + * the 2010 UNIX computers around. + * + * ------------------------------------------------------------------------ + * + * Copyright (c) 2010-2014, Salvatore Sanfilippo + * Copyright (c) 2010-2013, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#define UNUSED(x) (void)(x) + +/* ============================ UTF8 utilities ============================== */ + +static unsigned long wideCharTable[][2] = { + { 0x1100, 0x115F }, + { 0x231A, 0x231B }, + { 0x2329, 0x232A }, + { 0x23E9, 0x23EC }, + { 0x23F0, 0x23F0 }, + { 0x23F3, 0x23F3 }, + { 0x25FD, 0x25FE }, + { 0x2614, 0x2615 }, + { 0x2648, 0x2653 }, + { 0x267F, 0x267F }, + { 0x2693, 0x2693 }, + { 0x26A1, 0x26A1 }, + { 0x26AA, 0x26AB }, + { 0x26BD, 0x26BE }, + { 0x26C4, 0x26C5 }, + { 0x26CE, 0x26CE }, + { 0x26D4, 0x26D4 }, + { 0x26EA, 0x26EA }, + { 0x26F2, 0x26F3 }, + { 0x26F5, 0x26F5 }, + { 0x26FA, 0x26FA }, + { 0x26FD, 0x26FD }, + { 0x2705, 0x2705 }, + { 0x270A, 0x270B }, + { 0x2728, 0x2728 }, + { 0x274C, 0x274C }, + { 0x274E, 0x274E }, + { 0x2753, 0x2755 }, + { 0x2757, 0x2757 }, + { 0x2795, 0x2797 }, + { 0x27B0, 0x27B0 }, + { 0x27BF, 0x27BF }, + { 0x2B1B, 0x2B1C }, + { 0x2B50, 0x2B50 }, + { 0x2B55, 0x2B55 }, + { 0x2E80, 0x2E99 }, + { 0x2E9B, 0x2EF3 }, + { 0x2F00, 0x2FD5 }, + { 0x2FF0, 0x2FFB }, + { 0x3000, 0x303E }, + { 0x3041, 0x3096 }, + { 0x3099, 0x30FF }, + { 0x3105, 0x312F }, + { 0x3131, 0x318E }, + { 0x3190, 0x31E3 }, + { 0x31F0, 0x321E }, + { 0x3220, 0x3247 }, + { 0x3250, 0x4DBF }, + { 0x4E00, 0xA48C }, + { 0xA490, 0xA4C6 }, + { 0xA960, 0xA97C }, + { 0xAC00, 0xD7A3 }, + { 0xF900, 0xFAFF }, + { 0xFE10, 0xFE19 }, + { 0xFE30, 0xFE52 }, + { 0xFE54, 0xFE66 }, + { 0xFE68, 0xFE6B }, + { 0xFF01, 0xFF60 }, + { 0xFFE0, 0xFFE6 }, + { 0x16FE0, 0x16FE4 }, + { 0x16FF0, 0x16FF1 }, + { 0x17000, 0x187F7 }, + { 0x18800, 0x18CD5 }, + { 0x18D00, 0x18D08 }, + { 0x1B000, 0x1B11E }, + { 0x1B150, 0x1B152 }, + { 0x1B164, 0x1B167 }, + { 0x1B170, 0x1B2FB }, + { 0x1F004, 0x1F004 }, + { 0x1F0CF, 0x1F0CF }, + { 0x1F18E, 0x1F18E }, + { 0x1F191, 0x1F19A }, + { 0x1F200, 0x1F202 }, + { 0x1F210, 0x1F23B }, + { 0x1F240, 0x1F248 }, + { 0x1F250, 0x1F251 }, + { 0x1F260, 0x1F265 }, + { 0x1F300, 0x1F320 }, + { 0x1F32D, 0x1F335 }, + { 0x1F337, 0x1F37C }, + { 0x1F37E, 0x1F393 }, + { 0x1F3A0, 0x1F3CA }, + { 0x1F3CF, 0x1F3D3 }, + { 0x1F3E0, 0x1F3F0 }, + { 0x1F3F4, 0x1F3F4 }, + { 0x1F3F8, 0x1F43E }, + { 0x1F440, 0x1F440 }, + { 0x1F442, 0x1F4FC }, + { 0x1F4FF, 0x1F53D }, + { 0x1F54B, 0x1F54E }, + { 0x1F550, 0x1F567 }, + { 0x1F57A, 0x1F57A }, + { 0x1F595, 0x1F596 }, + { 0x1F5A4, 0x1F5A4 }, + { 0x1F5FB, 0x1F64F }, + { 0x1F680, 0x1F6C5 }, + { 0x1F6CC, 0x1F6CC }, + { 0x1F6D0, 0x1F6D2 }, + { 0x1F6D5, 0x1F6D7 }, + { 0x1F6EB, 0x1F6EC }, + { 0x1F6F4, 0x1F6FC }, + { 0x1F7E0, 0x1F7EB }, + { 0x1F90C, 0x1F93A }, + { 0x1F93C, 0x1F945 }, + { 0x1F947, 0x1F978 }, + { 0x1F97A, 0x1F9CB }, + { 0x1F9CD, 0x1F9FF }, + { 0x1FA70, 0x1FA74 }, + { 0x1FA78, 0x1FA7A }, + { 0x1FA80, 0x1FA86 }, + { 0x1FA90, 0x1FAA8 }, + { 0x1FAB0, 0x1FAB6 }, + { 0x1FAC0, 0x1FAC2 }, + { 0x1FAD0, 0x1FAD6 }, + { 0x20000, 0x2FFFD }, + { 0x30000, 0x3FFFD }, +}; + +static size_t wideCharTableSize = sizeof(wideCharTable) / sizeof(wideCharTable[0]); + +static unsigned long combiningCharTable[] = { + 0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307, + 0x0308, 0x0309, 0x030A, 0x030B, 0x030C, 0x030D, 0x030E, 0x030F, + 0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317, + 0x0318, 0x0319, 0x031A, 0x031B, 0x031C, 0x031D, 0x031E, 0x031F, + 0x0320, 0x0321, 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327, + 0x0328, 0x0329, 0x032A, 0x032B, 0x032C, 0x032D, 0x032E, 0x032F, + 0x0330, 0x0331, 0x0332, 0x0333, 0x0334, 0x0335, 0x0336, 0x0337, + 0x0338, 0x0339, 0x033A, 0x033B, 0x033C, 0x033D, 0x033E, 0x033F, + 0x0340, 0x0341, 0x0342, 0x0343, 0x0344, 0x0345, 0x0346, 0x0347, + 0x0348, 0x0349, 0x034A, 0x034B, 0x034C, 0x034D, 0x034E, 0x034F, + 0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357, + 0x0358, 0x0359, 0x035A, 0x035B, 0x035C, 0x035D, 0x035E, 0x035F, + 0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367, + 0x0368, 0x0369, 0x036A, 0x036B, 0x036C, 0x036D, 0x036E, 0x036F, + 0x0483, 0x0484, 0x0485, 0x0486, 0x0487, 0x0591, 0x0592, 0x0593, + 0x0594, 0x0595, 0x0596, 0x0597, 0x0598, 0x0599, 0x059A, 0x059B, + 0x059C, 0x059D, 0x059E, 0x059F, 0x05A0, 0x05A1, 0x05A2, 0x05A3, + 0x05A4, 0x05A5, 0x05A6, 0x05A7, 0x05A8, 0x05A9, 0x05AA, 0x05AB, + 0x05AC, 0x05AD, 0x05AE, 0x05AF, 0x05B0, 0x05B1, 0x05B2, 0x05B3, + 0x05B4, 0x05B5, 0x05B6, 0x05B7, 0x05B8, 0x05B9, 0x05BA, 0x05BB, + 0x05BC, 0x05BD, 0x05BF, 0x05C1, 0x05C2, 0x05C4, 0x05C5, 0x05C7, + 0x0610, 0x0611, 0x0612, 0x0613, 0x0614, 0x0615, 0x0616, 0x0617, + 0x0618, 0x0619, 0x061A, 0x064B, 0x064C, 0x064D, 0x064E, 0x064F, + 0x0650, 0x0651, 0x0652, 0x0653, 0x0654, 0x0655, 0x0656, 0x0657, + 0x0658, 0x0659, 0x065A, 0x065B, 0x065C, 0x065D, 0x065E, 0x065F, + 0x0670, 0x06D6, 0x06D7, 0x06D8, 0x06D9, 0x06DA, 0x06DB, 0x06DC, + 0x06DF, 0x06E0, 0x06E1, 0x06E2, 0x06E3, 0x06E4, 0x06E7, 0x06E8, + 0x06EA, 0x06EB, 0x06EC, 0x06ED, 0x0711, 0x0730, 0x0731, 0x0732, + 0x0733, 0x0734, 0x0735, 0x0736, 0x0737, 0x0738, 0x0739, 0x073A, + 0x073B, 0x073C, 0x073D, 0x073E, 0x073F, 0x0740, 0x0741, 0x0742, + 0x0743, 0x0744, 0x0745, 0x0746, 0x0747, 0x0748, 0x0749, 0x074A, + 0x07A6, 0x07A7, 0x07A8, 0x07A9, 0x07AA, 0x07AB, 0x07AC, 0x07AD, + 0x07AE, 0x07AF, 0x07B0, 0x07EB, 0x07EC, 0x07ED, 0x07EE, 0x07EF, + 0x07F0, 0x07F1, 0x07F2, 0x07F3, 0x07FD, 0x0816, 0x0817, 0x0818, + 0x0819, 0x081B, 0x081C, 0x081D, 0x081E, 0x081F, 0x0820, 0x0821, + 0x0822, 0x0823, 0x0825, 0x0826, 0x0827, 0x0829, 0x082A, 0x082B, + 0x082C, 0x082D, 0x0859, 0x085A, 0x085B, 0x08D3, 0x08D4, 0x08D5, + 0x08D6, 0x08D7, 0x08D8, 0x08D9, 0x08DA, 0x08DB, 0x08DC, 0x08DD, + 0x08DE, 0x08DF, 0x08E0, 0x08E1, 0x08E3, 0x08E4, 0x08E5, 0x08E6, + 0x08E7, 0x08E8, 0x08E9, 0x08EA, 0x08EB, 0x08EC, 0x08ED, 0x08EE, + 0x08EF, 0x08F0, 0x08F1, 0x08F2, 0x08F3, 0x08F4, 0x08F5, 0x08F6, + 0x08F7, 0x08F8, 0x08F9, 0x08FA, 0x08FB, 0x08FC, 0x08FD, 0x08FE, + 0x08FF, 0x0900, 0x0901, 0x0902, 0x093A, 0x093C, 0x0941, 0x0942, + 0x0943, 0x0944, 0x0945, 0x0946, 0x0947, 0x0948, 0x094D, 0x0951, + 0x0952, 0x0953, 0x0954, 0x0955, 0x0956, 0x0957, 0x0962, 0x0963, + 0x0981, 0x09BC, 0x09C1, 0x09C2, 0x09C3, 0x09C4, 0x09CD, 0x09E2, + 0x09E3, 0x09FE, 0x0A01, 0x0A02, 0x0A3C, 0x0A41, 0x0A42, 0x0A47, + 0x0A48, 0x0A4B, 0x0A4C, 0x0A4D, 0x0A51, 0x0A70, 0x0A71, 0x0A75, + 0x0A81, 0x0A82, 0x0ABC, 0x0AC1, 0x0AC2, 0x0AC3, 0x0AC4, 0x0AC5, + 0x0AC7, 0x0AC8, 0x0ACD, 0x0AE2, 0x0AE3, 0x0AFA, 0x0AFB, 0x0AFC, + 0x0AFD, 0x0AFE, 0x0AFF, 0x0B01, 0x0B3C, 0x0B3F, 0x0B41, 0x0B42, + 0x0B43, 0x0B44, 0x0B4D, 0x0B55, 0x0B56, 0x0B62, 0x0B63, 0x0B82, + 0x0BC0, 0x0BCD, 0x0C00, 0x0C04, 0x0C3E, 0x0C3F, 0x0C40, 0x0C46, + 0x0C47, 0x0C48, 0x0C4A, 0x0C4B, 0x0C4C, 0x0C4D, 0x0C55, 0x0C56, + 0x0C62, 0x0C63, 0x0C81, 0x0CBC, 0x0CBF, 0x0CC6, 0x0CCC, 0x0CCD, + 0x0CE2, 0x0CE3, 0x0D00, 0x0D01, 0x0D3B, 0x0D3C, 0x0D41, 0x0D42, + 0x0D43, 0x0D44, 0x0D4D, 0x0D62, 0x0D63, 0x0D81, 0x0DCA, 0x0DD2, + 0x0DD3, 0x0DD4, 0x0DD6, 0x0E31, 0x0E34, 0x0E35, 0x0E36, 0x0E37, + 0x0E38, 0x0E39, 0x0E3A, 0x0E47, 0x0E48, 0x0E49, 0x0E4A, 0x0E4B, + 0x0E4C, 0x0E4D, 0x0E4E, 0x0EB1, 0x0EB4, 0x0EB5, 0x0EB6, 0x0EB7, + 0x0EB8, 0x0EB9, 0x0EBA, 0x0EBB, 0x0EBC, 0x0EC8, 0x0EC9, 0x0ECA, + 0x0ECB, 0x0ECC, 0x0ECD, 0x0F18, 0x0F19, 0x0F35, 0x0F37, 0x0F39, + 0x0F71, 0x0F72, 0x0F73, 0x0F74, 0x0F75, 0x0F76, 0x0F77, 0x0F78, + 0x0F79, 0x0F7A, 0x0F7B, 0x0F7C, 0x0F7D, 0x0F7E, 0x0F80, 0x0F81, + 0x0F82, 0x0F83, 0x0F84, 0x0F86, 0x0F87, 0x0F8D, 0x0F8E, 0x0F8F, + 0x0F90, 0x0F91, 0x0F92, 0x0F93, 0x0F94, 0x0F95, 0x0F96, 0x0F97, + 0x0F99, 0x0F9A, 0x0F9B, 0x0F9C, 0x0F9D, 0x0F9E, 0x0F9F, 0x0FA0, + 0x0FA1, 0x0FA2, 0x0FA3, 0x0FA4, 0x0FA5, 0x0FA6, 0x0FA7, 0x0FA8, + 0x0FA9, 0x0FAA, 0x0FAB, 0x0FAC, 0x0FAD, 0x0FAE, 0x0FAF, 0x0FB0, + 0x0FB1, 0x0FB2, 0x0FB3, 0x0FB4, 0x0FB5, 0x0FB6, 0x0FB7, 0x0FB8, + 0x0FB9, 0x0FBA, 0x0FBB, 0x0FBC, 0x0FC6, 0x102D, 0x102E, 0x102F, + 0x1030, 0x1032, 0x1033, 0x1034, 0x1035, 0x1036, 0x1037, 0x1039, + 0x103A, 0x103D, 0x103E, 0x1058, 0x1059, 0x105E, 0x105F, 0x1060, + 0x1071, 0x1072, 0x1073, 0x1074, 0x1082, 0x1085, 0x1086, 0x108D, + 0x109D, 0x135D, 0x135E, 0x135F, 0x1712, 0x1713, 0x1714, 0x1732, + 0x1733, 0x1734, 0x1752, 0x1753, 0x1772, 0x1773, 0x17B4, 0x17B5, + 0x17B7, 0x17B8, 0x17B9, 0x17BA, 0x17BB, 0x17BC, 0x17BD, 0x17C6, + 0x17C9, 0x17CA, 0x17CB, 0x17CC, 0x17CD, 0x17CE, 0x17CF, 0x17D0, + 0x17D1, 0x17D2, 0x17D3, 0x17DD, 0x180B, 0x180C, 0x180D, 0x1885, + 0x1886, 0x18A9, 0x1920, 0x1921, 0x1922, 0x1927, 0x1928, 0x1932, + 0x1939, 0x193A, 0x193B, 0x1A17, 0x1A18, 0x1A1B, 0x1A56, 0x1A58, + 0x1A59, 0x1A5A, 0x1A5B, 0x1A5C, 0x1A5D, 0x1A5E, 0x1A60, 0x1A62, + 0x1A65, 0x1A66, 0x1A67, 0x1A68, 0x1A69, 0x1A6A, 0x1A6B, 0x1A6C, + 0x1A73, 0x1A74, 0x1A75, 0x1A76, 0x1A77, 0x1A78, 0x1A79, 0x1A7A, + 0x1A7B, 0x1A7C, 0x1A7F, 0x1AB0, 0x1AB1, 0x1AB2, 0x1AB3, 0x1AB4, + 0x1AB5, 0x1AB6, 0x1AB7, 0x1AB8, 0x1AB9, 0x1ABA, 0x1ABB, 0x1ABC, + 0x1ABD, 0x1ABF, 0x1AC0, 0x1B00, 0x1B01, 0x1B02, 0x1B03, 0x1B34, + 0x1B36, 0x1B37, 0x1B38, 0x1B39, 0x1B3A, 0x1B3C, 0x1B42, 0x1B6B, + 0x1B6C, 0x1B6D, 0x1B6E, 0x1B6F, 0x1B70, 0x1B71, 0x1B72, 0x1B73, + 0x1B80, 0x1B81, 0x1BA2, 0x1BA3, 0x1BA4, 0x1BA5, 0x1BA8, 0x1BA9, + 0x1BAB, 0x1BAC, 0x1BAD, 0x1BE6, 0x1BE8, 0x1BE9, 0x1BED, 0x1BEF, + 0x1BF0, 0x1BF1, 0x1C2C, 0x1C2D, 0x1C2E, 0x1C2F, 0x1C30, 0x1C31, + 0x1C32, 0x1C33, 0x1C36, 0x1C37, 0x1CD0, 0x1CD1, 0x1CD2, 0x1CD4, + 0x1CD5, 0x1CD6, 0x1CD7, 0x1CD8, 0x1CD9, 0x1CDA, 0x1CDB, 0x1CDC, + 0x1CDD, 0x1CDE, 0x1CDF, 0x1CE0, 0x1CE2, 0x1CE3, 0x1CE4, 0x1CE5, + 0x1CE6, 0x1CE7, 0x1CE8, 0x1CED, 0x1CF4, 0x1CF8, 0x1CF9, 0x1DC0, + 0x1DC1, 0x1DC2, 0x1DC3, 0x1DC4, 0x1DC5, 0x1DC6, 0x1DC7, 0x1DC8, + 0x1DC9, 0x1DCA, 0x1DCB, 0x1DCC, 0x1DCD, 0x1DCE, 0x1DCF, 0x1DD0, + 0x1DD1, 0x1DD2, 0x1DD3, 0x1DD4, 0x1DD5, 0x1DD6, 0x1DD7, 0x1DD8, + 0x1DD9, 0x1DDA, 0x1DDB, 0x1DDC, 0x1DDD, 0x1DDE, 0x1DDF, 0x1DE0, + 0x1DE1, 0x1DE2, 0x1DE3, 0x1DE4, 0x1DE5, 0x1DE6, 0x1DE7, 0x1DE8, + 0x1DE9, 0x1DEA, 0x1DEB, 0x1DEC, 0x1DED, 0x1DEE, 0x1DEF, 0x1DF0, + 0x1DF1, 0x1DF2, 0x1DF3, 0x1DF4, 0x1DF5, 0x1DF6, 0x1DF7, 0x1DF8, + 0x1DF9, 0x1DFB, 0x1DFC, 0x1DFD, 0x1DFE, 0x1DFF, 0x20D0, 0x20D1, + 0x20D2, 0x20D3, 0x20D4, 0x20D5, 0x20D6, 0x20D7, 0x20D8, 0x20D9, + 0x20DA, 0x20DB, 0x20DC, 0x20E1, 0x20E5, 0x20E6, 0x20E7, 0x20E8, + 0x20E9, 0x20EA, 0x20EB, 0x20EC, 0x20ED, 0x20EE, 0x20EF, 0x20F0, + 0x2CEF, 0x2CF0, 0x2CF1, 0x2D7F, 0x2DE0, 0x2DE1, 0x2DE2, 0x2DE3, + 0x2DE4, 0x2DE5, 0x2DE6, 0x2DE7, 0x2DE8, 0x2DE9, 0x2DEA, 0x2DEB, + 0x2DEC, 0x2DED, 0x2DEE, 0x2DEF, 0x2DF0, 0x2DF1, 0x2DF2, 0x2DF3, + 0x2DF4, 0x2DF5, 0x2DF6, 0x2DF7, 0x2DF8, 0x2DF9, 0x2DFA, 0x2DFB, + 0x2DFC, 0x2DFD, 0x2DFE, 0x2DFF, 0x302A, 0x302B, 0x302C, 0x302D, + 0x3099, 0x309A, 0xA66F, 0xA674, 0xA675, 0xA676, 0xA677, 0xA678, + 0xA679, 0xA67A, 0xA67B, 0xA67C, 0xA67D, 0xA69E, 0xA69F, 0xA6F0, + 0xA6F1, 0xA802, 0xA806, 0xA80B, 0xA825, 0xA826, 0xA82C, 0xA8C4, + 0xA8C5, 0xA8E0, 0xA8E1, 0xA8E2, 0xA8E3, 0xA8E4, 0xA8E5, 0xA8E6, + 0xA8E7, 0xA8E8, 0xA8E9, 0xA8EA, 0xA8EB, 0xA8EC, 0xA8ED, 0xA8EE, + 0xA8EF, 0xA8F0, 0xA8F1, 0xA8FF, 0xA926, 0xA927, 0xA928, 0xA929, + 0xA92A, 0xA92B, 0xA92C, 0xA92D, 0xA947, 0xA948, 0xA949, 0xA94A, + 0xA94B, 0xA94C, 0xA94D, 0xA94E, 0xA94F, 0xA950, 0xA951, 0xA980, + 0xA981, 0xA982, 0xA9B3, 0xA9B6, 0xA9B7, 0xA9B8, 0xA9B9, 0xA9BC, + 0xA9BD, 0xA9E5, 0xAA29, 0xAA2A, 0xAA2B, 0xAA2C, 0xAA2D, 0xAA2E, + 0xAA31, 0xAA32, 0xAA35, 0xAA36, 0xAA43, 0xAA4C, 0xAA7C, 0xAAB0, + 0xAAB2, 0xAAB3, 0xAAB4, 0xAAB7, 0xAAB8, 0xAABE, 0xAABF, 0xAAC1, + 0xAAEC, 0xAAED, 0xAAF6, 0xABE5, 0xABE8, 0xABED, 0xFB1E, 0xFE00, + 0xFE01, 0xFE02, 0xFE03, 0xFE04, 0xFE05, 0xFE06, 0xFE07, 0xFE08, + 0xFE09, 0xFE0A, 0xFE0B, 0xFE0C, 0xFE0D, 0xFE0E, 0xFE0F, 0xFE20, + 0xFE21, 0xFE22, 0xFE23, 0xFE24, 0xFE25, 0xFE26, 0xFE27, 0xFE28, + 0xFE29, 0xFE2A, 0xFE2B, 0xFE2C, 0xFE2D, 0xFE2E, 0xFE2F, 0x101FD, + 0x102E0, 0x10376, 0x10377, 0x10378, 0x10379, 0x1037A, 0x10A01, 0x10A02, + 0x10A03, 0x10A05, 0x10A06, 0x10A0C, 0x10A0D, 0x10A0E, 0x10A0F, 0x10A38, + 0x10A39, 0x10A3A, 0x10A3F, 0x10AE5, 0x10AE6, 0x10D24, 0x10D25, 0x10D26, + 0x10D27, 0x10EAB, 0x10EAC, 0x10F46, 0x10F47, 0x10F48, 0x10F49, 0x10F4A, + 0x10F4B, 0x10F4C, 0x10F4D, 0x10F4E, 0x10F4F, 0x10F50, 0x11001, 0x11038, + 0x11039, 0x1103A, 0x1103B, 0x1103C, 0x1103D, 0x1103E, 0x1103F, 0x11040, + 0x11041, 0x11042, 0x11043, 0x11044, 0x11045, 0x11046, 0x1107F, 0x11080, + 0x11081, 0x110B3, 0x110B4, 0x110B5, 0x110B6, 0x110B9, 0x110BA, 0x11100, + 0x11101, 0x11102, 0x11127, 0x11128, 0x11129, 0x1112A, 0x1112B, 0x1112D, + 0x1112E, 0x1112F, 0x11130, 0x11131, 0x11132, 0x11133, 0x11134, 0x11173, + 0x11180, 0x11181, 0x111B6, 0x111B7, 0x111B8, 0x111B9, 0x111BA, 0x111BB, + 0x111BC, 0x111BD, 0x111BE, 0x111C9, 0x111CA, 0x111CB, 0x111CC, 0x111CF, + 0x1122F, 0x11230, 0x11231, 0x11234, 0x11236, 0x11237, 0x1123E, 0x112DF, + 0x112E3, 0x112E4, 0x112E5, 0x112E6, 0x112E7, 0x112E8, 0x112E9, 0x112EA, + 0x11300, 0x11301, 0x1133B, 0x1133C, 0x11340, 0x11366, 0x11367, 0x11368, + 0x11369, 0x1136A, 0x1136B, 0x1136C, 0x11370, 0x11371, 0x11372, 0x11373, + 0x11374, 0x11438, 0x11439, 0x1143A, 0x1143B, 0x1143C, 0x1143D, 0x1143E, + 0x1143F, 0x11442, 0x11443, 0x11444, 0x11446, 0x1145E, 0x114B3, 0x114B4, + 0x114B5, 0x114B6, 0x114B7, 0x114B8, 0x114BA, 0x114BF, 0x114C0, 0x114C2, + 0x114C3, 0x115B2, 0x115B3, 0x115B4, 0x115B5, 0x115BC, 0x115BD, 0x115BF, + 0x115C0, 0x115DC, 0x115DD, 0x11633, 0x11634, 0x11635, 0x11636, 0x11637, + 0x11638, 0x11639, 0x1163A, 0x1163D, 0x1163F, 0x11640, 0x116AB, 0x116AD, + 0x116B0, 0x116B1, 0x116B2, 0x116B3, 0x116B4, 0x116B5, 0x116B7, 0x1171D, + 0x1171E, 0x1171F, 0x11722, 0x11723, 0x11724, 0x11725, 0x11727, 0x11728, + 0x11729, 0x1172A, 0x1172B, 0x1182F, 0x11830, 0x11831, 0x11832, 0x11833, + 0x11834, 0x11835, 0x11836, 0x11837, 0x11839, 0x1183A, 0x1193B, 0x1193C, + 0x1193E, 0x11943, 0x119D4, 0x119D5, 0x119D6, 0x119D7, 0x119DA, 0x119DB, + 0x119E0, 0x11A01, 0x11A02, 0x11A03, 0x11A04, 0x11A05, 0x11A06, 0x11A07, + 0x11A08, 0x11A09, 0x11A0A, 0x11A33, 0x11A34, 0x11A35, 0x11A36, 0x11A37, + 0x11A38, 0x11A3B, 0x11A3C, 0x11A3D, 0x11A3E, 0x11A47, 0x11A51, 0x11A52, + 0x11A53, 0x11A54, 0x11A55, 0x11A56, 0x11A59, 0x11A5A, 0x11A5B, 0x11A8A, + 0x11A8B, 0x11A8C, 0x11A8D, 0x11A8E, 0x11A8F, 0x11A90, 0x11A91, 0x11A92, + 0x11A93, 0x11A94, 0x11A95, 0x11A96, 0x11A98, 0x11A99, 0x11C30, 0x11C31, + 0x11C32, 0x11C33, 0x11C34, 0x11C35, 0x11C36, 0x11C38, 0x11C39, 0x11C3A, + 0x11C3B, 0x11C3C, 0x11C3D, 0x11C3F, 0x11C92, 0x11C93, 0x11C94, 0x11C95, + 0x11C96, 0x11C97, 0x11C98, 0x11C99, 0x11C9A, 0x11C9B, 0x11C9C, 0x11C9D, + 0x11C9E, 0x11C9F, 0x11CA0, 0x11CA1, 0x11CA2, 0x11CA3, 0x11CA4, 0x11CA5, + 0x11CA6, 0x11CA7, 0x11CAA, 0x11CAB, 0x11CAC, 0x11CAD, 0x11CAE, 0x11CAF, + 0x11CB0, 0x11CB2, 0x11CB3, 0x11CB5, 0x11CB6, 0x11D31, 0x11D32, 0x11D33, + 0x11D34, 0x11D35, 0x11D36, 0x11D3A, 0x11D3C, 0x11D3D, 0x11D3F, 0x11D40, + 0x11D41, 0x11D42, 0x11D43, 0x11D44, 0x11D45, 0x11D47, 0x11D90, 0x11D91, + 0x11D95, 0x11D97, 0x11EF3, 0x11EF4, 0x16AF0, 0x16AF1, 0x16AF2, 0x16AF3, + 0x16AF4, 0x16B30, 0x16B31, 0x16B32, 0x16B33, 0x16B34, 0x16B35, 0x16B36, + 0x16F4F, 0x16F8F, 0x16F90, 0x16F91, 0x16F92, 0x16FE4, 0x1BC9D, 0x1BC9E, + 0x1D167, 0x1D168, 0x1D169, 0x1D17B, 0x1D17C, 0x1D17D, 0x1D17E, 0x1D17F, + 0x1D180, 0x1D181, 0x1D182, 0x1D185, 0x1D186, 0x1D187, 0x1D188, 0x1D189, + 0x1D18A, 0x1D18B, 0x1D1AA, 0x1D1AB, 0x1D1AC, 0x1D1AD, 0x1D242, 0x1D243, + 0x1D244, 0x1DA00, 0x1DA01, 0x1DA02, 0x1DA03, 0x1DA04, 0x1DA05, 0x1DA06, + 0x1DA07, 0x1DA08, 0x1DA09, 0x1DA0A, 0x1DA0B, 0x1DA0C, 0x1DA0D, 0x1DA0E, + 0x1DA0F, 0x1DA10, 0x1DA11, 0x1DA12, 0x1DA13, 0x1DA14, 0x1DA15, 0x1DA16, + 0x1DA17, 0x1DA18, 0x1DA19, 0x1DA1A, 0x1DA1B, 0x1DA1C, 0x1DA1D, 0x1DA1E, + 0x1DA1F, 0x1DA20, 0x1DA21, 0x1DA22, 0x1DA23, 0x1DA24, 0x1DA25, 0x1DA26, + 0x1DA27, 0x1DA28, 0x1DA29, 0x1DA2A, 0x1DA2B, 0x1DA2C, 0x1DA2D, 0x1DA2E, + 0x1DA2F, 0x1DA30, 0x1DA31, 0x1DA32, 0x1DA33, 0x1DA34, 0x1DA35, 0x1DA36, + 0x1DA3B, 0x1DA3C, 0x1DA3D, 0x1DA3E, 0x1DA3F, 0x1DA40, 0x1DA41, 0x1DA42, + 0x1DA43, 0x1DA44, 0x1DA45, 0x1DA46, 0x1DA47, 0x1DA48, 0x1DA49, 0x1DA4A, + 0x1DA4B, 0x1DA4C, 0x1DA4D, 0x1DA4E, 0x1DA4F, 0x1DA50, 0x1DA51, 0x1DA52, + 0x1DA53, 0x1DA54, 0x1DA55, 0x1DA56, 0x1DA57, 0x1DA58, 0x1DA59, 0x1DA5A, + 0x1DA5B, 0x1DA5C, 0x1DA5D, 0x1DA5E, 0x1DA5F, 0x1DA60, 0x1DA61, 0x1DA62, + 0x1DA63, 0x1DA64, 0x1DA65, 0x1DA66, 0x1DA67, 0x1DA68, 0x1DA69, 0x1DA6A, + 0x1DA6B, 0x1DA6C, 0x1DA75, 0x1DA84, 0x1DA9B, 0x1DA9C, 0x1DA9D, 0x1DA9E, + 0x1DA9F, 0x1DAA1, 0x1DAA2, 0x1DAA3, 0x1DAA4, 0x1DAA5, 0x1DAA6, 0x1DAA7, + 0x1DAA8, 0x1DAA9, 0x1DAAA, 0x1DAAB, 0x1DAAC, 0x1DAAD, 0x1DAAE, 0x1DAAF, + 0x1E000, 0x1E001, 0x1E002, 0x1E003, 0x1E004, 0x1E005, 0x1E006, 0x1E008, + 0x1E009, 0x1E00A, 0x1E00B, 0x1E00C, 0x1E00D, 0x1E00E, 0x1E00F, 0x1E010, + 0x1E011, 0x1E012, 0x1E013, 0x1E014, 0x1E015, 0x1E016, 0x1E017, 0x1E018, + 0x1E01B, 0x1E01C, 0x1E01D, 0x1E01E, 0x1E01F, 0x1E020, 0x1E021, 0x1E023, + 0x1E024, 0x1E026, 0x1E027, 0x1E028, 0x1E029, 0x1E02A, 0x1E130, 0x1E131, + 0x1E132, 0x1E133, 0x1E134, 0x1E135, 0x1E136, 0x1E2EC, 0x1E2ED, 0x1E2EE, + 0x1E2EF, 0x1E8D0, 0x1E8D1, 0x1E8D2, 0x1E8D3, 0x1E8D4, 0x1E8D5, 0x1E8D6, + 0x1E944, 0x1E945, 0x1E946, 0x1E947, 0x1E948, 0x1E949, 0x1E94A, 0xE0100, + 0xE0101, 0xE0102, 0xE0103, 0xE0104, 0xE0105, 0xE0106, 0xE0107, 0xE0108, + 0xE0109, 0xE010A, 0xE010B, 0xE010C, 0xE010D, 0xE010E, 0xE010F, 0xE0110, + 0xE0111, 0xE0112, 0xE0113, 0xE0114, 0xE0115, 0xE0116, 0xE0117, 0xE0118, + 0xE0119, 0xE011A, 0xE011B, 0xE011C, 0xE011D, 0xE011E, 0xE011F, 0xE0120, + 0xE0121, 0xE0122, 0xE0123, 0xE0124, 0xE0125, 0xE0126, 0xE0127, 0xE0128, + 0xE0129, 0xE012A, 0xE012B, 0xE012C, 0xE012D, 0xE012E, 0xE012F, 0xE0130, + 0xE0131, 0xE0132, 0xE0133, 0xE0134, 0xE0135, 0xE0136, 0xE0137, 0xE0138, + 0xE0139, 0xE013A, 0xE013B, 0xE013C, 0xE013D, 0xE013E, 0xE013F, 0xE0140, + 0xE0141, 0xE0142, 0xE0143, 0xE0144, 0xE0145, 0xE0146, 0xE0147, 0xE0148, + 0xE0149, 0xE014A, 0xE014B, 0xE014C, 0xE014D, 0xE014E, 0xE014F, 0xE0150, + 0xE0151, 0xE0152, 0xE0153, 0xE0154, 0xE0155, 0xE0156, 0xE0157, 0xE0158, + 0xE0159, 0xE015A, 0xE015B, 0xE015C, 0xE015D, 0xE015E, 0xE015F, 0xE0160, + 0xE0161, 0xE0162, 0xE0163, 0xE0164, 0xE0165, 0xE0166, 0xE0167, 0xE0168, + 0xE0169, 0xE016A, 0xE016B, 0xE016C, 0xE016D, 0xE016E, 0xE016F, 0xE0170, + 0xE0171, 0xE0172, 0xE0173, 0xE0174, 0xE0175, 0xE0176, 0xE0177, 0xE0178, + 0xE0179, 0xE017A, 0xE017B, 0xE017C, 0xE017D, 0xE017E, 0xE017F, 0xE0180, + 0xE0181, 0xE0182, 0xE0183, 0xE0184, 0xE0185, 0xE0186, 0xE0187, 0xE0188, + 0xE0189, 0xE018A, 0xE018B, 0xE018C, 0xE018D, 0xE018E, 0xE018F, 0xE0190, + 0xE0191, 0xE0192, 0xE0193, 0xE0194, 0xE0195, 0xE0196, 0xE0197, 0xE0198, + 0xE0199, 0xE019A, 0xE019B, 0xE019C, 0xE019D, 0xE019E, 0xE019F, 0xE01A0, + 0xE01A1, 0xE01A2, 0xE01A3, 0xE01A4, 0xE01A5, 0xE01A6, 0xE01A7, 0xE01A8, + 0xE01A9, 0xE01AA, 0xE01AB, 0xE01AC, 0xE01AD, 0xE01AE, 0xE01AF, 0xE01B0, + 0xE01B1, 0xE01B2, 0xE01B3, 0xE01B4, 0xE01B5, 0xE01B6, 0xE01B7, 0xE01B8, + 0xE01B9, 0xE01BA, 0xE01BB, 0xE01BC, 0xE01BD, 0xE01BE, 0xE01BF, 0xE01C0, + 0xE01C1, 0xE01C2, 0xE01C3, 0xE01C4, 0xE01C5, 0xE01C6, 0xE01C7, 0xE01C8, + 0xE01C9, 0xE01CA, 0xE01CB, 0xE01CC, 0xE01CD, 0xE01CE, 0xE01CF, 0xE01D0, + 0xE01D1, 0xE01D2, 0xE01D3, 0xE01D4, 0xE01D5, 0xE01D6, 0xE01D7, 0xE01D8, + 0xE01D9, 0xE01DA, 0xE01DB, 0xE01DC, 0xE01DD, 0xE01DE, 0xE01DF, 0xE01E0, + 0xE01E1, 0xE01E2, 0xE01E3, 0xE01E4, 0xE01E5, 0xE01E6, 0xE01E7, 0xE01E8, + 0xE01E9, 0xE01EA, 0xE01EB, 0xE01EC, 0xE01ED, 0xE01EE, 0xE01EF, +}; + +static unsigned long combiningCharTableSize = sizeof(combiningCharTable) / sizeof(combiningCharTable[0]); + +/* Check if the code is a wide character + */ +static int isWideChar(unsigned long cp) { + size_t i; + for (i = 0; i < wideCharTableSize; i++) + if (wideCharTable[i][0] <= cp && cp <= wideCharTable[i][1]) return 1; + return 0; +} + +/* Check if the code is a combining character + */ +static int isCombiningChar(unsigned long cp) { + size_t i; + for (i = 0; i < combiningCharTableSize; i++) + if (combiningCharTable[i] == cp) return 1; + return 0; +} + +/* Get length of previous UTF8 character + */ +static size_t prevUtf8CharLen(const char* buf, int pos) { + int end = pos--; + while (pos >= 0 && ((unsigned char)buf[pos] & 0xC0) == 0x80) + pos--; + return end - pos; +} + +/* Convert UTF8 to Unicode code point + */ +static size_t utf8BytesToCodePoint(const char* buf, size_t len, int* cp) { + if (len) { + unsigned char byte = buf[0]; + if ((byte & 0x80) == 0) { + *cp = byte; + return 1; + } else if ((byte & 0xE0) == 0xC0) { + if (len >= 2) { + *cp = (((unsigned long)(buf[0] & 0x1F)) << 6) | + ((unsigned long)(buf[1] & 0x3F)); + return 2; + } + } else if ((byte & 0xF0) == 0xE0) { + if (len >= 3) { + *cp = (((unsigned long)(buf[0] & 0x0F)) << 12) | + (((unsigned long)(buf[1] & 0x3F)) << 6) | + ((unsigned long)(buf[2] & 0x3F)); + return 3; + } + } else if ((byte & 0xF8) == 0xF0) { + if (len >= 4) { + *cp = (((unsigned long)(buf[0] & 0x07)) << 18) | + (((unsigned long)(buf[1] & 0x3F)) << 12) | + (((unsigned long)(buf[2] & 0x3F)) << 6) | + ((unsigned long)(buf[3] & 0x3F)); + return 4; + } + } + } + return 0; +} + +/* Get length of next grapheme + */ +size_t linenoiseUtf8NextCharLen(const char* buf, size_t buf_len, size_t pos, size_t *col_len) { + size_t beg = pos; + int cp; + size_t len = utf8BytesToCodePoint(buf + pos, buf_len - pos, &cp); + if (isCombiningChar(cp)) { + /* NOTREACHED */ + return 0; + } + if (col_len != NULL) *col_len = isWideChar(cp) ? 2 : 1; + pos += len; + while (pos < buf_len) { + int point; + len = utf8BytesToCodePoint(buf + pos, buf_len - pos, &point); + if (!isCombiningChar(point)) return pos - beg; + pos += len; + } + return pos - beg; +} + +/* Get length of previous grapheme + */ +size_t linenoiseUtf8PrevCharLen(const char* buf, size_t buf_len, size_t pos, size_t *col_len) { + UNUSED(buf_len); + size_t end = pos; + while (pos > 0) { + size_t len = prevUtf8CharLen(buf, pos); + pos -= len; + int cp; + utf8BytesToCodePoint(buf + pos, len, &cp); + if (!isCombiningChar(cp)) { + if (col_len != NULL) *col_len = isWideChar(cp) ? 2 : 1; + return end - pos; + } + } + /* NOTREACHED */ + return 0; +} + +/* Read a Unicode from file. + */ +size_t linenoiseUtf8ReadCode(int fd, char* buf, size_t buf_len, int* cp) { + if (buf_len < 1) return -1; + size_t nread = read(fd,&buf[0],1); + if (nread <= 0) return nread; + + unsigned char byte = buf[0]; + if ((byte & 0x80) == 0) { + ; + } else if ((byte & 0xE0) == 0xC0) { + if (buf_len < 2) return -1; + nread = read(fd,&buf[1],1); + if (nread <= 0) return nread; + } else if ((byte & 0xF0) == 0xE0) { + if (buf_len < 3) return -1; + nread = read(fd,&buf[1],2); + if (nread <= 0) return nread; + } else if ((byte & 0xF8) == 0xF0) { + if (buf_len < 3) return -1; + nread = read(fd,&buf[1],3); + if (nread <= 0) return nread; + } else { + return -1; + } + + return utf8BytesToCodePoint(buf, buf_len, cp); +} diff --git a/src/cli/encodings/utf8.h b/src/cli/encodings/utf8.h new file mode 100755 index 00000000..d401bc86 --- /dev/null +++ b/src/cli/encodings/utf8.h @@ -0,0 +1,55 @@ +/* encodings/utf8.h -- VERSION 1.0 + * + * Guerrilla line editing library against the idea that a line editing lib + * needs to be 20,000 lines of C code. + * + * See linenoise.c for more information. + * + * ------------------------------------------------------------------------ + * + * Copyright (c) 2010-2014, Salvatore Sanfilippo + * Copyright (c) 2010-2013, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __LINENOISE_ENCODINGS_UTF8_H +#define __LINENOISE_ENCODINGS_UTF8_H + +#ifdef __cplusplus +extern "C" { +#endif + +size_t linenoiseUtf8PrevCharLen(const char* buf, size_t buf_len, size_t pos, size_t *col_len); +size_t linenoiseUtf8NextCharLen(const char* buf, size_t buf_len, size_t pos, size_t *col_len); +size_t linenoiseUtf8ReadCode(int fd, char* buf, size_t buf_len, int* cp); + +#ifdef __cplusplus +} +#endif + +#endif /* __LINENOISE_ENCODINGS_UTF8_H */ + diff --git a/src/cli/linenoise.c b/src/cli/linenoise.c old mode 100644 new mode 100755 index d4c0f169..b13919b0 --- a/src/cli/linenoise.c +++ b/src/cli/linenoise.c @@ -120,12 +120,14 @@ #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 #define LINENOISE_MAX_LINE 4096 +#define UNUSED(x) (void)(x) static char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; static linenoiseCompletionCallback *completionCallback = NULL; static linenoiseHintsCallback *hintsCallback = NULL; static linenoiseFreeHintsCallback *freeHintsCallback = NULL; static struct termios orig_termios; /* In order to restore at exit.*/ +static int maskmode = 0; /* Show "***" instead of input. For passwords. */ static int rawmode = 0; /* For atexit() function to check if restore is needed*/ static int mlmode = 0; /* Multi line mode. Default is single line. */ static int atexit_registered = 0; /* Register atexit just 1 time. */ @@ -144,7 +146,7 @@ struct linenoiseState { const char *prompt; /* Prompt to display. */ size_t plen; /* Prompt length. */ size_t pos; /* Current cursor position. */ - size_t oldpos; /* Previous refresh cursor position. */ + size_t oldcolpos; /* Previous refresh cursor column position. */ size_t len; /* Current edited line length. */ size_t cols; /* Number of columns in terminal. */ size_t maxrows; /* Maximum num of rows used so far (multiline mode) */ @@ -152,33 +154,145 @@ struct linenoiseState { }; enum KEY_ACTION{ - KEY_NULL = 0, /* NULL */ - CTRL_A = 1, /* Ctrl+a */ - CTRL_B = 2, /* Ctrl-b */ - CTRL_C = 3, /* Ctrl-c */ - CTRL_D = 4, /* Ctrl-d */ - CTRL_E = 5, /* Ctrl-e */ - CTRL_F = 6, /* Ctrl-f */ - CTRL_H = 8, /* Ctrl-h */ - TAB = 9, /* Tab */ - CTRL_K = 11, /* Ctrl+k */ - CTRL_L = 12, /* Ctrl+l */ - ENTER = 13, /* Enter */ - CTRL_N = 14, /* Ctrl-n */ - CTRL_P = 16, /* Ctrl-p */ - CTRL_T = 20, /* Ctrl-t */ - CTRL_U = 21, /* Ctrl+u */ - CTRL_W = 23, /* Ctrl+w */ - ESC = 27, /* Escape */ - BACKSPACE = 127 /* Backspace */ + KEY_NULL = 0, /* NULL */ + CTRL_A = 1, /* Ctrl+a */ + CTRL_B = 2, /* Ctrl-b */ + CTRL_C = 3, /* Ctrl-c */ + CTRL_D = 4, /* Ctrl-d */ + CTRL_E = 5, /* Ctrl-e */ + CTRL_F = 6, /* Ctrl-f */ + CTRL_H = 8, /* Ctrl-h */ + TAB = 9, /* Tab */ + CTRL_K = 11, /* Ctrl+k */ + CTRL_L = 12, /* Ctrl+l */ + ENTER = 13, /* Enter */ + CTRL_N = 14, /* Ctrl-n */ + CTRL_P = 16, /* Ctrl-p */ + CTRL_T = 20, /* Ctrl-t */ + CTRL_U = 21, /* Ctrl+u */ + CTRL_W = 23, /* Ctrl+w */ + ESC = 27, /* Escape */ + BACKSPACE = 127 /* Backspace */ }; static void linenoiseAtExit(void); int linenoiseHistoryAdd(const char *line); static void refreshLine(struct linenoiseState *l); +/* Debugging macro. */ +#if 0 +FILE *lndebug_fp = NULL; +#define lndebug(...) \ + do { \ + if (lndebug_fp == NULL) { \ + lndebug_fp = fopen("/tmp/lndebug.txt","a"); \ + fprintf(lndebug_fp, \ + "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \ + (int)l->len,(int)l->pos,(int)l->oldcolpos,plen,rows,rpos, \ + (int)l->maxrows,old_rows); \ + } \ + fprintf(lndebug_fp, ", " __VA_ARGS__); \ + fflush(lndebug_fp); \ + } while (0) +#else +#define lndebug(fmt, ...) +#endif + +/* ========================== Encoding functions ============================= */ + +/* Get byte length and column length of the previous character */ +static size_t defaultPrevCharLen(const char *buf, size_t buf_len, size_t pos, size_t *col_len) { + UNUSED(buf); UNUSED(buf_len); UNUSED(pos); + if (col_len != NULL) *col_len = 1; + return 1; +} + +/* Get byte length and column length of the next character */ +static size_t defaultNextCharLen(const char *buf, size_t buf_len, size_t pos, size_t *col_len) { + UNUSED(buf); UNUSED(buf_len); UNUSED(pos); + if (col_len != NULL) *col_len = 1; + return 1; +} + +/* Read bytes of the next character */ +static size_t defaultReadCode(int fd, char *buf, size_t buf_len, int* c) { + if (buf_len < 1) return -1; + int nread = read(fd,&buf[0],1); + if (nread == 1) *c = buf[0]; + return nread; +} + +/* Set default encoding functions */ +static linenoisePrevCharLen *prevCharLen = defaultPrevCharLen; +static linenoiseNextCharLen *nextCharLen = defaultNextCharLen; +static linenoiseReadCode *readCode = defaultReadCode; + +/* Set used defined encoding functions */ +void linenoiseSetEncodingFunctions( + linenoisePrevCharLen *prevCharLenFunc, + linenoiseNextCharLen *nextCharLenFunc, + linenoiseReadCode *readCodeFunc) { + prevCharLen = prevCharLenFunc; + nextCharLen = nextCharLenFunc; + readCode = readCodeFunc; +} + +/* Get column length from begining of buffer to current byte position */ +static size_t columnPos(const char *buf, size_t buf_len, size_t pos) { + size_t ret = 0; + size_t off = 0; + while (off < pos) { + size_t col_len; + size_t len = nextCharLen(buf,buf_len,off,&col_len); + off += len; + ret += col_len; + } + return ret; +} + +/* Get column length from begining of buffer to current byte position for multiline mode*/ +static size_t columnPosForMultiLine(const char *buf, size_t buf_len, size_t pos, size_t cols, size_t ini_pos) { + size_t ret = 0; + size_t colwid = ini_pos; + + size_t off = 0; + while (off < buf_len) { + size_t col_len; + size_t len = nextCharLen(buf,buf_len,off,&col_len); + + int dif = (int)(colwid + col_len) - (int)cols; + if (dif > 0) { + ret += dif; + colwid = col_len; + } else if (dif == 0) { + colwid = 0; + } else { + colwid += col_len; + } + + if (off >= pos) break; + off += len; + ret += col_len; + } + + return ret; +} + /* ======================= Low level terminal handling ====================== */ +/* Enable "mask mode". When it is enabled, instead of the input that + * the user is typing, the terminal will just display a corresponding + * number of asterisks, like "****". This is useful for passwords and other + * secrets that should not be displayed. */ +void linenoiseMaskModeEnable(void) { + maskmode = 1; +} + +/* Disable mask mode. */ +void linenoiseMaskModeDisable(void) { + maskmode = 0; +} + /* Set if to use or not the multi line mode. */ void linenoiseSetMultiLine(int ml) { mlmode = ml; @@ -227,7 +341,7 @@ static int enableRawMode(int fd) { rawmode = 1; return 0; - fatal: +fatal: errno = ENOTTY; return -1; } @@ -294,7 +408,7 @@ static int getColumns(int ifd, int ofd) { return ws.ws_col; } - failed: +failed: return 80; } @@ -329,10 +443,10 @@ static void freeCompletions(linenoiseCompletions *lc) { * * The state of the editing is encapsulated into the pointed linenoiseState * structure as described in the structure definition. */ -static int completeLine(struct linenoiseState *ls) { +static int completeLine(struct linenoiseState *ls, char *cbuf, size_t cbuf_len, int *c) { linenoiseCompletions lc = { 0, NULL }; - int nread, nwritten; - char c = 0; + int nread = 0, nwritten; + *c = 0; completionCallback(ls->buf,&lc); if (lc.len == 0) { @@ -355,13 +469,14 @@ static int completeLine(struct linenoiseState *ls) { refreshLine(ls); } - nread = read(ls->ifd,&c,1); + nread = readCode(ls->ifd,cbuf,cbuf_len,c); if (nread <= 0) { freeCompletions(&lc); - return -1; + *c = -1; + return nread; } - switch(c) { + switch(*c) { case 9: /* tab */ i = (i+1) % (lc.len+1); if (i == lc.len) linenoiseBeep(); @@ -384,7 +499,7 @@ static int completeLine(struct linenoiseState *ls) { } freeCompletions(&lc); - return c; /* Return last read character */ + return nread; } /* Register a callback function to be called for tab-completion. */ @@ -455,14 +570,15 @@ static void abFree(struct abuf *ab) { /* Helper of refreshSingleLine() and refreshMultiLine() to show hints * to the right of the prompt. */ -void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) { +void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int pcollen) { char seq[64]; - if (hintsCallback && plen+l->len < l->cols) { + size_t collen = pcollen+columnPos(l->buf,l->len,l->len); + if (hintsCallback && collen < l->cols) { int color = -1, bold = 0; char *hint = hintsCallback(l->buf,&color,&bold); if (hint) { int hintlen = strlen(hint); - int hintmaxlen = l->cols-(plen+l->len); + int hintmaxlen = l->cols-collen; if (hintlen > hintmaxlen) hintlen = hintmaxlen; if (bold == 1 && color == -1) color = 37; if (color != -1 || bold != 0) @@ -479,26 +595,62 @@ void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) { } } +/* Check if text is an ANSI escape sequence + */ +static int isAnsiEscape(const char *buf, size_t buf_len, size_t* len) { + if (buf_len > 2 && !memcmp("\033[", buf, 2)) { + size_t off = 2; + while (off < buf_len) { + switch (buf[off++]) { + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': case 'G': case 'H': case 'J': case 'K': + case 'S': case 'T': case 'f': case 'm': + *len = off; + return 1; + } + } + } + return 0; +} + +/* Get column length of prompt text + */ +static size_t promptTextColumnLen(const char *prompt, size_t plen) { + char buf[LINENOISE_MAX_LINE]; + size_t buf_len = 0; + size_t off = 0; + while (off < plen) { + size_t len; + if (isAnsiEscape(prompt + off, plen - off, &len)) { + off += len; + continue; + } + buf[buf_len++] = prompt[off++]; + } + return columnPos(buf,buf_len,buf_len); +} + /* Single line low level line refresh. * * Rewrite the currently edited line accordingly to the buffer content, * cursor position, and number of columns of the terminal. */ static void refreshSingleLine(struct linenoiseState *l) { char seq[64]; - size_t plen = strlen(l->prompt); + size_t pcollen = promptTextColumnLen(l->prompt,strlen(l->prompt)); int fd = l->ofd; char *buf = l->buf; size_t len = l->len; size_t pos = l->pos; struct abuf ab; - while((plen+pos) >= l->cols) { - buf++; - len--; - pos--; + while((pcollen+columnPos(buf,len,pos)) >= l->cols) { + int chlen = nextCharLen(buf,len,0,NULL); + buf += chlen; + len -= chlen; + pos -= chlen; } - while (plen+len > l->cols) { - len--; + while (pcollen+columnPos(buf,len,len) > l->cols) { + len -= prevCharLen(buf,len,len,NULL); } abInit(&ab); @@ -507,14 +659,18 @@ static void refreshSingleLine(struct linenoiseState *l) { abAppend(&ab,seq,strlen(seq)); /* Write the prompt and the current buffer content */ abAppend(&ab,l->prompt,strlen(l->prompt)); - abAppend(&ab,buf,len); + if (maskmode == 1) { + while (len--) abAppend(&ab,"*",1); + } else { + abAppend(&ab,buf,len); + } /* Show hits if any. */ - refreshShowHints(&ab,l,plen); + refreshShowHints(&ab,l,pcollen); /* Erase to right */ snprintf(seq,64,"\x1b[0K"); abAppend(&ab,seq,strlen(seq)); /* Move cursor to original position. */ - snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen)); + snprintf(seq,64,"\r\x1b[%dC", (int)(columnPos(buf,len,pos)+pcollen)); abAppend(&ab,seq,strlen(seq)); if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ abFree(&ab); @@ -526,9 +682,11 @@ static void refreshSingleLine(struct linenoiseState *l) { * cursor position, and number of columns of the terminal. */ static void refreshMultiLine(struct linenoiseState *l) { char seq[64]; - int plen = strlen(l->prompt); - int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */ - int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */ + size_t pcollen = promptTextColumnLen(l->prompt,strlen(l->prompt)); + int colpos = columnPosForMultiLine(l->buf, l->len, l->len, l->cols, pcollen); + int colpos2; /* cursor column position. */ + int rows = (pcollen+colpos+l->cols-1)/l->cols; /* rows used by current buf. */ + int rpos = (pcollen+l->oldcolpos+l->cols)/l->cols; /* cursor relative row. */ int rpos2; /* rpos after refresh. */ int col; /* colum position, zero-based. */ int old_rows = l->maxrows; @@ -542,33 +700,45 @@ static void refreshMultiLine(struct linenoiseState *l) { * going to the last row. */ abInit(&ab); if (old_rows-rpos > 0) { + lndebug("go down %d", old_rows-rpos); snprintf(seq,64,"\x1b[%dB", old_rows-rpos); abAppend(&ab,seq,strlen(seq)); } /* Now for every row clear it, go up. */ for (j = 0; j < old_rows-1; j++) { + lndebug("clear+up"); snprintf(seq,64,"\r\x1b[0K\x1b[1A"); abAppend(&ab,seq,strlen(seq)); } /* Clean the top line. */ + lndebug("clear"); snprintf(seq,64,"\r\x1b[0K"); abAppend(&ab,seq,strlen(seq)); /* Write the prompt and the current buffer content */ abAppend(&ab,l->prompt,strlen(l->prompt)); - abAppend(&ab,l->buf,l->len); + if (maskmode == 1) { + unsigned int i; + for (i = 0; i < l->len; i++) abAppend(&ab,"*",1); + } else { + abAppend(&ab,l->buf,l->len); + } /* Show hits if any. */ - refreshShowHints(&ab,l,plen); + refreshShowHints(&ab,l,pcollen); + + /* Get column length to cursor position */ + colpos2 = columnPosForMultiLine(l->buf,l->len,l->pos,l->cols,pcollen); /* If we are at the very end of the screen with our prompt, we need to * emit a newline and move the prompt to the first column. */ if (l->pos && l->pos == l->len && - (l->pos+plen) % l->cols == 0) + (colpos2+pcollen) % l->cols == 0) { + lndebug(""); abAppend(&ab,"\n",1); snprintf(seq,64,"\r"); abAppend(&ab,seq,strlen(seq)); @@ -577,23 +747,27 @@ static void refreshMultiLine(struct linenoiseState *l) { } /* Move cursor to right position. */ - rpos2 = (plen+l->pos+l->cols)/l->cols; /* current cursor relative row. */ + rpos2 = (pcollen+colpos2+l->cols)/l->cols; /* current cursor relative row. */ + lndebug("rpos2 %d", rpos2); /* Go up till we reach the expected positon. */ if (rows-rpos2 > 0) { + lndebug("go-up %d", rows-rpos2); snprintf(seq,64,"\x1b[%dA", rows-rpos2); abAppend(&ab,seq,strlen(seq)); } /* Set column. */ - col = (plen+(int)l->pos) % (int)l->cols; + col = (pcollen + colpos2) % l->cols; + lndebug("set col %d", 1+col); if (col) snprintf(seq,64,"\r\x1b[%dC", col); else snprintf(seq,64,"\r"); abAppend(&ab,seq,strlen(seq)); - l->oldpos = l->pos; + lndebug("\n"); + l->oldcolpos = colpos2; if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ abFree(&ab); @@ -611,51 +785,30 @@ static void refreshLine(struct linenoiseState *l) { /* Insert the character 'c' at cursor current position. * * On error writing to the terminal -1 is returned, otherwise 0. */ -int linenoiseEditInsert(struct linenoiseState *l, char c) { - if (l->len < l->buflen) { +int linenoiseEditInsert(struct linenoiseState *l, const char *cbuf, int clen) { + if (l->len+clen <= l->buflen) { if (l->len == l->pos) { - bool tab = false; - if (c == '\t') { - c = ' '; - for (int i = 0; i < 4; i++) { - l->buf[l->pos] = c; - l->pos++; - l->len++; - tab = true; - } - } else { - l->buf[l->pos] = c; - l->pos++; - l->len++; - } + memcpy(&l->buf[l->pos],cbuf,clen); + l->pos+=clen; + l->len+=clen;; l->buf[l->len] = '\0'; - - if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) { + if ((!mlmode && promptTextColumnLen(l->prompt,l->plen)+columnPos(l->buf,l->len,l->len) < l->cols && !hintsCallback)) { /* Avoid a full update of the line in the * trivial case. */ - if (tab) { - if (write(l->ofd, " ", 4) == -1) return -1; + if (maskmode == 1) { + static const char d = '*'; + if (write(l->ofd,&d,1) == -1) return -1; } else { - if (write(l->ofd, &c, 1) == -1) return -1; + if (write(l->ofd,cbuf,clen) == -1) return -1; } } else { refreshLine(l); } } else { - if (c == '\t') { - memmove(l->buf+l->pos+4,l->buf+l->pos,l->len-l->pos); - for (int i = 0; i < 4; i++) { - l->buf[l->pos] = ' '; - l->len++; - l->pos++; - } - } else { - memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos); - l->buf[l->pos] = c; - l->len++; - l->pos++; - } - + memmove(l->buf+l->pos+clen,l->buf+l->pos,l->len-l->pos); + memcpy(&l->buf[l->pos],cbuf,clen); + l->pos+=clen; + l->len+=clen; l->buf[l->len] = '\0'; refreshLine(l); } @@ -666,7 +819,7 @@ int linenoiseEditInsert(struct linenoiseState *l, char c) { /* Move cursor on the left. */ void linenoiseEditMoveLeft(struct linenoiseState *l) { if (l->pos > 0) { - l->pos--; + l->pos -= prevCharLen(l->buf,l->len,l->pos,NULL); refreshLine(l); } } @@ -674,7 +827,7 @@ void linenoiseEditMoveLeft(struct linenoiseState *l) { /* Move cursor on the right. */ void linenoiseEditMoveRight(struct linenoiseState *l) { if (l->pos != l->len) { - l->pos++; + l->pos += nextCharLen(l->buf,l->len,l->pos,NULL); refreshLine(l); } } @@ -725,25 +878,9 @@ void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) { * position. Basically this is what happens with the "Delete" keyboard key. */ void linenoiseEditDelete(struct linenoiseState *l) { if (l->len > 0 && l->pos < l->len) { - bool tab = true; - if (l->len - l->pos > 4) { - for (int i = 0; i < 4; i++) { - if (l->buf[l->pos + i] != ' ') { - tab = false; - break; - } - } - } else { - tab = false; - } - - if (tab) { - memmove(l->buf + l->pos, l->buf + l->pos + 4, l->len - l->pos - 4); - l->len -= 4; - } else { - memmove(l->buf + l->pos, l->buf + l->pos + 1, l->len - l->pos - 1); - l->len--; - } + int chlen = nextCharLen(l->buf,l->len,l->pos,NULL); + memmove(l->buf+l->pos,l->buf+l->pos+chlen,l->len-l->pos-chlen); + l->len-=chlen; l->buf[l->len] = '\0'; refreshLine(l); } @@ -752,33 +889,16 @@ void linenoiseEditDelete(struct linenoiseState *l) { /* Backspace implementation. */ void linenoiseEditBackspace(struct linenoiseState *l) { if (l->pos > 0 && l->len > 0) { - bool tab = true; - if (l->pos > 3) { - for (int i = 0; i < 4; i++) { - if (l->buf[l->pos - i - 1] != ' ') { - tab = false; - break; - } - } - } else { - tab = false; - } - - if (tab) { - memmove(l->buf + l->pos - 4, l->buf + l->pos, l->len - l->pos); - l->pos -= 4; - l->len -= 4; - } else { - memmove(l->buf + l->pos - 1, l->buf + l->pos, l->len - l->pos); - l->pos--; - l->len--; - } + int chlen = prevCharLen(l->buf,l->len,l->pos,NULL); + memmove(l->buf+l->pos-chlen,l->buf+l->pos,l->len-l->pos); + l->pos-=chlen; + l->len-=chlen; l->buf[l->len] = '\0'; refreshLine(l); } } -/* Delete the previous word, maintaining the cursor at the start of the +/* Delete the previosu word, maintaining the cursor at the start of the * current word. */ void linenoiseEditDeletePrevWord(struct linenoiseState *l) { size_t old_pos = l->pos; @@ -814,7 +934,7 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, l.buflen = buflen; l.prompt = prompt; l.plen = strlen(prompt); - l.oldpos = l.pos = 0; + l.oldcolpos = l.pos = 0; l.len = 0; l.cols = getColumns(stdin_fd, stdout_fd); l.maxrows = 0; @@ -830,18 +950,19 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, if (write(l.ofd,prompt,l.plen) == -1) return -1; while(1) { - char c; + int c; + char cbuf[32]; // large enough for any encoding? int nread; char seq[3]; - nread = read(l.ifd,&c,1); + nread = readCode(l.ifd,cbuf,sizeof(cbuf),&c); if (nread <= 0) return l.len; /* Only autocomplete when the callback is set. It returns < 0 when * there was an error reading from fd. Otherwise it will return the * character that should be handled next. */ if (c == 9 && completionCallback != NULL) { - c = completeLine(&l); + nread = completeLine(&l,cbuf,sizeof(cbuf),&c); /* Return on errors */ if (c < 0) return l.len; /* Read next character when 0 */ @@ -849,138 +970,138 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, } switch(c) { - case ENTER: /* enter */ + case ENTER: /* enter */ + history_len--; + free(history[history_len]); + if (mlmode) linenoiseEditMoveEnd(&l); + if (hintsCallback) { + /* Force a refresh without hints to leave the previous + * line as the user typed it after a newline. */ + linenoiseHintsCallback *hc = hintsCallback; + hintsCallback = NULL; + refreshLine(&l); + hintsCallback = hc; + } + return (int)l.len; + case CTRL_C: /* ctrl-c */ + errno = EAGAIN; + return -1; + case BACKSPACE: /* backspace */ + case 8: /* ctrl-h */ + linenoiseEditBackspace(&l); + break; + case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the + line is empty, act as end-of-file. */ + if (l.len > 0) { + linenoiseEditDelete(&l); + } else { history_len--; free(history[history_len]); - if (mlmode) linenoiseEditMoveEnd(&l); - if (hintsCallback) { - /* Force a refresh without hints to leave the previous - * line as the user typed it after a newline. */ - linenoiseHintsCallback *hc = hintsCallback; - hintsCallback = NULL; - refreshLine(&l); - hintsCallback = hc; - } - return (int)l.len; - case CTRL_C: /* ctrl-c */ - errno = EAGAIN; return -1; - case BACKSPACE: /* backspace */ - case 8: /* ctrl-h */ - linenoiseEditBackspace(&l); - break; - case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the - line is empty, act as end-of-file. */ - if (l.len > 0) { - linenoiseEditDelete(&l); - } else { - history_len--; - free(history[history_len]); - return -1; - } - break; - case CTRL_T: /* ctrl-t, swaps current character with previous. */ - if (l.pos > 0 && l.pos < l.len) { - int aux = buf[l.pos-1]; - buf[l.pos-1] = buf[l.pos]; - buf[l.pos] = aux; - if (l.pos != l.len-1) l.pos++; - refreshLine(&l); - } - break; - case CTRL_B: /* ctrl-b */ - linenoiseEditMoveLeft(&l); - break; - case CTRL_F: /* ctrl-f */ - linenoiseEditMoveRight(&l); - break; - case CTRL_P: /* ctrl-p */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); - break; - case CTRL_N: /* ctrl-n */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); - break; - case ESC: /* escape sequence */ - /* Read the next two bytes representing the escape sequence. - * Use two calls to handle slow terminals returning the two - * chars at different times. */ - if (read(l.ifd,seq,1) == -1) break; - if (read(l.ifd,seq+1,1) == -1) break; - - /* ESC [ sequences. */ - if (seq[0] == '[') { - if (seq[1] >= '0' && seq[1] <= '9') { - /* Extended escape, read additional byte. */ - if (read(l.ifd,seq+2,1) == -1) break; - if (seq[2] == '~') { - switch(seq[1]) { - case '3': /* Delete key. */ - linenoiseEditDelete(&l); - break; - } - } - } else { + } + break; + case CTRL_T: /* ctrl-t, swaps current character with previous. */ + if (l.pos > 0 && l.pos < l.len) { + int aux = buf[l.pos-1]; + buf[l.pos-1] = buf[l.pos]; + buf[l.pos] = aux; + if (l.pos != l.len-1) l.pos++; + refreshLine(&l); + } + break; + case CTRL_B: /* ctrl-b */ + linenoiseEditMoveLeft(&l); + break; + case CTRL_F: /* ctrl-f */ + linenoiseEditMoveRight(&l); + break; + case CTRL_P: /* ctrl-p */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); + break; + case CTRL_N: /* ctrl-n */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); + break; + case ESC: /* escape sequence */ + /* Read the next two bytes representing the escape sequence. + * Use two calls to handle slow terminals returning the two + * chars at different times. */ + if (read(l.ifd,seq,1) == -1) break; + if (read(l.ifd,seq+1,1) == -1) break; + + /* ESC [ sequences. */ + if (seq[0] == '[') { + if (seq[1] >= '0' && seq[1] <= '9') { + /* Extended escape, read additional byte. */ + if (read(l.ifd,seq+2,1) == -1) break; + if (seq[2] == '~') { switch(seq[1]) { - case 'A': /* Up */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); - break; - case 'B': /* Down */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); - break; - case 'C': /* Right */ - linenoiseEditMoveRight(&l); - break; - case 'D': /* Left */ - linenoiseEditMoveLeft(&l); - break; - case 'H': /* Home */ - linenoiseEditMoveHome(&l); - break; - case 'F': /* End*/ - linenoiseEditMoveEnd(&l); - break; + case '3': /* Delete key. */ + linenoiseEditDelete(&l); + break; } } - } - - /* ESC O sequences. */ - else if (seq[0] == 'O') { + } else { switch(seq[1]) { - case 'H': /* Home */ - linenoiseEditMoveHome(&l); - break; - case 'F': /* End*/ - linenoiseEditMoveEnd(&l); - break; + case 'A': /* Up */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); + break; + case 'B': /* Down */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); + break; + case 'C': /* Right */ + linenoiseEditMoveRight(&l); + break; + case 'D': /* Left */ + linenoiseEditMoveLeft(&l); + break; + case 'H': /* Home */ + linenoiseEditMoveHome(&l); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(&l); + break; } } - break; - default: - if (linenoiseEditInsert(&l,c)) return -1; - break; - case CTRL_U: /* Ctrl+u, delete the whole line. */ - buf[0] = '\0'; - l.pos = l.len = 0; - refreshLine(&l); - break; - case CTRL_K: /* Ctrl+k, delete from current to end of line. */ - buf[l.pos] = '\0'; - l.len = l.pos; - refreshLine(&l); - break; - case CTRL_A: /* Ctrl+a, go to the start of the line */ - linenoiseEditMoveHome(&l); - break; - case CTRL_E: /* ctrl+e, go to the end of the line */ - linenoiseEditMoveEnd(&l); - break; - case CTRL_L: /* ctrl+l, clear screen */ - linenoiseClearScreen(); - refreshLine(&l); - break; - case CTRL_W: /* ctrl+w, delete previous word */ - linenoiseEditDeletePrevWord(&l); - break; + } + + /* ESC O sequences. */ + else if (seq[0] == 'O') { + switch(seq[1]) { + case 'H': /* Home */ + linenoiseEditMoveHome(&l); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(&l); + break; + } + } + break; + default: + if (linenoiseEditInsert(&l,cbuf,nread)) return -1; + break; + case CTRL_U: /* Ctrl+u, delete the whole line. */ + buf[0] = '\0'; + l.pos = l.len = 0; + refreshLine(&l); + break; + case CTRL_K: /* Ctrl+k, delete from current to end of line. */ + buf[l.pos] = '\0'; + l.len = l.pos; + refreshLine(&l); + break; + case CTRL_A: /* Ctrl+a, go to the start of the line */ + linenoiseEditMoveHome(&l); + break; + case CTRL_E: /* ctrl+e, go to the end of the line */ + linenoiseEditMoveEnd(&l); + break; + case CTRL_L: /* ctrl+l, clear screen */ + linenoiseClearScreen(); + refreshLine(&l); + break; + case CTRL_W: /* ctrl+w, delete previous word */ + linenoiseEditDeletePrevWord(&l); + break; } } return l.len; @@ -993,7 +1114,7 @@ void linenoisePrintKeyCodes(void) { char quit[4]; printf("Linenoise key codes debugging mode.\n" - "Press keys to see scan codes. Type 'quit' at any time to exit.\n"); + "Press keys to see scan codes. Type 'quit' at any time to exit.\n"); if (enableRawMode(STDIN_FILENO) == -1) return; memset(quit,' ',4); while(1) { @@ -1007,7 +1128,7 @@ void linenoisePrintKeyCodes(void) { if (memcmp(quit,"quit",sizeof(quit)) == 0) break; printf("'%c' %02x (%d) (type quit to exit)\n", - isprint(c) ? c : '?', (int)c, (int)c); + isprint((int)c) ? c : '?', (int)c, (int)c); printf("\r"); /* Go left edge manually, we are in raw mode. */ fflush(stdout); } @@ -1234,4 +1355,4 @@ int linenoiseHistoryLoad(const char *filename) { fclose(fp); return 0; } -#endif +#endif \ No newline at end of file diff --git a/src/cli/linenoise.h b/src/cli/linenoise.h old mode 100644 new mode 100755 index 6f053b1f..ceca1eb2 --- a/src/cli/linenoise.h +++ b/src/cli/linenoise.h @@ -39,15 +39,13 @@ #ifndef __LINENOISE_H #define __LINENOISE_H -#include - #ifdef __cplusplus extern "C" { #endif typedef struct linenoiseCompletions { - size_t len; - char **cvec; + size_t len; + char **cvec; } linenoiseCompletions; typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); @@ -67,6 +65,17 @@ int linenoiseHistoryLoad(const char *filename); void linenoiseClearScreen(void); void linenoiseSetMultiLine(int ml); void linenoisePrintKeyCodes(void); +void linenoiseMaskModeEnable(void); +void linenoiseMaskModeDisable(void); + +typedef size_t (linenoisePrevCharLen)(const char *buf, size_t buf_len, size_t pos, size_t *col_len); +typedef size_t (linenoiseNextCharLen)(const char *buf, size_t buf_len, size_t pos, size_t *col_len); +typedef size_t (linenoiseReadCode)(int fd, char *buf, size_t buf_len, int* c); + +void linenoiseSetEncodingFunctions( + linenoisePrevCharLen *prevCharLenFunc, + linenoiseNextCharLen *nextCharLenFunc, + linenoiseReadCode *readCodeFunc); #ifdef __cplusplus } diff --git a/src/cli/main.c b/src/cli/main.c index 13228c4c..b12c1ff1 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -13,6 +13,7 @@ #ifndef DISABLE_LINENOISE #include "linenoise.h" +#include "encodings/utf8.h" static bool replCountBraces(char *line) { int leftBraces = 0; @@ -69,6 +70,12 @@ static void repl(DictuVM *vm) { char *line; #ifndef DISABLE_LINENOISE + + linenoiseSetEncodingFunctions( + linenoiseUtf8PrevCharLen, + linenoiseUtf8NextCharLen, + linenoiseUtf8ReadCode); + linenoiseHistoryLoad("history.txt"); while((line = linenoise(">>> ")) != NULL) { From 5fb8bee1cb8cc2f85e623b22e59d811d6e109e53 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Fri, 8 Jan 2021 19:40:58 +0000 Subject: [PATCH 21/41] Do not attempt to compile linenoise files if disabled. Also handle cntrl + D --- src/cli/CMakeLists.txt | 3 +++ src/cli/linenoise.c | 4 +--- src/cli/main.c | 5 +++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 67b11357..beec1c0c 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -3,6 +3,9 @@ set(DISABLE_LINENOISE OFF CACHE BOOL "Determines if the REPL uses linenoise. Lin SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}) if(DISABLE_LINENOISE) + list(FILTER sources EXCLUDE REGEX "linenoise.c") + list(FILTER headers EXCLUDE REGEX "linenoise.h") + add_compile_definitions(DISABLE_LINENOISE) endif() diff --git a/src/cli/linenoise.c b/src/cli/linenoise.c index b13919b0..eabe6ba6 100755 --- a/src/cli/linenoise.c +++ b/src/cli/linenoise.c @@ -103,7 +103,6 @@ * */ -#ifndef DISABLE_LINENOISE #include #include #include @@ -1354,5 +1353,4 @@ int linenoiseHistoryLoad(const char *filename) { } fclose(fp); return 0; -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/src/cli/main.c b/src/cli/main.c index b12c1ff1..10502004 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -145,6 +145,11 @@ static void repl(DictuVM *vm) { } } + if (line[0] == '\0') { + printf("\n"); + break; + } + dictuInterpret(vm, "repl", line); lineLength = 0; line[0] = '\0'; From e30c905abf1b512f1f2f019f8be81f4fb363bfbb Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Fri, 8 Jan 2021 19:57:15 +0000 Subject: [PATCH 22/41] Correctly handle new files in CMakeLists --- src/CMakeLists.txt | 4 ++-- src/cli/CMakeLists.txt | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dc6270d5..a0f3feea 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,8 +6,8 @@ set(libraries) set(INCLUDE_DIR include/) # Remove CLI files -list(FILTER sources EXCLUDE REGEX "(main|linenoise).c") -list(FILTER headers EXCLUDE REGEX "linenoise.h") +list(FILTER sources EXCLUDE REGEX "(main|linenoise|utf8).c") +list(FILTER headers EXCLUDE REGEX "(linenoise|utf8).h") find_library(SQLITE_LIB SQLite3) set(THREADS) diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index beec1c0c..e86031ae 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -1,10 +1,9 @@ -set(DICTU_CLI_SRC main.c linenoise.c linenoise.h) +set(DICTU_CLI_SRC main.c linenoise.c linenoise.h encodings/utf8.c encodings/utf8.h) set(DISABLE_LINENOISE OFF CACHE BOOL "Determines if the REPL uses linenoise. Linenoise requires termios.") SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}) if(DISABLE_LINENOISE) - list(FILTER sources EXCLUDE REGEX "linenoise.c") - list(FILTER headers EXCLUDE REGEX "linenoise.h") + list(FILTER DICTU_CLI_SRC EXCLUDE REGEX "(linenoise|utf8)\.(c|h)") add_compile_definitions(DISABLE_LINENOISE) endif() From e1449202c727b1ab6ade7554cc8a7005934ff5bb Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Sat, 9 Jan 2021 23:52:09 +0000 Subject: [PATCH 23/41] Handle scenario where webserver sends no response back correctly --- src/optionals/http.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/optionals/http.c b/src/optionals/http.c index 21cdfc68..30fd1343 100644 --- a/src/optionals/http.c +++ b/src/optionals/http.c @@ -101,7 +101,12 @@ static char *dictToPostArgs(ObjDict *dict) { static ObjDict *endRequest(DictuVM *vm, CURL *curl, Response response) { // Get status code curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response.statusCode); - ObjString *content = takeString(vm, response.res, response.len); + ObjString *content; + if (response.res != NULL) { + content = takeString(vm, response.res, response.len); + } else { + content = copyString(vm, "", 0); + } // Push to stack to avoid GC push(vm, OBJ_VAL(content)); From f1f9efd141098ac5cdafb93b9ce7ee67ae5e36c4 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Sun, 10 Jan 2021 02:17:51 +0000 Subject: [PATCH 24/41] Remove prefix operators and fix issue in REPL --- examples/bubbleSort.du | 2 +- .../chain-of-responsibility.du | 2 +- examples/design-patterns/observer.du | 2 +- examples/factorial.du | 2 +- examples/fizzBuzz.du | 2 +- examples/isPalindrome.du | 2 +- src/cli/main.c | 2 +- src/vm/value.h | 36 ------------------- tests/benchmarks/dict-methods/deepCopy.du | 2 +- tests/benchmarks/dict-methods/exists.du | 2 +- tests/benchmarks/dict-methods/remove.du | 4 +-- tests/benchmarks/dict-methods/shallowCopy.du | 2 +- tests/benchmarks/dictionaries.du | 6 ++-- tests/benchmarks/fib.du | 2 +- tests/benchmarks/for.du | 4 +-- tests/benchmarks/list-methods/contains.du | 2 +- tests/benchmarks/list-methods/deepCopy.du | 2 +- tests/benchmarks/list-methods/join.du | 2 +- tests/benchmarks/list-methods/pop.du | 4 +-- tests/benchmarks/list-methods/push.du | 2 +- tests/benchmarks/list-methods/shallowCopy.du | 2 +- tests/benchmarks/string-methods/contains.du | 2 +- tests/benchmarks/string-methods/endsWith.du | 2 +- tests/benchmarks/string-methods/find.du | 2 +- tests/benchmarks/string-methods/format.du | 2 +- tests/benchmarks/string-methods/leftStrip.du | 2 +- tests/benchmarks/string-methods/lower.du | 2 +- tests/benchmarks/string-methods/replace.du | 2 +- tests/benchmarks/string-methods/rightStrip.du | 2 +- tests/benchmarks/string-methods/split.du | 2 +- tests/benchmarks/string-methods/startsWith.du | 2 +- tests/benchmarks/string-methods/strip.du | 2 +- tests/benchmarks/string-methods/upper.du | 2 +- tests/benchmarks/stringEquality.du | 2 +- tests/dicts/len.du | 4 +-- tests/dicts/remove.du | 4 +-- tests/lists/len.du | 4 +-- tests/loops/break.du | 10 +++--- tests/loops/continue.du | 6 ++-- tests/loops/loop.du | 10 +++--- tests/path/listdir.du | 2 +- tests/random/random.du | 2 +- tests/random/range.du | 4 +-- tests/random/select.du | 4 +-- tests/strings/subscript.du | 2 +- 45 files changed, 64 insertions(+), 100 deletions(-) diff --git a/examples/bubbleSort.du b/examples/bubbleSort.du index 70dd08fb..9ada5010 100644 --- a/examples/bubbleSort.du +++ b/examples/bubbleSort.du @@ -1,7 +1,7 @@ def bubbleSort(list) { var sortedList = list.copy(); - for (var i = 0; i < sortedList.len(); ++i) { + for (var i = 0; i < sortedList.len(); i += 1) { for (var j = 0; j < sortedList.len() - 1; ++j) { if (sortedList[j] > sortedList[j+1]) { var temp = sortedList[j+1]; diff --git a/examples/design-patterns/chain-of-responsibility.du b/examples/design-patterns/chain-of-responsibility.du index b239fbb6..07497a4f 100644 --- a/examples/design-patterns/chain-of-responsibility.du +++ b/examples/design-patterns/chain-of-responsibility.du @@ -48,7 +48,7 @@ class DogHandler < BaseHandler { def businessLogic(handler) { var food = ["Nut", "Banana", "Coffee"]; - for (var i = 0; i < food.len(); ++i) { + for (var i = 0; i < food.len(); i += 1) { print("Who wants a: {}".format(food[i])); var response = handler.handle(food[i]); diff --git a/examples/design-patterns/observer.du b/examples/design-patterns/observer.du index 8b74e771..62ade812 100644 --- a/examples/design-patterns/observer.du +++ b/examples/design-patterns/observer.du @@ -25,7 +25,7 @@ class Publisher { * Notify all subscribed observers */ notify() { - for (var i = 0; i < this.observers.len(); ++i) { + for (var i = 0; i < this.observers.len(); i += 1) { this.observers[i].update(); } } diff --git a/examples/factorial.du b/examples/factorial.du index 9e727424..d1adf354 100644 --- a/examples/factorial.du +++ b/examples/factorial.du @@ -2,7 +2,7 @@ var amount = input("Enter a number: ").toNumber().unwrap(); var num = 1; if (amount > 0) { - for (var i = 1; i < amount + 1; ++i) { + for (var i = 1; i < amount + 1; i += 1) { num *= i; } diff --git a/examples/fizzBuzz.du b/examples/fizzBuzz.du index 1cea9be1..4cfe77c7 100644 --- a/examples/fizzBuzz.du +++ b/examples/fizzBuzz.du @@ -1,4 +1,4 @@ -for (var i = 1; i < 101; ++i) { +for (var i = 1; i < 101; i += 1) { if (i % 15 == 0) { print("FizzBuzz"); } else if (i % 3 == 0) { diff --git a/examples/isPalindrome.du b/examples/isPalindrome.du index f71a4d30..68725faa 100644 --- a/examples/isPalindrome.du +++ b/examples/isPalindrome.du @@ -1,6 +1,6 @@ def isPalindrome(arg) { var len = arg.len(); - for (var i = 0; i < len/2; ++i) { + for (var i = 0; i < len/2; i += 1) { if (arg[i] != arg[len - 1 - i]) { return false; } diff --git a/src/cli/main.c b/src/cli/main.c index 10502004..7f962d58 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -89,12 +89,12 @@ static void repl(DictuVM *vm) { while (!replCountBraces(fullLine) || !replCountQuotes(fullLine)) { free(line); line = linenoise("... "); - int lineLength = strlen(line); if (line == NULL) { return; } + int lineLength = strlen(line); char *temp = realloc(fullLine, sizeof(char) * fullLineLength + lineLength); if (temp == NULL) { diff --git a/src/vm/value.h b/src/vm/value.h index 4f8b863b..84f788ce 100644 --- a/src/vm/value.h +++ b/src/vm/value.h @@ -13,8 +13,6 @@ typedef struct sObjFile ObjFile; typedef struct sObjAbstract ObjAbstract; typedef struct sObjResult ObjResult; -#ifdef NAN_TAGGING - // A mask that selects the sign bit. #define SIGN_BIT ((uint64_t)1 << 63) @@ -73,40 +71,6 @@ static inline Value numToValue(double num) { return data.bits64; } -#else - -typedef enum { - VAL_BOOL, - VAL_NIL, // [user-types] - VAL_NUMBER, - VAL_OBJ -} ValueType; - -typedef struct { - ValueType type; - union { - bool boolean; - double number; - Obj* obj; - } as; // [as] -} Value; - -#define IS_BOOL(value) ((value).type == VAL_BOOL) -#define IS_NIL(value) ((value).type == VAL_NIL) -#define IS_NUMBER(value) ((value).type == VAL_NUMBER) -#define IS_OBJ(value) ((value).type == VAL_OBJ) - -#define AS_OBJ(value) ((value).as.obj) -#define AS_BOOL(value) ((value).as.boolean) -#define AS_NUMBER(value) ((value).as.number) - -#define BOOL_VAL(value) ((Value){ VAL_BOOL, { .boolean = value } }) -#define NIL_VAL ((Value){ VAL_NIL, { .number = 0 } }) -#define NUMBER_VAL(value) ((Value){ VAL_NUMBER, { .number = value } }) -#define OBJ_VAL(object) ((Value){ VAL_OBJ, { .obj = (Obj*)object } }) - -#endif - typedef struct { int capacity; int count; diff --git a/tests/benchmarks/dict-methods/deepCopy.du b/tests/benchmarks/dict-methods/deepCopy.du index 27e1c3ac..dc46169f 100644 --- a/tests/benchmarks/dict-methods/deepCopy.du +++ b/tests/benchmarks/dict-methods/deepCopy.du @@ -1,7 +1,7 @@ var start = System.clock(); var x = {"Dictu": "is great!"}; -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { x.deepCopy(); } diff --git a/tests/benchmarks/dict-methods/exists.du b/tests/benchmarks/dict-methods/exists.du index 55943e9d..24705376 100644 --- a/tests/benchmarks/dict-methods/exists.du +++ b/tests/benchmarks/dict-methods/exists.du @@ -2,7 +2,7 @@ var start = System.clock(); var x = {"dictu": "is great!"}; -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { x.exists("dictu"); } diff --git a/tests/benchmarks/dict-methods/remove.du b/tests/benchmarks/dict-methods/remove.du index 2e356910..9208d9fa 100644 --- a/tests/benchmarks/dict-methods/remove.du +++ b/tests/benchmarks/dict-methods/remove.du @@ -1,12 +1,12 @@ var x = {}; -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { x[i] = "Dictu is great!"; } var start = System.clock(); -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { x.remove(i); } diff --git a/tests/benchmarks/dict-methods/shallowCopy.du b/tests/benchmarks/dict-methods/shallowCopy.du index c58296b3..12136962 100644 --- a/tests/benchmarks/dict-methods/shallowCopy.du +++ b/tests/benchmarks/dict-methods/shallowCopy.du @@ -1,7 +1,7 @@ var start = System.clock(); var x = {"Dictu": "is great!"}; -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { x.copy(); } diff --git a/tests/benchmarks/dictionaries.du b/tests/benchmarks/dictionaries.du index 80245e35..65fc7a0b 100644 --- a/tests/benchmarks/dictionaries.du +++ b/tests/benchmarks/dictionaries.du @@ -2,18 +2,18 @@ var start = System.clock(); var dict = {}; -for (var i = 1; i < 1000001; ++i) { +for (var i = 1; i < 1000001; i += 1) { dict[str(i)] = i; } var sum = 0; -for (var i = 1; i < 1000001; ++i) { +for (var i = 1; i < 1000001; i += 1) { sum = sum + dict[str(i)]; } print(sum); -for (var i = 1; i < 1000001; ++i) { +for (var i = 1; i < 1000001; i += 1) { dict.remove(str(i)); } diff --git a/tests/benchmarks/fib.du b/tests/benchmarks/fib.du index 8029c0b4..e364569e 100644 --- a/tests/benchmarks/fib.du +++ b/tests/benchmarks/fib.du @@ -7,7 +7,7 @@ class Fib { var start = System.clock(); -for (var i = 0; i < 5; ++i) { +for (var i = 0; i < 5; i += 1) { print(Fib.get(28)); } diff --git a/tests/benchmarks/for.du b/tests/benchmarks/for.du index 3812bf79..add4db74 100644 --- a/tests/benchmarks/for.du +++ b/tests/benchmarks/for.du @@ -2,14 +2,14 @@ var list = []; var start = System.clock(); -for (var i = 0; i < 1000000; ++i) { +for (var i = 0; i < 1000000; i += 1) { list.push(i); } var sum = 0; var leng = list.len(); -for (var i = 0; i < leng; ++i) { +for (var i = 0; i < leng; i += 1) { sum += i; } diff --git a/tests/benchmarks/list-methods/contains.du b/tests/benchmarks/list-methods/contains.du index a7222a27..24cfdb08 100644 --- a/tests/benchmarks/list-methods/contains.du +++ b/tests/benchmarks/list-methods/contains.du @@ -1,7 +1,7 @@ var start = System.clock(); var x = ["Dictu is great!"]; -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { x.contains("Dictu is great!"); } diff --git a/tests/benchmarks/list-methods/deepCopy.du b/tests/benchmarks/list-methods/deepCopy.du index 52549a83..9f0882b2 100644 --- a/tests/benchmarks/list-methods/deepCopy.du +++ b/tests/benchmarks/list-methods/deepCopy.du @@ -1,7 +1,7 @@ var start = System.clock(); var x = ["Dictu is great!"]; -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { x.deepCopy(); } diff --git a/tests/benchmarks/list-methods/join.du b/tests/benchmarks/list-methods/join.du index 86cc3c4d..a2e32284 100644 --- a/tests/benchmarks/list-methods/join.du +++ b/tests/benchmarks/list-methods/join.du @@ -1,6 +1,6 @@ var start = System.clock(); -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'].join(""); } diff --git a/tests/benchmarks/list-methods/pop.du b/tests/benchmarks/list-methods/pop.du index ea7d4bc0..348ebd20 100644 --- a/tests/benchmarks/list-methods/pop.du +++ b/tests/benchmarks/list-methods/pop.du @@ -1,12 +1,12 @@ var x = []; -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { x.push("Dictu is great!"); } var start = System.clock(); -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { x.pop(); } diff --git a/tests/benchmarks/list-methods/push.du b/tests/benchmarks/list-methods/push.du index 0dbb9454..76258480 100644 --- a/tests/benchmarks/list-methods/push.du +++ b/tests/benchmarks/list-methods/push.du @@ -1,7 +1,7 @@ var start = System.clock(); var x = []; -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { x.push("Dictu is great!"); } diff --git a/tests/benchmarks/list-methods/shallowCopy.du b/tests/benchmarks/list-methods/shallowCopy.du index cebb7dac..03d993ba 100644 --- a/tests/benchmarks/list-methods/shallowCopy.du +++ b/tests/benchmarks/list-methods/shallowCopy.du @@ -1,7 +1,7 @@ var start = System.clock(); var x = ["Dictu is great!"]; -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { x.copy(); } diff --git a/tests/benchmarks/string-methods/contains.du b/tests/benchmarks/string-methods/contains.du index d70a792a..4e1ecba4 100644 --- a/tests/benchmarks/string-methods/contains.du +++ b/tests/benchmarks/string-methods/contains.du @@ -1,7 +1,7 @@ var start = System.clock(); var x; -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { x = "Dictu is great!".contains("is"); } diff --git a/tests/benchmarks/string-methods/endsWith.du b/tests/benchmarks/string-methods/endsWith.du index 977eef7f..c0521e4b 100644 --- a/tests/benchmarks/string-methods/endsWith.du +++ b/tests/benchmarks/string-methods/endsWith.du @@ -1,7 +1,7 @@ var start = System.clock(); var x; -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { x = "Dictu is great!".endsWith("great!"); } diff --git a/tests/benchmarks/string-methods/find.du b/tests/benchmarks/string-methods/find.du index 9fd09131..0f6d95a7 100644 --- a/tests/benchmarks/string-methods/find.du +++ b/tests/benchmarks/string-methods/find.du @@ -1,7 +1,7 @@ var start = System.clock(); var x; -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { x = "Dictu is great!".find("is"); } diff --git a/tests/benchmarks/string-methods/format.du b/tests/benchmarks/string-methods/format.du index e23eaee7..caa4053e 100644 --- a/tests/benchmarks/string-methods/format.du +++ b/tests/benchmarks/string-methods/format.du @@ -1,7 +1,7 @@ var start = System.clock(); var x; -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { x = "{} {}".format("test", "test"); } diff --git a/tests/benchmarks/string-methods/leftStrip.du b/tests/benchmarks/string-methods/leftStrip.du index 764704e9..87e60048 100644 --- a/tests/benchmarks/string-methods/leftStrip.du +++ b/tests/benchmarks/string-methods/leftStrip.du @@ -1,7 +1,7 @@ var start = System.clock(); var x; -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { x = " test".strip(); } diff --git a/tests/benchmarks/string-methods/lower.du b/tests/benchmarks/string-methods/lower.du index 0a28c902..8c179d54 100644 --- a/tests/benchmarks/string-methods/lower.du +++ b/tests/benchmarks/string-methods/lower.du @@ -1,7 +1,7 @@ var start = System.clock(); var x; -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { x = "DICTU IS GREAT!".lower(); } diff --git a/tests/benchmarks/string-methods/replace.du b/tests/benchmarks/string-methods/replace.du index 90591433..547c58d3 100644 --- a/tests/benchmarks/string-methods/replace.du +++ b/tests/benchmarks/string-methods/replace.du @@ -1,7 +1,7 @@ var start = System.clock(); var x; -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { x = "aaaaa".replace("a", "e"); } diff --git a/tests/benchmarks/string-methods/rightStrip.du b/tests/benchmarks/string-methods/rightStrip.du index 07626c2b..0e283e60 100644 --- a/tests/benchmarks/string-methods/rightStrip.du +++ b/tests/benchmarks/string-methods/rightStrip.du @@ -1,7 +1,7 @@ var start = System.clock(); var x; -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { x = "test ".strip(); } diff --git a/tests/benchmarks/string-methods/split.du b/tests/benchmarks/string-methods/split.du index 409ba7d9..6adcbd87 100644 --- a/tests/benchmarks/string-methods/split.du +++ b/tests/benchmarks/string-methods/split.du @@ -1,7 +1,7 @@ var start = System.clock(); var x; -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { x = "Dictu is great!".split(" "); } diff --git a/tests/benchmarks/string-methods/startsWith.du b/tests/benchmarks/string-methods/startsWith.du index 2f1c1e35..af39bcef 100644 --- a/tests/benchmarks/string-methods/startsWith.du +++ b/tests/benchmarks/string-methods/startsWith.du @@ -1,7 +1,7 @@ var start = System.clock(); var x; -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { x = "Dictu is great!".startsWith("Dictu"); } diff --git a/tests/benchmarks/string-methods/strip.du b/tests/benchmarks/string-methods/strip.du index 65d203f1..2416ca12 100644 --- a/tests/benchmarks/string-methods/strip.du +++ b/tests/benchmarks/string-methods/strip.du @@ -1,7 +1,7 @@ var start = System.clock(); var x; -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { x = " test ".strip(); } diff --git a/tests/benchmarks/string-methods/upper.du b/tests/benchmarks/string-methods/upper.du index b282044a..b17d9341 100644 --- a/tests/benchmarks/string-methods/upper.du +++ b/tests/benchmarks/string-methods/upper.du @@ -1,7 +1,7 @@ var start = System.clock(); var x; -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { x = "dictu is great!".upper(); } diff --git a/tests/benchmarks/stringEquality.du b/tests/benchmarks/stringEquality.du index 4ae10e95..a428ebf6 100644 --- a/tests/benchmarks/stringEquality.du +++ b/tests/benchmarks/stringEquality.du @@ -2,7 +2,7 @@ var start = System.clock(); var count = 0; -for (var i = 0; i < 1000000; ++i) { +for (var i = 0; i < 1000000; i += 1) { if ("abc" == "abc") count = count + 1; diff --git a/tests/dicts/len.du b/tests/dicts/len.du index b58798f5..9f4c582e 100644 --- a/tests/dicts/len.du +++ b/tests/dicts/len.du @@ -13,14 +13,14 @@ assert({ 1: "one", 2: "two", 3: "three"}.len() == 3); var x = {}; assert(x.len() == 0); -for (var i = 0; i < 1000; ++i) { +for (var i = 0; i < 1000; i += 1) { assert(x.len() == i); x[i] = i; } assert(x.len() == 1000); -for (var i = 999; i >= 0; --i) { +for (var i = 999; i >= 0; i -= 1) { x.remove(i); assert(x.len() == i); } diff --git a/tests/dicts/remove.du b/tests/dicts/remove.du index 6eaf536d..f7f0c808 100644 --- a/tests/dicts/remove.du +++ b/tests/dicts/remove.du @@ -33,11 +33,11 @@ assert(myDict == {10.5: 10.5}); myDict.remove(10.5); assert(myDict == {}); -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { myDict[i] = i; } -for (var i = 0; i < 10000; ++i) { +for (var i = 0; i < 10000; i += 1) { myDict.remove(i); } diff --git a/tests/lists/len.du b/tests/lists/len.du index df859548..4992b2f4 100644 --- a/tests/lists/len.du +++ b/tests/lists/len.du @@ -13,7 +13,7 @@ assert([1, 2, [3, 4]].len() == 3); var x = []; -for (var i = 0; i < 1000; ++i) { +for (var i = 0; i < 1000; i += 1) { assert(x.len() == i); x.push(i); } @@ -21,7 +21,7 @@ for (var i = 0; i < 1000; ++i) { assert(x.len() == 1000); -for (var i = 1000; i > 0; --i) { +for (var i = 1000; i > 0; i -= 1) { assert(x.len() == i); x.pop(); } diff --git a/tests/loops/break.du b/tests/loops/break.du index 5267e80d..19080c5f 100644 --- a/tests/loops/break.du +++ b/tests/loops/break.du @@ -7,7 +7,7 @@ var x = []; // Single break statement -for (var i = 0; i < 10; ++i) { +for (var i = 0; i < 10; i += 1) { if (i > 5) break; @@ -19,7 +19,7 @@ assert(x == [0, 1, 2, 3, 4, 5]); var x = []; // Two break statements -for (var i = 0; i < 10; ++i) { +for (var i = 0; i < 10; i += 1) { if (i > 5) break; @@ -40,7 +40,7 @@ while (i < 10) { break; y.push(i); - ++i; + i += 1; } assert(y == [0, 1, 2, 3, 4, 5]); @@ -48,7 +48,7 @@ assert(y == [0, 1, 2, 3, 4, 5]); var z = []; // Nested for loop with multiple breaks -for (var i = 0; i < 10; ++i) { +for (var i = 0; i < 10; i += 1) { for (var j = 0; j < 10; ++j) { if (j > 5) break; @@ -90,7 +90,7 @@ while (i < 5) { break; z.push(i); - ++i; + i += 1; } assert(z == [0, 1, 2, 3, 0, 0, 1, 2, 3, 1, 0, 1, 2, 3, 2, 0, 1, 2, 3, 3, 0, 1, 2, 3, 0, 0, 1, 2, 3, 0, 0, 1, 2, 3, 1, 0, 1, 2, 3, 2, 0, 1, 2, 3, 3, 0, 1, 2, 3, 1, 0, 1, 2, 3, 0, 0, 1, 2, 3, 1, 0, 1, 2, 3, 2, 0, 1, 2, 3, 3, 0, 1, 2, 3, 2, 0, 1, 2, 3, 0, 0, 1, 2, 3, 1, 0, 1, 2, 3, 2, 0, 1, 2, 3, 3, 0, 1, 2, 3, 3, 0, 1, 2, 3, 0, 0, 1, 2, 3, 1, 0, 1, 2, 3, 2, 0, 1, 2, 3, 3, 0, 1, 2, 3]); \ No newline at end of file diff --git a/tests/loops/continue.du b/tests/loops/continue.du index 5aedd1f1..4448252d 100644 --- a/tests/loops/continue.du +++ b/tests/loops/continue.du @@ -6,7 +6,7 @@ var x = []; -for (var i = 0; i < 10; ++i) { +for (var i = 0; i < 10; i += 1) { if (i % 2 == 0) continue; @@ -19,7 +19,7 @@ var i = 0; var y = []; while (i < 10) { - ++i; + i += 1; if (i % 2 == 0) continue; @@ -30,7 +30,7 @@ assert(y == [1, 3, 5, 7, 9]); var z = []; -for (var i = 0; i < 5; ++i) { +for (var i = 0; i < 5; i += 1) { for (var j = 0; j < 5; ++j) { if (j % 2 == 0) continue; diff --git a/tests/loops/loop.du b/tests/loops/loop.du index bad49e58..19c57bbd 100644 --- a/tests/loops/loop.du +++ b/tests/loops/loop.du @@ -6,7 +6,7 @@ var x = []; -for (var i = 0; i < 10; ++i) { +for (var i = 0; i < 10; i += 1) { x.push(i); } @@ -15,7 +15,7 @@ assert(x == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); var x = []; var i = 0; -for (; i < 10; ++i) { +for (; i < 10; i += 1) { x.push(i); } @@ -24,7 +24,7 @@ assert(x == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); var x = []; var i = 0; -for (i = 5; i < 10; ++i) { +for (i = 5; i < 10; i += 1) { x.push(i); } @@ -35,7 +35,7 @@ var y = []; while (i < 10) { y.push(i); - ++i; + i += 1; } assert(y == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); @@ -49,7 +49,7 @@ while { break; } y.push(i); - ++i; + i += 1; } assert(y == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); diff --git a/tests/path/listdir.du b/tests/path/listdir.du index 37c733bd..1bc135e4 100644 --- a/tests/path/listdir.du +++ b/tests/path/listdir.du @@ -8,7 +8,7 @@ import Path; var dir_contents = Path.listdir("tests/path/test_dir"); var exp_dir_contents = ["test_file_1", "test_file_2", "test_file_3"]; -for (var i = 0; i < exp_dir_contents.len(); ++i) { +for (var i = 0; i < exp_dir_contents.len(); i += 1) { var exp_inode = exp_dir_contents[i]; assert(dir_contents.contains(exp_inode)); exp_dir_contents.remove(exp_inode); diff --git a/tests/random/random.du b/tests/random/random.du index b3975cc0..b95623c9 100644 --- a/tests/random/random.du +++ b/tests/random/random.du @@ -8,7 +8,7 @@ import Random; var num_randomizations = 100; -for (var i = 0; i < num_randomizations; ++i) { +for (var i = 0; i < num_randomizations; i += 1) { assert(Random.random() >= 0); assert(Random.random() <= 1); } diff --git a/tests/random/range.du b/tests/random/range.du index c8ba45af..f6524bd4 100644 --- a/tests/random/range.du +++ b/tests/random/range.du @@ -11,14 +11,14 @@ var histogram = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0}; var num_randomizations = 500; var percent_error_threshold = 0.05; -for (var i = 0; i < num_randomizations; ++i) { +for (var i = 0; i < num_randomizations; i += 1) { histogram[Random.range(1, 5)] += 1; } var exp_percent = 0.2; var min_percent = exp_percent - percent_error_threshold; var max_percent = exp_percent + percent_error_threshold; -for (var i = 0; i < histogram.len(); ++i) { +for (var i = 0; i < histogram.len(); i += 1) { var key = histogram.keys()[i]; var percent_of_key = histogram[key] / num_randomizations; assert(min_percent <= percent_of_key); diff --git a/tests/random/select.du b/tests/random/select.du index 5628d064..c4139590 100644 --- a/tests/random/select.du +++ b/tests/random/select.du @@ -11,14 +11,14 @@ var histogram = {1: 0, 3: 0, 5: 0, 7: 0, 9: 0}; var num_randomizations = 500; var percent_error_threshold = 0.05; -for (var i = 0; i < num_randomizations; ++i) { +for (var i = 0; i < num_randomizations; i += 1) { histogram[Random.select([1, 3, 5, 7, 9])] += 1; } var exp_percent = 0.2; var min_percent = exp_percent - percent_error_threshold; var max_percent = exp_percent + percent_error_threshold; -for (var i = 0; i < histogram.len(); ++i) { +for (var i = 0; i < histogram.len(); i += 1) { var key = histogram.keys()[i]; var percent_of_key = histogram[key] / num_randomizations; assert(min_percent <= percent_of_key); diff --git a/tests/strings/subscript.du b/tests/strings/subscript.du index 7a101b4e..0113bc1e 100644 --- a/tests/strings/subscript.du +++ b/tests/strings/subscript.du @@ -15,6 +15,6 @@ assert(string[-1] == "!"); assert(string[-2] == "t"); assert(string[-3] == "a"); -for (var i = 0; i < string.len(); ++i) { +for (var i = 0; i < string.len(); i += 1) { assert(string[i] == stringList[i]); } From 0c56813dd8b5992a9d713e667b09da59c6cc8742 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Sun, 10 Jan 2021 02:20:26 +0000 Subject: [PATCH 25/41] Remove prefix operators --- docs/docs/operators.md | 2 -- src/vm/compiler.c | 53 ++---------------------------------------- 2 files changed, 2 insertions(+), 53 deletions(-) diff --git a/docs/docs/operators.md b/docs/docs/operators.md index 1337e220..5bcc2cae 100644 --- a/docs/docs/operators.md +++ b/docs/docs/operators.md @@ -15,8 +15,6 @@ nav_order: 6 | - | Subtracts the values on either side of the operator together | 10 - 10 | | * | Multiplies the values on either side of the operator together | 10 * 2 | | / | Divides the values on either side of the operator together. | 10 / 3 | -| ++ Prefix | Increments the value of a variable by 1 | ++x | -| \-\- Prefix | Decrements the value of a variable by 1 | \-\-x | | % | Modulo of values on either side of the operator | 10 % 2 | | ** | Exponent (power) of the values | 2 ** 2 | | & | Bitwise AND of the values | 10 & 2 | diff --git a/src/vm/compiler.c b/src/vm/compiler.c index 67e7380c..9c9d5828 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -1273,55 +1273,6 @@ static void unary(Compiler *compiler, bool canAssign) { } } -static void prefix(Compiler *compiler, bool canAssign) { - UNUSED(canAssign); - - TokenType operatorType = compiler->parser->previous.type; - Token cur = compiler->parser->current; - consume(compiler, TOKEN_IDENTIFIER, "Expected variable"); - namedVariable(compiler, compiler->parser->previous, true); - - int arg; - bool instance = false; - - if (match(compiler, TOKEN_DOT)) { - consume(compiler, TOKEN_IDENTIFIER, "Expect property name after '.'."); - arg = identifierConstant(compiler, &compiler->parser->previous); - emitBytes(compiler, OP_GET_PROPERTY_NO_POP, arg); - instance = true; - } - - switch (operatorType) { - case TOKEN_PLUS_PLUS: { - emitByte(compiler, OP_INCREMENT); - break; - } - case TOKEN_MINUS_MINUS: - emitByte(compiler, OP_DECREMENT); - break; - default: - return; - } - - if (instance) { - emitBytes(compiler, OP_SET_PROPERTY, arg); - } else { - uint8_t setOp; - arg = resolveLocal(compiler, &cur, false); - if (arg != -1) { - setOp = OP_SET_LOCAL; - } else if ((arg = resolveUpvalue(compiler, &cur)) != -1) { - setOp = OP_SET_UPVALUE; - } else { - arg = identifierConstant(compiler, &cur); - setOp = OP_SET_MODULE; - } - - checkConst(compiler, setOp, arg); - emitBytes(compiler, setOp, (uint8_t) arg); - } -} - ParseRule rules[] = { {grouping, call, PREC_CALL}, // TOKEN_LEFT_PAREN {NULL, NULL, PREC_NONE}, // TOKEN_RIGHT_PAREN @@ -1335,8 +1286,8 @@ ParseRule rules[] = { {NULL, binary, PREC_TERM}, // TOKEN_PLUS {NULL, ternary, PREC_ASSIGNMENT}, // TOKEN_QUESTION {NULL, chain, PREC_CHAIN}, // TOKEN_QUESTION_DOT - {prefix, NULL, PREC_NONE}, // TOKEN_PLUS_PLUS - {prefix, NULL, PREC_NONE}, // TOKEN_MINUS_MINUS + {NULL, NULL, PREC_NONE}, // TOKEN_PLUS_PLUS + {NULL, NULL, PREC_NONE}, // TOKEN_MINUS_MINUS {NULL, NULL, PREC_NONE}, // TOKEN_PLUS_EQUALS {NULL, NULL, PREC_NONE}, // TOKEN_MINUS_EQUALS {NULL, NULL, PREC_NONE}, // TOKEN_MULTIPLY_EQUALS From 3074ba701fd56d2553b0676f3e72eafaf5a17139 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Sun, 10 Jan 2021 02:25:55 +0000 Subject: [PATCH 26/41] Update tests to remove prefix operators --- tests/classes/properties.du | 4 --- tests/loops/break.du | 6 ++--- tests/loops/continue.du | 2 +- tests/operators/import.du | 1 - tests/operators/precedence.du | 3 +-- tests/operators/prefix.du | 46 ----------------------------------- 6 files changed, 5 insertions(+), 57 deletions(-) delete mode 100644 tests/operators/prefix.du diff --git a/tests/classes/properties.du b/tests/classes/properties.du index 6be09dbc..ea071ef8 100644 --- a/tests/classes/properties.du +++ b/tests/classes/properties.du @@ -27,10 +27,6 @@ obj.x *= 10; assert(obj.x == 200); obj.x /= 10; assert(obj.x == 20); -++obj.x; -assert(obj.x == 21); ---obj.x; -assert(obj.x == 20); assert(obj.hasAttribute("x")); assert(!obj.hasAttribute("y")); diff --git a/tests/loops/break.du b/tests/loops/break.du index 19080c5f..a36da1bc 100644 --- a/tests/loops/break.du +++ b/tests/loops/break.du @@ -49,7 +49,7 @@ var z = []; // Nested for loop with multiple breaks for (var i = 0; i < 10; i += 1) { - for (var j = 0; j < 10; ++j) { + for (var j = 0; j < 10; j += 1) { if (j > 5) break; @@ -78,13 +78,13 @@ while (i < 5) { break; z.push(m); - ++m; + m += 1; } if (j > 3) break; z.push(j); - ++j; + j += 1; } if (i > 3) break; diff --git a/tests/loops/continue.du b/tests/loops/continue.du index 4448252d..bea61a35 100644 --- a/tests/loops/continue.du +++ b/tests/loops/continue.du @@ -31,7 +31,7 @@ assert(y == [1, 3, 5, 7, 9]); var z = []; for (var i = 0; i < 5; i += 1) { - for (var j = 0; j < 5; ++j) { + for (var j = 0; j < 5; j += 1) { if (j % 2 == 0) continue; diff --git a/tests/operators/import.du b/tests/operators/import.du index 962471b1..c2053896 100644 --- a/tests/operators/import.du +++ b/tests/operators/import.du @@ -14,7 +14,6 @@ import "multiply.du"; import "divide.du"; import "pow.du"; import "modulo.du"; -import "prefix.du"; import "assignment.du"; import "bitwise.du"; import "precedence.du"; diff --git a/tests/operators/precedence.du b/tests/operators/precedence.du index 3ed124a3..5c4c6b07 100644 --- a/tests/operators/precedence.du +++ b/tests/operators/precedence.du @@ -8,7 +8,6 @@ var x = 10; assert(x + 1 * 10 / 2 == 15); assert(-x + 10 * 2 + 10 / 2 == 15); -assert(++x + 2 * 10 / 2 - 2 == 19); -assert(-2 ** 2 + ++x == 16); +assert(x + 2 * 10 / 2 - 2 == 18); assert(10 ^ 2 ** 2 - 2 / 2 == 9); \ No newline at end of file diff --git a/tests/operators/prefix.du b/tests/operators/prefix.du deleted file mode 100644 index 9e3009bf..00000000 --- a/tests/operators/prefix.du +++ /dev/null @@ -1,46 +0,0 @@ -/** - * prefix.du - * - * Testing prefix operators - */ - -var x = 10; -++x; - -assert(x == 11); - ---x; -assert(x == 10); - -/** - * Known issue with prefix operator not working with dictionaries - */ - -// var y = {"test": 10}; -// ++y["test"]; -// -// assert(y["test"] == 11); -// -// --y["test"]; -// assert(--y["test"] == 10); - -class Test { - init() { - this.x = 10; - } -} - -var obj = Test(); -++obj.x; -assert(obj.x == 11); - -/** - * Known issue with prefix operator and nested objects - */ - -// var newObj = Test(); -// -// newObj.o = obj; -// -// ++newObj.o.x; -// assert(obj.x == 12); \ No newline at end of file From 1fe59d6aa54c2844b16158c5f7de2e4a8e0a8c35 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Sun, 10 Jan 2021 23:09:17 +0000 Subject: [PATCH 27/41] Update the license for 2021 --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index dda101a5..307c4d02 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2020 Jason Hall +Copyright (c) 2019-2021 Jason Hall Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 2c2fdddd6546bdcdc40ea406b959a8878ef048b5 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Mon, 11 Jan 2021 21:54:10 +0000 Subject: [PATCH 28/41] Remove prefix opcodes and parsing --- src/vm/compiler.c | 2 -- src/vm/debug.c | 4 ---- src/vm/opcodes.h | 2 -- src/vm/scanner.c | 8 ++------ src/vm/scanner.h | 1 - src/vm/vm.c | 19 ------------------- 6 files changed, 2 insertions(+), 34 deletions(-) diff --git a/src/vm/compiler.c b/src/vm/compiler.c index 9c9d5828..d6bd6a29 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -1286,8 +1286,6 @@ ParseRule rules[] = { {NULL, binary, PREC_TERM}, // TOKEN_PLUS {NULL, ternary, PREC_ASSIGNMENT}, // TOKEN_QUESTION {NULL, chain, PREC_CHAIN}, // TOKEN_QUESTION_DOT - {NULL, NULL, PREC_NONE}, // TOKEN_PLUS_PLUS - {NULL, NULL, PREC_NONE}, // TOKEN_MINUS_MINUS {NULL, NULL, PREC_NONE}, // TOKEN_PLUS_EQUALS {NULL, NULL, PREC_NONE}, // TOKEN_MINUS_EQUALS {NULL, NULL, PREC_NONE}, // TOKEN_MULTIPLY_EQUALS diff --git a/src/vm/debug.c b/src/vm/debug.c index 56e9f9ff..480c985d 100644 --- a/src/vm/debug.c +++ b/src/vm/debug.c @@ -163,10 +163,6 @@ int disassembleInstruction(Chunk *chunk, int offset) { return simpleInstruction("OP_LESS", offset); case OP_ADD: return simpleInstruction("OP_ADD", offset); - case OP_INCREMENT: - return simpleInstruction("OP_INCREMENT", offset); - case OP_DECREMENT: - return simpleInstruction("OP_DECREMENT", offset); case OP_MULTIPLY: return simpleInstruction("OP_MULTIPLY", offset); case OP_DIVIDE: diff --git a/src/vm/opcodes.h b/src/vm/opcodes.h index 5db6bad2..86c91928 100644 --- a/src/vm/opcodes.h +++ b/src/vm/opcodes.h @@ -28,8 +28,6 @@ OPCODE(EQUAL) OPCODE(GREATER) OPCODE(LESS) OPCODE(ADD) -OPCODE(INCREMENT) -OPCODE(DECREMENT) OPCODE(MULTIPLY) OPCODE(DIVIDE) OPCODE(POW) diff --git a/src/vm/scanner.c b/src/vm/scanner.c index 4d577514..4f072dbf 100644 --- a/src/vm/scanner.c +++ b/src/vm/scanner.c @@ -388,18 +388,14 @@ Token scanToken(Scanner *scanner) { case '%': return makeToken(scanner, TOKEN_PERCENT); case '-': { - if (match(scanner, '-')) { - return makeToken(scanner, TOKEN_MINUS_MINUS); - } else if (match(scanner, '=')) { + if (match(scanner, '=')) { return makeToken(scanner, TOKEN_MINUS_EQUALS); } else { return makeToken(scanner, TOKEN_MINUS); } } case '+': { - if (match(scanner, '+')) { - return makeToken(scanner, TOKEN_PLUS_PLUS); - } else if (match(scanner, '=')) { + if (match(scanner, '=')) { return makeToken(scanner, TOKEN_PLUS_EQUALS); } else { return makeToken(scanner, TOKEN_PLUS); diff --git a/src/vm/scanner.h b/src/vm/scanner.h index 650b98d1..546471c6 100644 --- a/src/vm/scanner.h +++ b/src/vm/scanner.h @@ -10,7 +10,6 @@ typedef enum { TOKEN_QUESTION, TOKEN_QUESTION_DOT, - TOKEN_PLUS_PLUS, TOKEN_MINUS_MINUS, TOKEN_PLUS_EQUALS, TOKEN_MINUS_EQUALS, TOKEN_MULTIPLY_EQUALS, TOKEN_DIVIDE_EQUALS, diff --git a/src/vm/vm.c b/src/vm/vm.c index 75b35050..c0538342 100644 --- a/src/vm/vm.c +++ b/src/vm/vm.c @@ -993,25 +993,6 @@ static DictuInterpretResult run(DictuVM *vm) { DISPATCH(); } - CASE_CODE(INCREMENT): { - if (!IS_NUMBER(peek(vm, 0))) { - RUNTIME_ERROR("Operand must be a number."); - } - - push(vm, NUMBER_VAL(AS_NUMBER(pop(vm)) + 1)); - DISPATCH(); - } - - CASE_CODE(DECREMENT): { - if (!IS_NUMBER(peek(vm, 0))) { - RUNTIME_ERROR("Operand must be a number."); - - } - - push(vm, NUMBER_VAL(AS_NUMBER(pop(vm)) - 1)); - DISPATCH(); - } - CASE_CODE(MULTIPLY): BINARY_OP(NUMBER_VAL, *, double); DISPATCH(); From d75e9f44fbd966b948437d458cc07249286b8d73 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Mon, 11 Jan 2021 21:55:10 +0000 Subject: [PATCH 29/41] Remove prefix opcodes from compiler --- src/vm/compiler.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vm/compiler.c b/src/vm/compiler.c index d6bd6a29..98ba689b 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -1658,8 +1658,6 @@ static int getArgCount(uint8_t code, const ValueArray constants, int ip) { case OP_GREATER: case OP_LESS: case OP_ADD: - case OP_INCREMENT: - case OP_DECREMENT: case OP_MULTIPLY: case OP_DIVIDE: case OP_POW: From 3fdab003e17f9c6ea4808da5a3337b993f6cf5fc Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Mon, 11 Jan 2021 22:33:18 +0000 Subject: [PATCH 30/41] Better error messages for binary ops --- src/vm/natives.c | 53 ++----------------------------- src/vm/value.c | 83 ++++++++++++++++++++++++++++++++++++++++++++++++ src/vm/value.h | 2 ++ src/vm/vm.c | 7 ++-- 4 files changed, 90 insertions(+), 55 deletions(-) diff --git a/src/vm/natives.c b/src/vm/natives.c index 98f3b850..752ca9db 100644 --- a/src/vm/natives.c +++ b/src/vm/natives.c @@ -13,57 +13,8 @@ static Value typeNative(DictuVM *vm, int argCount, Value *args) { return EMPTY_VAL; } - if (IS_BOOL(args[0])) { - return OBJ_VAL(copyString(vm, "bool", 4)); - } else if (IS_NIL(args[0])) { - return OBJ_VAL(copyString(vm, "nil", 3)); - } else if (IS_NUMBER(args[0])) { - return OBJ_VAL(copyString(vm, "number", 6)); - } else if (IS_OBJ(args[0])) { - switch (OBJ_TYPE(args[0])) { - case OBJ_CLASS: { - switch (AS_CLASS(args[0])->type) { - case CLASS_DEFAULT: - case CLASS_ABSTRACT: { - return OBJ_VAL(copyString(vm, "class", 5)); - } - case CLASS_TRAIT: { - return OBJ_VAL(copyString(vm, "trait", 5)); - } - } - - break; - } - case OBJ_MODULE: { - return OBJ_VAL(copyString(vm, "module", 6)); - } - case OBJ_INSTANCE: { - ObjString *className = AS_INSTANCE(args[0])->klass->name; - return OBJ_VAL(copyString(vm, className->chars, className->length)); - } - case OBJ_BOUND_METHOD: - return OBJ_VAL(copyString(vm, "method", 6)); - case OBJ_CLOSURE: - case OBJ_FUNCTION: - return OBJ_VAL(copyString(vm, "function", 8)); - case OBJ_STRING: - return OBJ_VAL(copyString(vm, "string", 6)); - case OBJ_LIST: - return OBJ_VAL(copyString(vm, "list", 4)); - case OBJ_DICT: - return OBJ_VAL(copyString(vm, "dict", 4)); - case OBJ_SET: - return OBJ_VAL(copyString(vm, "set", 3)); - case OBJ_NATIVE: - return OBJ_VAL(copyString(vm, "native", 6)); - case OBJ_FILE: - return OBJ_VAL(copyString(vm, "file", 4)); - default: - break; - } - } - - return OBJ_VAL(copyString(vm, "Unknown Type", 12)); + int length = 0; + return OBJ_VAL(takeString(vm, valueTypeToString(vm, args[0], &length), length)); } static Value setNative(DictuVM *vm, int argCount, Value *args) { diff --git a/src/vm/value.c b/src/vm/value.c index e281ae62..661c3dcc 100644 --- a/src/vm/value.c +++ b/src/vm/value.c @@ -358,6 +358,89 @@ char *valueToString(Value value) { return unknown; } +// Calling function needs to free memory +char *valueTypeToString(DictuVM *vm, Value value, int *length) { +#define CONVERT(typeString, size) \ + do { \ + char *string = ALLOCATE(vm, char, size + 1); \ + memcpy(string, #typeString, size); \ + string[size] = '\0'; \ + *length = size; \ + return string; \ + } while (false) + +#define CONVERT_VARIABLE(typeString, size) \ + do { \ + char *string = ALLOCATE(vm, char, size + 1); \ + memcpy(string, typeString, size); \ + string[size] = '\0'; \ + *length = size; \ + return string; \ + } while (false) + + + if (IS_BOOL(value)) { + CONVERT(bool, 4); + } else if (IS_NIL(value)) { + CONVERT(nil, 3); + } else if (IS_NUMBER(value)) { + CONVERT(number, 6); + } else if (IS_OBJ(value)) { + switch (OBJ_TYPE(value)) { + case OBJ_CLASS: { + switch (AS_CLASS(value)->type) { + case CLASS_DEFAULT: + case CLASS_ABSTRACT: { + CONVERT(class, 5); + } + case CLASS_TRAIT: { + CONVERT(trait, 5); + } + } + + break; + } + case OBJ_MODULE: { + CONVERT(module, 6); + } + case OBJ_INSTANCE: { + ObjString *className = AS_INSTANCE(value)->klass->name; + + CONVERT_VARIABLE(className->chars, className->length); + } + case OBJ_BOUND_METHOD: { + CONVERT(method, 6); + } + case OBJ_CLOSURE: + case OBJ_FUNCTION: { + CONVERT(function, 8); + } + case OBJ_STRING: { + CONVERT(string, 6); + } + case OBJ_LIST: { + CONVERT(list, 4); + } + case OBJ_DICT: { + CONVERT(dict, 4); + } + case OBJ_SET: { + CONVERT(set, 3); + } + case OBJ_NATIVE: { + CONVERT(native, 6); + } + case OBJ_FILE: { + CONVERT(file, 4); + } + default: + break; + } + } + + CONVERT(unknown, 7); +} + void printValue(Value value) { char *output = valueToString(value); printf("%s", output); diff --git a/src/vm/value.h b/src/vm/value.h index 84f788ce..943c46de 100644 --- a/src/vm/value.h +++ b/src/vm/value.h @@ -103,6 +103,8 @@ void graySet(DictuVM *vm, ObjSet *set); char *valueToString(Value value); +char *valueTypeToString(DictuVM *vm, Value value, int *length); + void printValue(Value value); #endif diff --git a/src/vm/vm.c b/src/vm/vm.c index 75b35050..73ee702b 100644 --- a/src/vm/vm.c +++ b/src/vm/vm.c @@ -615,9 +615,8 @@ static DictuInterpretResult run(DictuVM *vm) { #define BINARY_OP(valueType, op, type) \ do { \ if (!IS_NUMBER(peek(vm, 0)) || !IS_NUMBER(peek(vm, 1))) { \ - frame->ip = ip; \ - runtimeError(vm, "Operands must be numbers."); \ - return INTERPRET_RUNTIME_ERROR; \ + int length = 0; \ + RUNTIME_ERROR("Unsupported operand types for "#op": '%s', '%s'", valueTypeToString(vm, peek(vm, 0), &length), valueTypeToString(vm, peek(vm, 1), &length)); \ } \ \ type b = AS_NUMBER(pop(vm)); \ @@ -988,7 +987,7 @@ static DictuInterpretResult run(DictuVM *vm) { push(vm, OBJ_VAL(finalList)); } else { - RUNTIME_ERROR("Unsupported operand types."); + RUNTIME_ERROR("Unsupported operand types for +: %s, %s", valueToString(peek(vm, 0)), valueToString(peek(vm, 1))); } DISPATCH(); } From a51635562855a628a42628e03ac52733d40979d9 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Mon, 11 Jan 2021 22:48:04 +0000 Subject: [PATCH 31/41] Better error message for binary operators along with dedicated opcode for subtraction --- src/vm/compiler.c | 8 ++++---- src/vm/debug.c | 2 ++ src/vm/opcodes.h | 1 + src/vm/vm.c | 33 +++++++++++++++++++++++---------- 4 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/vm/compiler.c b/src/vm/compiler.c index 9c9d5828..85725159 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -575,7 +575,7 @@ static void binary(Compiler *compiler, Token previousToken, bool canAssign) { emitByte(compiler, OP_ADD); break; case TOKEN_MINUS: - emitBytes(compiler, OP_NEGATE, OP_ADD); + emitByte(compiler, OP_SUBTRACT); break; case TOKEN_STAR: emitByte(compiler, OP_MULTIPLY); @@ -655,7 +655,7 @@ static void dot(Compiler *compiler, Token previousToken, bool canAssign) { } else if (canAssign && match(compiler, TOKEN_MINUS_EQUALS)) { emitBytes(compiler, OP_GET_PROPERTY_NO_POP, name); expression(compiler); - emitBytes(compiler, OP_NEGATE, OP_ADD); + emitByte(compiler, OP_SUBTRACT); emitBytes(compiler, OP_SET_PROPERTY, name); } else if (canAssign && match(compiler, TOKEN_MULTIPLY_EQUALS)) { emitBytes(compiler, OP_GET_PROPERTY_NO_POP, name); @@ -1024,7 +1024,7 @@ static void subscript(Compiler *compiler, Token previousToken, bool canAssign) { } else if (canAssign && match(compiler, TOKEN_MINUS_EQUALS)) { expression(compiler); emitByte(compiler, OP_PUSH); - emitBytes(compiler, OP_NEGATE, OP_ADD); + emitByte(compiler, OP_SUBTRACT); emitByte(compiler, OP_SUBSCRIPT_ASSIGN); } else if (canAssign && match(compiler, TOKEN_MULTIPLY_EQUALS)) { expression(compiler); @@ -1100,7 +1100,7 @@ static void namedVariable(Compiler *compiler, Token name, bool canAssign) { checkConst(compiler, setOp, arg); namedVariable(compiler, name, false); expression(compiler); - emitBytes(compiler, OP_NEGATE, OP_ADD); + emitByte(compiler, OP_SUBTRACT); emitBytes(compiler, setOp, (uint8_t) arg); } else if (canAssign && match(compiler, TOKEN_MULTIPLY_EQUALS)) { checkConst(compiler, setOp, arg); diff --git a/src/vm/debug.c b/src/vm/debug.c index 56e9f9ff..36b4ceb7 100644 --- a/src/vm/debug.c +++ b/src/vm/debug.c @@ -163,6 +163,8 @@ int disassembleInstruction(Chunk *chunk, int offset) { return simpleInstruction("OP_LESS", offset); case OP_ADD: return simpleInstruction("OP_ADD", offset); + case OP_SUBTRACT: + return simpleInstruction("OP_SUBTRACT", offset); case OP_INCREMENT: return simpleInstruction("OP_INCREMENT", offset); case OP_DECREMENT: diff --git a/src/vm/opcodes.h b/src/vm/opcodes.h index 5db6bad2..550baa59 100644 --- a/src/vm/opcodes.h +++ b/src/vm/opcodes.h @@ -28,6 +28,7 @@ OPCODE(EQUAL) OPCODE(GREATER) OPCODE(LESS) OPCODE(ADD) +OPCODE(SUBTRACT) OPCODE(INCREMENT) OPCODE(DECREMENT) OPCODE(MULTIPLY) diff --git a/src/vm/vm.c b/src/vm/vm.c index 73ee702b..a4a2bd0c 100644 --- a/src/vm/vm.c +++ b/src/vm/vm.c @@ -612,16 +612,24 @@ static DictuInterpretResult run(DictuVM *vm) { #define READ_STRING() AS_STRING(READ_CONSTANT()) - #define BINARY_OP(valueType, op, type) \ - do { \ - if (!IS_NUMBER(peek(vm, 0)) || !IS_NUMBER(peek(vm, 1))) { \ - int length = 0; \ - RUNTIME_ERROR("Unsupported operand types for "#op": '%s', '%s'", valueTypeToString(vm, peek(vm, 0), &length), valueTypeToString(vm, peek(vm, 1), &length)); \ - } \ - \ - type b = AS_NUMBER(pop(vm)); \ - type a = AS_NUMBER(pop(vm)); \ - push(vm, valueType(a op b)); \ + #define BINARY_OP(valueType, op, type) \ + do { \ + if (!IS_NUMBER(peek(vm, 0)) || !IS_NUMBER(peek(vm, 1))) { \ + int firstValLength = 0; \ + int secondValLength = 0; \ + char *firstVal = valueTypeToString(vm, peek(vm, 0), &firstValLength); \ + char *secondVal = valueTypeToString(vm, peek(vm, 1), &secondValLength); \ + \ + STORE_FRAME; \ + runtimeError(vm, "Unsupported operand types for "#op": '%s', '%s'", firstVal, secondVal); \ + FREE_ARRAY(vm, char, firstVal, firstValLength + 1); \ + FREE_ARRAY(vm, char, secondVal, secondValLength + 1); \ + return INTERPRET_RUNTIME_ERROR; \ + } \ + \ + type b = AS_NUMBER(pop(vm)); \ + type a = AS_NUMBER(pop(vm)); \ + push(vm, valueType(a op b)); \ } while (false) #define STORE_FRAME frame->ip = ip @@ -992,6 +1000,11 @@ static DictuInterpretResult run(DictuVM *vm) { DISPATCH(); } + CASE_CODE(SUBTRACT): { + BINARY_OP(NUMBER_VAL, -, double); + DISPATCH(); + } + CASE_CODE(INCREMENT): { if (!IS_NUMBER(peek(vm, 0))) { RUNTIME_ERROR("Operand must be a number."); From c20d62a8b9a2395669b74a1f1c3bce0f88f48020 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Tue, 12 Jan 2021 18:07:21 +0000 Subject: [PATCH 32/41] Create a macro similar to binary op for operators that require function calls --- src/vm/value.c | 2 ++ src/vm/vm.c | 41 ++++++++++++++++++++++++----------------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/vm/value.c b/src/vm/value.c index 661c3dcc..a6dda8d3 100644 --- a/src/vm/value.c +++ b/src/vm/value.c @@ -439,6 +439,8 @@ char *valueTypeToString(DictuVM *vm, Value value, int *length) { } CONVERT(unknown, 7); +#undef CONVERT +#undef CONVERT_VARIABLE } void printValue(Value value) { diff --git a/src/vm/vm.c b/src/vm/vm.c index a4a2bd0c..90e0df39 100644 --- a/src/vm/vm.c +++ b/src/vm/vm.c @@ -613,6 +613,26 @@ static DictuInterpretResult run(DictuVM *vm) { #define READ_STRING() AS_STRING(READ_CONSTANT()) #define BINARY_OP(valueType, op, type) \ + do { \ + if (!IS_NUMBER(peek(vm, 0)) || !IS_NUMBER(peek(vm, 1))) { \ + int firstValLength = 0; \ + int secondValLength = 0; \ + char *firstVal = valueTypeToString(vm, peek(vm, 1), &firstValLength); \ + char *secondVal = valueTypeToString(vm, peek(vm, 0), &secondValLength); \ + \ + STORE_FRAME; \ + runtimeError(vm, "Unsupported operand types for "#op": '%s', '%s'", firstVal, secondVal); \ + FREE_ARRAY(vm, char, firstVal, firstValLength + 1); \ + FREE_ARRAY(vm, char, secondVal, secondValLength + 1); \ + return INTERPRET_RUNTIME_ERROR; \ + } \ + \ + type b = AS_NUMBER(pop(vm)); \ + type a = AS_NUMBER(pop(vm)); \ + push(vm, valueType(a op b)); \ + } while (false) + + #define BINARY_OP_FUNCTION(valueType, op, func, type) \ do { \ if (!IS_NUMBER(peek(vm, 0)) || !IS_NUMBER(peek(vm, 1))) { \ int firstValLength = 0; \ @@ -629,7 +649,7 @@ static DictuInterpretResult run(DictuVM *vm) { \ type b = AS_NUMBER(pop(vm)); \ type a = AS_NUMBER(pop(vm)); \ - push(vm, valueType(a op b)); \ + push(vm, valueType(func(a, b))); \ } while (false) #define STORE_FRAME frame->ip = ip @@ -1033,26 +1053,12 @@ static DictuInterpretResult run(DictuVM *vm) { DISPATCH(); CASE_CODE(POW): { - if (!IS_NUMBER(peek(vm, 0)) || !IS_NUMBER(peek(vm, 1))) { - RUNTIME_ERROR("Operands must be a numbers."); - } - - double b = AS_NUMBER(pop(vm)); - double a = AS_NUMBER(pop(vm)); - - push(vm, NUMBER_VAL(powf(a, b))); + BINARY_OP_FUNCTION(NUMBER_VAL, **, powf, double); DISPATCH(); } CASE_CODE(MOD): { - if (!IS_NUMBER(peek(vm, 0)) || !IS_NUMBER(peek(vm, 1))) { - RUNTIME_ERROR("Operands must be a numbers."); - } - - double b = AS_NUMBER(pop(vm)); - double a = AS_NUMBER(pop(vm)); - - push(vm, NUMBER_VAL(fmod(a, b))); + BINARY_OP_FUNCTION(NUMBER_VAL, **, fmod, double); DISPATCH(); } @@ -1761,6 +1767,7 @@ static DictuInterpretResult run(DictuVM *vm) { #undef READ_CONSTANT #undef READ_STRING #undef BINARY_OP +#undef BINARY_OP_FUNCTION #undef STORE_FRAME #undef RUNTIME_ERROR From 77b1268338c855ff78a3f11f884df6704dafa118 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Tue, 12 Jan 2021 19:26:59 +0000 Subject: [PATCH 33/41] Better error reporting for wrong properties on classes / instances --- src/vm/vm.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/vm/vm.c b/src/vm/vm.c index e17d5080..a8bf4173 100644 --- a/src/vm/vm.c +++ b/src/vm/vm.c @@ -869,7 +869,7 @@ static DictuInterpretResult run(DictuVM *vm) { klass = klass->superclass; } - RUNTIME_ERROR("Undefined property '%s'.", name->chars); + RUNTIME_ERROR("'%s' instance has no property: '%s'.", instance->klass->name->chars, name->chars); } else if (IS_MODULE(peek(vm, 0))) { ObjModule *module = AS_MODULE(peek(vm, 0)); ObjString *name = READ_STRING(); @@ -881,6 +881,8 @@ static DictuInterpretResult run(DictuVM *vm) { } } else if (IS_CLASS(peek(vm, 0))) { ObjClass *klass = AS_CLASS(peek(vm, 0)); + // Used to keep a reference to the class for the runtime error below + ObjClass *klassStore = klass; ObjString *name = READ_STRING(); Value value; @@ -893,9 +895,16 @@ static DictuInterpretResult run(DictuVM *vm) { klass = klass->superclass; } + + RUNTIME_ERROR("'%s' class has no property: '%s'.", klassStore->name->chars, name->chars); } - RUNTIME_ERROR("Only instances have properties."); + STORE_FRAME; + int valLength = 0; + char *val = valueTypeToString(vm, peek(vm, 0), &valLength); + runtimeError(vm, "'%s' type has no properties", val); + FREE_ARRAY(vm, char, val, valLength + 1); + return INTERPRET_RUNTIME_ERROR; } CASE_CODE(GET_PROPERTY_NO_POP): { @@ -927,7 +936,7 @@ static DictuInterpretResult run(DictuVM *vm) { klass = klass->superclass; } - RUNTIME_ERROR("Undefined property '%s'.", name->chars); + RUNTIME_ERROR("'%s' instance has no property: '%s'.", instance->klass->name->chars, name->chars); } CASE_CODE(SET_PROPERTY): { @@ -1061,7 +1070,12 @@ static DictuInterpretResult run(DictuVM *vm) { CASE_CODE(NEGATE): if (!IS_NUMBER(peek(vm, 0))) { - RUNTIME_ERROR("Operand must be a number."); + STORE_FRAME; + int valLength = 0; + char *val = valueTypeToString(vm, peek(vm, 0), &valLength); + runtimeError(vm, "Unsupported operand type for unary -: '%s'", val); + FREE_ARRAY(vm, char, val, valLength + 1); + return INTERPRET_RUNTIME_ERROR; } push(vm, NUMBER_VAL(-AS_NUMBER(pop(vm)))); From 9f16bd5c98eb7d7204ce2f9c1e328c5b867cff68 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Tue, 12 Jan 2021 19:43:35 +0000 Subject: [PATCH 34/41] Better error reporting for missing module property. Fixed an issue with importing in the repl --- src/vm/util.c | 5 +++++ src/vm/vm.c | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/vm/util.c b/src/vm/util.c index 5d7a1edf..aa7e9de6 100644 --- a/src/vm/util.c +++ b/src/vm/util.c @@ -101,6 +101,11 @@ ObjString *getDirectory(DictuVM *vm, char *source) { runtimeError(vm, "Unable to resolve path '%s'", source); exit(1); } + + if (vm->repl) { + return copyString(vm, res, strlen(res)); + } + return dirname(vm, res, strlen(res)); } diff --git a/src/vm/vm.c b/src/vm/vm.c index a8bf4173..93e50e9a 100644 --- a/src/vm/vm.c +++ b/src/vm/vm.c @@ -879,6 +879,8 @@ static DictuInterpretResult run(DictuVM *vm) { push(vm, value); DISPATCH(); } + + RUNTIME_ERROR("'%s' module has no property: '%s'.", module->name->chars, name->chars); } else if (IS_CLASS(peek(vm, 0))) { ObjClass *klass = AS_CLASS(peek(vm, 0)); // Used to keep a reference to the class for the runtime error below From 95db53a4e2c1bed3fda855f159b569ea2e65cc63 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Tue, 12 Jan 2021 20:04:09 +0000 Subject: [PATCH 35/41] New opcode for setting class variables as previously it would return the class --- src/vm/compiler.c | 3 ++- src/vm/debug.c | 2 ++ src/vm/opcodes.h | 1 + src/vm/vm.c | 17 ++++++++++++++++- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/vm/compiler.c b/src/vm/compiler.c index c4db123b..5fe520b0 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -1459,7 +1459,7 @@ static void parseClassBody(Compiler *compiler) { consume(compiler, TOKEN_EQUAL, "Expect '=' after expression."); expression(compiler); - emitBytes(compiler, OP_SET_PROPERTY, name); + emitBytes(compiler, OP_SET_CLASS_VAR, name); consume(compiler, TOKEN_SEMICOLON, "Expect ';' after variable declaration."); } else { @@ -1693,6 +1693,7 @@ static int getArgCount(uint8_t code, const ValueArray constants, int ip) { case OP_GET_PROPERTY: case OP_GET_PROPERTY_NO_POP: case OP_SET_PROPERTY: + case OP_SET_CLASS_VAR: case OP_SET_INIT_PROPERTIES: case OP_GET_SUPER: case OP_CALL: diff --git a/src/vm/debug.c b/src/vm/debug.c index beda6603..4a705222 100644 --- a/src/vm/debug.c +++ b/src/vm/debug.c @@ -151,6 +151,8 @@ int disassembleInstruction(Chunk *chunk, int offset) { return constantInstruction("OP_GET_PROPERTY_NO_POP", chunk, offset); case OP_SET_PROPERTY: return constantInstruction("OP_SET_PROPERTY", chunk, offset); + case OP_SET_CLASS_VAR: + return constantInstruction("OP_SET_CLASS_VAR", chunk, offset); case OP_SET_INIT_PROPERTIES: return constantInstruction("OP_SET_INIT_PROPERTIES", chunk, offset); case OP_GET_SUPER: diff --git a/src/vm/opcodes.h b/src/vm/opcodes.h index 17313ae0..2e1ab2a1 100644 --- a/src/vm/opcodes.h +++ b/src/vm/opcodes.h @@ -22,6 +22,7 @@ OPCODE(SET_UPVALUE) OPCODE(GET_PROPERTY) OPCODE(GET_PROPERTY_NO_POP) OPCODE(SET_PROPERTY) +OPCODE(SET_CLASS_VAR) OPCODE(SET_INIT_PROPERTIES) OPCODE(GET_SUPER) OPCODE(EQUAL) diff --git a/src/vm/vm.c b/src/vm/vm.c index 93e50e9a..33fb76f0 100644 --- a/src/vm/vm.c +++ b/src/vm/vm.c @@ -953,10 +953,25 @@ static DictuInterpretResult run(DictuVM *vm) { ObjClass *klass = AS_CLASS(peek(vm, 1)); tableSet(vm, &klass->properties, READ_STRING(), peek(vm, 0)); pop(vm); + pop(vm); + push(vm, NIL_VAL); DISPATCH(); } - RUNTIME_ERROR("Only instances have properties."); + STORE_FRAME; + int valLength = 0; + char *val = valueTypeToString(vm, peek(vm, 1), &valLength); + runtimeError(vm, "Can not set property on type '%s'", val); + FREE_ARRAY(vm, char, val, valLength + 1); + return INTERPRET_RUNTIME_ERROR; + } + + CASE_CODE(SET_CLASS_VAR): { + // No type check required as this opcode is only ever emitted when parsing a class + ObjClass *klass = AS_CLASS(peek(vm, 1)); + tableSet(vm, &klass->properties, READ_STRING(), peek(vm, 0)); + pop(vm); + DISPATCH(); } CASE_CODE(SET_INIT_PROPERTIES): { From 87b66bc3544fff69ec6f8cd35f26719cfce9a03d Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Tue, 12 Jan 2021 23:13:09 +0000 Subject: [PATCH 36/41] Improve error messages for subscripting --- src/vm/vm.c | 63 ++++++++++++++++++----------------------------------- 1 file changed, 21 insertions(+), 42 deletions(-) diff --git a/src/vm/vm.c b/src/vm/vm.c index 33fb76f0..181b257c 100644 --- a/src/vm/vm.c +++ b/src/vm/vm.c @@ -649,7 +649,7 @@ static DictuInterpretResult run(DictuVM *vm) { \ type b = AS_NUMBER(pop(vm)); \ type a = AS_NUMBER(pop(vm)); \ - push(vm, valueType(func(a, b))); \ + push(vm, valueType(func(a, b))); \ } while (false) #define STORE_FRAME frame->ip = ip @@ -661,6 +661,16 @@ static DictuInterpretResult run(DictuVM *vm) { return INTERPRET_RUNTIME_ERROR; \ } while (0) + #define RUNTIME_ERROR_TYPE(error, distance) \ + do { \ + STORE_FRAME; \ + int valLength = 0; \ + char *val = valueTypeToString(vm, peek(vm, distance), &valLength); \ + runtimeError(vm, error, val); \ + FREE_ARRAY(vm, char, val, valLength + 1); \ + return INTERPRET_RUNTIME_ERROR; \ + } while (0) + #ifdef COMPUTED_GOTO static void* dispatchTable[] = { @@ -901,12 +911,7 @@ static DictuInterpretResult run(DictuVM *vm) { RUNTIME_ERROR("'%s' class has no property: '%s'.", klassStore->name->chars, name->chars); } - STORE_FRAME; - int valLength = 0; - char *val = valueTypeToString(vm, peek(vm, 0), &valLength); - runtimeError(vm, "'%s' type has no properties", val); - FREE_ARRAY(vm, char, val, valLength + 1); - return INTERPRET_RUNTIME_ERROR; + RUNTIME_ERROR_TYPE("'%s' type has no properties", 0); } CASE_CODE(GET_PROPERTY_NO_POP): { @@ -958,12 +963,7 @@ static DictuInterpretResult run(DictuVM *vm) { DISPATCH(); } - STORE_FRAME; - int valLength = 0; - char *val = valueTypeToString(vm, peek(vm, 1), &valLength); - runtimeError(vm, "Can not set property on type '%s'", val); - FREE_ARRAY(vm, char, val, valLength + 1); - return INTERPRET_RUNTIME_ERROR; + RUNTIME_ERROR_TYPE("Can not set property on type '%s'", 1); } CASE_CODE(SET_CLASS_VAR): { @@ -1087,12 +1087,7 @@ static DictuInterpretResult run(DictuVM *vm) { CASE_CODE(NEGATE): if (!IS_NUMBER(peek(vm, 0))) { - STORE_FRAME; - int valLength = 0; - char *val = valueTypeToString(vm, peek(vm, 0), &valLength); - runtimeError(vm, "Unsupported operand type for unary -: '%s'", val); - FREE_ARRAY(vm, char, val, valLength + 1); - return INTERPRET_RUNTIME_ERROR; + RUNTIME_ERROR_TYPE("Unsupported operand type for unary -: '%s'", 0); } push(vm, NUMBER_VAL(-AS_NUMBER(pop(vm)))); @@ -1309,11 +1304,7 @@ static DictuInterpretResult run(DictuVM *vm) { Value subscriptValue = peek(vm, 1); if (!IS_OBJ(subscriptValue)) { - if (IS_RESULT(subscriptValue)) { - RUNTIME_ERROR("Can only subscript on lists, strings or dictionaries not Result, don't forget to .unwrap()."); - } - - RUNTIME_ERROR("Can only subscript on lists, strings or dictionaries."); + RUNTIME_ERROR_TYPE("'%s' is not subscriptable", 1); } switch (getObjType(subscriptValue)) { @@ -1374,12 +1365,8 @@ static DictuInterpretResult run(DictuVM *vm) { RUNTIME_ERROR("Key %s does not exist within dictionary.", valueToString(indexValue)); } - case OBJ_RESULT: { - RUNTIME_ERROR("Can only subscript on lists, strings or dictionaries not Result, don't forget to .unwrap()."); - } - default: { - RUNTIME_ERROR("Can only subscript on lists, strings or dictionaries."); + RUNTIME_ERROR_TYPE("'%s' is not subscriptable", 1); } } } @@ -1390,11 +1377,7 @@ static DictuInterpretResult run(DictuVM *vm) { Value subscriptValue = peek(vm, 2); if (!IS_OBJ(subscriptValue)) { - if (IS_RESULT(subscriptValue)) { - RUNTIME_ERROR("Can only subscript on lists, strings or dictionaries not Result, don't forget to .unwrap()."); - } - - RUNTIME_ERROR("Can only subscript on lists, strings or dictionaries."); + RUNTIME_ERROR_TYPE("'%s' does not support item assignment", 2); } switch (getObjType(subscriptValue)) { @@ -1436,7 +1419,7 @@ static DictuInterpretResult run(DictuVM *vm) { } default: { - RUNTIME_ERROR("Only lists and dictionaries support subscript assignment."); + RUNTIME_ERROR_TYPE("'%s' does not support item assignment", 2); } } } @@ -1521,7 +1504,7 @@ static DictuInterpretResult run(DictuVM *vm) { } default: { - RUNTIME_ERROR("Can only slice on lists and strings."); + RUNTIME_ERROR_TYPE("'%s' does not support item assignment", 2); } } @@ -1539,11 +1522,7 @@ static DictuInterpretResult run(DictuVM *vm) { Value subscriptValue = peek(vm, 2); if (!IS_OBJ(subscriptValue)) { - if (IS_RESULT(subscriptValue)) { - RUNTIME_ERROR("Can only subscript on lists, strings or dictionaries not Result, don't forget to .unwrap()."); - } - - RUNTIME_ERROR("Can only subscript on lists, strings or dictionaries."); + RUNTIME_ERROR_TYPE("'%s' does not support item assignment", 2); } switch (getObjType(subscriptValue)) { @@ -1586,7 +1565,7 @@ static DictuInterpretResult run(DictuVM *vm) { } default: { - RUNTIME_ERROR("Only lists and dictionaries support subscript assignment."); + RUNTIME_ERROR_TYPE("'%s' does not support item assignment", 2); } } DISPATCH(); From 6584841c09c4d238e3802811c0565ea68346e35d Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Tue, 12 Jan 2021 23:17:56 +0000 Subject: [PATCH 37/41] Rename opcode from PUSH to SUBSCRIPT_PUSH PUSH as an opcode gives the impression of pushing a value to the stack, when in reality it's used for pushing values from a list/dict when using mutating operators such as +=, -= and so on --- src/vm/compiler.c | 17 ++++--- src/vm/debug.c | 4 +- src/vm/opcodes.h | 2 +- src/vm/vm.c | 110 +++++++++++++++++++++++----------------------- 4 files changed, 66 insertions(+), 67 deletions(-) diff --git a/src/vm/compiler.c b/src/vm/compiler.c index 5fe520b0..63629f06 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -1019,32 +1019,31 @@ static void subscript(Compiler *compiler, Token previousToken, bool canAssign) { emitByte(compiler, OP_SUBSCRIPT_ASSIGN); } else if (canAssign && match(compiler, TOKEN_PLUS_EQUALS)) { expression(compiler); - emitBytes(compiler, OP_PUSH, OP_ADD); + emitBytes(compiler, OP_SUBSCRIPT_PUSH, OP_ADD); emitByte(compiler, OP_SUBSCRIPT_ASSIGN); } else if (canAssign && match(compiler, TOKEN_MINUS_EQUALS)) { expression(compiler); - emitByte(compiler, OP_PUSH); - emitByte(compiler, OP_SUBTRACT); + emitBytes(compiler, OP_SUBSCRIPT_PUSH, OP_SUBTRACT); emitByte(compiler, OP_SUBSCRIPT_ASSIGN); } else if (canAssign && match(compiler, TOKEN_MULTIPLY_EQUALS)) { expression(compiler); - emitBytes(compiler, OP_PUSH, OP_MULTIPLY); + emitBytes(compiler, OP_SUBSCRIPT_PUSH, OP_MULTIPLY); emitByte(compiler, OP_SUBSCRIPT_ASSIGN); } else if (canAssign && match(compiler, TOKEN_DIVIDE_EQUALS)) { expression(compiler); - emitBytes(compiler, OP_PUSH, OP_DIVIDE); + emitBytes(compiler, OP_SUBSCRIPT_PUSH, OP_DIVIDE); emitByte(compiler, OP_SUBSCRIPT_ASSIGN); } else if (canAssign && match(compiler, TOKEN_AMPERSAND_EQUALS)) { expression(compiler); - emitBytes(compiler, OP_PUSH, OP_BITWISE_AND); + emitBytes(compiler, OP_SUBSCRIPT_PUSH, OP_BITWISE_AND); emitByte(compiler, OP_SUBSCRIPT_ASSIGN); } else if (canAssign && match(compiler, TOKEN_CARET_EQUALS)) { expression(compiler); - emitBytes(compiler, OP_PUSH, OP_BITWISE_XOR); + emitBytes(compiler, OP_SUBSCRIPT_PUSH, OP_BITWISE_XOR); emitByte(compiler, OP_SUBSCRIPT_ASSIGN); } else if (canAssign && match(compiler, TOKEN_PIPE_EQUALS)) { expression(compiler); - emitBytes(compiler, OP_PUSH, OP_BITWISE_OR); + emitBytes(compiler, OP_SUBSCRIPT_PUSH, OP_BITWISE_OR); emitByte(compiler, OP_SUBSCRIPT_ASSIGN); } else { emitByte(compiler, OP_SUBSCRIPT); @@ -1651,8 +1650,8 @@ static int getArgCount(uint8_t code, const ValueArray constants, int ip) { case OP_FALSE: case OP_SUBSCRIPT: case OP_SUBSCRIPT_ASSIGN: + case OP_SUBSCRIPT_PUSH: case OP_SLICE: - case OP_PUSH: case OP_POP: case OP_EQUAL: case OP_GREATER: diff --git a/src/vm/debug.c b/src/vm/debug.c index 4a705222..958b9ff1 100644 --- a/src/vm/debug.c +++ b/src/vm/debug.c @@ -215,8 +215,8 @@ int disassembleInstruction(Chunk *chunk, int offset) { return simpleInstruction("OP_SUBSCRIPT_ASSIGN", offset); case OP_SLICE: return simpleInstruction("OP_SLICE", offset); - case OP_PUSH: - return simpleInstruction("OP_PUSH", offset); + case OP_SUBSCRIPT_PUSH: + return simpleInstruction("OP_SUBSCRIPT_PUSH", offset); case OP_NEW_DICT: return byteInstruction("OP_NEW_DICT", chunk, offset); case OP_CALL: diff --git a/src/vm/opcodes.h b/src/vm/opcodes.h index 2e1ab2a1..0a74e313 100644 --- a/src/vm/opcodes.h +++ b/src/vm/opcodes.h @@ -7,8 +7,8 @@ OPCODE(UNPACK_LIST) OPCODE(NEW_DICT) OPCODE(SUBSCRIPT) OPCODE(SUBSCRIPT_ASSIGN) +OPCODE(SUBSCRIPT_PUSH) OPCODE(SLICE) -OPCODE(PUSH) OPCODE(POP) OPCODE(GET_LOCAL) OPCODE(SET_LOCAL) diff --git a/src/vm/vm.c b/src/vm/vm.c index 181b257c..893107a2 100644 --- a/src/vm/vm.c +++ b/src/vm/vm.c @@ -1424,6 +1424,61 @@ static DictuInterpretResult run(DictuVM *vm) { } } + CASE_CODE(SUBSCRIPT_PUSH): { + Value value = peek(vm, 0); + Value indexValue = peek(vm, 1); + Value subscriptValue = peek(vm, 2); + + if (!IS_OBJ(subscriptValue)) { + RUNTIME_ERROR_TYPE("'%s' does not support item assignment", 2); + } + + switch (getObjType(subscriptValue)) { + case OBJ_LIST: { + if (!IS_NUMBER(indexValue)) { + RUNTIME_ERROR("List index must be a number."); + } + + ObjList *list = AS_LIST(subscriptValue); + int index = AS_NUMBER(indexValue); + + // Allow negative indexes + if (index < 0) + index = list->values.count + index; + + if (index >= 0 && index < list->values.count) { + vm->stackTop[-1] = list->values.values[index]; + push(vm, value); + DISPATCH(); + } + + RUNTIME_ERROR("List index out of bounds."); + } + + case OBJ_DICT: { + ObjDict *dict = AS_DICT(subscriptValue); + if (!isValidKey(indexValue)) { + RUNTIME_ERROR("Dictionary key must be an immutable type."); + } + + Value dictValue; + if (!dictGet(dict, indexValue, &dictValue)) { + RUNTIME_ERROR("Key %s does not exist within dictionary.", valueToString(indexValue)); + } + + vm->stackTop[-1] = dictValue; + push(vm, value); + + DISPATCH(); + } + + default: { + RUNTIME_ERROR_TYPE("'%s' does not support item assignment", 2); + } + } + DISPATCH(); + } + CASE_CODE(SLICE): { Value sliceEndIndex = peek(vm, 0); Value sliceStartIndex = peek(vm, 1); @@ -1516,61 +1571,6 @@ static DictuInterpretResult run(DictuVM *vm) { DISPATCH(); } - CASE_CODE(PUSH): { - Value value = peek(vm, 0); - Value indexValue = peek(vm, 1); - Value subscriptValue = peek(vm, 2); - - if (!IS_OBJ(subscriptValue)) { - RUNTIME_ERROR_TYPE("'%s' does not support item assignment", 2); - } - - switch (getObjType(subscriptValue)) { - case OBJ_LIST: { - if (!IS_NUMBER(indexValue)) { - RUNTIME_ERROR("List index must be a number."); - } - - ObjList *list = AS_LIST(subscriptValue); - int index = AS_NUMBER(indexValue); - - // Allow negative indexes - if (index < 0) - index = list->values.count + index; - - if (index >= 0 && index < list->values.count) { - vm->stackTop[-1] = list->values.values[index]; - push(vm, value); - DISPATCH(); - } - - RUNTIME_ERROR("List index out of bounds."); - } - - case OBJ_DICT: { - ObjDict *dict = AS_DICT(subscriptValue); - if (!isValidKey(indexValue)) { - RUNTIME_ERROR("Dictionary key must be an immutable type."); - } - - Value dictValue; - if (!dictGet(dict, indexValue, &dictValue)) { - RUNTIME_ERROR("Key %s does not exist within dictionary.", valueToString(indexValue)); - } - - vm->stackTop[-1] = dictValue; - push(vm, value); - - DISPATCH(); - } - - default: { - RUNTIME_ERROR_TYPE("'%s' does not support item assignment", 2); - } - } - DISPATCH(); - } - CASE_CODE(CALL): { int argCount = READ_BYTE(); frame->ip = ip; From c19fa684903432457a733bdce414c9e5b8014666 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Wed, 13 Jan 2021 22:04:42 +0000 Subject: [PATCH 38/41] Dark / light mode based on CSS rather than JS --- docs/_includes/css/just-the-docs.scss.liquid | 8 +++++- docs/_includes/head.html | 29 +++++++------------- docs/_sass/custom/custom.scss | 8 ++++-- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/docs/_includes/css/just-the-docs.scss.liquid b/docs/_includes/css/just-the-docs.scss.liquid index 495cd6dd..f8552e47 100755 --- a/docs/_includes/css/just-the-docs.scss.liquid +++ b/docs/_includes/css/just-the-docs.scss.liquid @@ -2,6 +2,12 @@ $logo: "{{ site.logo | absolute_url }}"; {% endif %} @import "./support/support"; -@import "./color_schemes/{{ include.color_scheme }}"; +@import "./color_schemes/light"; +@import "./color_schemes/dictu"; @import "./modules"; +@media (prefers-color-scheme: dark) { + @import "./color_schemes/dark"; + @import "./color_schemes/dictu"; + @import "./modules"; +} {% include css/custom.scss.liquid %} diff --git a/docs/_includes/head.html b/docs/_includes/head.html index 585d8147..90673863 100755 --- a/docs/_includes/head.html +++ b/docs/_includes/head.html @@ -15,27 +15,18 @@ diff --git a/docs/_sass/custom/custom.scss b/docs/_sass/custom/custom.scss index b4769b09..a5c33b73 100755 --- a/docs/_sass/custom/custom.scss +++ b/docs/_sass/custom/custom.scss @@ -18,15 +18,19 @@ body { background-size: auto 20px; } -.dark-mode { +@media (prefers-color-scheme: dark) { .site-logo { filter: invert(1); } - a { + a, a.btn { color: #ffffff; } + .nav-list-expander { + color: #2c84fa !important; + } + .btn.btn-primary { background-color: #767676; background-image: linear-gradient(#858585, #767676); From 14a740b2c7988552f269bf1f095a15f2e03cf98d Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Wed, 13 Jan 2021 22:31:41 +0000 Subject: [PATCH 39/41] Correctly handle charset and update default anchor tag colour --- docs/_includes/css/just-the-docs.scss.liquid | 3 +-- docs/_sass/color_schemes/dictu.scss | 2 -- docs/_sass/content.scss | 2 -- docs/_sass/custom/custom.scss | 10 +++++----- 4 files changed, 6 insertions(+), 11 deletions(-) delete mode 100644 docs/_sass/color_schemes/dictu.scss mode change 100755 => 100644 docs/_sass/custom/custom.scss diff --git a/docs/_includes/css/just-the-docs.scss.liquid b/docs/_includes/css/just-the-docs.scss.liquid index f8552e47..e9f8a0d8 100755 --- a/docs/_includes/css/just-the-docs.scss.liquid +++ b/docs/_includes/css/just-the-docs.scss.liquid @@ -1,13 +1,12 @@ +@charset "UTF-8"; {% if site.logo %} $logo: "{{ site.logo | absolute_url }}"; {% endif %} @import "./support/support"; @import "./color_schemes/light"; -@import "./color_schemes/dictu"; @import "./modules"; @media (prefers-color-scheme: dark) { @import "./color_schemes/dark"; - @import "./color_schemes/dictu"; @import "./modules"; } {% include css/custom.scss.liquid %} diff --git a/docs/_sass/color_schemes/dictu.scss b/docs/_sass/color_schemes/dictu.scss deleted file mode 100644 index e0e45d32..00000000 --- a/docs/_sass/color_schemes/dictu.scss +++ /dev/null @@ -1,2 +0,0 @@ -$link-color: #000000; -$btn-primary-color: #808080; \ No newline at end of file diff --git a/docs/_sass/content.scss b/docs/_sass/content.scss index bc906edd..e43d4fcf 100755 --- a/docs/_sass/content.scss +++ b/docs/_sass/content.scss @@ -1,5 +1,3 @@ -@charset "UTF-8"; - // // Styles for rendered markdown in the .main-content container // diff --git a/docs/_sass/custom/custom.scss b/docs/_sass/custom/custom.scss old mode 100755 new mode 100644 index a5c33b73..8186b3b2 --- a/docs/_sass/custom/custom.scss +++ b/docs/_sass/custom/custom.scss @@ -18,19 +18,19 @@ body { background-size: auto 20px; } +.nav-list-link { + color: #000000; +} + @media (prefers-color-scheme: dark) { .site-logo { filter: invert(1); } - a, a.btn { + .nav-list-link { color: #ffffff; } - .nav-list-expander { - color: #2c84fa !important; - } - .btn.btn-primary { background-color: #767676; background-image: linear-gradient(#858585, #767676); From 17183b467998e0bfbe9c8bda40bd19b036b67643 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Wed, 13 Jan 2021 23:08:24 +0000 Subject: [PATCH 40/41] Separate function calls out into two stages --- src/vm/natives.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vm/natives.c b/src/vm/natives.c index 752ca9db..80e7abd7 100644 --- a/src/vm/natives.c +++ b/src/vm/natives.c @@ -14,7 +14,8 @@ static Value typeNative(DictuVM *vm, int argCount, Value *args) { } int length = 0; - return OBJ_VAL(takeString(vm, valueTypeToString(vm, args[0], &length), length)); + char *type = valueTypeToString(vm, args[0], &length); + return OBJ_VAL(takeString(vm, type, length)); } static Value setNative(DictuVM *vm, int argCount, Value *args) { From 1c6caafb6f08f8846a2d7fb0c8655a0af5c46f1f Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Wed, 13 Jan 2021 23:26:51 +0000 Subject: [PATCH 41/41] Bump version to 0.16.0 --- docs/_config.yml | 2 +- src/include/dictu_include.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/_config.yml b/docs/_config.yml index 00c3aec6..05d5d70a 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -5,7 +5,7 @@ description: >- color_scheme: "dictu" # Custom theme logo: "/assets/images/dictu-logo/dictu-wordmark.svg" -version: "0.15.0" +version: "0.16.0" github_username: dictu-lang search_enabled: true diff --git a/src/include/dictu_include.h b/src/include/dictu_include.h index b2d63504..0565804e 100644 --- a/src/include/dictu_include.h +++ b/src/include/dictu_include.h @@ -4,7 +4,7 @@ #include #define DICTU_MAJOR_VERSION "0" -#define DICTU_MINOR_VERSION "15" +#define DICTU_MINOR_VERSION "16" #define DICTU_PATCH_VERSION "0" #define DICTU_STRING_VERSION "Dictu Version: " DICTU_MAJOR_VERSION "." DICTU_MINOR_VERSION "." DICTU_PATCH_VERSION "\n"