From 70f8e79e93cbbc71212928f20a07006ffc999b80 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Wed, 9 Nov 2022 18:37:38 -0700 Subject: [PATCH 001/109] update http client timeout and client example Signed-off-by: Brian Downs --- examples/httpClient.du | 8 +++----- src/optionals/http/http.c | 4 ++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/examples/httpClient.du b/examples/httpClient.du index 22eacf4f..8cd6b232 100644 --- a/examples/httpClient.du +++ b/examples/httpClient.du @@ -27,15 +27,13 @@ import System; print(res.unwrap().json().unwrap()); httpClient.setInsecure(true); - // res = httpClient.get("https://httpbin.org/uuid"); - res = httpClient.get("https://localhost:4433"); + res = httpClient.get("https://httpbin.org/uuid"); if (not res.success()) { print(res.unwrapError()); System.exit(1); } - print(res.unwrap().content); - // const uuid = res.unwrap().json().unwrap()["uuid"]; - // print("UUID: {}".format(uuid)); + const uuid = res.unwrap().json().unwrap()["uuid"]; + print("UUID: {}".format(uuid)); res = httpClient.post("https://httpbin.org/post", {}); if (not res.success()) { diff --git a/src/optionals/http/http.c b/src/optionals/http/http.c index 60d3c26c..6ccdf5b9 100644 --- a/src/optionals/http/http.c +++ b/src/optionals/http/http.c @@ -651,7 +651,7 @@ static Value httpClientSetTimeout(DictuVM *vm, int argCount, Value *args) { HttpClient *httpClient = AS_HTTP_CLIENT(args[0]); - curl_easy_setopt(httpClient->curl, CURLOPT_TIMEOUT, AS_NUMBER(args[1])); + curl_easy_setopt(httpClient->curl, CURLOPT_TIMEOUT, (long)AS_NUMBER(args[1])); return NIL_VAL; } @@ -915,7 +915,7 @@ ObjAbstract *newHttpClient(DictuVM *vm, ObjDict *opts) { return abstract; } - curl_easy_setopt(httpClient->curl, CURLOPT_TIMEOUT, AS_NUMBER(entry->value)); + curl_easy_setopt(httpClient->curl, CURLOPT_TIMEOUT, (long)AS_NUMBER(entry->value)); } else if (strstr(key, "headers")) { if (IS_EMPTY(entry->value)) { continue; From f9be608ad50cae108c145297cd9d29f72df1a03c Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 11 Nov 2022 17:12:55 -0700 Subject: [PATCH 002/109] add support for adding shebang to scripts Signed-off-by: Brian Downs --- README.md | 4 ++-- docs/docs/getting-started.md | 5 +++++ examples/httpClient.du | 2 ++ examples/shebang.du | 3 +++ src/vm/scanner.c | 7 ++++++- 5 files changed, 18 insertions(+), 3 deletions(-) create mode 100755 examples/shebang.du diff --git a/README.md b/README.md index 7592b9d7..261cd66f 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Documentation for Dictu can be found [here](https://dictu-lang.com/) [![CI](https://github.com/Jason2605/Dictu/workflows/CI/badge.svg)](https://github.com/Jason2605/Dictu/actions) ## Example programs -```js +```cs import System; const guess = 10; @@ -36,7 +36,7 @@ while { } ``` -```js +```cs def fibonacci(num) { if (num < 2) { return num; diff --git a/docs/docs/getting-started.md b/docs/docs/getting-started.md index e185ced7..e46a8d63 100644 --- a/docs/docs/getting-started.md +++ b/docs/docs/getting-started.md @@ -33,6 +33,11 @@ Hello World! As you see, `Hello, World!` is printed to the screen. You have just created your first Dictu program, now onto bigger and better things! +The `hello-world.du` file can be called directly from the shell. 2 things are required: + +* Make the file file executable: `chmod +x hello-world.du` +* Add a shebang line to the top of the script: `#!/usr/bin/env dictu` + ## REPL The REPL is a language shell, allowing you to run code on the fly via your terminal. This is very useful for debugging, or small little checks you may want to perform. When you enter the REPL, there is a magic variable `_` which stores the last returned value (`nil` excluded). diff --git a/examples/httpClient.du b/examples/httpClient.du index 8cd6b232..3cd88927 100644 --- a/examples/httpClient.du +++ b/examples/httpClient.du @@ -1,3 +1,5 @@ +#!/usr/local/bin/dictu + import HTTP; import JSON; import System; diff --git a/examples/shebang.du b/examples/shebang.du new file mode 100755 index 00000000..64840908 --- /dev/null +++ b/examples/shebang.du @@ -0,0 +1,3 @@ +#!/usr/bin/env dictu + +print("worked!"); \ No newline at end of file diff --git a/src/vm/scanner.c b/src/vm/scanner.c index 251b1b14..a9776de6 100644 --- a/src/vm/scanner.c +++ b/src/vm/scanner.c @@ -82,7 +82,12 @@ static void skipWhitespace(Scanner *scanner) { scanner->line++; advance(scanner); break; - + case '#': + if (peekNext(scanner) == '!') { + // Ignore shebang line + while (peek(scanner) != '\n' && !isAtEnd(scanner)) advance(scanner); + } + break; case '/': if (peekNext(scanner) == '*') { // Multiline comments From 039f8a410faf6f47510d534149fbc99a5f744698 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 11 Nov 2022 17:14:17 -0700 Subject: [PATCH 003/109] remove shebang Signed-off-by: Brian Downs --- examples/httpClient.du | 2 -- examples/shebang.du | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/httpClient.du b/examples/httpClient.du index 3cd88927..8cd6b232 100644 --- a/examples/httpClient.du +++ b/examples/httpClient.du @@ -1,5 +1,3 @@ -#!/usr/local/bin/dictu - import HTTP; import JSON; import System; diff --git a/examples/shebang.du b/examples/shebang.du index 64840908..431b0e03 100755 --- a/examples/shebang.du +++ b/examples/shebang.du @@ -1,3 +1,3 @@ #!/usr/bin/env dictu -print("worked!"); \ No newline at end of file +print("worked!"); From 510eee3c8ab03048814cf6ad869d16e3e9ea4298 Mon Sep 17 00:00:00 2001 From: Jason Hall Date: Tue, 15 Nov 2022 20:41:30 +0000 Subject: [PATCH 004/109] Stop at non-option argparse --- src/cli/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/main.c b/src/cli/main.c index 7f962d58..4972cc13 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -221,7 +221,7 @@ int main(int argc, char *argv[]) { }; struct argparse argparse; - argparse_init(&argparse, options, usage, 0); + argparse_init(&argparse, options, usage, ARGPARSE_STOP_AT_NON_OPTION); argc = argparse_parse(&argparse, argc, (const char **)argv); if (version) { From c1231e3c4981e245b867eac926cf1711c3e60dbb Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 15 Nov 2022 21:47:53 -0700 Subject: [PATCH 005/109] add follow redirects to http Signed-off-by: Brian Downs --- src/optionals/http/http.c | 39 +++++++++++++++++++++++++++++++++++++++ tests/http/client.du | 5 +++++ 2 files changed, 44 insertions(+) diff --git a/src/optionals/http/http.c b/src/optionals/http/http.c index 6ccdf5b9..8f8c7daa 100644 --- a/src/optionals/http/http.c +++ b/src/optionals/http/http.c @@ -466,6 +466,7 @@ static Value get(DictuVM *vm, int argCount, Value *args) { curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, writeHeaders); curl_easy_setopt(curl, CURLOPT_HEADERDATA, &response); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); /* Perform the request, res will get the return code */ curlResponse = curl_easy_perform(curl); @@ -579,6 +580,7 @@ static Value post(DictuVM *vm, int argCount, Value *args) { curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, writeHeaders); curl_easy_setopt(curl, CURLOPT_HEADERDATA, &response); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); /* Perform the request, res will get the return code */ curlResponse = curl_easy_perform(curl); @@ -682,6 +684,28 @@ static Value httpClientSetInsecure(DictuVM *vm, int argCount, Value *args) { return NIL_VAL; } +static Value httpClientSetFollowRedirects(DictuVM *vm, int argCount, Value *args) { + if (argCount != 1) { + runtimeError(vm, "setFollowRedirects() takes 1 argument (%d given).", argCount); + return EMPTY_VAL; + } + + if (!IS_BOOL(args[1])) { + runtimeError(vm, "setFollowRedirects value must be a bool"); + return EMPTY_VAL; + } + + HttpClient *httpClient = AS_HTTP_CLIENT(args[0]); + + if (AS_BOOL(args[1])) { + curl_easy_setopt(httpClient->curl, CURLOPT_FOLLOWLOCATION, 1L); + } else { + curl_easy_setopt(httpClient->curl, CURLOPT_FOLLOWLOCATION, 0L); + } + + return NIL_VAL; +} + static Value httpClientSetHeaders(DictuVM *vm, int argCount, Value *args) { if (argCount != 1) { runtimeError(vm, "setHeaders() takes 1 argument (%d given).", argCount); @@ -888,6 +912,7 @@ ObjAbstract *newHttpClient(DictuVM *vm, ObjDict *opts) { httpClient->curl = curl_easy_init(); curl_easy_setopt(httpClient->curl, CURLOPT_HEADERFUNCTION, writeHeaders); + curl_easy_setopt(httpClient->curl, CURLOPT_FOLLOWLOCATION, 1L); if (opts->count != 0) { for (int i = 0; i <= opts->capacityMask; i++) { @@ -948,6 +973,19 @@ ObjAbstract *newHttpClient(DictuVM *vm, ObjDict *opts) { curl_easy_setopt(httpClient->curl, CURLOPT_SSL_VERIFYHOST, 0); curl_easy_setopt(httpClient->curl, CURLOPT_SSL_VERIFYPEER, 0); } + } else if (strstr(key, "follow_redirects")) { + if (IS_EMPTY(entry->value)) { + continue; + } + + if (!IS_BOOL(entry->value)) { + runtimeError(vm, "HTTP client option \"follow_redirects\" value must be a bool"); + return abstract; + } + + if (!AS_BOOL(entry->value)) { + curl_easy_setopt(httpClient->curl, CURLOPT_FOLLOWLOCATION, 0L); + } } else if (strstr(key, "keyFile")) { if (IS_EMPTY(entry->value)) { continue; @@ -1010,6 +1048,7 @@ ObjAbstract *newHttpClient(DictuVM *vm, ObjDict *opts) { defineNative(vm, &abstract->values, "setTimeout", httpClientSetTimeout); defineNative(vm, &abstract->values, "setHeaders", httpClientSetHeaders); defineNative(vm, &abstract->values, "setInsecure", httpClientSetInsecure); + defineNative(vm, &abstract->values, "setFollowRedirects", httpClientSetFollowRedirects); defineNative(vm, &abstract->values, "setKeyFile", httpClientSetKeyFile); defineNative(vm, &abstract->values, "setCertFile", httpClientSetCertFile); defineNative(vm, &abstract->values, "setKeyPass", httpClientSetKeyPass); diff --git a/tests/http/client.du b/tests/http/client.du index 9b2e0c73..3a63fe05 100644 --- a/tests/http/client.du +++ b/tests/http/client.du @@ -39,6 +39,11 @@ class TestHttpClient < UnitTest { this.assertNil(ret); } + testSetFollowRedirects() { + const ret = this.httpClient.setFollowRedirects(false); + this.assertNil(ret); + } + testSetKeyFile() { const ret = this.httpClient.setKeyFile("/path/to/key/file"); this.assertNil(ret); From 69e31ac7777d495061e5780790956f4c324a9975 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 18 Nov 2022 18:21:20 -0700 Subject: [PATCH 006/109] pr remediations Signed-off-by: Brian Downs --- src/optionals/http/http.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/optionals/http/http.c b/src/optionals/http/http.c index 8f8c7daa..813a8fff 100644 --- a/src/optionals/http/http.c +++ b/src/optionals/http/http.c @@ -904,7 +904,7 @@ static Value httpClientPost(DictuVM *vm, int argCount, Value *args) { return newResultError(vm, errorString); } -ObjAbstract *newHttpClient(DictuVM *vm, ObjDict *opts) { +Value newHttpClient(DictuVM *vm, ObjDict *opts) { ObjAbstract *abstract = newAbstract(vm, freeHttpClient, httpClientToString); push(vm, OBJ_VAL(abstract)); @@ -928,7 +928,7 @@ ObjAbstract *newHttpClient(DictuVM *vm, ObjDict *opts) { key = s->chars; } else { runtimeError(vm, "HTTP client options key must be a string"); - return abstract; + return EMPTY_VAL; } if (strstr(key, "timeout")) { @@ -937,7 +937,7 @@ ObjAbstract *newHttpClient(DictuVM *vm, ObjDict *opts) { } if (!IS_NUMBER(entry->value)) { runtimeError(vm, "HTTP client option \"timeout\" value must be a number"); - return abstract; + return EMPTY_VAL; } curl_easy_setopt(httpClient->curl, CURLOPT_TIMEOUT, (long)AS_NUMBER(entry->value)); @@ -948,7 +948,7 @@ ObjAbstract *newHttpClient(DictuVM *vm, ObjDict *opts) { if (!IS_LIST(entry->value)) { runtimeError(vm, "HTTP client option \"headers\" value must be a list"); - return abstract; + return EMPTY_VAL; } ObjList *headers = AS_LIST(entry->value); @@ -965,7 +965,7 @@ ObjAbstract *newHttpClient(DictuVM *vm, ObjDict *opts) { if (!IS_BOOL(entry->value)) { runtimeError(vm, "HTTP client option \"insecure\" value must be a bool"); - return abstract; + return EMPTY_VAL; } if (AS_BOOL(entry->value)) { @@ -980,7 +980,7 @@ ObjAbstract *newHttpClient(DictuVM *vm, ObjDict *opts) { if (!IS_BOOL(entry->value)) { runtimeError(vm, "HTTP client option \"follow_redirects\" value must be a bool"); - return abstract; + return EMPTY_VAL; } if (!AS_BOOL(entry->value)) { @@ -993,7 +993,7 @@ ObjAbstract *newHttpClient(DictuVM *vm, ObjDict *opts) { if (!IS_STRING(entry->value)) { runtimeError(vm, "HTTP client option \"keyFile\" value must be a string"); - return abstract; + return EMPTY_VAL; } char *keyFile = AS_STRING(entry->value)->chars; @@ -1009,7 +1009,7 @@ ObjAbstract *newHttpClient(DictuVM *vm, ObjDict *opts) { if (!IS_STRING(entry->value)) { runtimeError(vm, "HTTP client option \"certFile\" value must be a string"); - return abstract; + return EMPTY_VAL; } char *certFile = AS_STRING(entry->value)->chars; @@ -1025,7 +1025,7 @@ ObjAbstract *newHttpClient(DictuVM *vm, ObjDict *opts) { if (!IS_STRING(entry->value)) { runtimeError(vm, "HTTP client option key \"keyPasswd\" value must be a string"); - return abstract; + return EMPTY_VAL; } char *keyPasswd = AS_STRING(entry->value)->chars; @@ -1054,7 +1054,7 @@ ObjAbstract *newHttpClient(DictuVM *vm, ObjDict *opts) { defineNative(vm, &abstract->values, "setKeyPass", httpClientSetKeyPass); pop(vm); - return abstract; + return OBJ_VAL(abstract); } static Value newClient(DictuVM *vm, int argCount, Value *args) { @@ -1070,8 +1070,7 @@ static Value newClient(DictuVM *vm, int argCount, Value *args) { ObjDict *opts = AS_DICT(args[0]); - ObjAbstract *hc = newHttpClient(vm, opts); - return OBJ_VAL(hc); + return newHttpClient(vm, opts); } Value createHTTPModule(DictuVM *vm) { From 49b4e7ffce3d0f6ba34aee81eb87734c4962515f Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 18 Nov 2022 19:30:12 -0700 Subject: [PATCH 007/109] update unit test module source Signed-off-by: Brian Downs --- src/optionals/unittest/unittest-source.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/optionals/unittest/unittest-source.h b/src/optionals/unittest/unittest-source.h index 92186c7f..00ef6fc7 100644 --- a/src/optionals/unittest/unittest-source.h +++ b/src/optionals/unittest/unittest-source.h @@ -2,11 +2,11 @@ "import System;\n" \ "\n" \ "abstract class UnitTest {\n" \ -" var METHOD_NAME_PADDING = ' ';\n" \ -" var RESULTS_PADDING = ' ';\n" \ -" var ASSERTION_PADDING = ' ';\n" \ -" var forceOnlyFailures = false;\n" \ -" var forceExitOnFailure = false;\n" \ +" const METHOD_NAME_PADDING = ' ';\n" \ +" const RESULTS_PADDING = ' ';\n" \ +" const ASSERTION_PADDING = ' ';\n" \ +" var forceOnlyFailures = false;\n" \ +" var forceExitOnFailure = false;\n" \ "\n" \ " init(var onlyFailures = false, var exitOnFailure = false) {\n" \ " this.results = {\n" \ From 969673107c19988cde3ab7e94fba84a89f4529ba Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 18 Nov 2022 19:46:25 -0700 Subject: [PATCH 008/109] update code and file names Signed-off-by: Brian Downs --- examples/{reverse_proxy.du => reverseProxy.du} | 0 examples/{structured_logger.du => structuredLogger.du} | 0 examples/{template_engine.du => templateEngine.du} | 0 scripts/generate.du | 2 +- src/optionals/unittest/unittest.du | 6 +++--- 5 files changed, 4 insertions(+), 4 deletions(-) rename examples/{reverse_proxy.du => reverseProxy.du} (100%) rename examples/{structured_logger.du => structuredLogger.du} (100%) rename examples/{template_engine.du => templateEngine.du} (100%) diff --git a/examples/reverse_proxy.du b/examples/reverseProxy.du similarity index 100% rename from examples/reverse_proxy.du rename to examples/reverseProxy.du diff --git a/examples/structured_logger.du b/examples/structuredLogger.du similarity index 100% rename from examples/structured_logger.du rename to examples/structuredLogger.du diff --git a/examples/template_engine.du b/examples/templateEngine.du similarity index 100% rename from examples/template_engine.du rename to examples/templateEngine.du diff --git a/scripts/generate.du b/scripts/generate.du index 941efe65..eb42d7b2 100644 --- a/scripts/generate.du +++ b/scripts/generate.du @@ -25,7 +25,7 @@ def generate(filename) { } } -var files = [ +const files = [ 'src/vm/datatypes/lists/list', 'src/vm/datatypes/dicts/dict', 'src/vm/datatypes/result/result', diff --git a/src/optionals/unittest/unittest.du b/src/optionals/unittest/unittest.du index 8805df75..7e05d72e 100644 --- a/src/optionals/unittest/unittest.du +++ b/src/optionals/unittest/unittest.du @@ -2,9 +2,9 @@ import Inspect; import System; abstract class UnitTest { - var METHOD_NAME_PADDING = ' '; - var RESULTS_PADDING = ' '; - var ASSERTION_PADDING = ' '; + const METHOD_NAME_PADDING = ' '; + const RESULTS_PADDING = ' '; + const ASSERTION_PADDING = ' '; var forceOnlyFailures = false; var forceExitOnFailure = false; From ceda7cc9fb9a65af829a1e2b8c4b6e2f8e71286c Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 18 Nov 2022 19:49:14 -0700 Subject: [PATCH 009/109] update file names Signed-off-by: Brian Downs --- .../{chain-of-responsibility.du => chainOfResponsibility.du} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/design-patterns/{chain-of-responsibility.du => chainOfResponsibility.du} (100%) diff --git a/examples/design-patterns/chain-of-responsibility.du b/examples/design-patterns/chainOfResponsibility.du similarity index 100% rename from examples/design-patterns/chain-of-responsibility.du rename to examples/design-patterns/chainOfResponsibility.du From b1476cfb2a1fd52132a1d325ccf0093cf8d3a00d Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 18 Nov 2022 19:55:22 -0700 Subject: [PATCH 010/109] update example file names Signed-off-by: Brian Downs --- examples/runExamples.du | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/runExamples.du b/examples/runExamples.du index 27df535e..7be8c3fa 100644 --- a/examples/runExamples.du +++ b/examples/runExamples.du @@ -3,7 +3,7 @@ */ import "design-patterns/builder.du"; -import "design-patterns/chain-of-responsibility.du"; +import "design-patterns/chainOfResponsibility.du"; import "design-patterns/factoryMethod.du"; import "design-patterns/observer.du"; import "binarySearchTree.du"; @@ -17,5 +17,5 @@ import "inheritance.du"; import "isPalindrome.du"; import "factorial.du"; import "pathWalker.du"; -import "structured_logger.du"; -import "template_engine.du"; +import "structuredLogger.du"; +import "templateEngine.du"; From 9483a0817ca88cd2f412bd73a77ac1ba08c45a78 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Wed, 30 Nov 2022 23:15:12 -0700 Subject: [PATCH 011/109] initial Signed-off-by: Brian Downs --- examples/argparse.du | 31 +++++++++ src/optionals/argparse/argparse-source.h | 85 ++++++++++++++++++++++++ src/optionals/argparse/argparse.c | 16 +++++ src/optionals/argparse/argparse.du | 38 +++++++++++ src/optionals/argparse/argparse.h | 11 +++ src/optionals/env/env-source.h | 1 - src/optionals/optionals.c | 37 ++++++----- src/optionals/optionals.h | 1 + tests/argparse/import.du | 7 ++ tests/argparse/parser.du | 55 +++++++++++++++ 10 files changed, 263 insertions(+), 19 deletions(-) create mode 100644 examples/argparse.du create mode 100644 src/optionals/argparse/argparse-source.h create mode 100644 src/optionals/argparse/argparse.c create mode 100644 src/optionals/argparse/argparse.du create mode 100644 src/optionals/argparse/argparse.h create mode 100644 tests/argparse/import.du create mode 100644 tests/argparse/parser.du diff --git a/examples/argparse.du b/examples/argparse.du new file mode 100644 index 00000000..a51c27ad --- /dev/null +++ b/examples/argparse.du @@ -0,0 +1,31 @@ +import System; + +from Argparse import Parser; + +const NAME = "argparser"; +const USAGE = "Usage: {} [-vh] +Options: + -h help menu + -v show version +Examples: + {} -h".format(NAME, NAME); + +{ // main + const parser = Argparse.newParser(NAME, "Example program", USAGE); + parser.addString("-b", "birth month", false, "month"); + parser.addBool("-h", "help menu", false, "help"); + parser.addNumber("--port", "server port", false); + parser.addBool("-s", "run securely", true, "secure"); + parser.addList("-u", "active users", true, "users"); + var args = parser.parse().match( + def(result) => result, + def(error) => { + print(error); + System.exit(1); + } + ); + + //print(parser.usage()); + print(args); + System.exit(0); +} diff --git a/src/optionals/argparse/argparse-source.h b/src/optionals/argparse/argparse-source.h new file mode 100644 index 00000000..c5d2766f --- /dev/null +++ b/src/optionals/argparse/argparse-source.h @@ -0,0 +1,85 @@ +#define DICTU_ENV_SOURCE "import Argparse; \ +import System; \ +\ +class Arg { \ + init(argType, flag, desc, required) { \ + this.setAttribute('type', argType); \ + this.setAttribute('flag', flag); \ + this.setAttribute('desc', desc); \ + this.setAttribute('required', required); \ + } \ +} \ +\ +class Args { \ + init() {} \ +} \ +\ +class Parser { \ + private name; \ + private desc; \ + private usage; \ +\ + var args = []; \ +\ + init(var name, var desc, var usage) {} \ +\ + addString(flag, desc, required, ...metavar) { \ + this.args.push(Arg('string', flag, desc, required)); \ + } \ +\ + addNumber(flag, desc, required, ...metavar) { \ + this.args.push(Arg('number', flag, desc, required)); \ + } \ +\ + addBool(flag, desc, required, ...metavar) { \ + this.args.push(Arg('bool', flag, desc, required)); \ + } \ +\ + addList(flag, desc, required, ...metavar) { \ + this.args.push(Arg('list', flag, desc, required)); \ + } \ +\ + usage() { \ + if (this.usage == '') { \ + return ""; \ + } \ +\ + return this.usage; \ + } \ +\ + parse() { \ + var args = Args(); \ +\ + for (var i = 0; i < System.argv.len(); i+=1) { \ + for (var j = 0; j < this.args.len(); j+=1) { \ + if (System.argv[i] == this.args[j].flag) { \ + if (this.args[j].type == 'bool') { \ + args.setAttribute(this.args[j].flag.replace('-', ''), true); \ + } else if (this.args[j].type == 'list') { \ + if (i == (System.argv.len() - 1) or System.argv[i+1][0] == '-') { \ + return Error('{} requires an argument'); \ + } \ +\ + args.setAttribute(this.args[j].flag.replace('-', ''), System.argv[i+1].split(',')); \ + } else if (this.args[j].type == 'number') { \ + const res = System.argv[i+1].toNumber(); \ + if (not res.success()) { \ + return Error('{} arg must be a number'.format(System.argv[i])); \ + } \ +\ + args.setAttribute(this.args[j].flag.replace('-', ''), res.unwrap()); \ + } else { \ + if (i == (System.argv.len() - 1) or System.argv[i+1][0] == '-') { \ + return Error('{} requires an argument'); \ + } \ +\ + args.setAttribute(this.args[j].flag.replace('-', ''), System.argv[i+1]); \ + } \ + } \ + } \ + } \ +\ + return Success(args); \ + } \ +} \ +" diff --git a/src/optionals/argparse/argparse.c b/src/optionals/argparse/argparse.c new file mode 100644 index 00000000..557a746b --- /dev/null +++ b/src/optionals/argparse/argparse.c @@ -0,0 +1,16 @@ +#include "argparse.h" + +#include "argparse-source.h" + +Value createArgParseModule(DictuVM *vm) { + ObjClosure *closure = compileModuleToClosure(vm, "Argparse", DICTU_ENV_SOURCE); + + if (closure == NULL) { + return EMPTY_VAL; + } + + push(vm, OBJ_VAL(closure)); + pop(vm); + + return OBJ_VAL(closure); +} diff --git a/src/optionals/argparse/argparse.du b/src/optionals/argparse/argparse.du new file mode 100644 index 00000000..396fe4d3 --- /dev/null +++ b/src/optionals/argparse/argparse.du @@ -0,0 +1,38 @@ +from Argparse import Parser; + +enum argType { + stringType, + numberType, + boolType, + listType +} + +class Arg { + init() {} +} + +class Parser { + private args = []; + + init(var name, var desc, var usage) {} + + private addArg(argType, flag, desc, required, ...metavar) { + this.args.push(Args()); + return Success(); + } + + addString(flag, desc, required, ...metavar) { + return this.addArg(flag, desc, required, ...metavar); + } + + addNumber(flag, desc, required, ...metavar) {} + + addBool(flag, desc, required, ...metavar) {} + + addList(flag, desc, required, ...metavar) {} + + parse() { + + return this.args; + } +} \ No newline at end of file diff --git a/src/optionals/argparse/argparse.h b/src/optionals/argparse/argparse.h new file mode 100644 index 00000000..0042a6a5 --- /dev/null +++ b/src/optionals/argparse/argparse.h @@ -0,0 +1,11 @@ +#ifndef dictu_argparse_h +#define dictu_argparse_h + +#include + +#include "../optionals.h" +#include "../../vm/vm.h" + +Value createArgParseModule(DictuVM *vm); + +#endif //dictu_argparse_h diff --git a/src/optionals/env/env-source.h b/src/optionals/env/env-source.h index 78b80b2d..4aeb8920 100644 --- a/src/optionals/env/env-source.h +++ b/src/optionals/env/env-source.h @@ -29,4 +29,3 @@ "\n" \ " return Success(nil);\n" \ "}\n" \ - diff --git a/src/optionals/optionals.c b/src/optionals/optionals.c index 41413bb4..4b1926f7 100644 --- a/src/optionals/optionals.c +++ b/src/optionals/optionals.c @@ -1,25 +1,26 @@ #include "optionals.h" BuiltinModules modules[] = { - {"Math", &createMathsModule, false}, - {"Env", &createEnvModule, true}, - {"JSON", &createJSONModule, false}, - {"Log", &createLogModule, false}, - {"Path", &createPathModule, false}, - {"Datetime", &createDatetimeModule, false}, - {"Socket", &createSocketModule, false}, - {"Random", &createRandomModule, false}, - {"Base64", &createBase64Module, false}, - {"Hashlib", &createHashlibModule, false}, - {"Sqlite", &createSqliteModule, false}, - {"Process", &createProcessModule, false}, - {"System", &createSystemModule, false}, - {"Term", &createTermModule, false}, - {"UnitTest", &createUnitTestModule, true}, - {"Inspect", &createInspectModule, false}, - {"Object", &createObjectModule, true}, + {"Argparse", &createArgParseModule, false}, + {"Math", &createMathsModule, false}, + {"Env", &createEnvModule, true}, + {"JSON", &createJSONModule, false}, + {"Log", &createLogModule, false}, + {"Path", &createPathModule, false}, + {"Datetime", &createDatetimeModule, false}, + {"Socket", &createSocketModule, false}, + {"Random", &createRandomModule, false}, + {"Base64", &createBase64Module, false}, + {"Hashlib", &createHashlibModule, false}, + {"Sqlite", &createSqliteModule, false}, + {"Process", &createProcessModule, false}, + {"System", &createSystemModule, false}, + {"Term", &createTermModule, false}, + {"UnitTest", &createUnitTestModule, true}, + {"Inspect", &createInspectModule, false}, + {"Object", &createObjectModule, true}, #ifndef DISABLE_HTTP - {"HTTP", &createHTTPModule, true}, + {"HTTP", &createHTTPModule, true}, #endif {NULL, NULL, false} }; diff --git a/src/optionals/optionals.h b/src/optionals/optionals.h index 090d90ab..95b36113 100644 --- a/src/optionals/optionals.h +++ b/src/optionals/optionals.h @@ -2,6 +2,7 @@ #define dictu_optionals_h #include "../vm/util.h" +#include "argparse/argparse.h" #include "math.h" #include "env/env.h" #include "system.h" diff --git a/tests/argparse/import.du b/tests/argparse/import.du new file mode 100644 index 00000000..e2555561 --- /dev/null +++ b/tests/argparse/import.du @@ -0,0 +1,7 @@ +/** + * import.du + * + * General import file for all the Argparse tests + */ + +import "parser.du"; diff --git a/tests/argparse/parser.du b/tests/argparse/parser.du new file mode 100644 index 00000000..c8db9018 --- /dev/null +++ b/tests/argparse/parser.du @@ -0,0 +1,55 @@ +/** + * parser.du + * + * Testing the Argparse.Parser.parse() method + */ +import Base64; + +from UnitTest import UnitTest; +from Argparse import Parser; + +class TestArgparser < UnitTest { + + private parser; + + setUp() { + this.parser = Parser("parser_test", "Test Program", "usage: parser_test "); + } + + testNewParser() { + this.assertNotNil(this.parser); + } + + testAddString() { + const ret = this.parser.addString("-s", "string arg", false, ""); + this.assertNil(ret); + } + + testAddNumber() { + const ret = this.parser.addNumber("-n", "number arg", true, ""); + this.assertNil(ret); + } + + testAddBool() { + const ret = this.parser.addBool("-b", "bool arg", true, "bool"); + this.assertNil(ret); + } + + testAddList() { + const ret = this.parser.addString("-l", "list arg", true, ""); + this.assertNil(ret); + } + + testParse() { + this.parser.addString("-s", "string arg", false); + this.parser.addBool("-b", "bool arg", true); + this.parser.addList("-l", "list arg", true); + this.parser.addNumber("-n", "list arg", true); + var args = this.parser.parse(); + this.assertType(args, "result"); + this.assertTruthy(args.success()); + print(args.unwrap().l); + } +} + +TestArgparser().run(); From 00d63aa7c51509318f5b7e4a40fc91fc75da5c29 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Thu, 1 Dec 2022 17:30:34 -0700 Subject: [PATCH 012/109] mostly working Signed-off-by: Brian Downs --- src/optionals/argparse/argparse-source.h | 93 +++++++++++++------- src/optionals/argparse/argparse.du | 106 ++++++++++++++++++----- tests/argparse/parser.du | 11 +-- 3 files changed, 153 insertions(+), 57 deletions(-) diff --git a/src/optionals/argparse/argparse-source.h b/src/optionals/argparse/argparse-source.h index c5d2766f..448bd59a 100644 --- a/src/optionals/argparse/argparse-source.h +++ b/src/optionals/argparse/argparse-source.h @@ -1,85 +1,114 @@ #define DICTU_ENV_SOURCE "import Argparse; \ import System; \ \ -class Arg { \ - init(argType, flag, desc, required) { \ - this.setAttribute('type', argType); \ - this.setAttribute('flag', flag); \ - this.setAttribute('desc', desc); \ - this.setAttribute('required', required); \ - } \ -} \ -\ class Args { \ - init() {} \ + init(private name, private desc) {} \ +\ + toString() { \ + return 'usage: {}\n \ + {}\n \ + '.format(this.name, this.desc); \ + } \ } \ \ class Parser { \ private name; \ private desc; \ - private usage; \ + private userUsage; \ + private args; \ +\ + var preArgs = []; \ +\ + init(var name, var desc, var userUsage) { \ + this.args = Args(name, desc); \ + } \ +\ + private flagExists(flag) { \ + for (var i = 0; i < this.preArgs.len(); i+=1) { \ + if (this.preArgs[i]['flag'] == flag) { \ + return true;\ + } \ + } \ \ - var args = []; \ + return false; \ + } \ \ - init(var name, var desc, var usage) {} \ + private addFlag(flagType, flag, desc, required, ...metavar) { \ + if (not this.flagExists(flag)) { \ + this.preArgs.push({ \ + 'type': flagType, \ + 'flag': flag, \ + 'desc': desc, \ + 'required': required, \ + 'metavr': metavar \ + }); \ + } \ + } \ \ addString(flag, desc, required, ...metavar) { \ - this.args.push(Arg('string', flag, desc, required)); \ + this.addFlag('string', flag, desc, required, ...metavar); \ } \ \ addNumber(flag, desc, required, ...metavar) { \ - this.args.push(Arg('number', flag, desc, required)); \ + this.addFlag('number', flag, desc, required, ...metavar); \ } \ \ addBool(flag, desc, required, ...metavar) { \ - this.args.push(Arg('bool', flag, desc, required)); \ + this.addFlag('bool', flag, desc, required, ...metavar); \ } \ \ addList(flag, desc, required, ...metavar) { \ - this.args.push(Arg('list', flag, desc, required)); \ + this.addFlag('list', flag, desc, required, ...metavar); \ } \ \ usage() { \ - if (this.usage == '') { \ - return ""; \ + if (this.userUsage == '') { \ + var u = 'usage: {}\n {}\n\n'.format(this.name, this.desc); \ + for (var i = 0; i < this.preArgs.len(); i+=1) { \ + u += ' {} {}\n'.format(this.preArgs[i]['flag'], this.preArgs[i]['desc']); \ + } \ +\ + return u; \ } \ \ - return this.usage; \ + return this.userUsage; \ } \ \ parse() { \ - var args = Args(); \ -\ for (var i = 0; i < System.argv.len(); i+=1) { \ - for (var j = 0; j < this.args.len(); j+=1) { \ - if (System.argv[i] == this.args[j].flag) { \ - if (this.args[j].type == 'bool') { \ - args.setAttribute(this.args[j].flag.replace('-', ''), true); \ - } else if (this.args[j].type == 'list') { \ + for (var j = 0; j < this.preArgs.len(); j+=1) { \ + if (System.argv[i] == this.preArgs[j]['flag']) { \ + if (this.preArgs[j]['type'] == 'bool') { \ + print(this.preArgs[j]['type']); \ + this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), true); \ + } else if (this.preArgs[j]['type'] == 'list') { \ if (i == (System.argv.len() - 1) or System.argv[i+1][0] == '-') { \ return Error('{} requires an argument'); \ } \ \ - args.setAttribute(this.args[j].flag.replace('-', ''), System.argv[i+1].split(',')); \ - } else if (this.args[j].type == 'number') { \ + this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), System.argv[i+1].split(',')); \ + } else if (this.preArgs[j]['type'] == 'number') { \ + if (i == (System.argv.len() - 1) or System.argv[i+1][0] == '-') { \ + return Error('{} requires an argument'); \ + } \ const res = System.argv[i+1].toNumber(); \ if (not res.success()) { \ return Error('{} arg must be a number'.format(System.argv[i])); \ } \ \ - args.setAttribute(this.args[j].flag.replace('-', ''), res.unwrap()); \ + this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), res.unwrap()); \ } else { \ if (i == (System.argv.len() - 1) or System.argv[i+1][0] == '-') { \ return Error('{} requires an argument'); \ } \ \ - args.setAttribute(this.args[j].flag.replace('-', ''), System.argv[i+1]); \ + this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), System.argv[i+1]); \ } \ } \ } \ } \ \ - return Success(args); \ + return Success(this.args); \ } \ } \ " diff --git a/src/optionals/argparse/argparse.du b/src/optionals/argparse/argparse.du index 396fe4d3..3dcd94b4 100644 --- a/src/optionals/argparse/argparse.du +++ b/src/optionals/argparse/argparse.du @@ -1,38 +1,104 @@ -from Argparse import Parser; +import Argparse; +import System; -enum argType { - stringType, - numberType, - boolType, - listType +class Arg { + init(argType, flag, desc, required) { + this.setAttribute('type', argType); + this.setAttribute('flag', flag); + this.setAttribute('desc', desc); + this.setAttribute('required', required); + } } -class Arg { - init() {} +class Args { + init(private name, private desc) {} + + toString() { + return 'usage: {}\n + {}\n + '.format(this.name, this.desc);\ + } } class Parser { - private args = []; + private name; + private desc; + private userUsage; + private args; - init(var name, var desc, var usage) {} + var preArgs = []; - private addArg(argType, flag, desc, required, ...metavar) { - this.args.push(Args()); - return Success(); + init(var name, var desc, var userUsage) { + this.args = {}; } - addString(flag, desc, required, ...metavar) { - return this.addArg(flag, desc, required, ...metavar); + private addFlag(flagType, flag, desc, required, ...metavar) { + this.preArgs.push({ + 'type': flagType, + 'flag': flag, + 'desc': desc, + 'required': required, + 'metavr': metavar + }); } - addNumber(flag, desc, required, ...metavar) {} + // addString(flag, desc, required, ...metavar) { + // this.addFlag('string', flag, desc, required, ...metavar); + // } + + // addNumber(flag, desc, required, ...metavar) { + // this.addFlag('number', flag, desc, required, ...metavar); + // } + + // addBool(flag, desc, required, ...metavar) { + // this.addFlag('bool', flag, desc, required, ...metavar); + // } - addBool(flag, desc, required, ...metavar) {} + // addList(flag, desc, required, ...metavar) { + // this.addFlag('list', flag, desc, required, ...metavar); + // } - addList(flag, desc, required, ...metavar) {} + usage() { + if (this.userUsage == '') { + var u = "usage: {}\n".format(this.name); + u += this.args.forEach(def(a) => { + return " {} {}\n".format(a['flag'], a['desc']); + }); + } + + return this.userUsage; + } parse() { + for (var i = 0; i < System.argv.len(); i+=1) { + for (var j = 0; j < this.preArgs.len(); j+=1) { + if (System.argv[i] == this.preArgs[j]['flag']) { + if (this.preArgs[j]['type'] == 'bool') { + this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), true); + } else if (this.preArgs[j]['type'] == 'list') { + if (i == (System.argv.len() - 1) or System.argv[i+1][0] == '-') { + return Error('{} requires an argument'); + } + + this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), System.argv[i+1].split(',')); + } else if (this.preArgs[j]['type'] == 'number') { + const res = System.argv[i+1].toNumber(); + if (not res.success()) { + return Error('{} arg must be a number'.format(System.argv[i])); + } - return this.args; + this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), res.unwrap()); + } else { + if (i == (System.argv.len() - 1) or System.argv[i+1][0] == '-') { + return Error('{} requires an argument'); + } + + this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), System.argv[i+1]); + } + } + } + } + + return Success(this.args); } -} \ No newline at end of file +} diff --git a/tests/argparse/parser.du b/tests/argparse/parser.du index c8db9018..b9544c4c 100644 --- a/tests/argparse/parser.du +++ b/tests/argparse/parser.du @@ -13,7 +13,7 @@ class TestArgparser < UnitTest { private parser; setUp() { - this.parser = Parser("parser_test", "Test Program", "usage: parser_test "); + this.parser = Parser("parser_test", "Test Program", ""); } testNewParser() { @@ -44,11 +44,12 @@ class TestArgparser < UnitTest { this.parser.addString("-s", "string arg", false); this.parser.addBool("-b", "bool arg", true); this.parser.addList("-l", "list arg", true); - this.parser.addNumber("-n", "list arg", true); + this.parser.addNumber("-n", "short number arg", true); + this.parser.addNumber("--number", "long number arg", true); var args = this.parser.parse(); - this.assertType(args, "result"); - this.assertTruthy(args.success()); - print(args.unwrap().l); + print(this.parser.usage()); + // this.assertType(args, "result"); + // this.assertTruthy(args.success()); } } From a8aa95519080b6b07bd474b57518ca539931dc43 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Thu, 1 Dec 2022 20:19:15 -0700 Subject: [PATCH 013/109] all working Signed-off-by: Brian Downs --- src/optionals/argparse/argparse-source.h | 27 +++++++++- tests/argparse/parser.du | 63 ++++++++++++++++-------- 2 files changed, 69 insertions(+), 21 deletions(-) diff --git a/src/optionals/argparse/argparse-source.h b/src/optionals/argparse/argparse-source.h index 448bd59a..dd4217da 100644 --- a/src/optionals/argparse/argparse-source.h +++ b/src/optionals/argparse/argparse-source.h @@ -18,6 +18,7 @@ class Parser { \ private args; \ \ var preArgs = []; \ + var required = []; \ \ init(var name, var desc, var userUsage) { \ this.args = Args(name, desc); \ @@ -42,6 +43,10 @@ class Parser { \ 'required': required, \ 'metavr': metavar \ }); \ +\ + if (required) { \ + this.required.push(flag); \ + } \ } \ } \ \ @@ -73,13 +78,29 @@ class Parser { \ \ return this.userUsage; \ } \ +\ + private hasRequired() { \ + var found = 0; \ + for (var i = 0; i < System.argv.len(); i+=1) { \ + for (var j = 0; j < this.required.len(); j+=1) { \ + if (System.argv[i] == this.required[j]) { \ + found += 1;\ + } \ + } \ + } \ +\ + if (found == this.required.len()) { \ + return true; \ + } \ +\ + return false; \ + } \ \ parse() { \ for (var i = 0; i < System.argv.len(); i+=1) { \ for (var j = 0; j < this.preArgs.len(); j+=1) { \ if (System.argv[i] == this.preArgs[j]['flag']) { \ if (this.preArgs[j]['type'] == 'bool') { \ - print(this.preArgs[j]['type']); \ this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), true); \ } else if (this.preArgs[j]['type'] == 'list') { \ if (i == (System.argv.len() - 1) or System.argv[i+1][0] == '-') { \ @@ -107,6 +128,10 @@ class Parser { \ } \ } \ } \ +\ + if (not this.hasRequired()) { \ + return Error('1 or more required flags missing'); \ + } \ \ return Success(this.args); \ } \ diff --git a/tests/argparse/parser.du b/tests/argparse/parser.du index b9544c4c..3100df69 100644 --- a/tests/argparse/parser.du +++ b/tests/argparse/parser.du @@ -21,35 +21,58 @@ class TestArgparser < UnitTest { } testAddString() { - const ret = this.parser.addString("-s", "string arg", false, ""); - this.assertNil(ret); + this.parser.addString("-s", "string arg", false, ""); + const res = this.parser.parse(); + this.assertType(res, "result"); + this.assertNotNil(res); + this.assertTruthy(res.success()); + const args = res.unwrap(); + this.assertNotNil(args.s); } testAddNumber() { - const ret = this.parser.addNumber("-n", "number arg", true, ""); - this.assertNil(ret); + this.parser.addNumber("-n", "number arg", true, ""); + const res = this.parser.parse(); + this.assertType(res, "result"); + this.assertNotNil(res); + this.assertTruthy(res.success()); + const args = res.unwrap(); + this.assertNotNil(args.n); } testAddBool() { - const ret = this.parser.addBool("-b", "bool arg", true, "bool"); - this.assertNil(ret); + this.parser.addBool("-b", "bool arg", true, "bool"); + const res = this.parser.parse(); + this.assertType(res, "result"); + this.assertNotNil(res); + this.assertTruthy(res.success()); + const args = res.unwrap(); + this.assertNotNil(args.b); + } + + testAddLongflag() { + this.parser.addNumber("--port", "tcp port to listen on", true); + const res = this.parser.parse(); + this.assertType(res, "result"); + this.assertNotNil(res); + this.assertTruthy(res.success()); + const args = res.unwrap(); + this.assertNotNil(args.port); } testAddList() { - const ret = this.parser.addString("-l", "list arg", true, ""); - this.assertNil(ret); - } - - testParse() { - this.parser.addString("-s", "string arg", false); - this.parser.addBool("-b", "bool arg", true); - this.parser.addList("-l", "list arg", true); - this.parser.addNumber("-n", "short number arg", true); - this.parser.addNumber("--number", "long number arg", true); - var args = this.parser.parse(); - print(this.parser.usage()); - // this.assertType(args, "result"); - // this.assertTruthy(args.success()); + this.parser.addString("-l", "list arg", true, ""); + const res = this.parser.parse(); + this.assertType(res, "result"); + this.assertNotNil(res); + this.assertTruthy(res.success()); + const args = res.unwrap(); + this.assertNotNil(args.l); + } + + testUsage() { + const usage = this.parser.usage(); + this.assertNotEquals(usage, ""); } } From 480857cc70b5672fbc228b6cf1b6c0df053a0cd0 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Thu, 1 Dec 2022 20:44:00 -0700 Subject: [PATCH 014/109] passing tests Signed-off-by: Brian Downs --- src/optionals/argparse/argparse-source.h | 6 -- src/optionals/argparse/argparse.du | 108 ++++++++++++++--------- tests/argparse/parser.du | 20 ++++- tests/runTests.du | 1 + 4 files changed, 85 insertions(+), 50 deletions(-) diff --git a/src/optionals/argparse/argparse-source.h b/src/optionals/argparse/argparse-source.h index dd4217da..58761336 100644 --- a/src/optionals/argparse/argparse-source.h +++ b/src/optionals/argparse/argparse-source.h @@ -3,12 +3,6 @@ import System; \ \ class Args { \ init(private name, private desc) {} \ -\ - toString() { \ - return 'usage: {}\n \ - {}\n \ - '.format(this.name, this.desc); \ - } \ } \ \ class Parser { \ diff --git a/src/optionals/argparse/argparse.du b/src/optionals/argparse/argparse.du index 3dcd94b4..796b19dd 100644 --- a/src/optionals/argparse/argparse.du +++ b/src/optionals/argparse/argparse.du @@ -1,23 +1,8 @@ import Argparse; import System; -class Arg { - init(argType, flag, desc, required) { - this.setAttribute('type', argType); - this.setAttribute('flag', flag); - this.setAttribute('desc', desc); - this.setAttribute('required', required); - } -} - class Args { init(private name, private desc) {} - - toString() { - return 'usage: {}\n - {}\n - '.format(this.name, this.desc);\ - } } class Parser { @@ -27,48 +12,84 @@ class Parser { private args; var preArgs = []; + var required = []; init(var name, var desc, var userUsage) { - this.args = {}; + this.args = Args(name, desc); + } + + private flagExists(flag) { + for (var i = 0; i < this.preArgs.len(); i+=1) { + if (this.preArgs[i]['flag'] == flag) { + return true;\ + } + } + + return false; } private addFlag(flagType, flag, desc, required, ...metavar) { - this.preArgs.push({ - 'type': flagType, - 'flag': flag, - 'desc': desc, - 'required': required, - 'metavr': metavar - }); + if (not this.flagExists(flag)) { + this.preArgs.push({ + 'type': flagType, + 'flag': flag, + 'desc': desc, + 'required': required, + 'metavr': metavar + }); + + if (required) { + this.required.push(flag); + } + } } - // addString(flag, desc, required, ...metavar) { - // this.addFlag('string', flag, desc, required, ...metavar); - // } + addString(flag, desc, required, ...metavar) { + this.addFlag('string', flag, desc, required, ...metavar); + } - // addNumber(flag, desc, required, ...metavar) { - // this.addFlag('number', flag, desc, required, ...metavar); - // } + addNumber(flag, desc, required, ...metavar) { + this.addFlag('number', flag, desc, required, ...metavar); + } - // addBool(flag, desc, required, ...metavar) { - // this.addFlag('bool', flag, desc, required, ...metavar); - // } + addBool(flag, desc, required, ...metavar) { + this.addFlag('bool', flag, desc, required, ...metavar); + } - // addList(flag, desc, required, ...metavar) { - // this.addFlag('list', flag, desc, required, ...metavar); - // } + addList(flag, desc, required, ...metavar) { + this.addFlag('list', flag, desc, required, ...metavar); + } usage() { if (this.userUsage == '') { - var u = "usage: {}\n".format(this.name); - u += this.args.forEach(def(a) => { - return " {} {}\n".format(a['flag'], a['desc']); - }); + var u = 'usage: {}\n {}\n\n'.format(this.name, this.desc); + for (var i = 0; i < this.preArgs.len(); i+=1) { + u += ' {} {}\n'.format(this.preArgs[i]['flag'], this.preArgs[i]['desc']); + } + + return u; } return this.userUsage; } + private hasRequired() { + var found = 0; + for (var i = 0; i < System.argv.len(); i+=1) { + for (var j = 0; j < this.required.len(); j+=1) { + if (System.argv[i] == this.required[j]) { + found += 1;\ + } + } + } + + if (found == this.required.len()) { + return true; + } + + return false; + } + parse() { for (var i = 0; i < System.argv.len(); i+=1) { for (var j = 0; j < this.preArgs.len(); j+=1) { @@ -82,6 +103,9 @@ class Parser { this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), System.argv[i+1].split(',')); } else if (this.preArgs[j]['type'] == 'number') { + if (i == (System.argv.len() - 1) or System.argv[i+1][0] == '-') { + return Error('{} requires an argument'); + } const res = System.argv[i+1].toNumber(); if (not res.success()) { return Error('{} arg must be a number'.format(System.argv[i])); @@ -99,6 +123,10 @@ class Parser { } } + if (not this.hasRequired()) { + return Error('1 or more required flags missing'); + } + return Success(this.args); } -} +} \ No newline at end of file diff --git a/tests/argparse/parser.du b/tests/argparse/parser.du index 3100df69..b0e0e05f 100644 --- a/tests/argparse/parser.du +++ b/tests/argparse/parser.du @@ -3,11 +3,20 @@ * * Testing the Argparse.Parser.parse() method */ -import Base64; +import System; from UnitTest import UnitTest; from Argparse import Parser; +System.argv.extend([ + "--number", "7", + "-n", "8080", + "-s", "string", + "-b", + "-l", "adsf,qwer", + "--port", "8080" +]); + class TestArgparser < UnitTest { private parser; @@ -21,7 +30,8 @@ class TestArgparser < UnitTest { } testAddString() { - this.parser.addString("-s", "string arg", false, ""); + print(System.argv); + this.parser.addString("-s", "string arg", false); const res = this.parser.parse(); this.assertType(res, "result"); this.assertNotNil(res); @@ -31,7 +41,8 @@ class TestArgparser < UnitTest { } testAddNumber() { - this.parser.addNumber("-n", "number arg", true, ""); + print(System.argv); + this.parser.addNumber("-n", "number arg", true); const res = this.parser.parse(); this.assertType(res, "result"); this.assertNotNil(res); @@ -41,6 +52,7 @@ class TestArgparser < UnitTest { } testAddBool() { + print(System.argv); this.parser.addBool("-b", "bool arg", true, "bool"); const res = this.parser.parse(); this.assertType(res, "result"); @@ -61,7 +73,7 @@ class TestArgparser < UnitTest { } testAddList() { - this.parser.addString("-l", "list arg", true, ""); + this.parser.addString("-l", "list arg", true); const res = this.parser.parse(); this.assertType(res, "result"); this.assertNotNil(res); diff --git a/tests/runTests.du b/tests/runTests.du index 694995f3..bc06f560 100644 --- a/tests/runTests.du +++ b/tests/runTests.du @@ -2,6 +2,7 @@ from UnitTest import UnitTest; UnitTest.forceOnlyFailures = true; +import "argparse/import.du"; import "bool/import.du"; import "nil/import.du"; import "number/import.du"; From b64101e6b0015625dd1e09295df7b142e380738d Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Thu, 1 Dec 2022 21:47:12 -0700 Subject: [PATCH 015/109] docs Signed-off-by: Brian Downs --- docs/docs/standard-lib/argparse.md | 85 ++++++++++++++++++++++++++++++ examples/argparse.du | 2 +- tests/argparse/parser.du | 32 +++++------ 3 files changed, 98 insertions(+), 21 deletions(-) create mode 100644 docs/docs/standard-lib/argparse.md diff --git a/docs/docs/standard-lib/argparse.md b/docs/docs/standard-lib/argparse.md new file mode 100644 index 00000000..f26431a6 --- /dev/null +++ b/docs/docs/standard-lib/argparse.md @@ -0,0 +1,85 @@ +--- +layout: default +title: Argparse +nav_order: 19 +parent: Standard Library +--- + +# Argparse +{: .no_toc } + +## Table of contents +{: .no_toc .text-delta } + +1. TOC +{:toc} + +--- + +## Argparse + +To make use of the Argparse module an import is required. + +```cs +from Argparse import Parse; +``` + +### New Parser(string, string, string) + +To create a new parser instance, call the `Parser` class with the 3 required string arguments; name, description, and user provided usage. + +```cs +var parser = Parser("prog_name", "Program to do all the things", ""); +``` + +### Parse.addString(string, string, bool, string -> optional) + +To add a new string argument, call the method below with at least the 3 required arguments; name, description, and boolean indicating the flag is required. A fourth argument can be passed to give the parser a custom name for the given flag. + +```cs +parser.addString("-b", "birth month", false, "month"); +``` + +### Parser.addNumber(string, string, bool, string -> optional) + +To add a new number argument, call the method below with at least the 3 required arguments; name, description, and boolean indicating the flag is required. A fourth argument can be passed to give the parser a custom name for the given flag. + +```cs +parser.addNumber("--port", "server port", false); +``` + +### Parser.addBool(string, string, bool, string -> optional) + +To add a new bool argument, call the method below with at least the 3 required arguments; name, description, and boolean indicating the flag is required. A fourth argument can be passed to give the parser a custom name for the given flag. + +```cs +parser.addBool("-s", "run securely", true, "secure"); +``` + +### Parser.addList(string, string, bool, string -> optional) + +To add a new list argument, call the method below with at least the 3 required arguments; name, description, and boolean indicating the flag is required. A fourth argument can be passed to give the parser a custom name for the given flag. + +```cs +parser.addList("-u", "active users", true, "users"); +``` + +### Parser.parse() + +The `parse` method needs to be called to process the given flags against the configured flags. `parse` returns a `Result` value that, on success, will need to be unwrapped to access an instance of the `Args` class. + +```cs +const args = parser.parse().match( + def (result) => result, + def (error) => { + print(error); + System.exit(1); + } +); +``` + +The value of type `Args` will be instantiated with fields matching the configured flags or the given metavar names. Below is an example using the list argument example from above. + +```cs +print("Users: {}".format(args.users)); +``` diff --git a/examples/argparse.du b/examples/argparse.du index a51c27ad..04082882 100644 --- a/examples/argparse.du +++ b/examples/argparse.du @@ -11,7 +11,7 @@ Examples: {} -h".format(NAME, NAME); { // main - const parser = Argparse.newParser(NAME, "Example program", USAGE); + const parser = Parser(NAME, "Example program", USAGE); parser.addString("-b", "birth month", false, "month"); parser.addBool("-h", "help menu", false, "help"); parser.addNumber("--port", "server port", false); diff --git a/tests/argparse/parser.du b/tests/argparse/parser.du index b0e0e05f..ba31c3b5 100644 --- a/tests/argparse/parser.du +++ b/tests/argparse/parser.du @@ -1,7 +1,7 @@ /** * parser.du * - * Testing the Argparse.Parser.parse() method + * Testing the Argparse.Parser methods */ import System; @@ -18,7 +18,6 @@ System.argv.extend([ ]); class TestArgparser < UnitTest { - private parser; setUp() { @@ -29,35 +28,32 @@ class TestArgparser < UnitTest { this.assertNotNil(this.parser); } - testAddString() { - print(System.argv); - this.parser.addString("-s", "string arg", false); - const res = this.parser.parse(); + private addFlagCheck(res) { this.assertType(res, "result"); this.assertNotNil(res); this.assertTruthy(res.success()); + } + + testAddString() { + this.parser.addString("-s", "string arg", false); + const res = this.parser.parse(); const args = res.unwrap(); + this.addFlagCheck(res); this.assertNotNil(args.s); } testAddNumber() { - print(System.argv); this.parser.addNumber("-n", "number arg", true); const res = this.parser.parse(); - this.assertType(res, "result"); - this.assertNotNil(res); - this.assertTruthy(res.success()); const args = res.unwrap(); + this.addFlagCheck(res); this.assertNotNil(args.n); } testAddBool() { - print(System.argv); this.parser.addBool("-b", "bool arg", true, "bool"); const res = this.parser.parse(); - this.assertType(res, "result"); - this.assertNotNil(res); - this.assertTruthy(res.success()); + this.addFlagCheck(res); const args = res.unwrap(); this.assertNotNil(args.b); } @@ -65,9 +61,7 @@ class TestArgparser < UnitTest { testAddLongflag() { this.parser.addNumber("--port", "tcp port to listen on", true); const res = this.parser.parse(); - this.assertType(res, "result"); - this.assertNotNil(res); - this.assertTruthy(res.success()); + this.addFlagCheck(res); const args = res.unwrap(); this.assertNotNil(args.port); } @@ -75,9 +69,7 @@ class TestArgparser < UnitTest { testAddList() { this.parser.addString("-l", "list arg", true); const res = this.parser.parse(); - this.assertType(res, "result"); - this.assertNotNil(res); - this.assertTruthy(res.success()); + this.addFlagCheck(res); const args = res.unwrap(); this.assertNotNil(args.l); } From 4162baa9834ab20f7c7a370f0eeef7431660d672 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Thu, 1 Dec 2022 21:52:19 -0700 Subject: [PATCH 016/109] remove new line Signed-off-by: Brian Downs --- src/optionals/env/env-source.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/optionals/env/env-source.h b/src/optionals/env/env-source.h index 4aeb8920..037ec490 100644 --- a/src/optionals/env/env-source.h +++ b/src/optionals/env/env-source.h @@ -28,4 +28,4 @@ " }\n" \ "\n" \ " return Success(nil);\n" \ -"}\n" \ +"}\n" \ \ No newline at end of file From f538375e3fadb44bec11b4c806ce04b6f44914a2 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Thu, 1 Dec 2022 21:52:55 -0700 Subject: [PATCH 017/109] remove new line Signed-off-by: Brian Downs --- src/optionals/env/env-source.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/optionals/env/env-source.h b/src/optionals/env/env-source.h index 037ec490..4aeb8920 100644 --- a/src/optionals/env/env-source.h +++ b/src/optionals/env/env-source.h @@ -28,4 +28,4 @@ " }\n" \ "\n" \ " return Success(nil);\n" \ -"}\n" \ \ No newline at end of file +"}\n" \ From 89217ccc806d4571eb07a9e61e9d93a24a4be0d5 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Thu, 1 Dec 2022 21:56:51 -0700 Subject: [PATCH 018/109] remove new line Signed-off-by: Brian Downs --- src/optionals/env/env-source.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/optionals/env/env-source.h b/src/optionals/env/env-source.h index 4aeb8920..78b80b2d 100644 --- a/src/optionals/env/env-source.h +++ b/src/optionals/env/env-source.h @@ -29,3 +29,4 @@ "\n" \ " return Success(nil);\n" \ "}\n" \ + From e10e447f6de9504fbf5f28ac8fe112f6f8c3faa0 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 2 Dec 2022 19:30:11 -0700 Subject: [PATCH 019/109] add new line Signed-off-by: Brian Downs --- src/optionals/argparse/argparse.du | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/optionals/argparse/argparse.du b/src/optionals/argparse/argparse.du index 796b19dd..bfd79d4c 100644 --- a/src/optionals/argparse/argparse.du +++ b/src/optionals/argparse/argparse.du @@ -129,4 +129,4 @@ class Parser { return Success(this.args); } -} \ No newline at end of file +} From ac8f4f5a36c994f56b9ed2b14888c119a7df2ff6 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 9 Dec 2022 21:30:31 -0700 Subject: [PATCH 020/109] add time consts, tests, and docs Signed-off-by: Brian Downs --- docs/docs/standard-lib/datetime.md | 67 +++++++++++++++++------------- src/optionals/datetime.c | 8 ++++ tests/bool/toString.du | 2 +- tests/datetime/constants.du | 45 ++++++++++++++++++++ tests/datetime/import.du | 1 + tests/datetime/strptime.du | 1 - 6 files changed, 93 insertions(+), 31 deletions(-) create mode 100644 tests/datetime/constants.du diff --git a/docs/docs/standard-lib/datetime.md b/docs/docs/standard-lib/datetime.md index 70b53cf6..5eb8eaf8 100644 --- a/docs/docs/standard-lib/datetime.md +++ b/docs/docs/standard-lib/datetime.md @@ -24,6 +24,15 @@ To make use of the Datetime module an import is required. import Datetime; ``` +### Constants + +| Constant | Description | +| -------------------------- | ---------------------------------- | +| Datetime.SECONDS_IN_MINUTE | The number of seconds in a minute. | +| Datetime.SECONDS_IN_HOUR | The number of seconds in an hour. | +| Datetime.SECONDS_IN_DAY | The number of seconds in a day. | +| Datetime.SECONDS_IN_WEEK | The number of seconds in a week. | + ### Datetime.now() Returns a human readable locale datetime string. @@ -42,35 +51,35 @@ Datetime.now(); // Fri May 29 02:12:32 2020 ### Datetime formats -| Directive | Description | Example | -|------------|--------------------------------------------------------------------------------------|--------------------------| -| %a | Abbreviated weekday name | Mon, Tue, ... | -| %A | Full weekday name | Monday, Tuesday, ... | -| %b | Abbreviated month name | Jan, Feb, ... | -| %B | Full month name | January, February, ... | -| %c | Locale datetime format | Fri May 29 03:18:03 2020 | -| %C | The century number (year/100) as a 2-digit integer | 20 | -| %d | The day of the month as a decimal number | 01, 02, ... | -| %D | Equivalent to %m/%d/%y | 05/29/20 | -| %e | Like %d, but the leading zero is replaced by a space | Monday, Tuesday, ... | -| %F | Equivalent to %Y-%m-%d | 2020-05-29 | -| %H | The hour as a decimal number using a 24-hour clock (00-23) | 00, 01, 02, ... | -| %I | The hour as a decimal number using a 12-hour clock (01-12) | 00, 01, 02, ... | -| %j | The day of the year as a decimal number (001-366) | 001, 002, ... | -| %m | The month as a decimal number (01-12) | 01, 02, ... | -| %M | The minute as a decimal number (00-59) | 00, 01, 02, ... | -| %n | A newline character | | -| %p | Either AM or PM | | -| %P | Either am or pm | | -| %s | The number of seconds since the Epoch | 1590719271 | -| %S | The second as a decimal number (range 00 to 60) | 00, 01, 02, ... | -| %t | A tab character | | -| %u | The day of the week as a decimal (range 1 to 7) | 1, 2, 3 | -| %y | The year as a decimal number without a century (range 00 to 99) | 00, 01, 02, ... | -| %Y | The year as a decimal number including the century. | 2020, 2021, 2022, ... | -| %z | The +hhmm or -hhmm numeric timezone (that is, the hour and minute offset from UTC) | +0100, +0200, ... | -| %Z | The timezone name or abbreviation | BST | -| %% | A literal % | | +| Directive | Description | Example | +| --------- | ---------------------------------------------------------------------------------- | ------------------------ | +| %a | Abbreviated weekday name | Mon, Tue, ... | +| %A | Full weekday name | Monday, Tuesday, ... | +| %b | Abbreviated month name | Jan, Feb, ... | +| %B | Full month name | January, February, ... | +| %c | Locale datetime format | Fri May 29 03:18:03 2020 | +| %C | The century number (year/100) as a 2-digit integer | 20 | +| %d | The day of the month as a decimal number | 01, 02, ... | +| %D | Equivalent to %m/%d/%y | 05/29/20 | +| %e | Like %d, but the leading zero is replaced by a space | Monday, Tuesday, ... | +| %F | Equivalent to %Y-%m-%d | 2020-05-29 | +| %H | The hour as a decimal number using a 24-hour clock (00-23) | 00, 01, 02, ... | +| %I | The hour as a decimal number using a 12-hour clock (01-12) | 00, 01, 02, ... | +| %j | The day of the year as a decimal number (001-366) | 001, 002, ... | +| %m | The month as a decimal number (01-12) | 01, 02, ... | +| %M | The minute as a decimal number (00-59) | 00, 01, 02, ... | +| %n | A newline character | | +| %p | Either AM or PM | | +| %P | Either am or pm | | +| %s | The number of seconds since the Epoch | 1590719271 | +| %S | The second as a decimal number (range 00 to 60) | 00, 01, 02, ... | +| %t | A tab character | | +| %u | The day of the week as a decimal (range 1 to 7) | 1, 2, 3 | +| %y | The year as a decimal number without a century (range 00 to 99) | 00, 01, 02, ... | +| %Y | The year as a decimal number including the century. | 2020, 2021, 2022, ... | +| %z | The +hhmm or -hhmm numeric timezone (that is, the hour and minute offset from UTC) | +0100, +0200, ... | +| %Z | The timezone name or abbreviation | BST | +| %% | A literal % | | ### Datetime.strftime(string, number: time -> optional) diff --git a/src/optionals/datetime.c b/src/optionals/datetime.c index 1d870ed1..3919003f 100644 --- a/src/optionals/datetime.c +++ b/src/optionals/datetime.c @@ -170,6 +170,14 @@ Value createDatetimeModule(DictuVM *vm) { defineNative(vm, &module->values, "strptime", strptimeNative); #endif + /** + * Define Datetime properties + */ + defineNativeProperty(vm, &module->values, "SECONDS_IN_MINUTE", NUMBER_VAL(60)); + defineNativeProperty(vm, &module->values, "SECONDS_IN_HOUR", NUMBER_VAL(3600)); + defineNativeProperty(vm, &module->values, "SECONDS_IN_DAY", NUMBER_VAL(86400)); + defineNativeProperty(vm, &module->values, "SECONDS_IN_WEEK", NUMBER_VAL(604800)); + pop(vm); pop(vm); diff --git a/tests/bool/toString.du b/tests/bool/toString.du index 7e6463ea..a5e6abf0 100644 --- a/tests/bool/toString.du +++ b/tests/bool/toString.du @@ -23,4 +23,4 @@ class TestBoolToString < UnitTest { } } -TestBoolToString().run(); \ No newline at end of file +TestBoolToString().run(); diff --git a/tests/datetime/constants.du b/tests/datetime/constants.du new file mode 100644 index 00000000..c66b12fe --- /dev/null +++ b/tests/datetime/constants.du @@ -0,0 +1,45 @@ +/** + * constants.du + * + * Testing the Datetime constant values. + * + * A set of constants defined in the Datetime module. + */ + +import Datetime; + +from UnitTest import UnitTest; + +class TestDatetimeConstants < UnitTest { + const DATE_TIME_FORMAT = "%a %b %d %H:%M:%S %Y"; + + testSecondsInMinuteConstant() { + const startSec = Datetime.strptime(this.DATE_TIME_FORMAT, "Fri May 29 03:12:32 2020"); + const minAgo = startSec - Datetime.SECONDS_IN_MINUTE; + const endSec = Datetime.strptime(this.DATE_TIME_FORMAT, "Fri May 29 03:11:32 2020"); + this.assertTruthy(minAgo == endSec); + } + + testSecondsInHourConstant() { + const startSec = Datetime.strptime(this.DATE_TIME_FORMAT, "Fri May 29 03:12:32 2020"); + const hourAgo = startSec - Datetime.SECONDS_IN_HOUR; + const endSec = Datetime.strptime(this.DATE_TIME_FORMAT, "Fri May 29 02:12:32 2020"); + this.assertTruthy(hourAgo == endSec); + } + + testSecondsInDayConstant() { + const startSec = Datetime.strptime(this.DATE_TIME_FORMAT, "Fri May 29 03:12:32 2020"); + const dayAgo = startSec - Datetime.SECONDS_IN_DAY; + const endSec = Datetime.strptime(this.DATE_TIME_FORMAT, "Fri May 28 03:12:32 2020"); + this.assertTruthy(dayAgo == endSec); + } + + testSecondsInWeekConstant() { + const startSec = Datetime.strptime(this.DATE_TIME_FORMAT, "Fri May 29 03:12:32 2020"); + const weekAgo = startSec - Datetime.SECONDS_IN_WEEK; + const endSec = Datetime.strptime(this.DATE_TIME_FORMAT, "Fri May 22 03:12:32 2020"); + this.assertTruthy(weekAgo == endSec); + } +} + +TestDatetimeConstants().run(); diff --git a/tests/datetime/import.du b/tests/datetime/import.du index a5480042..042d113b 100644 --- a/tests/datetime/import.du +++ b/tests/datetime/import.du @@ -4,5 +4,6 @@ * General import file for all the datetime tests */ +import "constants.du"; import "strftime.du"; import "strptime.du"; diff --git a/tests/datetime/strptime.du b/tests/datetime/strptime.du index 71aad642..3fb2b6f0 100644 --- a/tests/datetime/strptime.du +++ b/tests/datetime/strptime.du @@ -52,7 +52,6 @@ class TestStrptimeDatetimeModule < UnitTest { } } - if (System.platform != "windows") { TestStrptimeDatetimeModule().run(); } \ No newline at end of file From da64a78423bfc695edb5710064027c6d0f8760a0 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 9 Dec 2022 21:42:03 -0700 Subject: [PATCH 021/109] add windows check in test Signed-off-by: Brian Downs --- tests/datetime/constants.du | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/datetime/constants.du b/tests/datetime/constants.du index c66b12fe..8e5b87ff 100644 --- a/tests/datetime/constants.du +++ b/tests/datetime/constants.du @@ -42,4 +42,6 @@ class TestDatetimeConstants < UnitTest { } } -TestDatetimeConstants().run(); +if (System.platform != "windows") { + TestDatetimeConstants().run(); +} From ca647e5d0dc1120081a39cf46aca11d71e013b1a Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 9 Dec 2022 21:46:09 -0700 Subject: [PATCH 022/109] add missing import Signed-off-by: Brian Downs --- tests/datetime/constants.du | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/datetime/constants.du b/tests/datetime/constants.du index 8e5b87ff..28a99e32 100644 --- a/tests/datetime/constants.du +++ b/tests/datetime/constants.du @@ -7,6 +7,7 @@ */ import Datetime; +import System; from UnitTest import UnitTest; From 8c9b433220e2de523a2d68f439bc0f6d3162dea7 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 13 Dec 2022 16:04:56 -0700 Subject: [PATCH 023/109] use generate script for header creeation Signed-off-by: Brian Downs --- scripts/generate.du | 1 + src/optionals/argparse/argparse-source.h | 266 +++++++++++------------ src/optionals/argparse/argparse.c | 2 +- src/optionals/unittest/unittest-source.h | 4 +- 4 files changed, 137 insertions(+), 136 deletions(-) diff --git a/scripts/generate.du b/scripts/generate.du index eb42d7b2..b51cbe0e 100644 --- a/scripts/generate.du +++ b/scripts/generate.du @@ -29,6 +29,7 @@ const files = [ 'src/vm/datatypes/lists/list', 'src/vm/datatypes/dicts/dict', 'src/vm/datatypes/result/result', + 'src/optionals/argparse/argparse', 'src/optionals/unittest/unittest', 'src/optionals/env/env', 'src/optionals/http/http', diff --git a/src/optionals/argparse/argparse-source.h b/src/optionals/argparse/argparse-source.h index 58761336..3da83811 100644 --- a/src/optionals/argparse/argparse-source.h +++ b/src/optionals/argparse/argparse-source.h @@ -1,133 +1,133 @@ -#define DICTU_ENV_SOURCE "import Argparse; \ -import System; \ -\ -class Args { \ - init(private name, private desc) {} \ -} \ -\ -class Parser { \ - private name; \ - private desc; \ - private userUsage; \ - private args; \ -\ - var preArgs = []; \ - var required = []; \ -\ - init(var name, var desc, var userUsage) { \ - this.args = Args(name, desc); \ - } \ -\ - private flagExists(flag) { \ - for (var i = 0; i < this.preArgs.len(); i+=1) { \ - if (this.preArgs[i]['flag'] == flag) { \ - return true;\ - } \ - } \ -\ - return false; \ - } \ -\ - private addFlag(flagType, flag, desc, required, ...metavar) { \ - if (not this.flagExists(flag)) { \ - this.preArgs.push({ \ - 'type': flagType, \ - 'flag': flag, \ - 'desc': desc, \ - 'required': required, \ - 'metavr': metavar \ - }); \ -\ - if (required) { \ - this.required.push(flag); \ - } \ - } \ - } \ -\ - addString(flag, desc, required, ...metavar) { \ - this.addFlag('string', flag, desc, required, ...metavar); \ - } \ -\ - addNumber(flag, desc, required, ...metavar) { \ - this.addFlag('number', flag, desc, required, ...metavar); \ - } \ -\ - addBool(flag, desc, required, ...metavar) { \ - this.addFlag('bool', flag, desc, required, ...metavar); \ - } \ -\ - addList(flag, desc, required, ...metavar) { \ - this.addFlag('list', flag, desc, required, ...metavar); \ - } \ -\ - usage() { \ - if (this.userUsage == '') { \ - var u = 'usage: {}\n {}\n\n'.format(this.name, this.desc); \ - for (var i = 0; i < this.preArgs.len(); i+=1) { \ - u += ' {} {}\n'.format(this.preArgs[i]['flag'], this.preArgs[i]['desc']); \ - } \ -\ - return u; \ - } \ -\ - return this.userUsage; \ - } \ -\ - private hasRequired() { \ - var found = 0; \ - for (var i = 0; i < System.argv.len(); i+=1) { \ - for (var j = 0; j < this.required.len(); j+=1) { \ - if (System.argv[i] == this.required[j]) { \ - found += 1;\ - } \ - } \ - } \ -\ - if (found == this.required.len()) { \ - return true; \ - } \ -\ - return false; \ - } \ -\ - parse() { \ - for (var i = 0; i < System.argv.len(); i+=1) { \ - for (var j = 0; j < this.preArgs.len(); j+=1) { \ - if (System.argv[i] == this.preArgs[j]['flag']) { \ - if (this.preArgs[j]['type'] == 'bool') { \ - this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), true); \ - } else if (this.preArgs[j]['type'] == 'list') { \ - if (i == (System.argv.len() - 1) or System.argv[i+1][0] == '-') { \ - return Error('{} requires an argument'); \ - } \ -\ - this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), System.argv[i+1].split(',')); \ - } else if (this.preArgs[j]['type'] == 'number') { \ - if (i == (System.argv.len() - 1) or System.argv[i+1][0] == '-') { \ - return Error('{} requires an argument'); \ - } \ - const res = System.argv[i+1].toNumber(); \ - if (not res.success()) { \ - return Error('{} arg must be a number'.format(System.argv[i])); \ - } \ -\ - this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), res.unwrap()); \ - } else { \ - if (i == (System.argv.len() - 1) or System.argv[i+1][0] == '-') { \ - return Error('{} requires an argument'); \ - } \ -\ - this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), System.argv[i+1]); \ - } \ - } \ - } \ - } \ -\ - if (not this.hasRequired()) { \ - return Error('1 or more required flags missing'); \ - } \ -\ - return Success(this.args); \ - } \ -} \ -" +#define DICTU_ARGPARSE_SOURCE "import Argparse;\n" \ +"import System;\n" \ +"\n" \ +"class Args {\n" \ +" init(private name, private desc) {}\n" \ +"}\n" \ +"\n" \ +"class Parser {\n" \ +" private name;\n" \ +" private desc;\n" \ +" private userUsage;\n" \ +" private args;\n" \ +"\n" \ +" var preArgs = [];\n" \ +" var required = [];\n" \ +"\n" \ +" init(var name, var desc, var userUsage) {\n" \ +" this.args = Args(name, desc);\n" \ +" }\n" \ +"\n" \ +" private flagExists(flag) {\n" \ +" for (var i = 0; i < this.preArgs.len(); i+=1) {\n" \ +" if (this.preArgs[i]['flag'] == flag) {\n" \ +" return true;\\n" \ +" }\n" \ +" }\n" \ +"\n" \ +" return false;\n" \ +" }\n" \ +"\n" \ +" private addFlag(flagType, flag, desc, required, ...metavar) {\n" \ +" if (not this.flagExists(flag)) {\n" \ +" this.preArgs.push({\n" \ +" 'type': flagType,\n" \ +" 'flag': flag,\n" \ +" 'desc': desc,\n" \ +" 'required': required,\n" \ +" 'metavr': metavar\n" \ +" });\n" \ +"\n" \ +" if (required) {\n" \ +" this.required.push(flag);\n" \ +" }\n" \ +" }\n" \ +" }\n" \ +"\n" \ +" addString(flag, desc, required, ...metavar) {\n" \ +" this.addFlag('string', flag, desc, required, ...metavar);\n" \ +" }\n" \ +"\n" \ +" addNumber(flag, desc, required, ...metavar) {\n" \ +" this.addFlag('number', flag, desc, required, ...metavar);\n" \ +" }\n" \ +"\n" \ +" addBool(flag, desc, required, ...metavar) {\n" \ +" this.addFlag('bool', flag, desc, required, ...metavar);\n" \ +" }\n" \ +"\n" \ +" addList(flag, desc, required, ...metavar) {\n" \ +" this.addFlag('list', flag, desc, required, ...metavar);\n" \ +" }\n" \ +"\n" \ +" usage() {\n" \ +" if (this.userUsage == '') {\n" \ +" var u = 'usage: {}\n {}\n\n'.format(this.name, this.desc);\n" \ +" for (var i = 0; i < this.preArgs.len(); i+=1) {\n" \ +" u += ' {} {}\n'.format(this.preArgs[i]['flag'], this.preArgs[i]['desc']);\n" \ +" }\n" \ +"\n" \ +" return u;\n" \ +" }\n" \ +"\n" \ +" return this.userUsage;\n" \ +" }\n" \ +"\n" \ +" private hasRequired() {\n" \ +" var found = 0;\n" \ +" for (var i = 0; i < System.argv.len(); i+=1) {\n" \ +" for (var j = 0; j < this.required.len(); j+=1) {\n" \ +" if (System.argv[i] == this.required[j]) {\n" \ +" found += 1;\\n" \ +" }\n" \ +" }\n" \ +" }\n" \ +"\n" \ +" if (found == this.required.len()) {\n" \ +" return true;\n" \ +" }\n" \ +"\n" \ +" return false;\n" \ +" }\n" \ +"\n" \ +" parse() {\n" \ +" for (var i = 0; i < System.argv.len(); i+=1) {\n" \ +" for (var j = 0; j < this.preArgs.len(); j+=1) {\n" \ +" if (System.argv[i] == this.preArgs[j]['flag']) {\n" \ +" if (this.preArgs[j]['type'] == 'bool') {\n" \ +" this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), true);\n" \ +" } else if (this.preArgs[j]['type'] == 'list') {\n" \ +" if (i == (System.argv.len() - 1) or System.argv[i+1][0] == '-') {\n" \ +" return Error('{} requires an argument');\n" \ +" }\n" \ +"\n" \ +" this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), System.argv[i+1].split(','));\n" \ +" } else if (this.preArgs[j]['type'] == 'number') {\n" \ +" if (i == (System.argv.len() - 1) or System.argv[i+1][0] == '-') {\n" \ +" return Error('{} requires an argument');\n" \ +" }\n" \ +" const res = System.argv[i+1].toNumber();\n" \ +" if (not res.success()) {\n" \ +" return Error('{} arg must be a number'.format(System.argv[i]));\n" \ +" }\n" \ +"\n" \ +" this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), res.unwrap());\n" \ +" } else {\n" \ +" if (i == (System.argv.len() - 1) or System.argv[i+1][0] == '-') {\n" \ +" return Error('{} requires an argument');\n" \ +" }\n" \ +"\n" \ +" this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), System.argv[i+1]);\n" \ +" }\n" \ +" }\n" \ +" }\n" \ +" }\n" \ +"\n" \ +" if (not this.hasRequired()) {\n" \ +" return Error('1 or more required flags missing');\n" \ +" }\n" \ +"\n" \ +" return Success(this.args);\n" \ +" }\n" \ +"}\n" \ + diff --git a/src/optionals/argparse/argparse.c b/src/optionals/argparse/argparse.c index 557a746b..ae8098e0 100644 --- a/src/optionals/argparse/argparse.c +++ b/src/optionals/argparse/argparse.c @@ -3,7 +3,7 @@ #include "argparse-source.h" Value createArgParseModule(DictuVM *vm) { - ObjClosure *closure = compileModuleToClosure(vm, "Argparse", DICTU_ENV_SOURCE); + ObjClosure *closure = compileModuleToClosure(vm, "Argparse", DICTU_ARGPARSE_SOURCE); if (closure == NULL) { return EMPTY_VAL; diff --git a/src/optionals/unittest/unittest-source.h b/src/optionals/unittest/unittest-source.h index 00ef6fc7..21aa2deb 100644 --- a/src/optionals/unittest/unittest-source.h +++ b/src/optionals/unittest/unittest-source.h @@ -5,8 +5,8 @@ " const METHOD_NAME_PADDING = ' ';\n" \ " const RESULTS_PADDING = ' ';\n" \ " const ASSERTION_PADDING = ' ';\n" \ -" var forceOnlyFailures = false;\n" \ -" var forceExitOnFailure = false;\n" \ +" var forceOnlyFailures = false;\n" \ +" var forceExitOnFailure = false;\n" \ "\n" \ " init(var onlyFailures = false, var exitOnFailure = false) {\n" \ " this.results = {\n" \ From 3bfbe8769b09edbc65f952c8ee214229d7028085 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 13 Dec 2022 16:18:41 -0700 Subject: [PATCH 024/109] fix errors in dictu code Signed-off-by: Brian Downs --- src/optionals/argparse/argparse.du | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/optionals/argparse/argparse.du b/src/optionals/argparse/argparse.du index bfd79d4c..8fcff1da 100644 --- a/src/optionals/argparse/argparse.du +++ b/src/optionals/argparse/argparse.du @@ -21,7 +21,7 @@ class Parser { private flagExists(flag) { for (var i = 0; i < this.preArgs.len(); i+=1) { if (this.preArgs[i]['flag'] == flag) { - return true;\ + return true; } } @@ -78,7 +78,7 @@ class Parser { for (var i = 0; i < System.argv.len(); i+=1) { for (var j = 0; j < this.required.len(); j+=1) { if (System.argv[i] == this.required[j]) { - found += 1;\ + found += 1; } } } From 6512898823cde067f96b17482094a7a95436040c Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 13 Dec 2022 16:39:04 -0700 Subject: [PATCH 025/109] updated source Signed-off-by: Brian Downs --- src/optionals/argparse/argparse-source.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/optionals/argparse/argparse-source.h b/src/optionals/argparse/argparse-source.h index 3da83811..c0f57b32 100644 --- a/src/optionals/argparse/argparse-source.h +++ b/src/optionals/argparse/argparse-source.h @@ -21,7 +21,7 @@ " private flagExists(flag) {\n" \ " for (var i = 0; i < this.preArgs.len(); i+=1) {\n" \ " if (this.preArgs[i]['flag'] == flag) {\n" \ -" return true;\\n" \ +" return true;\n" \ " }\n" \ " }\n" \ "\n" \ @@ -78,7 +78,7 @@ " for (var i = 0; i < System.argv.len(); i+=1) {\n" \ " for (var j = 0; j < this.required.len(); j+=1) {\n" \ " if (System.argv[i] == this.required[j]) {\n" \ -" found += 1;\\n" \ +" found += 1;\n" \ " }\n" \ " }\n" \ " }\n" \ From 075b29d07fb08c6b8ae0b57b64ea35ce5dec25cc Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 13 Dec 2022 19:44:21 -0700 Subject: [PATCH 026/109] make all the things work Signed-off-by: Brian Downs --- src/optionals/argparse/argparse-source.h | 47 ++++++++++++++++++------ src/optionals/argparse/argparse.du | 47 ++++++++++++++++++------ tests/argparse/parser.du | 2 +- 3 files changed, 73 insertions(+), 23 deletions(-) diff --git a/src/optionals/argparse/argparse-source.h b/src/optionals/argparse/argparse-source.h index c0f57b32..ef25e5e6 100644 --- a/src/optionals/argparse/argparse-source.h +++ b/src/optionals/argparse/argparse-source.h @@ -30,13 +30,18 @@ "\n" \ " private addFlag(flagType, flag, desc, required, ...metavar) {\n" \ " if (not this.flagExists(flag)) {\n" \ -" this.preArgs.push({\n" \ +" var arg = {\n" \ " 'type': flagType,\n" \ " 'flag': flag,\n" \ " 'desc': desc,\n" \ -" 'required': required,\n" \ -" 'metavr': metavar\n" \ -" });\n" \ +" 'required': required\n" \ +" };\n" \ +"\n" \ +" if (metavar.len() == 1) {\n" \ +" arg['metavar'] = metavar[0];\n" \ +" }\n" \ +"\n" \ +" this.preArgs.push(arg);\n" \ "\n" \ " if (required) {\n" \ " this.required.push(flag);\n" \ @@ -63,6 +68,7 @@ " usage() {\n" \ " if (this.userUsage == '') {\n" \ " var u = 'usage: {}\n {}\n\n'.format(this.name, this.desc);\n" \ +"\n" \ " for (var i = 0; i < this.preArgs.len(); i+=1) {\n" \ " u += ' {} {}\n'.format(this.preArgs[i]['flag'], this.preArgs[i]['desc']);\n" \ " }\n" \ @@ -95,29 +101,48 @@ " for (var j = 0; j < this.preArgs.len(); j+=1) {\n" \ " if (System.argv[i] == this.preArgs[j]['flag']) {\n" \ " if (this.preArgs[j]['type'] == 'bool') {\n" \ -" this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), true);\n" \ +" if (this.preArgs[j].exists('metavar') and this.preArgs[j]['metavar'] != '') {\n" \ +" this.args.setAttribute(this.preArgs[j]['metavar'], true);\n" \ +" } else {\n" \ +" this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), true);\n" \ +" }\n" \ +" \n" \ " } else if (this.preArgs[j]['type'] == 'list') {\n" \ " if (i == (System.argv.len() - 1) or System.argv[i+1][0] == '-') {\n" \ -" return Error('{} requires an argument');\n" \ +" return Error('{} requires an argument'.format(System.argv[i]));\n" \ " }\n" \ "\n" \ -" this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), System.argv[i+1].split(','));\n" \ +" if (this.preArgs[j].exists('metavar') and this.preArgs[j]['metavar'] != '') {\n" \ +" this.args.setAttribute(this.preArgs[j]['metavar'], System.argv[i+1].split(',')); \n" \ +" } else {\n" \ +" this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), System.argv[i+1].split(','));\n" \ +" }\n" \ " } else if (this.preArgs[j]['type'] == 'number') {\n" \ " if (i == (System.argv.len() - 1) or System.argv[i+1][0] == '-') {\n" \ -" return Error('{} requires an argument');\n" \ +" return Error('{} requires an argument'.format(System.argv[i]));\n" \ " }\n" \ +"\n" \ " const res = System.argv[i+1].toNumber();\n" \ " if (not res.success()) {\n" \ " return Error('{} arg must be a number'.format(System.argv[i]));\n" \ " }\n" \ "\n" \ -" this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), res.unwrap());\n" \ +" if (this.preArgs[j].exists('metavar') and this.preArgs[j]['metavar'] != '') {\n" \ +" print(this.preArgs[j]['metavar']);\n" \ +" this.args.setAttribute(this.preArgs[j]['metavar'], res.unwrap());\n" \ +" } else {\n" \ +" this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), res.unwrap());\n" \ +" }\n" \ " } else {\n" \ " if (i == (System.argv.len() - 1) or System.argv[i+1][0] == '-') {\n" \ -" return Error('{} requires an argument');\n" \ +" return Error('{} requires an argument'.format(System.argv[i]));\n" \ " }\n" \ "\n" \ -" this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), System.argv[i+1]);\n" \ +" if (this.preArgs[j].exists('metavar') and this.preArgs[j]['metavar'] != '') {\n" \ +" this.args.setAttribute(this.preArgs[j]['metavar'], System.argv[i+1]);\n" \ +" } else {\n" \ +" this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), System.argv[i+1]);\n" \ +" }\n" \ " }\n" \ " }\n" \ " }\n" \ diff --git a/src/optionals/argparse/argparse.du b/src/optionals/argparse/argparse.du index 8fcff1da..520c34dc 100644 --- a/src/optionals/argparse/argparse.du +++ b/src/optionals/argparse/argparse.du @@ -30,13 +30,18 @@ class Parser { private addFlag(flagType, flag, desc, required, ...metavar) { if (not this.flagExists(flag)) { - this.preArgs.push({ + var arg = { 'type': flagType, 'flag': flag, 'desc': desc, - 'required': required, - 'metavr': metavar - }); + 'required': required + }; + + if (metavar.len() == 1) { + arg['metavar'] = metavar[0]; + } + + this.preArgs.push(arg); if (required) { this.required.push(flag); @@ -63,6 +68,7 @@ class Parser { usage() { if (this.userUsage == '') { var u = 'usage: {}\n {}\n\n'.format(this.name, this.desc); + for (var i = 0; i < this.preArgs.len(); i+=1) { u += ' {} {}\n'.format(this.preArgs[i]['flag'], this.preArgs[i]['desc']); } @@ -95,29 +101,48 @@ class Parser { for (var j = 0; j < this.preArgs.len(); j+=1) { if (System.argv[i] == this.preArgs[j]['flag']) { if (this.preArgs[j]['type'] == 'bool') { - this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), true); + if (this.preArgs[j].exists('metavar') and this.preArgs[j]['metavar'] != '') { + this.args.setAttribute(this.preArgs[j]['metavar'], true); + } else { + this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), true); + } + } else if (this.preArgs[j]['type'] == 'list') { if (i == (System.argv.len() - 1) or System.argv[i+1][0] == '-') { - return Error('{} requires an argument'); + return Error('{} requires an argument'.format(System.argv[i])); } - this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), System.argv[i+1].split(',')); + if (this.preArgs[j].exists('metavar') and this.preArgs[j]['metavar'] != '') { + this.args.setAttribute(this.preArgs[j]['metavar'], System.argv[i+1].split(',')); + } else { + this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), System.argv[i+1].split(',')); + } } else if (this.preArgs[j]['type'] == 'number') { if (i == (System.argv.len() - 1) or System.argv[i+1][0] == '-') { - return Error('{} requires an argument'); + return Error('{} requires an argument'.format(System.argv[i])); } + const res = System.argv[i+1].toNumber(); if (not res.success()) { return Error('{} arg must be a number'.format(System.argv[i])); } - this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), res.unwrap()); + if (this.preArgs[j].exists('metavar') and this.preArgs[j]['metavar'] != '') { + print(this.preArgs[j]['metavar']); + this.args.setAttribute(this.preArgs[j]['metavar'], res.unwrap()); + } else { + this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), res.unwrap()); + } } else { if (i == (System.argv.len() - 1) or System.argv[i+1][0] == '-') { - return Error('{} requires an argument'); + return Error('{} requires an argument'.format(System.argv[i])); } - this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), System.argv[i+1]); + if (this.preArgs[j].exists('metavar') and this.preArgs[j]['metavar'] != '') { + this.args.setAttribute(this.preArgs[j]['metavar'], System.argv[i+1]); + } else { + this.args.setAttribute(this.preArgs[j]['flag'].replace('-', ''), System.argv[i+1]); + } } } } diff --git a/tests/argparse/parser.du b/tests/argparse/parser.du index ba31c3b5..2134194c 100644 --- a/tests/argparse/parser.du +++ b/tests/argparse/parser.du @@ -55,7 +55,7 @@ class TestArgparser < UnitTest { const res = this.parser.parse(); this.addFlagCheck(res); const args = res.unwrap(); - this.assertNotNil(args.b); + this.assertNotNil(args.bool); } testAddLongflag() { From 15c907936e674a22abfe2bbfb9b5844cf0e3855a Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 6 Dec 2022 18:06:47 -0700 Subject: [PATCH 027/109] add UUID module Signed-off-by: Brian Downs --- Docker/DictuUbuntuDockerfile | 2 +- docs/docs/standard-lib/uuid.md | 55 ++++++++++++++++++++ examples/uuid.du | 3 ++ src/optionals/optionals.c | 3 ++ src/optionals/optionals.h | 1 + src/optionals/uuid.c | 94 ++++++++++++++++++++++++++++++++++ src/optionals/uuid.h | 12 +++++ tests/uuid/generate.du | 24 +++++++++ tests/uuid/generateRandom.du | 23 +++++++++ tests/uuid/generateTime.du | 23 +++++++++ tests/uuid/import.du | 9 ++++ 11 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 docs/docs/standard-lib/uuid.md create mode 100644 examples/uuid.du create mode 100644 src/optionals/uuid.c create mode 100644 src/optionals/uuid.h create mode 100644 tests/uuid/generate.du create mode 100644 tests/uuid/generateRandom.du create mode 100644 tests/uuid/generateTime.du create mode 100644 tests/uuid/import.du diff --git a/Docker/DictuUbuntuDockerfile b/Docker/DictuUbuntuDockerfile index c0fddfc7..78e80b0a 100644 --- a/Docker/DictuUbuntuDockerfile +++ b/Docker/DictuUbuntuDockerfile @@ -8,7 +8,7 @@ RUN apt-get update \ && apt-get install -y --no-install-recommends build-essential \ && apt-get update \ && apt-get install -y --reinstall ca-certificates \ - && apt-get install -y --no-install-recommends git cmake libcurl4-gnutls-dev \ + && apt-get install -y --no-install-recommends git cmake libcurl4-gnutls-dev uuid-dev \ && rm -rf /var/lib/apt/lists/* RUN git clone https://github.com/dictu-lang/Dictu.git diff --git a/docs/docs/standard-lib/uuid.md b/docs/docs/standard-lib/uuid.md new file mode 100644 index 00000000..9e3db8bd --- /dev/null +++ b/docs/docs/standard-lib/uuid.md @@ -0,0 +1,55 @@ +--- +layout: default +title: UUID +nav_order: 19 +parent: Standard Library +--- + +# UUID +{: .no_toc } + +## Table of contents +{: .no_toc .text-delta } + +1. TOC +{:toc} + +--- + +## UUID + +To make use of the UUID module an import is required. + +```cs +import UUID; +``` + +### UUID.generate() + +Returns a Result value with a string representation of the UUID on success or an Error on failure. This function attempts to use `/dev/urandom` if available but if it's not, it uses alterntive means of generating randomness. + +```cs +const uuid = UUID.generate(0).unwrap(); +print(uuid); +// a9c313d8-5bdb-4537-af9a-0b08be1387fb +``` + +### UUID.generateRandom() + +Returns a Result value with a string representation of the UUID on success or an Error on failure. This function forces the use of the all-random UUID format. + +```cs +const uuid = UUID.generateRandom().unwrap(); +print(uuid); +// 95356011-f08c-46d9-9335-d5b988682211 +``` + +### UUID.generateTime() + +Returns a Result value with a string representation of the UUID on success or an Error on failure. This function forces the use of the alternative algorithm which uses the current time and local MAC address (if available). + +```cs +const uuid = UUID.generateTime().unwrap(); +print(uuid); +// 55edea51-e712-4352-a8c4-bb4eaa69d49d +``` diff --git a/examples/uuid.du b/examples/uuid.du new file mode 100644 index 00000000..5c11c6e2 --- /dev/null +++ b/examples/uuid.du @@ -0,0 +1,3 @@ +import UUID; + +print(UUID.generate().unwrap()); diff --git a/src/optionals/optionals.c b/src/optionals/optionals.c index 4b1926f7..cbb69c74 100644 --- a/src/optionals/optionals.c +++ b/src/optionals/optionals.c @@ -16,6 +16,9 @@ BuiltinModules modules[] = { {"Process", &createProcessModule, false}, {"System", &createSystemModule, false}, {"Term", &createTermModule, false}, +#ifndef DISABLE_UUID + {"UUID", &createUuidModule, false}, +#endif {"UnitTest", &createUnitTestModule, true}, {"Inspect", &createInspectModule, false}, {"Object", &createObjectModule, true}, diff --git a/src/optionals/optionals.h b/src/optionals/optionals.h index 95b36113..af897837 100644 --- a/src/optionals/optionals.h +++ b/src/optionals/optionals.h @@ -20,6 +20,7 @@ #include "process.h" #include "inspect.h" #include "term.h" +#include "uuid.h" #include "object/object.h" #include "unittest/unittest.h" diff --git a/src/optionals/uuid.c b/src/optionals/uuid.c new file mode 100644 index 00000000..dde081d1 --- /dev/null +++ b/src/optionals/uuid.c @@ -0,0 +1,94 @@ +#include "uuid.h" + +static uuid_t uuid; + +#define UUID_STRING_LEN 37 + +static Value uuidGenerateNative(DictuVM *vm, int argCount, Value *args) { + UNUSED(args); + + if (argCount != 0) { + runtimeError(vm, "generate() doesn't take any arguments (%d given)).", argCount); + return EMPTY_VAL; + } + + uuid_clear(uuid); + + uuid_generate(uuid); + + if (uuid_is_null(uuid)) { + return newResultError(vm, "error: failed to generate uuid"); + } + + char out[37] = {}; + uuid_unparse_lower(uuid, out); + + return newResultSuccess(vm, OBJ_VAL(copyString(vm, out, UUID_STRING_LEN))); +} + +static Value uuidGenRandomNative(DictuVM *vm, int argCount, Value *args) { + UNUSED(args); + + if (argCount != 0) { + runtimeError(vm, "generateRandom() doesn't take any arguments (%d given)).", argCount); + return EMPTY_VAL; + } + + uuid_clear(uuid); + + uuid_generate_random(uuid); + + if (uuid_is_null(uuid)) { + return newResultError(vm, "error: failed to generate uuid"); + } + + char out[37] = {}; + uuid_unparse_lower(uuid, out); + + return newResultSuccess(vm, OBJ_VAL(copyString(vm, out, UUID_STRING_LEN))); +} + +static Value uuidGenTimeNative(DictuVM *vm, int argCount, Value *args) { + UNUSED(args); + + if (argCount != 0) { + runtimeError(vm, "generateTime() doesn't take any arguments (%d given)).", argCount); + return EMPTY_VAL; + } + + uuid_clear(uuid); + + uuid_generate_time(uuid); + + if (uuid_is_null(uuid)) { + return newResultError(vm, "error: failed to generate uuid"); + } + + char out[37] = {}; + uuid_unparse_lower(uuid, out); + + return newResultSuccess(vm, OBJ_VAL(copyString(vm, out, UUID_STRING_LEN))); +} + +Value createUuidModule(DictuVM *vm) { + ObjString *name = copyString(vm, "UUID", 4); + push(vm, OBJ_VAL(name)); + ObjModule *module = newModule(vm, name); + push(vm, OBJ_VAL(module)); + + /** + * Define UUID methods + */ + defineNative(vm, &module->values, "generate", uuidGenerateNative); + defineNative(vm, &module->values, "generateRandom", uuidGenRandomNative); + defineNative(vm, &module->values, "generateTime", uuidGenTimeNative); + + /** + * Define UUID properties + */ + + pop(vm); + pop(vm); + + return OBJ_VAL(module); +} diff --git a/src/optionals/uuid.h b/src/optionals/uuid.h new file mode 100644 index 00000000..28b16d1d --- /dev/null +++ b/src/optionals/uuid.h @@ -0,0 +1,12 @@ +#ifndef dictu_uuid_h +#define dictu_uuid_h + +#include "optionals.h" +#include "../vm/vm.h" +#include "../vm/memory.h" + +#include + +Value createUuidModule(DictuVM *vm); + +#endif //dictu_uuid_h diff --git a/tests/uuid/generate.du b/tests/uuid/generate.du new file mode 100644 index 00000000..d6895af6 --- /dev/null +++ b/tests/uuid/generate.du @@ -0,0 +1,24 @@ +/** + * generate.du + * + * Testing the UUID.generate function. + * + * The UUID.generate function provides the ability to generate UUID. + */ +import UUID; + +from UnitTest import UnitTest; + +class TestUUIDGenerate < UnitTest { + + testGenerate() { + const ret = UUID.generate(); + this.assertTruthy(ret.success()); + const uuid = ret.unwrap(); + this.assertType(type(uuid), "string"); + this.assertTruthy(uuid.len() == 37); + print(uuid); + } +} + +TestUUIDGenerate().run(); diff --git a/tests/uuid/generateRandom.du b/tests/uuid/generateRandom.du new file mode 100644 index 00000000..7ee51e7f --- /dev/null +++ b/tests/uuid/generateRandom.du @@ -0,0 +1,23 @@ +/** + * generateRandom.du + * + * Testing the UUID.generateRandom function. + * + * The UUID.generateRandom function provides the ability to generate UUID. + */ +import UUID; + +from UnitTest import UnitTest; + +class TestUUIDGenerateRandom < UnitTest { + + testGenerate() { + const ret = UUID.generateRandom(); + this.assertTruthy(ret.success()); + const uuid = ret.unwrap(); + this.assertType(type(uuid), "string"); + this.assertTruthy(uuid.len() == 37); + } +} + +TestUUIDGenerateRandom().run(); diff --git a/tests/uuid/generateTime.du b/tests/uuid/generateTime.du new file mode 100644 index 00000000..61204398 --- /dev/null +++ b/tests/uuid/generateTime.du @@ -0,0 +1,23 @@ +/** + * generateRandom.du + * + * Testing the UUID.generateTime function. + * + * The UUID.generateTime function provides the ability to generate UUID. + */ +import UUID; + +from UnitTest import UnitTest; + +class TestUUIDGenerateTime < UnitTest { + + testGenerate() { + const ret = UUID.generateTime(); + this.assertTruthy(ret.success()); + const uuid = ret.unwrap(); + this.assertType(type(uuid), "string"); + this.assertTruthy(uuid.len() == 37); + } +} + +TestUUIDGenerateTime().run(); diff --git a/tests/uuid/import.du b/tests/uuid/import.du new file mode 100644 index 00000000..8e5b3b5c --- /dev/null +++ b/tests/uuid/import.du @@ -0,0 +1,9 @@ +/** + * import.du + * + * General import file for all the uuid tests + */ + +import "generate.du"; +import "generateRandom.du"; +import "generateTime.du"; From e356d13afd0fd7b2e992015a15988773cf4dd806 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 6 Dec 2022 18:16:13 -0700 Subject: [PATCH 028/109] update github action Signed-off-by: Brian Downs --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 87482065..5630394d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -36,7 +36,7 @@ jobs: - name: Make dictu and run tests (HTTP) run: | sudo apt-get update - sudo apt-get install -y libcurl4-openssl-dev + sudo apt-get install -y libcurl4-openssl-dev uuid-dev cmake -DCMAKE_BUILD_TYPE=Debug -B ./build cmake --build ./build ./dictu tests/runTests.du ci | tee /dev/stderr | grep -q 'Total bytes lost: 0' @@ -84,7 +84,7 @@ jobs: - name: Make dictu and run examples run: | sudo apt-get update - sudo apt-get install -y libcurl4-openssl-dev + sudo apt-get install -y libcurl4-openssl-dev uuid-dev cmake -DCMAKE_BUILD_TYPE=Debug -B ./build cmake --build ./build ./dictu examples/runExamples.du ci | tee /dev/stderr | grep -q 'Total bytes lost: 0' From 4c78537f1497ec430bdb0b2317dda7b761fe409d Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 6 Dec 2022 18:25:32 -0700 Subject: [PATCH 029/109] update github action Signed-off-by: Brian Downs --- .github/workflows/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5630394d..566e6df3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,6 +13,8 @@ jobs: - uses: actions/checkout@v2 - name: Make dictu and run checkTests run: | + sudo apt-get update + sudo apt-get install -y libcurl4-openssl-dev uuid-dev cmake -DCMAKE_BUILD_TYPE=Debug -DDISABLE_HTTP=1 -B ./build cmake --build ./build ./dictu scripts/checkTests.du ci From d1083a06e69f6cbd8dfdc4c7be4fc3c42ee3ba95 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 6 Dec 2022 18:31:26 -0700 Subject: [PATCH 030/109] update alpine dockerfile Signed-off-by: Brian Downs --- Docker/DictuAlpineDockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docker/DictuAlpineDockerfile b/Docker/DictuAlpineDockerfile index 5525d267..84aa21d7 100644 --- a/Docker/DictuAlpineDockerfile +++ b/Docker/DictuAlpineDockerfile @@ -2,7 +2,7 @@ FROM alpine WORKDIR Dictu -RUN apk add git make curl-dev gcc libc-dev cmake --no-cache +RUN apk add git make curl-dev gcc libc-dev cmake libuuid --no-cache RUN git clone https://github.com/dictu-lang/Dictu.git From 8f3baf551025652c8c04ad5e42503ecff086d78f Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 13 Dec 2022 16:37:28 -0700 Subject: [PATCH 031/109] add cmake flag Signed-off-by: Brian Downs --- src/CMakeLists.txt | 8 ++++++++ src/optionals/optionals.c | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a0f3feea..2170313f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -20,6 +20,14 @@ else() list(APPEND libraries curl) endif() +if(DISABLE_UUID) + list(FILTER sources EXCLUDE REGEX "uuid.c") + list(FILTER headers EXCLUDE REGEX "uuid.h") + add_compile_definitions(DISABLE_UUID) +else() + list(APPEND libraries uuid) +endif() + if(NOT SQLITE_LIB) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) diff --git a/src/optionals/optionals.c b/src/optionals/optionals.c index cbb69c74..c717d926 100644 --- a/src/optionals/optionals.c +++ b/src/optionals/optionals.c @@ -1,6 +1,7 @@ #include "optionals.h" BuiltinModules modules[] = { +<<<<<<< HEAD {"Argparse", &createArgParseModule, false}, {"Math", &createMathsModule, false}, {"Env", &createEnvModule, true}, @@ -22,6 +23,28 @@ BuiltinModules modules[] = { {"UnitTest", &createUnitTestModule, true}, {"Inspect", &createInspectModule, false}, {"Object", &createObjectModule, true}, +======= + {"Math", &createMathsModule, false}, + {"Env", &createEnvModule, true}, + {"JSON", &createJSONModule, false}, + {"Log", &createLogModule, false}, + {"Path", &createPathModule, false}, + {"Datetime", &createDatetimeModule, false}, + {"Socket", &createSocketModule, false}, + {"Random", &createRandomModule, false}, + {"Base64", &createBase64Module, false}, + {"Hashlib", &createHashlibModule, false}, + {"Sqlite", &createSqliteModule, false}, + {"Process", &createProcessModule, false}, + {"System", &createSystemModule, false}, + {"Term", &createTermModule, false}, +#ifndef DISABLE_UUID + {"UUID", &createUuidModule, false}, +#endif + {"UnitTest", &createUnitTestModule, true}, + {"Inspect", &createInspectModule, false}, + {"Object", &createObjectModule, true}, +>>>>>>> 58ad2f2 (add cmake flag) #ifndef DISABLE_HTTP {"HTTP", &createHTTPModule, true}, #endif From ed15c20cfee3e1e5d69473fc445a29019f4db088 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 13 Dec 2022 16:44:59 -0700 Subject: [PATCH 032/109] add test refs Signed-off-by: Brian Downs --- tests/runTests.du | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/runTests.du b/tests/runTests.du index bc06f560..a7fe281c 100644 --- a/tests/runTests.du +++ b/tests/runTests.du @@ -34,7 +34,11 @@ import "process/import.du"; import "inspect/import.du"; if (isDefined("HTTP")) { - import "http/import.du"; + import "http/import.du"; +} + +if (isDefined("UUID")) { + import "uuid/import.du"; } import "modules/import.du"; From 03cc7e798f341a6ec760ef1d4e1362af65933c97 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 13 Dec 2022 20:38:27 -0700 Subject: [PATCH 033/109] account for uuid being part of macos Signed-off-by: Brian Downs --- src/CMakeLists.txt | 4 +++- src/optionals/uuid.c | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2170313f..6dbd1fd7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -25,7 +25,9 @@ if(DISABLE_UUID) list(FILTER headers EXCLUDE REGEX "uuid.h") add_compile_definitions(DISABLE_UUID) else() - list(APPEND libraries uuid) + if(NOT APPLE) + list(APPEND libraries uuid) + endif() endif() if(NOT SQLITE_LIB) diff --git a/src/optionals/uuid.c b/src/optionals/uuid.c index dde081d1..b6c36727 100644 --- a/src/optionals/uuid.c +++ b/src/optionals/uuid.c @@ -20,7 +20,7 @@ static Value uuidGenerateNative(DictuVM *vm, int argCount, Value *args) { return newResultError(vm, "error: failed to generate uuid"); } - char out[37] = {}; + char out[UUID_STRING_LEN] = {}; uuid_unparse_lower(uuid, out); return newResultSuccess(vm, OBJ_VAL(copyString(vm, out, UUID_STRING_LEN))); @@ -42,7 +42,7 @@ static Value uuidGenRandomNative(DictuVM *vm, int argCount, Value *args) { return newResultError(vm, "error: failed to generate uuid"); } - char out[37] = {}; + char out[UUID_STRING_LEN] = {}; uuid_unparse_lower(uuid, out); return newResultSuccess(vm, OBJ_VAL(copyString(vm, out, UUID_STRING_LEN))); @@ -64,7 +64,7 @@ static Value uuidGenTimeNative(DictuVM *vm, int argCount, Value *args) { return newResultError(vm, "error: failed to generate uuid"); } - char out[37] = {}; + char out[UUID_STRING_LEN] = {}; uuid_unparse_lower(uuid, out); return newResultSuccess(vm, OBJ_VAL(copyString(vm, out, UUID_STRING_LEN))); From ce1627b331786328bce4b25a382a0f6cb99bc641 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Wed, 14 Dec 2022 08:44:33 -0700 Subject: [PATCH 034/109] fix conficts Signed-off-by: Brian Downs --- src/optionals/optionals.c | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/optionals/optionals.c b/src/optionals/optionals.c index c717d926..cbb69c74 100644 --- a/src/optionals/optionals.c +++ b/src/optionals/optionals.c @@ -1,7 +1,6 @@ #include "optionals.h" BuiltinModules modules[] = { -<<<<<<< HEAD {"Argparse", &createArgParseModule, false}, {"Math", &createMathsModule, false}, {"Env", &createEnvModule, true}, @@ -23,28 +22,6 @@ BuiltinModules modules[] = { {"UnitTest", &createUnitTestModule, true}, {"Inspect", &createInspectModule, false}, {"Object", &createObjectModule, true}, -======= - {"Math", &createMathsModule, false}, - {"Env", &createEnvModule, true}, - {"JSON", &createJSONModule, false}, - {"Log", &createLogModule, false}, - {"Path", &createPathModule, false}, - {"Datetime", &createDatetimeModule, false}, - {"Socket", &createSocketModule, false}, - {"Random", &createRandomModule, false}, - {"Base64", &createBase64Module, false}, - {"Hashlib", &createHashlibModule, false}, - {"Sqlite", &createSqliteModule, false}, - {"Process", &createProcessModule, false}, - {"System", &createSystemModule, false}, - {"Term", &createTermModule, false}, -#ifndef DISABLE_UUID - {"UUID", &createUuidModule, false}, -#endif - {"UnitTest", &createUnitTestModule, true}, - {"Inspect", &createInspectModule, false}, - {"Object", &createObjectModule, true}, ->>>>>>> 58ad2f2 (add cmake flag) #ifndef DISABLE_HTTP {"HTTP", &createHTTPModule, true}, #endif From a28d7417011d1381b8312d4ff1a3bc589181d441 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Wed, 14 Dec 2022 13:47:16 -0700 Subject: [PATCH 035/109] make nix only Signed-off-by: Brian Downs --- docs/docs/standard-lib/uuid.md | 6 ++++++ tests/system/uname.du | 12 ++++++------ tests/uuid/generate.du | 6 ++++-- tests/uuid/generateRandom.du | 6 ++++-- tests/uuid/generateTime.du | 6 ++++-- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/docs/docs/standard-lib/uuid.md b/docs/docs/standard-lib/uuid.md index 9e3db8bd..ca159acc 100644 --- a/docs/docs/standard-lib/uuid.md +++ b/docs/docs/standard-lib/uuid.md @@ -28,6 +28,8 @@ import UUID; Returns a Result value with a string representation of the UUID on success or an Error on failure. This function attempts to use `/dev/urandom` if available but if it's not, it uses alterntive means of generating randomness. +Note: This is not available on Windows systems. + ```cs const uuid = UUID.generate(0).unwrap(); print(uuid); @@ -38,6 +40,8 @@ print(uuid); Returns a Result value with a string representation of the UUID on success or an Error on failure. This function forces the use of the all-random UUID format. +Note: This is not available on Windows systems. + ```cs const uuid = UUID.generateRandom().unwrap(); print(uuid); @@ -48,6 +52,8 @@ print(uuid); Returns a Result value with a string representation of the UUID on success or an Error on failure. This function forces the use of the alternative algorithm which uses the current time and local MAC address (if available). +Note: This is not available on Windows systems. + ```cs const uuid = UUID.generateTime().unwrap(); print(uuid); diff --git a/tests/system/uname.du b/tests/system/uname.du index c3acf310..cca0c3b8 100644 --- a/tests/system/uname.du +++ b/tests/system/uname.du @@ -12,12 +12,12 @@ import System; class TestSystemUname < UnitTest { testSystemUname() { - if (System.platform != "windows") { - const uname = System.uname(); - this.assertNotNil(uname); - this.assertType(uname, "dict"); - } + const uname = System.uname(); + this.assertNotNil(uname); + this.assertType(uname, "dict"); } } -TestSystemUname().run(); +if (System.platform != "windows") { + TestSystemUname().run(); +} diff --git a/tests/uuid/generate.du b/tests/uuid/generate.du index d6895af6..b2297b9e 100644 --- a/tests/uuid/generate.du +++ b/tests/uuid/generate.du @@ -6,11 +6,11 @@ * The UUID.generate function provides the ability to generate UUID. */ import UUID; +import System; from UnitTest import UnitTest; class TestUUIDGenerate < UnitTest { - testGenerate() { const ret = UUID.generate(); this.assertTruthy(ret.success()); @@ -21,4 +21,6 @@ class TestUUIDGenerate < UnitTest { } } -TestUUIDGenerate().run(); +if (System.platform != "windows") { + TestUUIDGenerate().run(); +} diff --git a/tests/uuid/generateRandom.du b/tests/uuid/generateRandom.du index 7ee51e7f..0a66edcb 100644 --- a/tests/uuid/generateRandom.du +++ b/tests/uuid/generateRandom.du @@ -6,11 +6,11 @@ * The UUID.generateRandom function provides the ability to generate UUID. */ import UUID; +import System; from UnitTest import UnitTest; class TestUUIDGenerateRandom < UnitTest { - testGenerate() { const ret = UUID.generateRandom(); this.assertTruthy(ret.success()); @@ -20,4 +20,6 @@ class TestUUIDGenerateRandom < UnitTest { } } -TestUUIDGenerateRandom().run(); +if (System.platform != "windows") { + TestUUIDGenerateRandom().run(); +} diff --git a/tests/uuid/generateTime.du b/tests/uuid/generateTime.du index 61204398..efe05166 100644 --- a/tests/uuid/generateTime.du +++ b/tests/uuid/generateTime.du @@ -6,11 +6,11 @@ * The UUID.generateTime function provides the ability to generate UUID. */ import UUID; +import System; from UnitTest import UnitTest; class TestUUIDGenerateTime < UnitTest { - testGenerate() { const ret = UUID.generateTime(); this.assertTruthy(ret.success()); @@ -20,4 +20,6 @@ class TestUUIDGenerateTime < UnitTest { } } -TestUUIDGenerateTime().run(); +if (System.platform != "windows") { + TestUUIDGenerateTime().run(); +} From 78b426143d9a5dc93267060ba314627cf08c7976 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Wed, 14 Dec 2022 15:11:17 -0700 Subject: [PATCH 036/109] rope off code behind macro checks for windows Signed-off-by: Brian Downs --- src/optionals/uuid.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/optionals/uuid.c b/src/optionals/uuid.c index b6c36727..6e3201c2 100644 --- a/src/optionals/uuid.c +++ b/src/optionals/uuid.c @@ -4,6 +4,7 @@ static uuid_t uuid; #define UUID_STRING_LEN 37 +#ifndef _WIN32 static Value uuidGenerateNative(DictuVM *vm, int argCount, Value *args) { UNUSED(args); @@ -69,6 +70,7 @@ static Value uuidGenTimeNative(DictuVM *vm, int argCount, Value *args) { return newResultSuccess(vm, OBJ_VAL(copyString(vm, out, UUID_STRING_LEN))); } +#endif Value createUuidModule(DictuVM *vm) { ObjString *name = copyString(vm, "UUID", 4); @@ -79,10 +81,11 @@ Value createUuidModule(DictuVM *vm) { /** * Define UUID methods */ +#ifndef _WIN32 defineNative(vm, &module->values, "generate", uuidGenerateNative); defineNative(vm, &module->values, "generateRandom", uuidGenRandomNative); defineNative(vm, &module->values, "generateTime", uuidGenTimeNative); - +#endif /** * Define UUID properties */ From b6479baa51cfd1b0d58331eee8182f3c87824b8e Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Wed, 14 Dec 2022 15:14:18 -0700 Subject: [PATCH 037/109] rope off code behind macro checks for windows Signed-off-by: Brian Downs --- src/optionals/uuid.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/optionals/uuid.h b/src/optionals/uuid.h index 28b16d1d..f07eef74 100644 --- a/src/optionals/uuid.h +++ b/src/optionals/uuid.h @@ -5,7 +5,9 @@ #include "../vm/vm.h" #include "../vm/memory.h" +#ifndef _WIN32 #include +#endif Value createUuidModule(DictuVM *vm); From 623ef20b74581d2ebce5e7d27559c88f6343f0f1 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 16 Dec 2022 20:47:33 -0700 Subject: [PATCH 038/109] add copyFile func and docs Signed-off-by: Brian Downs --- docs/docs/standard-lib/system.md | 14 ++++++-- examples/copyFile.du | 57 ++++++++++++++++++++++++++++++++ src/optionals/system.c | 38 +++++++++++++++++++++ 3 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 examples/copyFile.du diff --git a/docs/docs/standard-lib/system.md b/docs/docs/standard-lib/system.md index 3e4c4ba2..dd891a9b 100644 --- a/docs/docs/standard-lib/system.md +++ b/docs/docs/standard-lib/system.md @@ -263,11 +263,21 @@ Makes a temporary directory. If an empty string is given, the temporary director The directory template passed in **must** end with "XXXXXX". -Returns a Result type and on success will unwrap to a the created directory name. +Returns a Result type and on success will unwrap to a string containing the created directory name. Note: This is not available on Windows systems. ```cs System.mkdirTemp().unwrap(); // "VOO16s" System.mkdirTemp("test_XXXXXX").unwrap(); // "test_0bL2qS" -``` \ No newline at end of file +``` + +### System.copyFile(string: src, string: dst) + +Copies the contents from the source file to the destination file. + +Returns a Result type and on success will unwrap to nil. + +```cs +System.copyFile(src, dst); +``` diff --git a/examples/copyFile.du b/examples/copyFile.du new file mode 100644 index 00000000..149035eb --- /dev/null +++ b/examples/copyFile.du @@ -0,0 +1,57 @@ +import Path; +import System; + +const srcFile = "src_file"; +const dstFile = "dst_file"; + +def cleanup(tmpDir) { + Path.listDir(tmpDir).forEach(def(f) => { + System.remove(Path.join(tmpDir, f)).match( + def(result) => result, + def(error) => { + print(error); + System.exit(1); + } + ); + }); + + System.rmdir(tmpDir).match( + def(result) => result, + def(error) => { + print(error); + System.exit(1); + } + ); +} + +{ // main + const tmpDir = System.mkdirTemp().unwrap(); + + with(Path.join(tmpDir, srcFile), 'w') { + file.write("blah blah blah"); + } + + print("source file!"); + with(Path.join(tmpDir, srcFile), 'r') { + print(file.read()); + } + + System.copyFile(Path.join(tmpDir, srcFile), Path.join(tmpDir, dstFile)).match( + def(result) => result, + def(error) => { + print(error); + System.exit(1); + } + ); + + print("destination file!"); + with(Path.join(tmpDir, srcFile), 'r') { + print(file.read()); + } + + System.sleep(10); + + cleanup(tmpDir); + + System.exit(0); +} diff --git a/src/optionals/system.c b/src/optionals/system.c index 1cc9df9f..21e03789 100644 --- a/src/optionals/system.c +++ b/src/optionals/system.c @@ -433,6 +433,43 @@ static Value chmodNative(DictuVM *vm, int argCount, Value *args) { return newResultSuccess(vm, NIL_VAL); } +static Value copyFileNative(DictuVM *vm, int argCount, Value *args) { + if (argCount != 2) { + runtimeError(vm, "copyFile() takes 2 arguments (%d given).", argCount); + return EMPTY_VAL; + } + + if (!IS_STRING(args[0]) || !IS_STRING(args[1])) { + runtimeError(vm, "copyFile() arguments must be strings."); + return EMPTY_VAL; + } + + char *srcFile = AS_STRING(args[0])->chars; + char *dstFile = AS_STRING(args[1])->chars; + + FILE *sf = fopen(srcFile, "r"); + if (sf == NULL) { + return newResultError(vm, "cannot open src file"); + } + + FILE *df = fopen(dstFile, "w"); + if (df == NULL) { + fclose(sf); + return newResultError(vm, "cannot open dst file"); + } + + char buffer = fgetc(sf); + while (buffer != EOF) { + fputc(buffer, df); + buffer = fgetc(sf); + } + + fclose(sf); + fclose(df); + + return newResultSuccess(vm, NIL_VAL); +} + void initArgv(DictuVM *vm, Table *table, int argc, char **argv) { ObjList *list = newList(vm); push(vm, OBJ_VAL(list)); @@ -532,6 +569,7 @@ Value createSystemModule(DictuVM *vm) { defineNative(vm, &module->values, "sleep", sleepNative); defineNative(vm, &module->values, "exit", exitNative); defineNative(vm, &module->values, "chmod", chmodNative); + defineNative(vm, &module->values, "copyFile", copyFileNative); /** * Define System properties From 648f0a5183af3de01fac2f455db0df282ae6b1b0 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 16 Dec 2022 21:33:51 -0700 Subject: [PATCH 039/109] add copyFile tests Signed-off-by: Brian Downs --- examples/copyFile.du | 4 ++-- tests/system/copyFile.du | 51 ++++++++++++++++++++++++++++++++++++++++ tests/system/import.du | 1 + 3 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 tests/system/copyFile.du diff --git a/examples/copyFile.du b/examples/copyFile.du index 149035eb..9f8ec5db 100644 --- a/examples/copyFile.du +++ b/examples/copyFile.du @@ -1,8 +1,8 @@ import Path; import System; -const srcFile = "src_file"; -const dstFile = "dst_file"; +const srcFile = "src_file", + dstFile = "dst_file"; def cleanup(tmpDir) { Path.listDir(tmpDir).forEach(def(f) => { diff --git a/tests/system/copyFile.du b/tests/system/copyFile.du new file mode 100644 index 00000000..9016d450 --- /dev/null +++ b/tests/system/copyFile.du @@ -0,0 +1,51 @@ +/** + * copyFile.du + * + * Testing the System.copyFile() function + * + * copyFile() copies the contents from the source file to the + * destination file. + */ +from UnitTest import UnitTest; + +import Path; +import System; + +const srcFile = "src_file", + dstFile = "dst_file"; + +class TestSystemCopyFile < UnitTest { + private tmpDir; + + setUp() { + this.tmpDir = System.mkdirTemp().unwrap(); + } + + tearDown() { + Path.listDir(this.tmpDir).forEach(def(f) => { + System.remove(Path.join(this.tmpDir, f)); + }); + + System.rmdir(this.tmpDir); + } + + testCopyFile() { + with(Path.join(this.tmpDir, srcFile), 'w') { + file.write("lots and lots of temp data!"); + } + + with(Path.join(this.tmpDir, srcFile), 'r') { + print(file.read()); + } + + const srcFullPath = Path.join(this.tmpDir, srcFile); + const dstFullPath = Path.join(this.tmpDir, dstFile); + + const res = System.copyFile(srcFullPath, dstFullPath); + + this.assertNotNil(res); + this.assertSuccess(res); + } +} + +TestSystemCopyFile().run(); diff --git a/tests/system/import.du b/tests/system/import.du index e3e04d74..bd07f0c0 100644 --- a/tests/system/import.du +++ b/tests/system/import.du @@ -5,6 +5,7 @@ */ import "access.du"; +import "copyFile.du"; import "version.du"; import "sleep.du"; import "getCWD.du"; From 221e36ce19b0f43245a5a9c4630f16c1068772f2 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 16 Dec 2022 21:42:02 -0700 Subject: [PATCH 040/109] use mkdir in test for windows compatibility Signed-off-by: Brian Downs --- tests/system/copyFile.du | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system/copyFile.du b/tests/system/copyFile.du index 9016d450..67b0d49e 100644 --- a/tests/system/copyFile.du +++ b/tests/system/copyFile.du @@ -18,7 +18,7 @@ class TestSystemCopyFile < UnitTest { private tmpDir; setUp() { - this.tmpDir = System.mkdirTemp().unwrap(); + this.tmpDir = System.mkdir("temp").unwrap(); } tearDown() { From 8a9e38b4b30910bbe8740ff2063367fe7154c795 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 16 Dec 2022 21:45:43 -0700 Subject: [PATCH 041/109] use mkdir in test for windows compatibility Signed-off-by: Brian Downs --- tests/system/copyFile.du | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/system/copyFile.du b/tests/system/copyFile.du index 67b0d49e..9ef369aa 100644 --- a/tests/system/copyFile.du +++ b/tests/system/copyFile.du @@ -18,7 +18,8 @@ class TestSystemCopyFile < UnitTest { private tmpDir; setUp() { - this.tmpDir = System.mkdir("temp").unwrap(); + this.tmpDir = "temp"; + System.mkdir("temp"); } tearDown() { From f1540ceaa885eafa6d5c4f08db46d27ce8552ac5 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Thu, 22 Dec 2022 19:53:04 -0700 Subject: [PATCH 042/109] fix status const name Signed-off-by: Brian Downs --- src/optionals/http/http.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/optionals/http/http.c b/src/optionals/http/http.c index 813a8fff..ecd61f14 100644 --- a/src/optionals/http/http.c +++ b/src/optionals/http/http.c @@ -1098,7 +1098,7 @@ Value createHTTPModule(DictuVM *vm) { defineNativeProperty(vm, &module->values, "STATUS_CODE_SWITCHING_PROTOCOLS", NUMBER_VAL(HTTP_STATUS_CODE_SWITCHING_PROTOCOLS)); defineNativeProperty(vm, &module->values, "STATUS_CODE_PROCESSING", NUMBER_VAL(HTTP_STATUS_CODE_PROCESSING)); defineNativeProperty(vm, &module->values, "STATUS_CODE_EARLY_HINTS", NUMBER_VAL(HTTP_STATUS_CODE_EARLY_HINTS)); - defineNativeProperty(vm, &module->values, "STATUS_OK", NUMBER_VAL(HTTP_STATUS_CODE_OK)); + defineNativeProperty(vm, &module->values, "STATUS_CODE_OK", NUMBER_VAL(HTTP_STATUS_CODE_OK)); defineNativeProperty(vm, &module->values, "STATUS_CODE_CREATED", NUMBER_VAL(HTTP_STATUS_CODE_CREATED)); defineNativeProperty(vm, &module->values, "STATUS_CODE_ACCEPTED", NUMBER_VAL(HTTP_STATUS_CODE_ACCEPTED)); From b4c59fadc1f493ef776d5c7c4e3dc7ce57ddfe3e Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Thu, 22 Dec 2022 19:56:44 -0700 Subject: [PATCH 043/109] fix test Signed-off-by: Brian Downs --- tests/http/constants.du | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/http/constants.du b/tests/http/constants.du index 70496ee4..d063ecc5 100644 --- a/tests/http/constants.du +++ b/tests/http/constants.du @@ -12,7 +12,7 @@ class TestHttpConstants < UnitTest { testHttpConstantsProvider() { return [ - {"constant": HTTP.STATUS_OK, "expected": 200}, + {"constant": HTTP.STATUS_CODE_OK, "expected": 200}, ]; } } From 48431dc75e099c0657cada8653c3d3d7881fe438 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Thu, 15 Dec 2022 19:24:05 -0700 Subject: [PATCH 044/109] more Signed-off-by: Brian Downs --- examples/methodAnnotations.du | 4 ++ src/vm/compiler.c | 87 ++++++++++++++++++++++++++++------- src/vm/compiler.h | 4 +- src/vm/debug.c | 2 + src/vm/memory.c | 2 +- src/vm/object.c | 2 +- src/vm/object.h | 3 +- src/vm/opcodes.h | 1 + src/vm/vm.c | 13 +++++- 9 files changed, 96 insertions(+), 22 deletions(-) create mode 100644 examples/methodAnnotations.du diff --git a/examples/methodAnnotations.du b/examples/methodAnnotations.du new file mode 100644 index 00000000..32e113ad --- /dev/null +++ b/examples/methodAnnotations.du @@ -0,0 +1,4 @@ +@Annotation +class AnnotatedClass { + init() {} +} diff --git a/src/vm/compiler.c b/src/vm/compiler.c index e1ac1283..4b9bc413 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -163,7 +163,8 @@ static void initCompiler(Parser *parser, Compiler *compiler, Compiler *parent, F compiler->class = NULL; compiler->loop = NULL; compiler->withBlock = false; - compiler->annotations = NULL; + compiler->classAnnotations = NULL; + compiler->methodAnnotations = NULL; if (parent != NULL) { compiler->class = parent->class; @@ -1628,6 +1629,7 @@ static void setupClassCompiler(Compiler *compiler, ClassCompiler *classCompiler, classCompiler->staticMethod = false; classCompiler->abstractClass = abstract; classCompiler->annotations = NULL; + classCompiler->methodAnnotations = NULL; initTable(&classCompiler->privateVariables); compiler->class = classCompiler; } @@ -1636,10 +1638,16 @@ static void endClassCompiler(Compiler *compiler, ClassCompiler *classCompiler) { freeTable(compiler->parser->vm, &classCompiler->privateVariables); compiler->class = compiler->class->enclosing; - if (compiler->annotations != NULL) { - int importConstant = makeConstant(compiler, OBJ_VAL(compiler->annotations)); + if (compiler->classAnnotations != NULL) { + int importConstant = makeConstant(compiler, OBJ_VAL(compiler->classAnnotations)); emitBytes(compiler, OP_DEFINE_CLASS_ANNOTATIONS, importConstant); - compiler->annotations = NULL; + compiler->classAnnotations = NULL; + } + + if (compiler->methodAnnotations != NULL) { + int importConstant = makeConstant(compiler, OBJ_VAL(compiler->methodAnnotations)); + emitBytes(compiler, OP_DEFINE_METHOD_ANNOTATIONS, importConstant); + compiler->methodAnnotations = NULL; } } @@ -1648,9 +1656,53 @@ static bool checkLiteralToken(Compiler *compiler) { check(compiler, TOKEN_TRUE) || check(compiler, TOKEN_FALSE) || check(compiler, TOKEN_NIL); } +static void parseMethodAnnotations(Compiler *compiler) { + DictuVM *vm = compiler->parser->vm; + compiler->methodAnnotations = newDict(vm); + + do { + consume(compiler, TOKEN_IDENTIFIER, "Expected annotation identifier"); + Value annotationName = OBJ_VAL(copyString(vm, compiler->parser->previous.start, + compiler->parser->previous.length)); + push(vm, annotationName); + + if (match(compiler, TOKEN_LEFT_PAREN)) { + if (!checkLiteralToken(compiler)) { + errorAtCurrent(compiler->parser, + "Annotations can only have literal values of type string, bool, number or nil."); + return; + } + + if (match(compiler, TOKEN_STRING)) { + Value string = parseString(compiler, false); + push(vm, string); + dictSet(vm, compiler->methodAnnotations, annotationName, string); + pop(vm); + } else if (match(compiler, TOKEN_NUMBER)) { + Value number = parseNumber(compiler, false); + dictSet(vm, compiler->methodAnnotations, annotationName, number); + } else if (match(compiler, TOKEN_TRUE)) { + dictSet(vm, compiler->methodAnnotations, annotationName, TRUE_VAL); + } else if (match(compiler, TOKEN_FALSE)) { + dictSet(vm, compiler->methodAnnotations, annotationName, FALSE_VAL); + } else if (match(compiler, TOKEN_NIL)) { + dictSet(vm, compiler->methodAnnotations, annotationName, NIL_VAL); + } else { + dictSet(vm, compiler->methodAnnotations, annotationName, NIL_VAL); + } + + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after annotation value."); + } else { + dictSet(vm, compiler->methodAnnotations, annotationName, NIL_VAL); + } + + pop(vm); + } while (match(compiler, TOKEN_AT)); +} + static void parseClassAnnotations(Compiler *compiler) { DictuVM *vm = compiler->parser->vm; - compiler->annotations = newDict(vm); + compiler->classAnnotations = newDict(vm); do { consume(compiler, TOKEN_IDENTIFIER, "Expected annotation identifier"); @@ -1668,24 +1720,24 @@ static void parseClassAnnotations(Compiler *compiler) { if (match(compiler, TOKEN_STRING)) { Value string = parseString(compiler, false); push(vm, string); - dictSet(vm, compiler->annotations, annotationName, string); + dictSet(vm, compiler->classAnnotations, annotationName, string); pop(vm); } else if (match(compiler, TOKEN_NUMBER)) { Value number = parseNumber(compiler, false); - dictSet(vm, compiler->annotations, annotationName, number); + dictSet(vm, compiler->classAnnotations, annotationName, number); } else if (match(compiler, TOKEN_TRUE)) { - dictSet(vm, compiler->annotations, annotationName, TRUE_VAL); + dictSet(vm, compiler->classAnnotations, annotationName, TRUE_VAL); } else if (match(compiler, TOKEN_FALSE)) { - dictSet(vm, compiler->annotations, annotationName, FALSE_VAL); + dictSet(vm, compiler->classAnnotations, annotationName, FALSE_VAL); } else if (match(compiler, TOKEN_NIL)) { - dictSet(vm, compiler->annotations, annotationName, NIL_VAL); + dictSet(vm, compiler->classAnnotations, annotationName, NIL_VAL); } else { - dictSet(vm, compiler->annotations, annotationName, NIL_VAL); + dictSet(vm, compiler->classAnnotations, annotationName, NIL_VAL); } consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after annotation value."); } else { - dictSet(vm, compiler->annotations, annotationName, NIL_VAL); + dictSet(vm, compiler->classAnnotations, annotationName, NIL_VAL); } pop(vm); @@ -2591,9 +2643,9 @@ static void declaration(Compiler *compiler) { return; } - if (compiler->annotations != NULL) { - errorAtCurrent(compiler->parser, "Annotations can only be applied to classes"); - } + // if (compiler->classAnnotations != NULL) { + // errorAtCurrent(compiler->parser, "Annotations can only be applied to classes"); + // } if (match(compiler, TOKEN_TRAIT)) { traitDeclaration(compiler); @@ -2609,6 +2661,7 @@ static void declaration(Compiler *compiler) { enumDeclaration(compiler); } else if (match(compiler, TOKEN_AT)) { parseClassAnnotations(compiler); + parseMethodAnnotations(compiler); } else { statement(compiler); } @@ -2726,11 +2779,13 @@ void grayCompilerRoots(DictuVM *vm) { while (classCompiler != NULL) { grayObject(vm, (Obj *) classCompiler->annotations); + grayObject(vm, (Obj *) classCompiler->methodAnnotations); grayTable(vm, &classCompiler->privateVariables); classCompiler = classCompiler->enclosing; } - grayObject(vm, (Obj *) compiler->annotations); + grayObject(vm, (Obj *) compiler->classAnnotations); + grayObject(vm, (Obj *) compiler->methodAnnotations); grayObject(vm, (Obj *) compiler->function); grayTable(vm, &compiler->stringConstants); compiler = compiler->enclosing; diff --git a/src/vm/compiler.h b/src/vm/compiler.h index e78329f6..c326ee26 100644 --- a/src/vm/compiler.h +++ b/src/vm/compiler.h @@ -64,6 +64,7 @@ typedef struct ClassCompiler { bool abstractClass; Table privateVariables; ObjDict *annotations; + ObjDict *methodAnnotations; } ClassCompiler; typedef struct Loop { @@ -102,7 +103,8 @@ typedef struct Compiler { int scopeDepth; bool withBlock; - ObjDict *annotations; + ObjDict *classAnnotations; + ObjDict *methodAnnotations; } Compiler; typedef void (*ParsePrefixFn)(Compiler *compiler, bool canAssign); diff --git a/src/vm/debug.c b/src/vm/debug.c index 4b72b197..d3212475 100644 --- a/src/vm/debug.c +++ b/src/vm/debug.c @@ -292,6 +292,8 @@ int disassembleInstruction(Chunk *chunk, int offset) { return constantInstruction("OP_METHOD", chunk, offset); case OP_DEFINE_CLASS_ANNOTATIONS: return constantInstruction("OP_DEFINE_CLASS_ANNOTATIONS", chunk, offset); + case OP_DEFINE_METHOD_ANNOTATIONS: + return constantInstruction("OP_DEFINE_METHOD_ANNOTATIONS", chunk, offset); case OP_ENUM: return constantInstruction("OP_ENUM", chunk, offset); case OP_SET_ENUM_VALUE: diff --git a/src/vm/memory.c b/src/vm/memory.c index bbe6fb88..8a8a55de 100644 --- a/src/vm/memory.c +++ b/src/vm/memory.c @@ -101,7 +101,7 @@ static void blackenObject(DictuVM *vm, Obj *object) { ObjClass *klass = (ObjClass *) object; grayObject(vm, (Obj *) klass->name); grayObject(vm, (Obj *) klass->superclass); - grayObject(vm, (Obj *) klass->annotations); + grayObject(vm, (Obj *) klass->classAnnotations); grayTable(vm, &klass->publicMethods); grayTable(vm, &klass->privateMethods); grayTable(vm, &klass->abstractMethods); diff --git a/src/vm/object.c b/src/vm/object.c index e300a587..c3bbf516 100644 --- a/src/vm/object.c +++ b/src/vm/object.c @@ -69,7 +69,7 @@ ObjClass *newClass(DictuVM *vm, ObjString *name, ObjClass *superclass, ClassType initTable(&klass->publicMethods); initTable(&klass->publicProperties); initTable(&klass->publicConstantProperties); - klass->annotations = NULL; + klass->classAnnotations = NULL; push(vm, OBJ_VAL(klass)); ObjString *nameString = copyString(vm, "_name", 5); diff --git a/src/vm/object.h b/src/vm/object.h index 12de3c34..83d831ab 100644 --- a/src/vm/object.h +++ b/src/vm/object.h @@ -225,7 +225,8 @@ typedef struct sObjClass { Table abstractMethods; Table publicProperties; Table publicConstantProperties; - ObjDict *annotations; + ObjDict *classAnnotations; + ObjDict *methodAnnotations; ClassType type; } ObjClass; diff --git a/src/vm/opcodes.h b/src/vm/opcodes.h index c392fb24..9af5a60f 100644 --- a/src/vm/opcodes.h +++ b/src/vm/opcodes.h @@ -57,6 +57,7 @@ OPCODE(EMPTY) OPCODE(CLASS) OPCODE(SUBCLASS) OPCODE(DEFINE_CLASS_ANNOTATIONS) +OPCODE(DEFINE_METHOD_ANNOTATIONS) OPCODE(END_CLASS) OPCODE(METHOD) OPCODE(ENUM) diff --git a/src/vm/vm.c b/src/vm/vm.c index 4f9417ac..565482d2 100644 --- a/src/vm/vm.c +++ b/src/vm/vm.c @@ -1135,7 +1135,7 @@ static DictuInterpretResult run(DictuVM *vm) { if (strcmp(name->chars, "annotations") == 0) { pop(vm); // Klass - push(vm, klassStore->annotations == NULL ? NIL_VAL : OBJ_VAL(klassStore->annotations)); + push(vm, klassStore->classAnnotations == NULL ? NIL_VAL : OBJ_VAL(klassStore->classAnnotations)); DISPATCH(); } @@ -2161,7 +2161,16 @@ static DictuInterpretResult run(DictuVM *vm) { ObjDict *dict = AS_DICT(READ_CONSTANT()); ObjClass *klass = AS_CLASS(peek(vm, 0)); - klass->annotations = dict; + klass->classAnnotations = dict; + + DISPATCH(); + } + + CASE_CODE(DEFINE_METHOD_ANNOTATIONS): { + ObjDict *dict = AS_DICT(READ_CONSTANT()); + ObjClass *klass = AS_CLASS(peek(vm, 0)); + + klass->methodAnnotations = dict; DISPATCH(); } From 9c7462dda6c18fa9fd4a5fa353758924bf1eb825 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 23 Dec 2022 22:36:27 -0700 Subject: [PATCH 045/109] compiler actually working Signed-off-by: Brian Downs --- examples/methodAnnotations.du | 10 +++++++++- src/vm/compiler.c | 10 ++++++---- src/vm/compiler.h | 2 +- src/vm/vm.c | 8 +++++++- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/examples/methodAnnotations.du b/examples/methodAnnotations.du index 32e113ad..a5830073 100644 --- a/examples/methodAnnotations.du +++ b/examples/methodAnnotations.du @@ -1,4 +1,12 @@ -@Annotation +@ResourceController("/api/v1") class AnnotatedClass { init() {} + + @Get("/releases") + releaseHandler() {} } + +const a = AnnotatedClass(); +print(a.getAttributes()); +print(a._class.classAnnotations); +print(a._class.methodAnnotations); diff --git a/src/vm/compiler.c b/src/vm/compiler.c index 4b9bc413..5b120226 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -1627,8 +1627,8 @@ static void setupClassCompiler(Compiler *compiler, ClassCompiler *classCompiler, classCompiler->hasSuperclass = false; classCompiler->enclosing = compiler->class; classCompiler->staticMethod = false; - classCompiler->abstractClass = abstract; - classCompiler->annotations = NULL; + classCompiler->abstractClass = abstract; + classCompiler->classAnnotations = NULL; classCompiler->methodAnnotations = NULL; initTable(&classCompiler->privateVariables); compiler->class = classCompiler; @@ -1768,6 +1768,9 @@ static void parseClassBody(Compiler *compiler) { emitByte(compiler, true); consume(compiler, TOKEN_SEMICOLON, "Expect ';' after class constant declaration."); + } else if (match(compiler, TOKEN_AT)) { + printf("found method annotation\n"); + parseMethodAnnotations(compiler); } else { if (match(compiler, TOKEN_PRIVATE)) { if (match(compiler, TOKEN_IDENTIFIER)) { @@ -2661,7 +2664,6 @@ static void declaration(Compiler *compiler) { enumDeclaration(compiler); } else if (match(compiler, TOKEN_AT)) { parseClassAnnotations(compiler); - parseMethodAnnotations(compiler); } else { statement(compiler); } @@ -2778,7 +2780,7 @@ void grayCompilerRoots(DictuVM *vm) { ClassCompiler *classCompiler = vm->compiler->class; while (classCompiler != NULL) { - grayObject(vm, (Obj *) classCompiler->annotations); + grayObject(vm, (Obj *) classCompiler->classAnnotations); grayObject(vm, (Obj *) classCompiler->methodAnnotations); grayTable(vm, &classCompiler->privateVariables); classCompiler = classCompiler->enclosing; diff --git a/src/vm/compiler.h b/src/vm/compiler.h index c326ee26..1a5f955d 100644 --- a/src/vm/compiler.h +++ b/src/vm/compiler.h @@ -63,7 +63,7 @@ typedef struct ClassCompiler { bool staticMethod; bool abstractClass; Table privateVariables; - ObjDict *annotations; + ObjDict *classAnnotations; ObjDict *methodAnnotations; } ClassCompiler; diff --git a/src/vm/vm.c b/src/vm/vm.c index 565482d2..71686161 100644 --- a/src/vm/vm.c +++ b/src/vm/vm.c @@ -1133,11 +1133,17 @@ static DictuInterpretResult run(DictuVM *vm) { klass = klass->superclass; } - if (strcmp(name->chars, "annotations") == 0) { + if (strcmp(name->chars, "classAnnotations") == 0) { pop(vm); // Klass push(vm, klassStore->classAnnotations == NULL ? NIL_VAL : OBJ_VAL(klassStore->classAnnotations)); DISPATCH(); } + + if (strcmp(name->chars, "methodAnnotations") == 0) { + pop(vm); // Klass + push(vm, klassStore->methodAnnotations == NULL ? NIL_VAL : OBJ_VAL(klassStore->methodAnnotations)); + DISPATCH(); + } RUNTIME_ERROR("'%s' class has no property: '%s'.", klassStore->name->chars, name->chars); } From 89cc873433e426096bb0185e79b5f4f9e1c58be5 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 23 Dec 2022 22:42:12 -0700 Subject: [PATCH 046/109] update tests for classAnnotations Signed-off-by: Brian Downs --- tests/classes/annotations.du | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/classes/annotations.du b/tests/classes/annotations.du index 55d6ae9c..a98340a6 100644 --- a/tests/classes/annotations.du +++ b/tests/classes/annotations.du @@ -1,7 +1,7 @@ /** - * annotations.du + * classannotations.du * - * Testing class annotations + * Testing class classannotations */ from UnitTest import UnitTest; @@ -20,17 +20,17 @@ class Test { class TestClassAnnotations < UnitTest { testClassAnnotationsEmpty() { - this.assertEquals(NoAnnotations.annotations, nil); + this.assertEquals(NoAnnotations.classannotations, nil); } testClassAnnotations() { - this.assertEquals(Test.annotations.len(), 6); - this.assertEquals(Test.annotations['EmptyAnnotation'], nil); - this.assertEquals(Test.annotations['TrueAnnotation'], true); - this.assertEquals(Test.annotations['FalseAnnotation'], false); - this.assertEquals(Test.annotations['NumberAnnotation'], 10); - this.assertEquals(Test.annotations['DecimalNumberAnnotation'], 10.5); - this.assertEquals(Test.annotations['NilAnnotation'], nil); + this.assertEquals(Test.classannotations.len(), 6); + this.assertEquals(Test.classannotations['EmptyAnnotation'], nil); + this.assertEquals(Test.classannotations['TrueAnnotation'], true); + this.assertEquals(Test.classannotations['FalseAnnotation'], false); + this.assertEquals(Test.classannotations['NumberAnnotation'], 10); + this.assertEquals(Test.classannotations['DecimalNumberAnnotation'], 10.5); + this.assertEquals(Test.classannotations['NilAnnotation'], nil); } } From 78a67d9fd875f9d7b83d865d98f1fe55a6813e65 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 23 Dec 2022 22:44:56 -0700 Subject: [PATCH 047/109] update tests for classAnnotations Signed-off-by: Brian Downs --- tests/classes/annotations.du | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/classes/annotations.du b/tests/classes/annotations.du index a98340a6..a60ef4ff 100644 --- a/tests/classes/annotations.du +++ b/tests/classes/annotations.du @@ -1,7 +1,7 @@ /** - * classannotations.du + * classAnnotations.du * - * Testing class classannotations + * Testing class classAnnotations */ from UnitTest import UnitTest; @@ -20,17 +20,17 @@ class Test { class TestClassAnnotations < UnitTest { testClassAnnotationsEmpty() { - this.assertEquals(NoAnnotations.classannotations, nil); + this.assertEquals(NoAnnotations.classAnnotations, nil); } testClassAnnotations() { - this.assertEquals(Test.classannotations.len(), 6); - this.assertEquals(Test.classannotations['EmptyAnnotation'], nil); - this.assertEquals(Test.classannotations['TrueAnnotation'], true); - this.assertEquals(Test.classannotations['FalseAnnotation'], false); - this.assertEquals(Test.classannotations['NumberAnnotation'], 10); - this.assertEquals(Test.classannotations['DecimalNumberAnnotation'], 10.5); - this.assertEquals(Test.classannotations['NilAnnotation'], nil); + this.assertEquals(Test.classAnnotations.len(), 6); + this.assertEquals(Test.classAnnotations['EmptyAnnotation'], nil); + this.assertEquals(Test.classAnnotations['TrueAnnotation'], true); + this.assertEquals(Test.classAnnotations['FalseAnnotation'], false); + this.assertEquals(Test.classAnnotations['NumberAnnotation'], 10); + this.assertEquals(Test.classAnnotations['DecimalNumberAnnotation'], 10.5); + this.assertEquals(Test.classAnnotations['NilAnnotation'], nil); } } From 89300a3e59b74daf8d153a03bb6cb36287297f14 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Mon, 26 Dec 2022 22:21:48 -0700 Subject: [PATCH 048/109] almost there... Signed-off-by: Brian Downs --- examples/methodAnnotations.du | 20 +++++++- src/vm/compiler.c | 86 ++++++++++++++++++++++++++--------- tests/classes/annotations.du | 19 +++++++- 3 files changed, 101 insertions(+), 24 deletions(-) diff --git a/examples/methodAnnotations.du b/examples/methodAnnotations.du index a5830073..3461eb27 100644 --- a/examples/methodAnnotations.du +++ b/examples/methodAnnotations.du @@ -1,12 +1,28 @@ +// @BaseClassAnnotation +// class BaseClass { +// @AbstractMethodAnnotation +// getHandler() {} + +// } + @ResourceController("/api/v1") class AnnotatedClass { init() {} - @Get("/releases") + @BasicAuth @Get("/releases") releaseHandler() {} + + @BasicAuth + @Get("/assets") + assetHandler() {} + + private someOtherHandler() {} } const a = AnnotatedClass(); -print(a.getAttributes()); + print(a._class.classAnnotations); print(a._class.methodAnnotations); +print(type(a._class.methodAnnotations)); +const methodAnnotations = a._class.methodAnnotations; +print(methodAnnotations["releaseHandler"]); diff --git a/src/vm/compiler.c b/src/vm/compiler.c index 5b120226..beba63da 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -1564,7 +1564,7 @@ static void function(Compiler *compiler, FunctionType type, AccessLevel level) { endCompiler(&fnCompiler); } -static void method(Compiler *compiler, bool private, Token *identifier) { +static void method(Compiler *compiler, bool private, Token *identifier, bool hasAnnotation) { AccessLevel level = ACCESS_PUBLIC; FunctionType type; @@ -1602,8 +1602,44 @@ static void method(Compiler *compiler, bool private, Token *identifier) { if (compiler->parser->previous.length == 4 && memcmp(compiler->parser->previous.start, "init", 4) == 0) { type = TYPE_INITIALIZER; + } else { + if (hasAnnotation) { + DictuVM *vm = compiler->parser->vm; + + for (int i = 0; i <= compiler->methodAnnotations->capacityMask; i++) { + DictItem *entry = &compiler->methodAnnotations->entries[i]; + if (IS_EMPTY(entry->key)) { + continue; + } + + char *key; + + if (IS_STRING(entry->key)) { + ObjString *s = AS_STRING(entry->key); + key = s->chars; + } else if (IS_NIL(entry->key)) { + key = malloc(5); + memcpy(key, "null", 4); + key[4] = '\0'; + } else { + key = valueToString(entry->key); + } + + if (strcmp(key, "methodName") == 0) { + ObjString *methodKey = copyString(vm, compiler->parser->previous.start, compiler->parser->previous.length); + push(vm, OBJ_VAL(methodKey)); + compiler->methodAnnotations->entries[i].key = OBJ_VAL(methodKey); + pop(vm); + } + + if (!IS_STRING(entry->key)) { + free(key); + } + } + } } + if (type != TYPE_ABSTRACT) { function(compiler, type, level); } else { @@ -1658,7 +1694,15 @@ static bool checkLiteralToken(Compiler *compiler) { static void parseMethodAnnotations(Compiler *compiler) { DictuVM *vm = compiler->parser->vm; - compiler->methodAnnotations = newDict(vm); + if (compiler->methodAnnotations == NULL) { + compiler->methodAnnotations = newDict(vm); + } + + ObjDict *annotationDict = newDict(vm); + ObjString *methodName = copyString(vm, "methodName", 10); + push(vm, OBJ_VAL(methodName)); + + dictSet(vm, compiler->methodAnnotations, OBJ_VAL(methodName), OBJ_VAL(annotationDict)); do { consume(compiler, TOKEN_IDENTIFIER, "Expected annotation identifier"); @@ -1676,28 +1720,30 @@ static void parseMethodAnnotations(Compiler *compiler) { if (match(compiler, TOKEN_STRING)) { Value string = parseString(compiler, false); push(vm, string); - dictSet(vm, compiler->methodAnnotations, annotationName, string); + dictSet(vm, annotationDict, annotationName, string); pop(vm); } else if (match(compiler, TOKEN_NUMBER)) { Value number = parseNumber(compiler, false); - dictSet(vm, compiler->methodAnnotations, annotationName, number); + dictSet(vm, annotationDict, annotationName, number); } else if (match(compiler, TOKEN_TRUE)) { - dictSet(vm, compiler->methodAnnotations, annotationName, TRUE_VAL); + dictSet(vm, annotationDict, annotationName, TRUE_VAL); } else if (match(compiler, TOKEN_FALSE)) { - dictSet(vm, compiler->methodAnnotations, annotationName, FALSE_VAL); + dictSet(vm, annotationDict, annotationName, FALSE_VAL); } else if (match(compiler, TOKEN_NIL)) { - dictSet(vm, compiler->methodAnnotations, annotationName, NIL_VAL); + dictSet(vm, annotationDict, annotationName, NIL_VAL); } else { - dictSet(vm, compiler->methodAnnotations, annotationName, NIL_VAL); + dictSet(vm, annotationDict, annotationName, NIL_VAL); } consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after annotation value."); } else { - dictSet(vm, compiler->methodAnnotations, annotationName, NIL_VAL); + dictSet(vm, annotationDict, annotationName, NIL_VAL); } pop(vm); } while (match(compiler, TOKEN_AT)); + + pop(vm); } static void parseClassAnnotations(Compiler *compiler) { @@ -1745,13 +1791,14 @@ static void parseClassAnnotations(Compiler *compiler) { } static void parseClassBody(Compiler *compiler) { + bool methodHasAnnotation = false; + while (!check(compiler, TOKEN_RIGHT_BRACE) && !check(compiler, TOKEN_EOF)) { if (match(compiler, TOKEN_USE)) { useStatement(compiler); } else if (match(compiler, TOKEN_VAR)) { consume(compiler, TOKEN_IDENTIFIER, "Expect class variable name."); uint8_t name = identifierConstant(compiler, &compiler->parser->previous); - consume(compiler, TOKEN_EQUAL, "Expect '=' after class variable identifier."); expression(compiler); emitBytes(compiler, OP_SET_CLASS_VAR, name); @@ -1761,7 +1808,6 @@ static void parseClassBody(Compiler *compiler) { } else if (match(compiler, TOKEN_CONST)) { consume(compiler, TOKEN_IDENTIFIER, "Expect class constant name."); uint8_t name = identifierConstant(compiler, &compiler->parser->previous); - consume(compiler, TOKEN_EQUAL, "Expect '=' after class constant identifier."); expression(compiler); emitBytes(compiler, OP_SET_CLASS_VAR, name); @@ -1769,7 +1815,7 @@ static void parseClassBody(Compiler *compiler) { consume(compiler, TOKEN_SEMICOLON, "Expect ';' after class constant declaration."); } else if (match(compiler, TOKEN_AT)) { - printf("found method annotation\n"); + methodHasAnnotation = true; parseMethodAnnotations(compiler); } else { if (match(compiler, TOKEN_PRIVATE)) { @@ -1781,14 +1827,16 @@ static void parseClassBody(Compiler *compiler) { AS_STRING(currentChunk(compiler)->constants.values[name]), EMPTY_VAL); continue; } - - method(compiler, true, &compiler->parser->previous); + method(compiler, true, &compiler->parser->previous, methodHasAnnotation); + methodHasAnnotation = false; continue; } - - method(compiler, true, NULL); + + method(compiler, true, NULL, methodHasAnnotation); + methodHasAnnotation = false; } else { - method(compiler, false, NULL); + method(compiler, false, NULL, methodHasAnnotation); + methodHasAnnotation = false; } } } @@ -2646,10 +2694,6 @@ static void declaration(Compiler *compiler) { return; } - // if (compiler->classAnnotations != NULL) { - // errorAtCurrent(compiler->parser, "Annotations can only be applied to classes"); - // } - if (match(compiler, TOKEN_TRAIT)) { traitDeclaration(compiler); } else if (match(compiler, TOKEN_ABSTRACT)) { diff --git a/tests/classes/annotations.du b/tests/classes/annotations.du index a60ef4ff..d832de43 100644 --- a/tests/classes/annotations.du +++ b/tests/classes/annotations.du @@ -14,7 +14,14 @@ class NoAnnotations {} @DecimalNumberAnnotation(10.5) @NilAnnotation(nil) class Test { - + + @EmptyAnnotation + @TrueAnnotation(true) + @FalseAnnotation(false) + @NumberAnnotation(10) + @DecimalNumberAnnotation(10.5) + @NilAnnotation(nil) + methodOnAClass() {} } @@ -32,6 +39,16 @@ class TestClassAnnotations < UnitTest { this.assertEquals(Test.classAnnotations['DecimalNumberAnnotation'], 10.5); this.assertEquals(Test.classAnnotations['NilAnnotation'], nil); } + + testMethodAnnotations() { + this.assertEquals(Test.methodAnnotations.len(), 1); + this.assertEquals(Test.methodAnnotations['methodOnAClass']['EmptyAnnotation'], nil); + this.assertEquals(Test.methodAnnotations['methodOnAClass']['TrueAnnotation'], true); + this.assertEquals(Test.methodAnnotations['methodOnAClass']['FalseAnnotation'], false); + this.assertEquals(Test.methodAnnotations['methodOnAClass']['NumberAnnotation'], 10); + this.assertEquals(Test.methodAnnotations['methodOnAClass']['DecimalNumberAnnotation'], 10.5); + this.assertEquals(Test.methodAnnotations['methodOnAClass']['NilAnnotation'], nil); + } } TestClassAnnotations().run(); \ No newline at end of file From c14896723c40a118c3ae040e9abb16d8cdec7971 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Mon, 26 Dec 2022 22:36:35 -0700 Subject: [PATCH 049/109] almost there... Signed-off-by: Brian Downs --- src/optionals/json.c | 6 +++--- src/vm/compiler.c | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/optionals/json.c b/src/optionals/json.c index b7bac074..a711e7db 100644 --- a/src/optionals/json.c +++ b/src/optionals/json.c @@ -150,9 +150,9 @@ json_value* stringifyJson(DictuVM *vm, Value value) { } json_object_push( - json, - key, - stringifyJson(vm, entry->value) + json, + key, + stringifyJson(vm, entry->value) ); if (!IS_STRING(entry->key)) { diff --git a/src/vm/compiler.c b/src/vm/compiler.c index beba63da..5de25ed6 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -1627,9 +1627,9 @@ static void method(Compiler *compiler, bool private, Token *identifier, bool has if (strcmp(key, "methodName") == 0) { ObjString *methodKey = copyString(vm, compiler->parser->previous.start, compiler->parser->previous.length); - push(vm, OBJ_VAL(methodKey)); + //push(vm, OBJ_VAL(methodKey)); compiler->methodAnnotations->entries[i].key = OBJ_VAL(methodKey); - pop(vm); + //pop(vm); } if (!IS_STRING(entry->key)) { From 1083d515bd8b8ba969e9c8eab011ba74a06d15e7 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 27 Dec 2022 19:53:36 -0700 Subject: [PATCH 050/109] update docs and example Signed-off-by: Brian Downs --- docs/docs/classes.md | 40 ++++++++++++++++++++++++++--------- examples/methodAnnotations.du | 8 +++++-- src/vm/compiler.c | 28 ++++++------------------ 3 files changed, 43 insertions(+), 33 deletions(-) diff --git a/docs/docs/classes.md b/docs/docs/classes.md index 97d99dcd..3ba5171d 100644 --- a/docs/docs/classes.md +++ b/docs/docs/classes.md @@ -649,8 +649,7 @@ anotherTestObj.isInstance(Test); // true ## Annotations -Annotations are metadata that are applied to classes that by themselves have no impact. -They, however, can provide user defined changes at runtime to given classes. +Annotations are metadata that are applied to classes and methods that by themselves have no impact. They, however, can provide user defined changes at runtime. ```cs @Annotation @@ -659,25 +658,43 @@ class AnnotatedClass { } ``` -Annotations are accessed via the `.annotations` property available on all classes. If annotations -are preset a dictionary is returned, otherwise the `.annotations` property is `nil`. +```cs +class ClassWithMethodAnnotation { + init() {} + + @MethodAnnotation + method() {} +} +``` + +Annotations are access via the `.classAnnotations` and `.methodAnnotations` proprties available on all classes. + +For class annotations, the returned data structure returned is a dictionary with keys set to the names of the annotations and their values if present. If no value is provided to the annotation, the value associated with the key is set to `nil`. + +For mathod annotations, the returned data structure is also a dictionary, however the keys are the method names and the values are also dictionaries containing the annotation name and associated values. If no value is provided to the annotation, the value associated with the key is set to `nil`. ```cs -print(AnnotatedClass.annotations); // {"Annotation": nil} +print(AnnotatedClass.classAnnotations); // {"Annotation": nil} +``` + +```cs +print(ClassWithMethodAnnotation.methodAnnotations); // {"method": {"MethodAnnotation": nil}} ``` Annotations can also be supplied a value, however, the value must be of type: nil, boolean, number or string. -``` +```cs @Annotation("Some extra value!") class AnnotatedClass { } -print(AnnotatedClass.annotations); // {"Annotation": "Some extra value!"} +print(AnnotatedClass.classAnnotations); // {"Annotation": "Some extra value!"} ``` -Multiple annotations can be supplied to classes. +Method annotations are available on all class methods except initializers. + +Multiple annotations can be supplied to classes and methods. ```cs @Annotation @@ -685,7 +702,10 @@ Multiple annotations can be supplied to classes. @SomeOtherAnnotation class AnnotatedClass { + @MethodAnnotation + @AnotherMethodAnnotation(10) + @SomeOtherMethodAnnotation("another one") + method() {} + } ``` - -**Note**: Annotations are not available on methods. \ No newline at end of file diff --git a/examples/methodAnnotations.du b/examples/methodAnnotations.du index 3461eb27..fd32f9dd 100644 --- a/examples/methodAnnotations.du +++ b/examples/methodAnnotations.du @@ -10,11 +10,15 @@ class AnnotatedClass { init() {} @BasicAuth @Get("/releases") - releaseHandler() {} + releaseHandler() { + // impl + } @BasicAuth @Get("/assets") - assetHandler() {} + private assetHandler() { + // impl + } private someOtherHandler() {} } diff --git a/src/vm/compiler.c b/src/vm/compiler.c index 5de25ed6..a8d0a271 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -1612,28 +1612,14 @@ static void method(Compiler *compiler, bool private, Token *identifier, bool has continue; } - char *key; - - if (IS_STRING(entry->key)) { - ObjString *s = AS_STRING(entry->key); - key = s->chars; - } else if (IS_NIL(entry->key)) { - key = malloc(5); - memcpy(key, "null", 4); - key[4] = '\0'; - } else { - key = valueToString(entry->key); - } + char *key = AS_STRING(entry->key)->chars; if (strcmp(key, "methodName") == 0) { + Value existingDict; + dictGet(compiler->methodAnnotations, entry->key, &existingDict); + dictDelete(vm, compiler->methodAnnotations, entry->key); ObjString *methodKey = copyString(vm, compiler->parser->previous.start, compiler->parser->previous.length); - //push(vm, OBJ_VAL(methodKey)); - compiler->methodAnnotations->entries[i].key = OBJ_VAL(methodKey); - //pop(vm); - } - - if (!IS_STRING(entry->key)) { - free(key); + dictSet(vm, compiler->methodAnnotations, OBJ_VAL(methodKey), existingDict); } } } @@ -1699,8 +1685,8 @@ static void parseMethodAnnotations(Compiler *compiler) { } ObjDict *annotationDict = newDict(vm); + push(vm, OBJ_VAL(annotationDict)); ObjString *methodName = copyString(vm, "methodName", 10); - push(vm, OBJ_VAL(methodName)); dictSet(vm, compiler->methodAnnotations, OBJ_VAL(methodName), OBJ_VAL(annotationDict)); @@ -1742,7 +1728,7 @@ static void parseMethodAnnotations(Compiler *compiler) { pop(vm); } while (match(compiler, TOKEN_AT)); - + pop(vm); } From 2edb7d3d064ec26ce6e25d5f394736a9cad25769 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 27 Dec 2022 20:01:11 -0700 Subject: [PATCH 051/109] update test Signed-off-by: Brian Downs --- tests/classes/annotations.du | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/classes/annotations.du b/tests/classes/annotations.du index d832de43..476ae9c0 100644 --- a/tests/classes/annotations.du +++ b/tests/classes/annotations.du @@ -42,6 +42,7 @@ class TestClassAnnotations < UnitTest { testMethodAnnotations() { this.assertEquals(Test.methodAnnotations.len(), 1); + this.assertEquals(Test.methodAnnotations['methodOnAClass'].len(), 6); this.assertEquals(Test.methodAnnotations['methodOnAClass']['EmptyAnnotation'], nil); this.assertEquals(Test.methodAnnotations['methodOnAClass']['TrueAnnotation'], true); this.assertEquals(Test.methodAnnotations['methodOnAClass']['FalseAnnotation'], false); From 1e2ae12fc87ffcde662f23f54db4f42962994688 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 27 Dec 2022 20:31:10 -0700 Subject: [PATCH 052/109] update examples Signed-off-by: Brian Downs --- examples/methodAnnotations.du | 7 ------- 1 file changed, 7 deletions(-) diff --git a/examples/methodAnnotations.du b/examples/methodAnnotations.du index fd32f9dd..9f7ef850 100644 --- a/examples/methodAnnotations.du +++ b/examples/methodAnnotations.du @@ -1,10 +1,3 @@ -// @BaseClassAnnotation -// class BaseClass { -// @AbstractMethodAnnotation -// getHandler() {} - -// } - @ResourceController("/api/v1") class AnnotatedClass { init() {} From a91e083ea64b7013a080548ca1cf1a2101cbabae Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 30 Dec 2022 21:40:14 -0700 Subject: [PATCH 053/109] edge case failure resolved Signed-off-by: Brian Downs --- src/vm/compiler.c | 30 ++++++++++++++---------------- src/vm/object.c | 1 + tests/classes/annotations.du | 4 ++++ 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/vm/compiler.c b/src/vm/compiler.c index a8d0a271..eadbe498 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -1564,7 +1564,7 @@ static void function(Compiler *compiler, FunctionType type, AccessLevel level) { endCompiler(&fnCompiler); } -static void method(Compiler *compiler, bool private, Token *identifier, bool hasAnnotation) { +static void method(Compiler *compiler, bool private, Token *identifier, bool *hasAnnotation) { AccessLevel level = ACCESS_PUBLIC; FunctionType type; @@ -1603,7 +1603,7 @@ static void method(Compiler *compiler, bool private, Token *identifier, bool has memcmp(compiler->parser->previous.start, "init", 4) == 0) { type = TYPE_INITIALIZER; } else { - if (hasAnnotation) { + if (*hasAnnotation) { DictuVM *vm = compiler->parser->vm; for (int i = 0; i <= compiler->methodAnnotations->capacityMask; i++) { @@ -1612,9 +1612,7 @@ static void method(Compiler *compiler, bool private, Token *identifier, bool has continue; } - char *key = AS_STRING(entry->key)->chars; - - if (strcmp(key, "methodName") == 0) { + if (strcmp(AS_STRING(entry->key)->chars, "annotatedMethodName") == 0) { Value existingDict; dictGet(compiler->methodAnnotations, entry->key, &existingDict); dictDelete(vm, compiler->methodAnnotations, entry->key); @@ -1622,6 +1620,7 @@ static void method(Compiler *compiler, bool private, Token *identifier, bool has dictSet(vm, compiler->methodAnnotations, OBJ_VAL(methodKey), existingDict); } } + *hasAnnotation = false; } } @@ -1686,8 +1685,8 @@ static void parseMethodAnnotations(Compiler *compiler) { ObjDict *annotationDict = newDict(vm); push(vm, OBJ_VAL(annotationDict)); - ObjString *methodName = copyString(vm, "methodName", 10); - + ObjString *methodName = copyString(vm, "annotatedMethodName", 19); + push(vm, OBJ_VAL(methodName)); dictSet(vm, compiler->methodAnnotations, OBJ_VAL(methodName), OBJ_VAL(annotationDict)); do { @@ -1695,7 +1694,7 @@ static void parseMethodAnnotations(Compiler *compiler) { Value annotationName = OBJ_VAL(copyString(vm, compiler->parser->previous.start, compiler->parser->previous.length)); push(vm, annotationName); - + if (match(compiler, TOKEN_LEFT_PAREN)) { if (!checkLiteralToken(compiler)) { errorAtCurrent(compiler->parser, @@ -1728,7 +1727,7 @@ static void parseMethodAnnotations(Compiler *compiler) { pop(vm); } while (match(compiler, TOKEN_AT)); - + pop(vm); pop(vm); } @@ -1813,16 +1812,15 @@ static void parseClassBody(Compiler *compiler) { AS_STRING(currentChunk(compiler)->constants.values[name]), EMPTY_VAL); continue; } - method(compiler, true, &compiler->parser->previous, methodHasAnnotation); - methodHasAnnotation = false; + + method(compiler, true, &compiler->parser->previous, &methodHasAnnotation); continue; } - - method(compiler, true, NULL, methodHasAnnotation); - methodHasAnnotation = false; + + method(compiler, true, NULL, &methodHasAnnotation); } else { - method(compiler, false, NULL, methodHasAnnotation); - methodHasAnnotation = false; + method(compiler, false, NULL, &methodHasAnnotation); + } } } diff --git a/src/vm/object.c b/src/vm/object.c index c3bbf516..a1167781 100644 --- a/src/vm/object.c +++ b/src/vm/object.c @@ -70,6 +70,7 @@ ObjClass *newClass(DictuVM *vm, ObjString *name, ObjClass *superclass, ClassType initTable(&klass->publicProperties); initTable(&klass->publicConstantProperties); klass->classAnnotations = NULL; + klass->methodAnnotations = NULL; push(vm, OBJ_VAL(klass)); ObjString *nameString = copyString(vm, "_name", 5); diff --git a/tests/classes/annotations.du b/tests/classes/annotations.du index 476ae9c0..d4a897b4 100644 --- a/tests/classes/annotations.du +++ b/tests/classes/annotations.du @@ -22,6 +22,8 @@ class Test { @DecimalNumberAnnotation(10.5) @NilAnnotation(nil) methodOnAClass() {} + + private noMethodAnnotations() {} } @@ -41,6 +43,8 @@ class TestClassAnnotations < UnitTest { } testMethodAnnotations() { + this.assertEquals(NoAnnotations.methodAnnotations, nil); + this.assertEquals(Test.methodAnnotations.len(), 1); this.assertEquals(Test.methodAnnotations['methodOnAClass'].len(), 6); this.assertEquals(Test.methodAnnotations['methodOnAClass']['EmptyAnnotation'], nil); From ef13d24118e9159c6055026fd9846ad60ac7fb8f Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 30 Dec 2022 21:48:50 -0700 Subject: [PATCH 054/109] adjust test Signed-off-by: Brian Downs --- tests/classes/annotations.du | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/classes/annotations.du b/tests/classes/annotations.du index d4a897b4..25afb488 100644 --- a/tests/classes/annotations.du +++ b/tests/classes/annotations.du @@ -43,7 +43,7 @@ class TestClassAnnotations < UnitTest { } testMethodAnnotations() { - this.assertEquals(NoAnnotations.methodAnnotations, nil); + //this.assertEquals(NoAnnotations.methodAnnotations, nil); this.assertEquals(Test.methodAnnotations.len(), 1); this.assertEquals(Test.methodAnnotations['methodOnAClass'].len(), 6); From 0fa65cfaadac3ceddb8089beb68a372239ff8215 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 30 Dec 2022 21:56:50 -0700 Subject: [PATCH 055/109] adjust test Signed-off-by: Brian Downs --- src/vm/compiler.c | 2 +- tests/classes/annotations.du | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vm/compiler.c b/src/vm/compiler.c index eadbe498..7fbf694e 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -1688,6 +1688,7 @@ static void parseMethodAnnotations(Compiler *compiler) { ObjString *methodName = copyString(vm, "annotatedMethodName", 19); push(vm, OBJ_VAL(methodName)); dictSet(vm, compiler->methodAnnotations, OBJ_VAL(methodName), OBJ_VAL(annotationDict)); + pop(vm); do { consume(compiler, TOKEN_IDENTIFIER, "Expected annotation identifier"); @@ -1728,7 +1729,6 @@ static void parseMethodAnnotations(Compiler *compiler) { pop(vm); } while (match(compiler, TOKEN_AT)); pop(vm); - pop(vm); } static void parseClassAnnotations(Compiler *compiler) { diff --git a/tests/classes/annotations.du b/tests/classes/annotations.du index 25afb488..d4a897b4 100644 --- a/tests/classes/annotations.du +++ b/tests/classes/annotations.du @@ -43,7 +43,7 @@ class TestClassAnnotations < UnitTest { } testMethodAnnotations() { - //this.assertEquals(NoAnnotations.methodAnnotations, nil); + this.assertEquals(NoAnnotations.methodAnnotations, nil); this.assertEquals(Test.methodAnnotations.len(), 1); this.assertEquals(Test.methodAnnotations['methodOnAClass'].len(), 6); From 5346837ddf899020bddb46ebcfa6a4f3981dbca6 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 30 Dec 2022 22:00:01 -0700 Subject: [PATCH 056/109] adjust test Signed-off-by: Brian Downs --- src/vm/compiler.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vm/compiler.c b/src/vm/compiler.c index 7fbf694e..af20ca4b 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -1689,7 +1689,8 @@ static void parseMethodAnnotations(Compiler *compiler) { push(vm, OBJ_VAL(methodName)); dictSet(vm, compiler->methodAnnotations, OBJ_VAL(methodName), OBJ_VAL(annotationDict)); pop(vm); - + pop(vm); + do { consume(compiler, TOKEN_IDENTIFIER, "Expected annotation identifier"); Value annotationName = OBJ_VAL(copyString(vm, compiler->parser->previous.start, @@ -1728,7 +1729,6 @@ static void parseMethodAnnotations(Compiler *compiler) { pop(vm); } while (match(compiler, TOKEN_AT)); - pop(vm); } static void parseClassAnnotations(Compiler *compiler) { @@ -1820,7 +1820,6 @@ static void parseClassBody(Compiler *compiler) { method(compiler, true, NULL, &methodHasAnnotation); } else { method(compiler, false, NULL, &methodHasAnnotation); - } } } From 33f159fc5889f225294e3a0c61f072e04ab67ae6 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Tue, 3 Jan 2023 19:16:41 +0000 Subject: [PATCH 057/109] Resolve segfault and add new opcode to compiler argcount --- src/vm/compiler.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vm/compiler.c b/src/vm/compiler.c index af20ca4b..a348d281 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -1615,9 +1615,13 @@ static void method(Compiler *compiler, bool private, Token *identifier, bool *ha if (strcmp(AS_STRING(entry->key)->chars, "annotatedMethodName") == 0) { Value existingDict; dictGet(compiler->methodAnnotations, entry->key, &existingDict); - dictDelete(vm, compiler->methodAnnotations, entry->key); + push(vm, OBJ_VAL(existingDict)); ObjString *methodKey = copyString(vm, compiler->parser->previous.start, compiler->parser->previous.length); dictSet(vm, compiler->methodAnnotations, OBJ_VAL(methodKey), existingDict); + pop(vm); + dictDelete(vm, compiler->methodAnnotations, entry->key); + + break; } } *hasAnnotation = false; @@ -2095,6 +2099,7 @@ static int getArgCount(uint8_t *code, const ValueArray constants, int ip) { case OP_NEW_DICT: case OP_CLOSE_FILE: case OP_MULTI_CASE: + case OP_DEFINE_METHOD_ANNOTATIONS: return 1; case OP_DEFINE_OPTIONAL: From c9842b5f6acb6f5eb20d271d60a68d3aecdc42e1 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 3 Jan 2023 16:14:43 -0700 Subject: [PATCH 058/109] Update docs/docs/classes.md Co-authored-by: Jason_000 --- docs/docs/classes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/classes.md b/docs/docs/classes.md index 3ba5171d..c2d9edc7 100644 --- a/docs/docs/classes.md +++ b/docs/docs/classes.md @@ -671,7 +671,7 @@ Annotations are access via the `.classAnnotations` and `.methodAnnotations` prop For class annotations, the returned data structure returned is a dictionary with keys set to the names of the annotations and their values if present. If no value is provided to the annotation, the value associated with the key is set to `nil`. -For mathod annotations, the returned data structure is also a dictionary, however the keys are the method names and the values are also dictionaries containing the annotation name and associated values. If no value is provided to the annotation, the value associated with the key is set to `nil`. +For method annotations, the returned data structure is also a dictionary, however the keys are the method names and the values are also dictionaries containing the annotation name and associated values. If no value is provided to the annotation, the value associated with the key is set to `nil`. ```cs print(AnnotatedClass.classAnnotations); // {"Annotation": nil} From 2f61f862aa813b00073f6591bfd3712d7a7dcf01 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 3 Jan 2023 16:18:20 -0700 Subject: [PATCH 059/109] Update src/vm/compiler.c Co-authored-by: Jason_000 --- src/vm/compiler.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vm/compiler.c b/src/vm/compiler.c index a348d281..61d74b3e 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -1670,7 +1670,7 @@ static void endClassCompiler(Compiler *compiler, ClassCompiler *classCompiler) { } if (compiler->methodAnnotations != NULL) { - int importConstant = makeConstant(compiler, OBJ_VAL(compiler->methodAnnotations)); + int methodAnnotationsConstant = makeConstant(compiler, OBJ_VAL(compiler->methodAnnotations)); emitBytes(compiler, OP_DEFINE_METHOD_ANNOTATIONS, importConstant); compiler->methodAnnotations = NULL; } From aa472274815bab7eb524aecd4508c35782430080 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 3 Jan 2023 16:18:27 -0700 Subject: [PATCH 060/109] Update src/vm/compiler.c Co-authored-by: Jason_000 --- src/vm/compiler.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vm/compiler.c b/src/vm/compiler.c index 61d74b3e..2fb1b57e 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -1664,7 +1664,7 @@ static void endClassCompiler(Compiler *compiler, ClassCompiler *classCompiler) { compiler->class = compiler->class->enclosing; if (compiler->classAnnotations != NULL) { - int importConstant = makeConstant(compiler, OBJ_VAL(compiler->classAnnotations)); + int classAnnotationsConstant = makeConstant(compiler, OBJ_VAL(compiler->classAnnotations)); emitBytes(compiler, OP_DEFINE_CLASS_ANNOTATIONS, importConstant); compiler->classAnnotations = NULL; } From f1399210629d77d9e2eb390f9ee8eadba2112143 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 3 Jan 2023 16:19:07 -0700 Subject: [PATCH 061/109] remove unnecessary grayed object addition Signed-off-by: Brian Downs --- src/vm/memory.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vm/memory.c b/src/vm/memory.c index 8a8a55de..f1ba1b07 100644 --- a/src/vm/memory.c +++ b/src/vm/memory.c @@ -101,7 +101,6 @@ static void blackenObject(DictuVM *vm, Obj *object) { ObjClass *klass = (ObjClass *) object; grayObject(vm, (Obj *) klass->name); grayObject(vm, (Obj *) klass->superclass); - grayObject(vm, (Obj *) klass->classAnnotations); grayTable(vm, &klass->publicMethods); grayTable(vm, &klass->privateMethods); grayTable(vm, &klass->abstractMethods); From 476e76d384cd643cc9406d225e58682fbbfd0e3b Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 3 Jan 2023 16:21:50 -0700 Subject: [PATCH 062/109] update var refs for review Signed-off-by: Brian Downs --- src/vm/compiler.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vm/compiler.c b/src/vm/compiler.c index 2fb1b57e..03e5c597 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -1665,13 +1665,13 @@ static void endClassCompiler(Compiler *compiler, ClassCompiler *classCompiler) { if (compiler->classAnnotations != NULL) { int classAnnotationsConstant = makeConstant(compiler, OBJ_VAL(compiler->classAnnotations)); - emitBytes(compiler, OP_DEFINE_CLASS_ANNOTATIONS, importConstant); + emitBytes(compiler, OP_DEFINE_CLASS_ANNOTATIONS, classAnnotationsConstant); compiler->classAnnotations = NULL; } if (compiler->methodAnnotations != NULL) { int methodAnnotationsConstant = makeConstant(compiler, OBJ_VAL(compiler->methodAnnotations)); - emitBytes(compiler, OP_DEFINE_METHOD_ANNOTATIONS, importConstant); + emitBytes(compiler, OP_DEFINE_METHOD_ANNOTATIONS, methodAnnotationsConstant); compiler->methodAnnotations = NULL; } } From 6cec9711dd2775dc3f24ece8c1d9008d3ea2876d Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 3 Jan 2023 18:12:00 -0700 Subject: [PATCH 063/109] allow method annotations on initializers, update tests, and docs Signed-off-by: Brian Downs --- docs/docs/classes.md | 2 -- src/vm/compiler.c | 39 ++++++++++++++++++------------------ tests/classes/annotations.du | 10 +++++++++ 3 files changed, 29 insertions(+), 22 deletions(-) diff --git a/docs/docs/classes.md b/docs/docs/classes.md index c2d9edc7..aa3855ca 100644 --- a/docs/docs/classes.md +++ b/docs/docs/classes.md @@ -692,8 +692,6 @@ class AnnotatedClass { print(AnnotatedClass.classAnnotations); // {"Annotation": "Some extra value!"} ``` -Method annotations are available on all class methods except initializers. - Multiple annotations can be supplied to classes and methods. ```cs diff --git a/src/vm/compiler.c b/src/vm/compiler.c index 03e5c597..aff4083c 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -1602,33 +1602,32 @@ static void method(Compiler *compiler, bool private, Token *identifier, bool *ha if (compiler->parser->previous.length == 4 && memcmp(compiler->parser->previous.start, "init", 4) == 0) { type = TYPE_INITIALIZER; - } else { - if (*hasAnnotation) { - DictuVM *vm = compiler->parser->vm; + } - for (int i = 0; i <= compiler->methodAnnotations->capacityMask; i++) { - DictItem *entry = &compiler->methodAnnotations->entries[i]; - if (IS_EMPTY(entry->key)) { - continue; - } + if (*hasAnnotation) { + DictuVM *vm = compiler->parser->vm; + + for (int i = 0; i <= compiler->methodAnnotations->capacityMask; i++) { + DictItem *entry = &compiler->methodAnnotations->entries[i]; + if (IS_EMPTY(entry->key)) { + continue; + } - if (strcmp(AS_STRING(entry->key)->chars, "annotatedMethodName") == 0) { - Value existingDict; - dictGet(compiler->methodAnnotations, entry->key, &existingDict); - push(vm, OBJ_VAL(existingDict)); - ObjString *methodKey = copyString(vm, compiler->parser->previous.start, compiler->parser->previous.length); - dictSet(vm, compiler->methodAnnotations, OBJ_VAL(methodKey), existingDict); - pop(vm); - dictDelete(vm, compiler->methodAnnotations, entry->key); + if (strcmp(AS_STRING(entry->key)->chars, "annotatedMethodName") == 0) { + Value existingDict; + dictGet(compiler->methodAnnotations, entry->key, &existingDict); + push(vm, OBJ_VAL(existingDict)); + ObjString *methodKey = copyString(vm, compiler->parser->previous.start, compiler->parser->previous.length); + dictSet(vm, compiler->methodAnnotations, OBJ_VAL(methodKey), existingDict); + pop(vm); + dictDelete(vm, compiler->methodAnnotations, entry->key); - break; - } + break; } - *hasAnnotation = false; } + *hasAnnotation = false; } - if (type != TYPE_ABSTRACT) { function(compiler, type, level); } else { diff --git a/tests/classes/annotations.du b/tests/classes/annotations.du index d4a897b4..a39d71e4 100644 --- a/tests/classes/annotations.du +++ b/tests/classes/annotations.du @@ -26,6 +26,12 @@ class Test { private noMethodAnnotations() {} } +class WithAnnotatetdInit { + + @InitAnnotation + init() {} +} + class TestClassAnnotations < UnitTest { testClassAnnotationsEmpty() { @@ -54,6 +60,10 @@ class TestClassAnnotations < UnitTest { this.assertEquals(Test.methodAnnotations['methodOnAClass']['DecimalNumberAnnotation'], 10.5); this.assertEquals(Test.methodAnnotations['methodOnAClass']['NilAnnotation'], nil); } + + testInitAnnotation() { + this.assertEquals(WithAnnotatetdInit.methodAnnotations['init']['InitAnnotation'], nil); + } } TestClassAnnotations().run(); \ No newline at end of file From e75c817d54257993a286e26066bc28a8b6cc2684 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Wed, 4 Jan 2023 10:09:09 +0000 Subject: [PATCH 064/109] Push correct value to stack and use double underscore for private value --- src/vm/compiler.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vm/compiler.c b/src/vm/compiler.c index aff4083c..9ca18c84 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -1613,11 +1613,11 @@ static void method(Compiler *compiler, bool private, Token *identifier, bool *ha continue; } - if (strcmp(AS_STRING(entry->key)->chars, "annotatedMethodName") == 0) { + if (strcmp(AS_STRING(entry->key)->chars, "__annotatedMethodName") == 0) { Value existingDict; dictGet(compiler->methodAnnotations, entry->key, &existingDict); - push(vm, OBJ_VAL(existingDict)); ObjString *methodKey = copyString(vm, compiler->parser->previous.start, compiler->parser->previous.length); + push(vm, OBJ_VAL(methodKey)); dictSet(vm, compiler->methodAnnotations, OBJ_VAL(methodKey), existingDict); pop(vm); dictDelete(vm, compiler->methodAnnotations, entry->key); @@ -1688,7 +1688,7 @@ static void parseMethodAnnotations(Compiler *compiler) { ObjDict *annotationDict = newDict(vm); push(vm, OBJ_VAL(annotationDict)); - ObjString *methodName = copyString(vm, "annotatedMethodName", 19); + ObjString *methodName = copyString(vm, "__annotatedMethodName", 21); push(vm, OBJ_VAL(methodName)); dictSet(vm, compiler->methodAnnotations, OBJ_VAL(methodName), OBJ_VAL(annotationDict)); pop(vm); From 364152db8ba0e886b8725b2a83613c9d17bbfd19 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Sun, 8 Jan 2023 19:44:36 -0700 Subject: [PATCH 065/109] error on invalid annotations Signed-off-by: Brian Downs --- src/vm/compiler.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/vm/compiler.c b/src/vm/compiler.c index 9ca18c84..6260a463 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -1778,13 +1778,28 @@ static void parseClassAnnotations(Compiler *compiler) { } while (match(compiler, TOKEN_AT)); } +static void invalidAnnotation(Compiler *compiler, const char *type) { + printf("Annotations not allowed on \"%s\" statements\n", type); + runtimeError(compiler->parser->vm, "Annotations only allowed on methods"); + exit(1); +} + static void parseClassBody(Compiler *compiler) { bool methodHasAnnotation = false; - + while (!check(compiler, TOKEN_RIGHT_BRACE) && !check(compiler, TOKEN_EOF)) { if (match(compiler, TOKEN_USE)) { + if (methodHasAnnotation) { + invalidAnnotation(compiler, "use"); + consume(compiler, TOKEN_USE, "Annotations not allowed on `use` statements\n"); + } + useStatement(compiler); } else if (match(compiler, TOKEN_VAR)) { + if (methodHasAnnotation) { + consume(compiler, TOKEN_VAR, "Annotations not allowed on `var` statements"); + } + consume(compiler, TOKEN_IDENTIFIER, "Expect class variable name."); uint8_t name = identifierConstant(compiler, &compiler->parser->previous); consume(compiler, TOKEN_EQUAL, "Expect '=' after class variable identifier."); @@ -1794,6 +1809,10 @@ static void parseClassBody(Compiler *compiler) { consume(compiler, TOKEN_SEMICOLON, "Expect ';' after class variable declaration."); } else if (match(compiler, TOKEN_CONST)) { + if (methodHasAnnotation) { + consume(compiler, TOKEN_CONST, "Annotations not allowed on `const` statements"); + } + consume(compiler, TOKEN_IDENTIFIER, "Expect class constant name."); uint8_t name = identifierConstant(compiler, &compiler->parser->previous); consume(compiler, TOKEN_EQUAL, "Expect '=' after class constant identifier."); @@ -1826,6 +1845,10 @@ static void parseClassBody(Compiler *compiler) { } } } + + if (methodHasAnnotation) { + consume(compiler, TOKEN_CLASS, "Annotations only allowed on methods"); + } } static void classDeclaration(Compiler *compiler) { From 0b356a332ae87e24587c548b1c069a731da722b2 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Sun, 8 Jan 2023 19:49:39 -0700 Subject: [PATCH 066/109] retrigger ci Signed-off-by: Brian Downs --- examples/runExamples.du | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/runExamples.du b/examples/runExamples.du index 7be8c3fa..db0f7afa 100644 --- a/examples/runExamples.du +++ b/examples/runExamples.du @@ -19,3 +19,4 @@ import "factorial.du"; import "pathWalker.du"; import "structuredLogger.du"; import "templateEngine.du"; + \ No newline at end of file From d3d7cd5f49818195ed8dbe16b1dba7aabc93fb66 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Mon, 9 Jan 2023 09:59:25 -0700 Subject: [PATCH 067/109] remove unneeded code Signed-off-by: Brian Downs --- src/vm/compiler.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/vm/compiler.c b/src/vm/compiler.c index 6260a463..a46b7bf9 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -1778,19 +1778,12 @@ static void parseClassAnnotations(Compiler *compiler) { } while (match(compiler, TOKEN_AT)); } -static void invalidAnnotation(Compiler *compiler, const char *type) { - printf("Annotations not allowed on \"%s\" statements\n", type); - runtimeError(compiler->parser->vm, "Annotations only allowed on methods"); - exit(1); -} - static void parseClassBody(Compiler *compiler) { bool methodHasAnnotation = false; while (!check(compiler, TOKEN_RIGHT_BRACE) && !check(compiler, TOKEN_EOF)) { if (match(compiler, TOKEN_USE)) { if (methodHasAnnotation) { - invalidAnnotation(compiler, "use"); consume(compiler, TOKEN_USE, "Annotations not allowed on `use` statements\n"); } From 6a67c806016400f6762789b2c20e05512827b5da Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Mon, 9 Jan 2023 19:36:29 -0700 Subject: [PATCH 068/109] update docs Signed-off-by: Brian Downs --- docs/docs/built-ins.md | 8 ++--- docs/docs/classes.md | 18 +++++----- docs/docs/collections/dictionaries.md | 19 +++++----- docs/docs/collections/lists.md | 33 ++++++++--------- docs/docs/collections/sets.md | 11 +++--- docs/docs/error-handling.md | 15 ++++---- docs/docs/files.md | 16 ++++----- docs/docs/standard-lib/argparse.md | 12 +++---- docs/docs/standard-lib/base64.md | 4 +-- docs/docs/standard-lib/datetime.md | 8 ++--- docs/docs/standard-lib/env.md | 6 ++-- docs/docs/standard-lib/hashlib.md | 10 +++--- docs/docs/standard-lib/http.md | 13 +++---- docs/docs/standard-lib/inspect.md | 6 ++-- docs/docs/standard-lib/json.md | 4 +-- docs/docs/standard-lib/log.md | 20 +++++------ docs/docs/standard-lib/math.md | 52 +++++++++++++-------------- docs/docs/standard-lib/object.md | 4 +-- docs/docs/standard-lib/path.md | 26 +++++++------- docs/docs/standard-lib/process.md | 4 +-- docs/docs/standard-lib/random.md | 6 ++-- docs/docs/standard-lib/sockets.md | 28 +++++++-------- docs/docs/standard-lib/sqlite.md | 4 +-- docs/docs/standard-lib/system.md | 42 +++++++++++----------- docs/docs/standard-lib/term.md | 4 +-- docs/docs/standard-lib/unittest.md | 4 +++ docs/docs/standard-lib/uuid.md | 6 ++-- docs/docs/strings.md | 36 +++++++++---------- docs/docs/thank-you.md | 2 +- 29 files changed, 216 insertions(+), 205 deletions(-) diff --git a/docs/docs/built-ins.md b/docs/docs/built-ins.md index 8f84a4bc..2164dd6e 100644 --- a/docs/docs/built-ins.md +++ b/docs/docs/built-ins.md @@ -45,7 +45,7 @@ printError("test"); // "test" printError(10, "test", nil, true); // 10, "test", nil, true ``` -### input(string: prompt -> optional) +### input(String: prompt -> Optional) -> String Gathers user input from stdin and returns the value as a string. `input()` has an optional prompt which will be shown to the user before they enter their string. @@ -55,7 +55,7 @@ input(); input("Input: "); ``` -### type(value) +### type(value) -> String Returns the type of a given value as a string. @@ -65,7 +65,7 @@ type(true); // "bool" type([]); // "list" ``` -### assert(boolean) +### assert(Boolean) Raise a runtime error if the given boolean is not true. @@ -74,7 +74,7 @@ assert(10 > 9); assert(9 > 10); // assert() was false! ``` -### isDefined(string) +### isDefined(String) -> Boolean Returns a boolean depending on whether a variable has been defined in the global scope. diff --git a/docs/docs/classes.md b/docs/docs/classes.md index aa3855ca..d21df6cb 100644 --- a/docs/docs/classes.md +++ b/docs/docs/classes.md @@ -122,7 +122,7 @@ class SomeClass { SomeClass().printMessage(); // Hello! ``` -### toString +### toString() -> String Classes and instances can both be converted to a string using the toString method. If you want a different string representation for an object you can overload the toString method in your class. @@ -148,7 +148,7 @@ print(TestOverload().toString()); // 'Testing object' ``` -### methods +### methods() -> List Sometimes we need to programmatically access methods that are stored within a class, this can be aided through the use of `.methods()`. This will return a list of strings, where the strings are the names of all public methods stored within a class. @@ -205,7 +205,7 @@ var myObject = Test(); print(myObject.x); // 10 ``` -### hasAttribute +### hasAttribute(String) -> Boolean Attempting to access an attribute of an object that does not exist will throw a runtime error, and instead before accessing an attribute that may not be there, you should check if the object has the given attribute. This is done via `hasAttribute`. @@ -226,7 +226,7 @@ print(myObject.hasAttribute("y")); // false print(myObject.z); // Undefined property 'z'. ``` -### getAttribute +### getAttribute(String) -> value Sometimes in Dictu we may wish to access an attribute of an object without knowing the attribute until runtime. We can do this via the `getAttribute` method. This method takes a string and an optional default value and returns either the attribute value or the default value (if there is no attribute and no default value, nil is returned). @@ -247,7 +247,7 @@ print(myObject.getAttribute("y", 100)); // 100 print(myObject.getAttribute("y")); // nil ``` -### getAttributes +### getAttributes() -> List The `getAttributes` method returns all public attributes on the given instance of a class. @@ -262,7 +262,7 @@ var myObject = Test(); print(myObject.getAttributes()); // ["x"] ``` -### setAttribute +### setAttribute(String, value) Similar concept to `getAttribute` however this allows us to set an attribute on an instance. @@ -571,7 +571,7 @@ print(myObject.x); // 100 To get around this, instances have two methods, obj.copy() and obj.deepCopy(). -### obj.copy() +### obj.copy() -> value This method will take a shallow copy of the object, and create a new copy of the instance. Mutable types are still references and will mutate on both new and old if changed. See obj.deepCopy() to avoid this. @@ -594,7 +594,7 @@ myNewObject.obj.x = 100; print(myObject.obj.x); // 100 ``` -### obj.deepCopy() +### obj.deepCopy() -> value This method will take a deep copy of the object, and create a new copy of the instance. The difference with deepCopy() is if the object contains references to any mutable datatypes these will also be copied and returned as new values meaning, @@ -620,7 +620,7 @@ print(myObject.obj.x); // 10 ## Checking instance types -### instance.isInstance(class) +### instance.isInstance(Class) -> Boolean Checking if an instance is of a given class is made very simple with the `isInstance` method. This method takes in a class as an argument and returns a boolean based on whether or not the object was instantiated from the given class. Since classes can inherit other diff --git a/docs/docs/collections/dictionaries.md b/docs/docs/collections/dictionaries.md index e2887751..6a3d0a89 100644 --- a/docs/docs/collections/dictionaries.md +++ b/docs/docs/collections/dictionaries.md @@ -48,7 +48,7 @@ var myDict = {"key": 1, "key1": true}; var myDict["key2"] = nil; // {"key": false, "key1": true, "key3": nil} ``` -### dict.get(string, value: default -> optional) +### dict.get(String, value: default -> Optional) -> Dict Returns the dictionary value at the given key, or returns the default value if the key does not exist in the dictionary. If the key does not exist and no default is provided `nil` is returned. @@ -61,7 +61,7 @@ myDict.get("unknown key", 10); // 10 myDict.get("unknown key"); // nil ``` -### dict.keys() +### dict.keys() -> List Returns a list of all of the dictionary keys. @@ -71,7 +71,8 @@ var myDict = {1: 2, "test": 3}; myDict.keys(); // [1, "test"] ``` -### dict.toString() +### dict.toString() -> String + Converts a dictionary to a string. ```cs @@ -79,7 +80,7 @@ Converts a dictionary to a string. {"1": {1: "1", "1": 1}, 1: "1"}.toString(); // '{"1": {"1": 1, 1: "1"}, 1: "1"}' ``` -### dict.toBool() +### dict.toBool() -> Boolean Converts a dictionary to a boolean. A dictionary is a "truthy" value when it has a length greater than 0. @@ -91,7 +92,7 @@ x["test"] = 1; x.toBool(); // true ``` -### dict.len() +### dict.len() -> Number Returns the length of the given dictionary. @@ -99,7 +100,7 @@ Returns the length of the given dictionary. {1: "one", 2: "two", 3: "three"}.len(); // 3 ``` -### dict.exists(string) +### dict.exists(String) -> Boolean To check if a key exists within a dictionary use `.exists()` @@ -109,7 +110,7 @@ var keyExists = myDict.exists("key"); // true var keyDoesNotExist = myDict.exists("unknown key"); // false ``` -### dict.remove(string) +### dict.remove(String) To remove a key from a dictionary use `.remove()`. @@ -132,7 +133,7 @@ var myDict1 = myDict.copy(); // Shallow copy var myDict2 = myDict.deepCopy(); // Deep copy ``` -### dict.forEach(func) +### dict.forEach(Func) To run a function on every element in a dictionary we use `.forEach`. The callback function passed to `.forEach` expects two parameters which will be the key-value pair of the dictionary. @@ -145,7 +146,7 @@ myDict.forEach(def (key, value) => { }); ``` -### dict.merge(anotherDict) +### dict.merge(Dict) To merge two dicts together we use `.merge`. This operation will take a shallow copy of the dict the `.merge` method was called on and add any items from the dictionary passed into the method. If there are keys that exist in both dictionaries diff --git a/docs/docs/collections/lists.md b/docs/docs/collections/lists.md index 945c5f7b..69e07170 100644 --- a/docs/docs/collections/lists.md +++ b/docs/docs/collections/lists.md @@ -61,7 +61,7 @@ myList.push(10); // [10] myList.push(11); // [10, 11] ``` -#### list.insert(value, number) +#### list.insert(value, Number) To insert a value into a list at a given index without replacing the value use .insert(). @@ -84,7 +84,7 @@ x + [11, 12]; print(x); // [10, 11, 12] ``` -#### list.extend(list) +#### list.extend(List) Similar to the + operator however this mutates the list the method is called on rather than returning a new list. @@ -99,7 +99,7 @@ x.extend([1, 2, 3]); print(x); // [1, 2, 3, 1, 2, 3] ``` -### list.toString() +### list.toString() -> String Converts a list to a string. @@ -107,7 +107,7 @@ Converts a list to a string. ["1", 11].toString(); // ['1', 11] ["1", [11, "1"]].toString(); // ['1', [11, '1']] ``` -### list.len() +### list.len() -> Number Returns the length of the given list. @@ -115,7 +115,7 @@ Returns the length of the given list. [1, 2, 3].len(); // 3 ``` -### list.toBool() +### list.toBool() -> Boolean Converts a list to a boolean. A list is a "truthy" value when it has a length greater than 0. @@ -125,7 +125,7 @@ Converts a list to a boolean. A list is a "truthy" value when it has a length gr [[]].toBool(); // true ``` -### list.contains(value) +### list.contains(value) -> Boolean To check if a value contains within a list we use `.contains()` @@ -135,7 +135,7 @@ myList.contains(2); // true myList.contains(10); // false ``` -### list.join(string: delimiter -> optional) +### list.join(String: delimiter -> Optional) -> String 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. Attempting to join an empty list will return an empty string. @@ -165,7 +165,7 @@ myList.remove(1); print(myList); // [2] ``` -### list.pop(number: index -> optional) +### list.pop(Number: index -> Optional) To remove a value from a list, with an optional index, use `.pop()` @@ -190,7 +190,7 @@ print(myList); // [4, 3, 2, 1] ``` ### Copying lists -#### list.copy() +#### list.copy() -> List When you are working with a mutable datatype taking a reference of a list when creating a new variable will modify the original list. ```cs @@ -208,7 +208,8 @@ print(list1); // [1, 2] print(list2); // [10, 2] ``` -#### list.deepCopy() +#### list.deepCopy() -> List + To get around this, we can deepCopy the list. ```cs var list1 = [[1, 2]]; @@ -233,7 +234,7 @@ list1.sort(); print(list1); // [-1, 1, 2, 3, 4, 5, 10] ``` -### list.forEach(func) +### list.forEach(Func) To run a function on every element in a list we use `.forEach`. The callback function passed to `.forEach` expects one parameter which will be the current value. @@ -246,7 +247,7 @@ myList.forEach(def (value) => { }); ``` -### list.map(func) +### list.map(Func) Similar to `.foreach`, `.map` will run a function for each element within the list, however the difference is that `.map` returns a new list of values generated from the callback function. @@ -257,7 +258,7 @@ parameter which is the current item in the list. print([1, 2, 3, 4, 5].map(def (x) => x * 2)); // [2, 4, 6, 8, 10] ``` -### list.filter(func) +### list.filter(Func) To filter out values within a list we use `.filter()`. Filter expects a single parameter which is a callback and if the callback returns a truthy value it will be appended to the list. The callback itself also expects one @@ -269,7 +270,7 @@ Note: `.filter()` returns a new list. print([1, 2, 3, 4, 5].filter(def (x) => x > 2)); // [3, 4, 5] ``` -### list.reduce(func, value: initial -> optional) +### list.reduce(Func, value: initial -> Optional) To reduce a list down to a single value we use `.reduce()`. Reduce expects at least one parameter which is a callback that will be executed on each item of the list. The value of the callback function is returned and saved for the next @@ -285,7 +286,7 @@ By default the initial value for `.reduce()` is 0, however we can change this to print(["Dictu ", "is", " great!"].reduce(def (accumulate, element) => accumulate + element, "")); // 'Dictu is great!' ``` -### list.find(func, number: start -> optional, number: end -> optional) +### list.find(Func, Number: start -> Optional, Number: end -> Optional) To find a single item within a list we use `.find()`. Find will search through each item in the list and as soon as the callback returns a truthy value, the item that satisfied the callback is returned, if none of the items satisfy the callback @@ -299,7 +300,7 @@ print([1, 2, 3, 4, 5, 6].find(def (item) => item % 2 == 0, 2)); // 4 print([1, 2, 3, 4, 5, 6].find(def (item) => item % 2 == 0, 2, 3)); // nil ``` -### list.findIndex(func, number: start -> optional, number: end -> optional) +### list.findIndex(Func, Number: start -> Optional, Number: end -> Optional) To find a single item within a list we use `.findIndex()`. Find will search through each item in the list and as soon as the callback returns a truthy value, the index at which the item that satisfied the callback is returned, if none of the items satisfy the callback diff --git a/docs/docs/collections/sets.md b/docs/docs/collections/sets.md index c541b945..46d3967d 100644 --- a/docs/docs/collections/sets.md +++ b/docs/docs/collections/sets.md @@ -24,7 +24,8 @@ var mySet = set("test", 10); print(mySet); // {10, "test"} ``` -### set.toString() +### set.toString() -> String + Converts a given set to a string. ```cs @@ -41,7 +42,7 @@ set_a.toString(); // '{"two", "one"}'); set_b.toString(); // '{2, 1}' ``` -### set.toBool() +### set.toBool() -> Boolean Converts a set to a boolean. A set is a "truthy" value when it has a length greater than 0. @@ -53,7 +54,7 @@ x.add("test"); x.toBool(); // true ``` -### set.len() +### set.len() -> Number Returns the length of the given set. @@ -72,7 +73,7 @@ var mySet = set(); mySet.add("Dictu!"); ``` -### set.contains(value) +### set.contains(value) -> Boolean To check if a set contains a value use `.contains()` @@ -83,7 +84,7 @@ print(mySet.contains("Dictu!")); // true print(mySet.contains("Other!")); // false ``` -### set.containsAll(value) +### set.containsAll(value) -> Boolean To check if a set contains all elements in a given list use `.containsAll()` diff --git a/docs/docs/error-handling.md b/docs/docs/error-handling.md index 67e7729e..a821cd40 100644 --- a/docs/docs/error-handling.md +++ b/docs/docs/error-handling.md @@ -22,6 +22,7 @@ type which will wrap a value on success or wrap a string on failure with a given error message. This wrapped value *must* be unwrapped before accessing it. ### Result type + Note, if returning a Result type from a function there is nothing in the interpreter that will enforce both Success and Error types can be returned, or even that these are the only types that can be returned, however it is very much recommended that if you return a Result type @@ -29,6 +30,7 @@ from a function, this is the only type you ever return - this will made handling much easier for the caller. #### Success + Creating a Success type is incredibly simple with the builtin `Success()` function. Any type can be passed to Success to be wrapped. @@ -38,6 +40,7 @@ print(result.unwrap()); // 10 ``` #### Error + Creating an Error type is incredibly simple with the builtin `Error()` function. Only a string can be passed to Error to be wrapped. @@ -46,7 +49,7 @@ var result = Error("Some error happened!!"); print(result.unwrapError()); // 'Some error happened!!' ``` -### .unwrap() +### .unwrap() -> value As previously explained to get a value out of a Result it needs to be unwrapped. If you attempt to unwrap a Result that is of ERROR status a runtime error will be raised. @@ -57,7 +60,7 @@ print(num); // print(num.unwrap()); // 10 ``` -### .unwrapError() +### .unwrapError() -> String A Result that has a type of ERROR will always contain an error message as to why it failed, however attempting to unwrap a Result that is an ERROR gives you a runtime error. Instead you must use @@ -68,7 +71,7 @@ error. "num".toNumber().unwrapError(); // 'Can not convert 'num' to number' ``` -### .success() +### .success() -> Boolean Check if a Result type is in a SUCCESS state, returns a boolean. @@ -77,7 +80,7 @@ Check if a Result type is in a SUCCESS state, returns a boolean. "number".toNumber().success(); // false ``` -### .match(func: success, func: error) +### .match(Func: success, Func: error) `.match` takes two callbacks that are ran depending upon the status of the result type. The callbacks passed to match must both have one parameter each, on success the unwrapped value is passed as the first argument and on @@ -93,7 +96,7 @@ var number = "10".toNumber().match( } ); -print(number); // 10 +print(Number); // 10 var number = "number".toNumber().match( def (result) => result, @@ -106,7 +109,7 @@ var number = "number".toNumber().match( print(number); ``` -### .matchWrap(func: success, func: error) +### .matchWrap(Func: success, Func: error) `.matchWrap` is exactly the same as `.wrap` however, the value returned from either callback function is implicitly wrapped back up into a Result object. This allows us to easily deal diff --git a/docs/docs/files.md b/docs/docs/files.md index 11a036d9..62d30eee 100644 --- a/docs/docs/files.md +++ b/docs/docs/files.md @@ -22,13 +22,13 @@ Opening files is very easy in Dictu. Syntax is `with(, ) { as a constant for you, and is the file object which has just been opened. Dictu will handle closing the file for you and happens when you leave the with scope automatically. Note, the path when opening files is relative to the process **not** the current script running. -| Open Mode | Description | -|-----------|--------------------------------------------------------| -| r | Opens a file for reading, the file must exist already. | -| w | Opens a file for writing, if a file does not exist one is created, else existing file is overwritten. | -| a | Opens a file for appending, if a file does not exist one is created, else appends text to the end of a file. | -| r+ | Opens a file for updating (read + write), the file must exist already. | -| w+ | Opens a file for updating (read + write), if a file does not exist one is created, else existing file is overwritten | +| Open Mode | Description | +| --------- | -------------------------------------------------------------------------------------------------------------------------- | +| r | Opens a file for reading, the file must exist already. | +| w | Opens a file for writing, if a file does not exist one is created, else existing file is overwritten. | +| a | Opens a file for appending, if a file does not exist one is created, else appends text to the end of a file. | +| r+ | Opens a file for updating (read + write), the file must exist already. | +| w+ | Opens a file for updating (read + write), if a file does not exist one is created, else existing file is overwritten | | a+ | Opens a file for updating (read + write), if a file does not exist one is created, else appends text to the end of a file. | ```cs @@ -77,7 +77,7 @@ with("test.txt", "r") { Another method which may come in useful when reading files is `seek()`. `seek()` allows you to move the file cursor so you can re-read a file, for example, without closing the file and reopening. -### Seek(number, number: from -> optional) +### Seek(Number, Number: from -> Optional) Both arguments passed to seek need to be of numeric value, however the `from` argument is optional. The first argument (offset) is the amount of characters you wish to move from the cursor position (negative offset for seeking backwards). The second argument (from) is for controlling where the cursor will be within the file, options are 0, 1 or 2. diff --git a/docs/docs/standard-lib/argparse.md b/docs/docs/standard-lib/argparse.md index f26431a6..b0f06098 100644 --- a/docs/docs/standard-lib/argparse.md +++ b/docs/docs/standard-lib/argparse.md @@ -24,7 +24,7 @@ To make use of the Argparse module an import is required. from Argparse import Parse; ``` -### New Parser(string, string, string) +### New Parser(String, String, String) -> Parser To create a new parser instance, call the `Parser` class with the 3 required string arguments; name, description, and user provided usage. @@ -32,7 +32,7 @@ To create a new parser instance, call the `Parser` class with the 3 required str var parser = Parser("prog_name", "Program to do all the things", ""); ``` -### Parse.addString(string, string, bool, string -> optional) +### Parse.addString(String, String, Bool, string -> Optional) To add a new string argument, call the method below with at least the 3 required arguments; name, description, and boolean indicating the flag is required. A fourth argument can be passed to give the parser a custom name for the given flag. @@ -40,7 +40,7 @@ To add a new string argument, call the method below with at least the 3 required parser.addString("-b", "birth month", false, "month"); ``` -### Parser.addNumber(string, string, bool, string -> optional) +### Parser.addNumber(String, String, Bool, String -> Optional) To add a new number argument, call the method below with at least the 3 required arguments; name, description, and boolean indicating the flag is required. A fourth argument can be passed to give the parser a custom name for the given flag. @@ -48,7 +48,7 @@ To add a new number argument, call the method below with at least the 3 required parser.addNumber("--port", "server port", false); ``` -### Parser.addBool(string, string, bool, string -> optional) +### Parser.addBool(String, String, Bool, String -> Optional) To add a new bool argument, call the method below with at least the 3 required arguments; name, description, and boolean indicating the flag is required. A fourth argument can be passed to give the parser a custom name for the given flag. @@ -56,7 +56,7 @@ To add a new bool argument, call the method below with at least the 3 required a parser.addBool("-s", "run securely", true, "secure"); ``` -### Parser.addList(string, string, bool, string -> optional) +### Parser.addList(String, String, Bool, String -> Optional) To add a new list argument, call the method below with at least the 3 required arguments; name, description, and boolean indicating the flag is required. A fourth argument can be passed to give the parser a custom name for the given flag. @@ -64,7 +64,7 @@ To add a new list argument, call the method below with at least the 3 required a parser.addList("-u", "active users", true, "users"); ``` -### Parser.parse() +### Parser.parse() -> Result The `parse` method needs to be called to process the given flags against the configured flags. `parse` returns a `Result` value that, on success, will need to be unwrapped to access an instance of the `Args` class. diff --git a/docs/docs/standard-lib/base64.md b/docs/docs/standard-lib/base64.md index 49d002d7..6f1fa1a5 100644 --- a/docs/docs/standard-lib/base64.md +++ b/docs/docs/standard-lib/base64.md @@ -23,7 +23,7 @@ To make use of the Base64 module an import is required. import Base64; ``` -### Base64.encode(string) +### Base64.encode(String) -> String Base64 encode a given string. @@ -31,7 +31,7 @@ Base64 encode a given string. Base64.encode("test"); // 'dGVzdA==' ``` -### Base64.decode(string) +### Base64.decode(String) -> String Base64 decode a given string. diff --git a/docs/docs/standard-lib/datetime.md b/docs/docs/standard-lib/datetime.md index 5eb8eaf8..5cc5e4cb 100644 --- a/docs/docs/standard-lib/datetime.md +++ b/docs/docs/standard-lib/datetime.md @@ -33,7 +33,7 @@ import Datetime; | Datetime.SECONDS_IN_DAY | The number of seconds in a day. | | Datetime.SECONDS_IN_WEEK | The number of seconds in a week. | -### Datetime.now() +### Datetime.now() -> String Returns a human readable locale datetime string. @@ -41,7 +41,7 @@ Returns a human readable locale datetime string. Datetime.now(); // Fri May 29 03:12:32 2020 ``` -### Datetime.nowUTC() +### Datetime.nowUTC() -> String Returns a human readable UTC datetime string. @@ -82,7 +82,7 @@ Datetime.now(); // Fri May 29 02:12:32 2020 | %% | A literal % | | -### Datetime.strftime(string, number: time -> optional) +### Datetime.strftime(String, Number: time -> Optional) -> String Returns a user-defined datetime formatted string, see [Datetime formats](#datetime-formats). `strftime` also takes an optional argument which is a UNIX timestamp, so the date is formatted from the given timestamp rather than @@ -95,7 +95,7 @@ var time = System.time(); Datetime.strftime("Some point in time %H:%M:%S", time); ``` -### Datetime.strptime(string, string) +### Datetime.strptime(String, String) -> Number Returns a number which is the number of seconds from epoch. `strptime` expects two parameters the first parameter being the date format, see [Datetime formats](#datetime-formats) and the second diff --git a/docs/docs/standard-lib/env.md b/docs/docs/standard-lib/env.md index 51409d6c..cfa3ba4a 100644 --- a/docs/docs/standard-lib/env.md +++ b/docs/docs/standard-lib/env.md @@ -24,7 +24,7 @@ To make use of the Env module an import is required. import Env; ``` -### Env.get(string, string: defaultValue -> optional) +### Env.get(String, String: defaultValue -> Optional) -> String -> Nil Get an environment variable. `.get()` will return a string if a valid environment variable is found otherwise nil. @@ -36,7 +36,7 @@ Env.get("valid key"); // "value" Env.get("bad key!", "default value!!!"); // "default value!!!" ``` -### Env.set(string, value) +### Env.set(String, String) Change or add an environment variable. You can clear an environment variable by passing a `nil` value. When setting an environment variable the key must be a string and the value must be either a string or nil. @@ -58,7 +58,7 @@ Note: This is not available on Windows systems. Env.clearAll(); ``` -### Env.readFile(string: path -> optional) +### Env.readFile(String: path -> Optional) -> Result To read environment variables from a file this helper method is provided. By default it will attempt to read `.env` unless a different path is supplied. diff --git a/docs/docs/standard-lib/hashlib.md b/docs/docs/standard-lib/hashlib.md index 538651a9..1e2d897d 100644 --- a/docs/docs/standard-lib/hashlib.md +++ b/docs/docs/standard-lib/hashlib.md @@ -23,7 +23,7 @@ To make use of the Hashlib module an import is required. import Hashlib; ``` -### Hashlib.sha256(string) +### Hashlib.sha256(String) -> String Hashes a given string using the SHA-256 algorithm. @@ -31,7 +31,7 @@ Hashes a given string using the SHA-256 algorithm. Hashlib.sha256("Dictu"); // 889bb2f43047c331bed74b1a9b309cc66adff6c6d4c3517547813ad67ba8d105 ``` -### Hashlib.hmac(string: key, string: payload, boolean: raw -> optional) +### Hashlib.hmac(String: key, String: payload, Boolean: raw -> Optional) -> String Generate a HMAC using the SHA-256 algorithm. The `raw` optional argument determines whether the output will be in raw bytes or whether it will be in a string format, default is string format (false). @@ -41,7 +41,7 @@ Hashlib.hmac("supersecretkey", "my message"); // 2dd2a27b79858666b9d9ba3cf73f1c8 Hashlib.hmac("supersecretkey", "my message", true); // ``` -### Hashlib.bcrypt(string, number: rounds -> optional) +### Hashlib.bcrypt(String, Number: rounds -> Optional) -> String Hashes a given string using the bcrypt algorithm. The rounds optional argument is the amount of rounds used to generate the hashed string, default is 8. @@ -51,7 +51,7 @@ Hashlib.bcrypt("my message"); // $2b$08$mkI2fcaukY0XX3qlpdtBgeXq7pAUr2bUw4Z1Okmn Hashlib.bcrypt("my message", 2); // $2b$04$pNdfFWIV2r31vmQmJOSwNe/CHupV/wpOHmmwsDjCZi45w8ttjA/WW ``` -### Hashlib.bcryptVerify(string: plainText, string: hash) +### Hashlib.bcryptVerify(String: plainText, String: hash) -> Boolean This verifies a given bcrypt hash matches the plaintext input with a boolean return value. The comparison method used is timing safe. @@ -62,7 +62,7 @@ Hashlib.bcryptVerify("my message", "$2b$04$pNdfFWIV2r31vmQmJOSwNe/CHupV/wpOHmmws Hashlib.bcryptVerify("my message", "wrong"); // false ``` -### Hashlib.verify(string: hash, string: hash) +### Hashlib.verify(String: hash, String: hash) -> Boolean Timing safe hash comparison. This should always be favoured over normal string comparison. diff --git a/docs/docs/standard-lib/http.md b/docs/docs/standard-lib/http.md index 1ee77198..816efa1e 100644 --- a/docs/docs/standard-lib/http.md +++ b/docs/docs/standard-lib/http.md @@ -24,7 +24,7 @@ To make use of the HTTP module an import is required. Along with the methods des import HTTP; ``` -### HTTP.get(string, list: headers -> optional, number: timeout -> optional) +### HTTP.get(String, list: headers -> Optional, Number: timeout -> Optional) -> Result Sends a HTTP GET request to a given URL. Timeout is given in seconds. Returns a Result and unwraps to a Response upon success. @@ -37,7 +37,7 @@ HTTP.get("https://httpbin.org/get", ["Content-Type: application/json"], 1); {"content": "...", "headers": ["...", "..."], "statusCode": 200} ``` -### HTTP.post(string, dictionary: postArgs -> optional, list: headers -> optional, number: timeout -> optional) +### HTTP.post(String, dictionary: postArgs -> Optional, list: headers -> Optional, Number: timeout -> Optional) -> Result Sends a HTTP POST request to a given URL. Timeout is given in seconds. Returns a Result and unwraps to a Response upon success. @@ -49,7 +49,7 @@ HTTP.post("https://httpbin.org/post", {"test": 10}, ["Content-Type: application/ HTTP.post("https://httpbin.org/post", {"test": 10}, ["Content-Type: application/json"], 1); ``` -### HTTP.newClient(dict) +### HTTP.newClient(Dict) -> HttpClient Creates a new HTTP client with a given set of options. @@ -69,7 +69,7 @@ const opts = { var httpClient = HTTP.newClient(opts); ``` -### httpClient.get(string) +### httpClient.get(String) -> Result Sends a HTTP GET request to a given URL. Returns a Result and unwraps to a Response upon success. @@ -80,7 +80,7 @@ httpClient.get("https://httpbin.org/get"); {"content": "...", "headers": ["...", "..."], "statusCode": 200} ``` -### httpClient.post(string, dictionary: postArgs) +### httpClient.post(String, Dict: postArgs) -> Result Sends a HTTP POST request to a given URL. Returns a Result and unwraps to a Response upon success. @@ -163,6 +163,7 @@ print(response.statusCode); 200 ``` -#### Response.json() +#### Response.json() -> Result + To quickly convert the raw string contained within the Response object we can use the helper `.json` method. This works exactly the same as `JSON.parse()` and will return a Result object. \ No newline at end of file diff --git a/docs/docs/standard-lib/inspect.md b/docs/docs/standard-lib/inspect.md index 6301f851..683ac835 100644 --- a/docs/docs/standard-lib/inspect.md +++ b/docs/docs/standard-lib/inspect.md @@ -23,7 +23,7 @@ To make use of the Inspect module an import is required. import Inspect; ``` -### Inspect.getFrameCount() +### Inspect.getFrameCount() -> Number This gives you the current frame count of the VM at the point of calling. @@ -39,7 +39,7 @@ def test() { test(); // 1 ``` -### Inspect.getLine(number: count -> optional) +### Inspect.getLine(Number: count -> optional) -> Number This gives you the line number within the file that the function was called from. @@ -51,7 +51,7 @@ Inspect.getLine(); // 1 Inspect.getLine(1000); // Optional argument passed to getLine() exceeds the frame count. ``` -### Inspect.getFile(number: count -> optional) +### Inspect.getFile(Number: count -> Optional) -> String This gives you the name of the file that the function was called from. diff --git a/docs/docs/standard-lib/json.md b/docs/docs/standard-lib/json.md index e72dc61b..3c657bf8 100644 --- a/docs/docs/standard-lib/json.md +++ b/docs/docs/standard-lib/json.md @@ -24,7 +24,7 @@ To make use of the JSON module an import is required. import JSON; ``` -### JSON.parse(string) +### JSON.parse(String) -> Result Parses a JSON string and turns it into a valid Dictu datatype. Returns a Result type and unwraps to datatype. @@ -36,7 +36,7 @@ JSON.parse('[1, 2, 3]'); // [1, 2, 3] JSON.parse('null'); // nil ``` -### JSON.stringify(value, number: indent -> optional) +### JSON.stringify(value, String: indent -> Optional) -> Result Stringify converts a Dictu value into a valid JSON string. Returns a Result type and unwraps to string. diff --git a/docs/docs/standard-lib/log.md b/docs/docs/standard-lib/log.md index 14ac250f..7f7b6067 100644 --- a/docs/docs/standard-lib/log.md +++ b/docs/docs/standard-lib/log.md @@ -32,7 +32,7 @@ import Log; | Log.stderr | The default file descriptor where a process can write error messages. | | Log.stdin | An input stream where data is sent to and read by a program. | -### Log.print(string) +### Log.print(String) ```cs Log.print("something extremely interesting"); // 2021/11/27 11:49:14 something extremely interesting @@ -44,7 +44,7 @@ Log.print("something extremely interesting"); // 2021/11/27 11:49:14 something e Log.println("hello, world! println"); // 2021/11/27 11:49:14 hello, world! print ``` -### Log.fatal(string) +### Log.fatal(String) Log the given output and exit the program with an exit code of 1. @@ -52,7 +52,7 @@ Log the given output and exit the program with an exit code of 1. Log.fatal("we've met a tragic end"); // 2021/11/27 11:49:14 we've met a tragic end ``` -### Log.fatalln(string) +### Log.fatalln(String) Log the given output with a new line and exit the program with an exit code of 1. @@ -60,7 +60,7 @@ Log the given output with a new line and exit the program with an exit code of 1 Log.fatalln("hello, world! fatalln"); // 2021/11/27 11:49:14 hello, world! print ``` -### Log.new(number) +### Log.new(Number) Create a new instance of a logger. @@ -68,7 +68,7 @@ Create a new instance of a logger. const log = Log.new(Log.stdout).unwrap(); ``` -### log.setPrefix(string) +### log.setPrefix(String) A prefix can be set on the logger that will be included in the output just before the user provided content. @@ -76,7 +76,7 @@ A prefix can be set on the logger that will be included in the output just befor log.setPrefix("prefix"); ``` -### log.unsetPrefix(string) +### log.unsetPrefix(String) Remove the prefix on the logger. This is a noop if there was no prefix previously set. @@ -84,19 +84,19 @@ Remove the prefix on the logger. This is a noop if there was no prefix previousl log.unsetPrefix(); ``` -### log.print(string) +### log.print(String) ```cs Log.print("something extremely interesting"); // 2021/11/27 11:49:14 something extremely interesting ``` -### log.println(string) +### log.println(String) ```cs Log.println("hello, world! println"); // 2021/11/27 11:49:14 hello, world! print ``` -### log.fatal(string) +### log.fatal(String) Log the given output and exit the program with an exit code of 1. @@ -104,7 +104,7 @@ Log the given output and exit the program with an exit code of 1. log.fatal("we've met a tragic end"); // 2021/11/27 11:49:14 we've met a tragic end ``` -### log.fatalln(string) +### log.fatalln(String) Log the given output with a new line and exit the program with an exit code of 1. diff --git a/docs/docs/standard-lib/math.md b/docs/docs/standard-lib/math.md index b4fb1879..0f7f2116 100644 --- a/docs/docs/standard-lib/math.md +++ b/docs/docs/standard-lib/math.md @@ -27,19 +27,19 @@ import Math; ### Constants -| Constant | Description | -|--------------|--------------------------------------------------------| -| Math.pi | The mathematical constant: 3.14159265358979 | -| Math.e | The mathematical constant: 2.71828182845905 | -| Math.phi | The mathematical constant: 1.61803398874989 | -| Math.sqrt2 | The mathematical constant: 1.41421356237309 | -| Math.sqrte | The mathematical constant: 1.61803398874989 | -| Math.sqrtpi | The mathematical constant: 1.77245385090551 | -| Math.sqrtphi | The mathematical constant: 1.27201964951406 | -| Math.ln2 | The mathematical constant: 0.69314718055994 | -| Math.ln10 | The mathematical constant: 2.30258509299404 | - -### Math.min(iterable) +| Constant | Description | +| ------------ | ------------------------------------------- | +| Math.pi | The mathematical constant: 3.14159265358979 | +| Math.e | The mathematical constant: 2.71828182845905 | +| Math.phi | The mathematical constant: 1.61803398874989 | +| Math.sqrt2 | The mathematical constant: 1.41421356237309 | +| Math.sqrte | The mathematical constant: 1.61803398874989 | +| Math.sqrtpi | The mathematical constant: 1.77245385090551 | +| Math.sqrtphi | The mathematical constant: 1.27201964951406 | +| Math.ln2 | The mathematical constant: 0.69314718055994 | +| Math.ln10 | The mathematical constant: 2.30258509299404 | + +### Math.min(Iterable) -> Number Return the smallest number within the iterable @@ -48,7 +48,7 @@ Math.min(1, 2, 3); // 1 Math.min([1, 2, 3]); // 1 ``` -### Math.max(iterable) +### Math.max(Iterable) -> Number Return the largest number within the iterable @@ -57,7 +57,7 @@ Math.max(1, 2, 3); // 3 Math.max([1, 2, 3]); // 3 ``` -### Math.average(iterable) +### Math.average(Iterable) -> Number Return the average of the iterable @@ -66,7 +66,7 @@ Math.average(1, 2, 3); // 2 Math.average([1, 2, 3]); // 2 ``` -### Math.sum(iterable) +### Math.sum(Iterable) -> Number Return the sum of the iterable @@ -75,7 +75,7 @@ Math.sum(1, 2, 3); // 6 Math.sum([1, 2, 3]); // 6 ``` -### Math.floor(number) +### Math.floor(Number) -> Number Return the largest integer less than or equal to the given input @@ -85,7 +85,7 @@ Math.floor(17.999); // 17 Math.floor(17); // 17 ``` -### Math.round(number) +### Math.round(Number) -> Number Round to the nearest integer @@ -95,7 +95,7 @@ Math.round(17.49); // 17 Math.round(17.5); // 18 ``` -### Math.ceil(number) +### Math.ceil(Number) -> Number Returns smallest integer greater than or equal to given input @@ -105,7 +105,7 @@ Math.ceil(17.999); // 18 Math.ceil(17); // 17 ``` -### Math.abs(number) +### Math.abs(Number) -> Number Returns absolute value of a given number @@ -115,7 +115,7 @@ Math.abs(10); // 10 Math.abs(-10.5); // 10.5 ``` -### Math.sqrt(number) +### Math.sqrt(Number) -> Number Returns the square root of a given number @@ -124,7 +124,7 @@ Math.sqrt(25); // 5 Math.sqrt(100); // 10 ``` -### Math.sin(number) +### Math.sin(Number) -> Number Returns the sin value of a given number in radian @@ -133,7 +133,7 @@ Math.sin(1); // 0.8414 Math.sin(50); // -0.2623 ``` -### Math.cos(number) +### Math.cos(Number) -> Number Returns the cos value of a given number in radian @@ -142,7 +142,7 @@ Math.cos(1); // 0.5403 Math.cos(50); // 0.9649 ``` -### Math.tan(number) +### Math.tan(Number) -> Number Returns the tan value of a given number in radian @@ -151,7 +151,7 @@ Math.tan(1); // 1.5574 Math.tan(50); // -0.2719 ``` -### Math.gcd(iterable) +### Math.gcd(Iterable) -> Number Return the greatest common divisor of the numbers within the iterable @@ -160,7 +160,7 @@ Math.gcd(32, 24, 12); // 4 Math.gcd([32, 24, 12]); // 4 ``` -### Math.lcm(iterable) +### Math.lcm(Iterable) -> Number Return the least common multiple of the numbers within the iterable diff --git a/docs/docs/standard-lib/object.md b/docs/docs/standard-lib/object.md index 4e4c742e..ea15c3b2 100644 --- a/docs/docs/standard-lib/object.md +++ b/docs/docs/standard-lib/object.md @@ -24,7 +24,7 @@ To make use of the Object module an import is required. import Object; ``` -### Object.getClassRef(string) +### Object.getClassRef(String) -> Result This method will attempt to get a class reference from the class name provided as a string. @@ -38,7 +38,7 @@ class Test {} Object.getClassRef("Test").unwrap(); // ``` -### Object.createFrom(string) +### Object.createFrom(String) -> Result This method will attempt to create a new object from the class name provided as a string. diff --git a/docs/docs/standard-lib/path.md b/docs/docs/standard-lib/path.md index 70b9d6f0..abe8cc9a 100644 --- a/docs/docs/standard-lib/path.md +++ b/docs/docs/standard-lib/path.md @@ -26,12 +26,12 @@ import Path; ### Constants -| Constant | Description | -|--------------------|--------------------------------------| -| Path.delimiter | System dependent path delimiter | -| Path.dirSeparator | System dependent directory separator | +| Constant | Description | +| ----------------- | ------------------------------------ | +| Path.delimiter | System dependent path delimiter | +| Path.dirSeparator | System dependent directory separator | -### Path.basename(string) +### Path.basename(String) -> String Returns the basename of the path. @@ -39,7 +39,7 @@ Returns the basename of the path. Path.basename("/usr/bin"); // 'bin' ``` -### Path.dirname(string) +### Path.dirname(String) -> String Returns the directory name of the path. @@ -47,7 +47,7 @@ Returns the directory name of the path. Path.dirname("/usr/bin"); // '/usr' ``` -### Path.extname(string) +### Path.extname(String) -> String Returns the extension portion of the path, including the dot. @@ -56,7 +56,7 @@ Path.extname("/tmp/t.ext"); // '.ext' Path.extname("/tmp/t"); // '' ``` -### Path.isAbsolute(string) +### Path.isAbsolute(String) -> Boolean Returns true if path is absolute, false otherwise. @@ -65,7 +65,7 @@ Path.isAbsolute("/usr"); // true Path.isAbsolute("usr"); // false ``` -### Path.realpath(string) +### Path.realpath(String) -> Result Returns A result type and unwraps the canonicalized absolute pathname as a string. @@ -75,7 +75,7 @@ Returns A result type and unwraps the canonicalized absolute pathname as a strin Path.realpath("/dir/../dir/../dir"); // '/dir' ``` -### Path.exists(string) +### Path.exists(String) -> Boolean Returns a boolean whether a file exists at a given path. @@ -83,7 +83,7 @@ Returns a boolean whether a file exists at a given path. Path.exists("some/path/to/a/file.du"); // true ``` -### Path.isDir(string) +### Path.isDir(String) -> Boolean Checks whether a given path points to a directory or not. @@ -93,7 +93,7 @@ Checks whether a given path points to a directory or not. Path.isDir("/usr/bin/"); //true ``` -### Path.listDir(string) +### Path.listDir(String) -> List Returns a list of strings containing the contents of the input path. @@ -103,7 +103,7 @@ Returns a list of strings containing the contents of the input path. Path.listDir("/"); // ["bin", "dev", "home", "lib", ...] ``` -### Path.join(iterable) +### Path.join(Iterable) -> String Returns the provided string arguments joined using the directory separator. diff --git a/docs/docs/standard-lib/process.md b/docs/docs/standard-lib/process.md index ad381df7..ca0c25c6 100644 --- a/docs/docs/standard-lib/process.md +++ b/docs/docs/standard-lib/process.md @@ -24,7 +24,7 @@ To make use of the Process module an import is required. import Process; ``` -### Process.exec(list) +### Process.exec(List) -> Result 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". @@ -36,7 +36,7 @@ It will return a Result that unwraps to `nil` on success. Process.exec(["ls", "-la"]); ``` -### Process.run(list, boolean: captureOutput -> optional) +### Process.run(List, Boolean: captureOutput -> Optional) -> Result Similar to `.run()` except this **will** wait for the external process to finish executing. diff --git a/docs/docs/standard-lib/random.md b/docs/docs/standard-lib/random.md index 61702e84..2ec40d9b 100644 --- a/docs/docs/standard-lib/random.md +++ b/docs/docs/standard-lib/random.md @@ -25,7 +25,7 @@ To make use of the Random module an import is required. import Random; ``` -### Random.random() +### Random.random() -> Number Return a random decimal between 0 and 1. @@ -34,7 +34,7 @@ Random.random(); // 0.314 Random.random(); // 0.271 ``` -### Random.range(number: lowest, number: highest) +### Random.range(Number: lowest, Number: highest) -> Number Returns a random integer between the lowest and highest inputs where both are inclusive. @@ -44,7 +44,7 @@ Random.range(1, 5); // 4 Random.range(0, 2); // 1 ``` -### Random.select(list) +### Random.select(List) -> Type from List Returns a value randomly selected from the list. diff --git a/docs/docs/standard-lib/sockets.md b/docs/docs/standard-lib/sockets.md index 7cf58a9d..90640376 100644 --- a/docs/docs/standard-lib/sockets.md +++ b/docs/docs/standard-lib/sockets.md @@ -26,14 +26,14 @@ import Socket; ### Constants -| Constant | Description | -|----------------------|---------------------------------| -| Socket.AF_INET | AF_INET protocol family | -| Socket.SOCK_STREAM | SOCK_STREAM protocol type | -| Socket.SOL_SOCKET | SOL_SOCKET option level | -| Socket.SO_REUSEADDR | SO_REUSEADDR allow socket reuse | +| Constant | Description | +| ------------------- | ------------------------------- | +| Socket.AF_INET | AF_INET protocol family | +| Socket.SOCK_STREAM | SOCK_STREAM protocol type | +| Socket.SOL_SOCKET | SOL_SOCKET option level | +| Socket.SO_REUSEADDR | SO_REUSEADDR allow socket reuse | -### Socket.create(number: family, number: type) +### Socket.create(Number: family, Number: type) -> Result 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. @@ -48,7 +48,7 @@ if (!result.success()) { var socket = result.unwrap(); ``` -### socket.bind(string, number) +### socket.bind(String, Number) -> Result This will bind a given socket object to an IP and port number. Returns a Result type and on success will unwrap to nil. @@ -61,7 +61,7 @@ if (!result.success()) { } ``` -### socket.connect(string, number) +### socket.connect(String, Number) -> Result This will connect to a socket on a given host and IP. Returns a Result type and on success will unwrap to nil. @@ -74,7 +74,7 @@ if (!result.success()) { } ``` -### socket.listen(number: backlog -> optional) +### socket.listen(Number: backlog -> Optional) -> Result This will enable connections to be made. The backlog parameter specifies how many pending connections can be queued before they begin to get rejected. If left unspecified @@ -89,7 +89,7 @@ if (!result.success()) { } ``` -### socket.accept() +### socket.accept() -> Result This will accept incoming connections. The socket must be bound to an address an listening for incoming connections before `.accept()` can be used. @@ -101,7 +101,7 @@ var [client, address] = socket.accept().unwrap(); print(address); // 127.0.0.1 ``` -### socket.write(string) +### socket.write(String) -> Result This will write data to the remote client socket. Returns a Result type and on success will unwrap to a number (amount of chars written). @@ -114,7 +114,7 @@ if (!result.success()) { } ``` -### socket.recv(number) +### socket.recv(Number) -> Result This will receive data from the client socket. The maximum amount of data to be read at a given time is specified by the argument passed to `recv()`. @@ -138,7 +138,7 @@ Closes a socket. socket.close(); ``` -### socket.setsocketopt(number: level, number: option) +### socket.setsocketopt(Number: level, Number: option) -> Result Set a socket option on a given socket. Returns a Result type and on success will unwrap to nil. diff --git a/docs/docs/standard-lib/sqlite.md b/docs/docs/standard-lib/sqlite.md index fc6ddcf1..8566422f 100644 --- a/docs/docs/standard-lib/sqlite.md +++ b/docs/docs/standard-lib/sqlite.md @@ -25,7 +25,7 @@ Note: Unlike SQLite and most other libraries, foreign keys **are** enabled by de import Sqlite; ``` -### Sqlite.connect(string: database) +### Sqlite.connect(String: database) -> Result This opens a connection to a SQLite database. Returns a Result type and on success wraps an abstract SQLite type. @@ -36,7 +36,7 @@ Sqlite.connect(":memory:").unwrap(); Sqlite.connect("my/database/file.db").unwrap(); ``` -### sqlite.execute(string: query, list: arguments -> optional) +### sqlite.execute(String: query, List: arguments -> Optional) -> Result The `execute` method is ran on the abstract that is returned from `.connect` rather than the `Sqlite` module, hence the lower case `sqlite`. The `execute` method executes an SQL query and can return one of 3 values. diff --git a/docs/docs/standard-lib/system.md b/docs/docs/standard-lib/system.md index dd891a9b..fd7ed7d3 100644 --- a/docs/docs/standard-lib/system.md +++ b/docs/docs/standard-lib/system.md @@ -49,7 +49,7 @@ import System; | System.W_OK | Test for write permission. | | System.R_OK | Test for read permission. | -### System.mkdir(string, number: mode -> optional) +### System.mkdir(String, Number: mode -> Optional) -> Result Make directory. Returns a Result type and on success will unwrap nil. @@ -69,7 +69,7 @@ var System.mkdir(dir, S_IRWXU|S_IRGRP|S_IXGRP|S_IXOTH|S_IROTH); ``` -### System.access(string, number) +### System.access(String, Number) -> Result Check user's permissions for a file Returns a Result type and on success will unwrap nil. @@ -82,7 +82,7 @@ var F_OK = System.F_OK; System.access("/", F_OK); ``` -### System.rmdir(string) +### System.rmdir(String) -> Result Remove directory. @@ -92,7 +92,7 @@ Returns a Result type and on success will unwrap nil. System.rmdir(dir); ``` -### System.remove(string) +### System.remove(String) -> Result Delete a file from filesystem. @@ -102,7 +102,7 @@ Returns a Result type and on success will unwrap nil. System.remove(file); ``` -### System.getpid() +### System.getpid() -> Number Returns the process ID (PID) of the calling process as a number. @@ -112,7 +112,7 @@ Returns the process ID (PID) of the calling process as a number. System.getpid(); ``` -### System.getppid() +### System.getppid() -> Number Returns the process ID of the parent of the calling process as a number. @@ -122,7 +122,7 @@ Returns the process ID of the parent of the calling process as a number. System.getppid(); ``` -### System.getuid() +### System.getuid() -> Number Returns the real user ID of the calling process as a number. @@ -132,7 +132,7 @@ Returns the real user ID of the calling process as a number. System.getuid(); ``` -### System.geteuid() +### System.geteuid() -> Number Returns the effective user ID of the calling process as a number. @@ -142,7 +142,7 @@ Returns the effective user ID of the calling process as a number. System.geteuid(); ``` -### System.getgid() +### System.getgid() -> Number Returns the real group ID of the calling process as a number. @@ -152,7 +152,7 @@ Returns the real group ID of the calling process as a number. System.getgid(); ``` -### System.getegid() +### System.getegid() -> Number Returns the effective group ID of the calling process as a number. @@ -162,7 +162,7 @@ Returns the effective group ID of the calling process as a number. System.getegid(); ``` -### System.getCWD() +### System.getCWD() -> Result Get the current working directory of the Dictu process. @@ -172,7 +172,7 @@ Returns a Result type and on success will unwrap a string. System.getCWD().unwrap(); // '/Some/Path/To/A/Directory' ``` -### System.setCWD(string) +### System.setCWD(String) -> Result Set current working directory of the Dictu process. @@ -184,7 +184,7 @@ if (!System.setCWD('/').success()) { } ``` -### System.sleep(number) +### System.sleep(Number) Sleep pauses execution of the program for a given amount of time in seconds. @@ -192,7 +192,7 @@ Sleep pauses execution of the program for a given amount of time in seconds. System.sleep(3); // Pauses execution for 3 seconds ``` -### System.clock() +### System.clock() -> Number Returns number of clock ticks since the start of the program as a number, useful for benchmarks. @@ -200,7 +200,7 @@ Returns number of clock ticks since the start of the program as a number, useful System.clock(); ``` -### System.time() +### System.time() -> Number Returns UNIX timestamp as a number. @@ -216,7 +216,7 @@ Manually trigger a garbage collection. System.collect(); ``` -### System.exit(number) +### System.exit(Number) When you wish to prematurely exit the script with a given exit code. @@ -229,7 +229,7 @@ Shell $ echo $?; // 10 ``` -### System.chmod(string, string) +### System.chmod(String, String) Set the permissions on a file or directory. @@ -237,7 +237,7 @@ Set the permissions on a file or directory. System.chmod("/usr/local/share", "755"); ``` -### System.chown(string, number, number) +### System.chown(String, Number, Number) Set the ownership of a file or directory with the given path, uid, and gid. @@ -247,7 +247,7 @@ Note: This is not available on Windows systems. System.chown("/path/to/file", 0, 0); ``` -### System.uname() +### System.uname() -> Dict Returns the name and version of the system along with operating system and hardware information. @@ -257,7 +257,7 @@ Note: This is not available on Windows systems. System.uname(); ``` -### System.mkdirTemp(string: directory_template -> optional) +### System.mkdirTemp(String: directory_template -> Optional) -> Result Makes a temporary directory. If an empty string is given, the temporary directory's name will be a random string created in the current working directory. If a string is passed in, the temporary directory will be created with that name in the current working directory. @@ -272,7 +272,7 @@ System.mkdirTemp().unwrap(); // "VOO16s" System.mkdirTemp("test_XXXXXX").unwrap(); // "test_0bL2qS" ``` -### System.copyFile(string: src, string: dst) +### System.copyFile(String: src, String: dst) -> Result Copies the contents from the source file to the destination file. diff --git a/docs/docs/standard-lib/term.md b/docs/docs/standard-lib/term.md index 6eba74ad..5e170901 100644 --- a/docs/docs/standard-lib/term.md +++ b/docs/docs/standard-lib/term.md @@ -24,7 +24,7 @@ To make use of the Term module an import is required. import Term; ``` -### Term.isatty(number) +### Term.isatty(Number) -> Boolean Returns a boolean indicating whether the file descriptor passed is attached to a tty. @@ -32,7 +32,7 @@ Returns a boolean indicating whether the file descriptor passed is attached to a Term.isatty(0); ``` -### Term.getSize() +### Term.getSize() -> Number Returns the number of rows, columns, horizontal and vertical pixels of the attached terminal. diff --git a/docs/docs/standard-lib/unittest.md b/docs/docs/standard-lib/unittest.md index d4ac892a..8ba25a17 100644 --- a/docs/docs/standard-lib/unittest.md +++ b/docs/docs/standard-lib/unittest.md @@ -17,6 +17,7 @@ parent: Standard Library --- ## UnitTest + Unit testing is a very important part of software development and something we as developers should always strive to complete. Dictu aims to make this slightly easier by having a unit test framework built within the language. @@ -201,6 +202,7 @@ Results: ## Settings ### Silencing Passing Tests + The default setting is that it will output `Success.` for tests that pass, but sometimes that can cause finding errors or tests that fail slightly harder so we can turn this off to only show output from failed tests. @@ -255,6 +257,7 @@ Results: ``` #### Global + The above shows us how we can silence passing tests for a single group (class) of Unit Tests but, if we wish to silence ass passing tests that would be a lot of verbosity added to every class. Instead we can use the global flag that is defined as a class variable on the UnitTest parent class. @@ -303,6 +306,7 @@ Results: ``` ### Exit On Failure + Sometimes we may want our test suite to stop as soon as a test fails. This is done very similarly to silencing passing tests. diff --git a/docs/docs/standard-lib/uuid.md b/docs/docs/standard-lib/uuid.md index ca159acc..5cc16796 100644 --- a/docs/docs/standard-lib/uuid.md +++ b/docs/docs/standard-lib/uuid.md @@ -24,7 +24,7 @@ To make use of the UUID module an import is required. import UUID; ``` -### UUID.generate() +### UUID.generate() -> Result Returns a Result value with a string representation of the UUID on success or an Error on failure. This function attempts to use `/dev/urandom` if available but if it's not, it uses alterntive means of generating randomness. @@ -36,7 +36,7 @@ print(uuid); // a9c313d8-5bdb-4537-af9a-0b08be1387fb ``` -### UUID.generateRandom() +### UUID.generateRandom() -> Result Returns a Result value with a string representation of the UUID on success or an Error on failure. This function forces the use of the all-random UUID format. @@ -48,7 +48,7 @@ print(uuid); // 95356011-f08c-46d9-9335-d5b988682211 ``` -### UUID.generateTime() +### UUID.generateTime() -> Result Returns a Result value with a string representation of the UUID on success or an Error on failure. This function forces the use of the alternative algorithm which uses the current time and local MAC address (if available). diff --git a/docs/docs/strings.md b/docs/docs/strings.md index d46f28b3..4de0e716 100644 --- a/docs/docs/strings.md +++ b/docs/docs/strings.md @@ -90,7 +90,7 @@ r"Dictu\trocks!"; // 'Dictu\trocks!' r"Dictu\trocks!".len(); // 12 ``` -### string.len() +### string.len() -> Number Returns the length of the given string. @@ -98,7 +98,7 @@ Returns the length of the given string. "string".len(); // 6 ``` -### string.lower() +### string.lower() -> String Returns a lowercase version of the given string. @@ -108,7 +108,7 @@ Returns a lowercase version of the given string. "dictu".lower(); // dictu ``` -### string.upper() +### string.upper() -> String Returns an uppercase version of the given string. @@ -118,7 +118,7 @@ Returns an uppercase version of the given string. "DICTU".upper(); // DICTU ``` -### string.toNumber() +### string.toNumber() -> Number Casts a string to a number. This method returns a Result type and will unwrap to a number. @@ -128,7 +128,7 @@ Casts a string to a number. This method returns a Result type and will unwrap to "10px".toNumber(); // ``` -### string.toBool() +### string.toBool() -> Boolean Casts a string to a boolean value. Any string except an empty string is considered `true`. @@ -137,7 +137,7 @@ Casts a string to a boolean value. Any string except an empty string is consider "false".toBool(); // true ``` -### string.startsWith(string) +### string.startsWith(string) -> Boolean Returns true if a string starts with a given string. @@ -149,7 +149,7 @@ Returns true if a string starts with a given string. "Dictu".startsWith("d"); // false ``` -### string.endsWith(string) +### string.endsWith(string) -> Boolean Returns true if a string ends with a given string. @@ -162,7 +162,7 @@ Returns true if a string ends with a given string. "Dictu".endsWith("U"); // false ``` -### string.split(string: delimiter, number: splitCount -> optional) +### string.split(String: delimiter, Number: splitCount -> Optional) -> List Returns a list of strings, split based on a given delimiter. Returns a list of all characters in a string if an empty string is passed as delimiter. @@ -177,7 +177,7 @@ as not passing a value and will split by all occurrences of the delimiter. "Dictu is awesome!".split(" ", 1); // ['Dictu', 'is awesome!'] ``` -### string.replace(string: old, string: new) +### string.replace(String: old, String: new) -> String Replaces part (a substring) of a string with another string. @@ -185,7 +185,7 @@ Replaces part (a substring) of a string with another string. "Dictu is okay...".replace("okay...", "awesome!"); // "Dictu is awesome!" ``` -### string.contains(string) +### string.contains(String) -> Boolean Returns true if a string contains another string. @@ -194,7 +194,7 @@ Returns true if a string contains another string. "Dictu is awesome!".contains("Dictu is awesome!"); // true ``` -### string.find(string, number: skip -> optional) +### string.find(String, Number: skip -> Optional) -> Number To find the index of a given substring, use the `.find()` method. This method takes an optional second parameter which can be used to skip the first `n` number of appearances of the substring. This method returns `-1` if the substring could not be found. Otherwise, it returns the index of the string. @@ -204,7 +204,7 @@ To find the index of a given substring, use the `.find()` method. This method ta "House".find("Lost Keys"); // -1 (Not found) ``` -### string.leftStrip() +### string.leftStrip() -> String Strips whitespace from the left side of a string and returns the result. @@ -212,7 +212,7 @@ Strips whitespace from the left side of a string and returns the result. " Dictu".leftStrip(); // "Dictu" ``` -### string.rightStrip() +### string.rightStrip() -> String Strips whitespace from the right side of a string and returns the result. @@ -220,7 +220,7 @@ Strips whitespace from the right side of a string and returns the result. "Dictu ".rightStrip(); // "Dictu" ``` -### string.strip() +### string.strip() -> String Strips whitespace from both sides of a string and returns the result. @@ -228,7 +228,7 @@ Strips whitespace from both sides of a string and returns the result. " Dictu ".strip(); // "Dictu" ``` -### string.format(...value: args...) +### string.format(...value: args...) -> String This method will replace any instances of `{}` with the provided parameters. It also casts all arguments to strings. @@ -239,7 +239,7 @@ This method will replace any instances of `{}` with the provided parameters. It "String: {}, Number: {}, Boolean: {}, List: {}, Dictionary: {}".format("String", 123, true, ["String", 123, true], {"key": "value"}); // "String: String, Number: 123, Boolean: true, List: ["String", 123, true], Dictionary: {"key": "value"}" ``` -### string.count(string) +### string.count(String) -> Number Returns the number of occurrences of a given substring within another string. @@ -249,7 +249,7 @@ Returns the number of occurrences of a given substring within another string. "Sooooooooooome characters".count("o"); // 11 ``` -### string.title() +### string.title() -> String Returns a title cased version of string with first letter of each word capitalized. @@ -259,7 +259,7 @@ Returns a title cased version of string with first letter of each word capitaliz "once upon a time".title(); // Once Upon A Time ``` -### string.repeat(number) +### string.repeat(Number) -> String Returns a new string with the original string repeated the given number of times. diff --git a/docs/docs/thank-you.md b/docs/docs/thank-you.md index 9c36143a..8a2f610b 100644 --- a/docs/docs/thank-you.md +++ b/docs/docs/thank-you.md @@ -14,4 +14,4 @@ Dictu has been proudly sponsored by [DigitalOcean](https://m.do.co/c/02bd923f5cd tooling that comes with these specific platforms. I've personally been using their services for many years prior to this sponsorship so can attest to their excellent service, so if you too wish - to sign up, you can do so via [this](https://m.do.co/c/02bd923f5cda) referral link! \ No newline at end of file + to sign up, you can do so via [this](https://m.do.co/c/02bd923f5cda) referral link! From 40260004bd31926c1360ec7637c95232a53cd77c Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 10 Jan 2023 16:48:32 -0700 Subject: [PATCH 069/109] pr comments Signed-off-by: Brian Downs --- docs/docs/collections/lists.md | 10 +++++----- docs/docs/standard-lib/env.md | 4 ++-- docs/docs/standard-lib/random.md | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/docs/collections/lists.md b/docs/docs/collections/lists.md index 69e07170..39395105 100644 --- a/docs/docs/collections/lists.md +++ b/docs/docs/collections/lists.md @@ -165,7 +165,7 @@ myList.remove(1); print(myList); // [2] ``` -### list.pop(Number: index -> Optional) +### list.pop(Number: index -> Optional) -> value To remove a value from a list, with an optional index, use `.pop()` @@ -247,7 +247,7 @@ myList.forEach(def (value) => { }); ``` -### list.map(Func) +### list.map(Func) -> List Similar to `.foreach`, `.map` will run a function for each element within the list, however the difference is that `.map` returns a new list of values generated from the callback function. @@ -258,7 +258,7 @@ parameter which is the current item in the list. print([1, 2, 3, 4, 5].map(def (x) => x * 2)); // [2, 4, 6, 8, 10] ``` -### list.filter(Func) +### list.filter(Func) -> List To filter out values within a list we use `.filter()`. Filter expects a single parameter which is a callback and if the callback returns a truthy value it will be appended to the list. The callback itself also expects one @@ -270,7 +270,7 @@ Note: `.filter()` returns a new list. print([1, 2, 3, 4, 5].filter(def (x) => x > 2)); // [3, 4, 5] ``` -### list.reduce(Func, value: initial -> Optional) +### list.reduce(Func, value: initial -> Optional) -> List To reduce a list down to a single value we use `.reduce()`. Reduce expects at least one parameter which is a callback that will be executed on each item of the list. The value of the callback function is returned and saved for the next @@ -286,7 +286,7 @@ By default the initial value for `.reduce()` is 0, however we can change this to print(["Dictu ", "is", " great!"].reduce(def (accumulate, element) => accumulate + element, "")); // 'Dictu is great!' ``` -### list.find(Func, Number: start -> Optional, Number: end -> Optional) +### list.find(Func, Number: start -> Optional, Number: end -> Optional) -> List To find a single item within a list we use `.find()`. Find will search through each item in the list and as soon as the callback returns a truthy value, the item that satisfied the callback is returned, if none of the items satisfy the callback diff --git a/docs/docs/standard-lib/env.md b/docs/docs/standard-lib/env.md index cfa3ba4a..3a0389e4 100644 --- a/docs/docs/standard-lib/env.md +++ b/docs/docs/standard-lib/env.md @@ -24,7 +24,7 @@ To make use of the Env module an import is required. import Env; ``` -### Env.get(String, String: defaultValue -> Optional) -> String -> Nil +### Env.get(String, String: defaultValue -> Optional) -> ?String Get an environment variable. `.get()` will return a string if a valid environment variable is found otherwise nil. @@ -36,7 +36,7 @@ Env.get("valid key"); // "value" Env.get("bad key!", "default value!!!"); // "default value!!!" ``` -### Env.set(String, String) +### Env.set(String, ?String) Change or add an environment variable. You can clear an environment variable by passing a `nil` value. When setting an environment variable the key must be a string and the value must be either a string or nil. diff --git a/docs/docs/standard-lib/random.md b/docs/docs/standard-lib/random.md index 2ec40d9b..a5aaeffb 100644 --- a/docs/docs/standard-lib/random.md +++ b/docs/docs/standard-lib/random.md @@ -44,7 +44,7 @@ Random.range(1, 5); // 4 Random.range(0, 2); // 1 ``` -### Random.select(List) -> Type from List +### Random.select(List) -> value Returns a value randomly selected from the list. From 5e12706e5efd97f6f77a9c43b9447bd64d3ab135 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 10 Jan 2023 17:02:12 -0700 Subject: [PATCH 070/109] add unwrapped type to Result in docs Signed-off-by: Brian Downs --- docs/docs/standard-lib/argparse.md | 2 +- docs/docs/standard-lib/http.md | 10 +++++----- docs/docs/standard-lib/json.md | 4 ++-- docs/docs/standard-lib/object.md | 4 ++-- docs/docs/standard-lib/path.md | 2 +- docs/docs/standard-lib/process.md | 4 ++-- docs/docs/standard-lib/sockets.md | 14 +++++++------- docs/docs/standard-lib/sqlite.md | 4 ++-- docs/docs/standard-lib/system.md | 18 +++++++++--------- docs/docs/standard-lib/uuid.md | 6 +++--- 10 files changed, 34 insertions(+), 34 deletions(-) diff --git a/docs/docs/standard-lib/argparse.md b/docs/docs/standard-lib/argparse.md index b0f06098..9148b666 100644 --- a/docs/docs/standard-lib/argparse.md +++ b/docs/docs/standard-lib/argparse.md @@ -64,7 +64,7 @@ To add a new list argument, call the method below with at least the 3 required a parser.addList("-u", "active users", true, "users"); ``` -### Parser.parse() -> Result +### Parser.parse() -> Result The `parse` method needs to be called to process the given flags against the configured flags. `parse` returns a `Result` value that, on success, will need to be unwrapped to access an instance of the `Args` class. diff --git a/docs/docs/standard-lib/http.md b/docs/docs/standard-lib/http.md index 816efa1e..845640ff 100644 --- a/docs/docs/standard-lib/http.md +++ b/docs/docs/standard-lib/http.md @@ -24,7 +24,7 @@ To make use of the HTTP module an import is required. Along with the methods des import HTTP; ``` -### HTTP.get(String, list: headers -> Optional, Number: timeout -> Optional) -> Result +### HTTP.get(String, list: headers -> Optional, Number: timeout -> Optional) -> Result Sends a HTTP GET request to a given URL. Timeout is given in seconds. Returns a Result and unwraps to a Response upon success. @@ -37,7 +37,7 @@ HTTP.get("https://httpbin.org/get", ["Content-Type: application/json"], 1); {"content": "...", "headers": ["...", "..."], "statusCode": 200} ``` -### HTTP.post(String, dictionary: postArgs -> Optional, list: headers -> Optional, Number: timeout -> Optional) -> Result +### HTTP.post(String, dictionary: postArgs -> Optional, list: headers -> Optional, Number: timeout -> Optional) -> Result Sends a HTTP POST request to a given URL. Timeout is given in seconds. Returns a Result and unwraps to a Response upon success. @@ -69,7 +69,7 @@ const opts = { var httpClient = HTTP.newClient(opts); ``` -### httpClient.get(String) -> Result +### httpClient.get(String) -> Result Sends a HTTP GET request to a given URL. Returns a Result and unwraps to a Response upon success. @@ -80,7 +80,7 @@ httpClient.get("https://httpbin.org/get"); {"content": "...", "headers": ["...", "..."], "statusCode": 200} ``` -### httpClient.post(String, Dict: postArgs) -> Result +### httpClient.post(String, Dict: postArgs) -> Result Sends a HTTP POST request to a given URL. Returns a Result and unwraps to a Response upon success. @@ -163,7 +163,7 @@ print(response.statusCode); 200 ``` -#### Response.json() -> Result +#### Response.json() -> Result To quickly convert the raw string contained within the Response object we can use the helper `.json` method. This works exactly the same as `JSON.parse()` and will return a Result object. \ No newline at end of file diff --git a/docs/docs/standard-lib/json.md b/docs/docs/standard-lib/json.md index 3c657bf8..97f8bbdd 100644 --- a/docs/docs/standard-lib/json.md +++ b/docs/docs/standard-lib/json.md @@ -24,7 +24,7 @@ To make use of the JSON module an import is required. import JSON; ``` -### JSON.parse(String) -> Result +### JSON.parse(String) -> Result Parses a JSON string and turns it into a valid Dictu datatype. Returns a Result type and unwraps to datatype. @@ -36,7 +36,7 @@ JSON.parse('[1, 2, 3]'); // [1, 2, 3] JSON.parse('null'); // nil ``` -### JSON.stringify(value, String: indent -> Optional) -> Result +### JSON.stringify(value, String: indent -> Optional) -> Result Stringify converts a Dictu value into a valid JSON string. Returns a Result type and unwraps to string. diff --git a/docs/docs/standard-lib/object.md b/docs/docs/standard-lib/object.md index ea15c3b2..4672e426 100644 --- a/docs/docs/standard-lib/object.md +++ b/docs/docs/standard-lib/object.md @@ -24,7 +24,7 @@ To make use of the Object module an import is required. import Object; ``` -### Object.getClassRef(String) -> Result +### Object.getClassRef(String) -> Result This method will attempt to get a class reference from the class name provided as a string. @@ -38,7 +38,7 @@ class Test {} Object.getClassRef("Test").unwrap(); // ``` -### Object.createFrom(String) -> Result +### Object.createFrom(String) -> Result This method will attempt to create a new object from the class name provided as a string. diff --git a/docs/docs/standard-lib/path.md b/docs/docs/standard-lib/path.md index abe8cc9a..beaed246 100644 --- a/docs/docs/standard-lib/path.md +++ b/docs/docs/standard-lib/path.md @@ -65,7 +65,7 @@ Path.isAbsolute("/usr"); // true Path.isAbsolute("usr"); // false ``` -### Path.realpath(String) -> Result +### Path.realpath(String) -> Result Returns A result type and unwraps the canonicalized absolute pathname as a string. diff --git a/docs/docs/standard-lib/process.md b/docs/docs/standard-lib/process.md index ca0c25c6..516ee95a 100644 --- a/docs/docs/standard-lib/process.md +++ b/docs/docs/standard-lib/process.md @@ -24,7 +24,7 @@ To make use of the Process module an import is required. import Process; ``` -### Process.exec(List) -> Result +### Process.exec(List) -> Result 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". @@ -36,7 +36,7 @@ It will return a Result that unwraps to `nil` on success. Process.exec(["ls", "-la"]); ``` -### Process.run(List, Boolean: captureOutput -> Optional) -> Result +### Process.run(List, Boolean: captureOutput -> Optional) -> Result Similar to `.run()` except this **will** wait for the external process to finish executing. diff --git a/docs/docs/standard-lib/sockets.md b/docs/docs/standard-lib/sockets.md index 90640376..f2613a98 100644 --- a/docs/docs/standard-lib/sockets.md +++ b/docs/docs/standard-lib/sockets.md @@ -33,7 +33,7 @@ import Socket; | Socket.SOL_SOCKET | SOL_SOCKET option level | | Socket.SO_REUSEADDR | SO_REUSEADDR allow socket reuse | -### Socket.create(Number: family, Number: type) -> Result +### Socket.create(Number: family, Number: type) -> Result 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. @@ -48,7 +48,7 @@ if (!result.success()) { var socket = result.unwrap(); ``` -### socket.bind(String, Number) -> Result +### socket.bind(String, Number) -> Result This will bind a given socket object to an IP and port number. Returns a Result type and on success will unwrap to nil. @@ -61,7 +61,7 @@ if (!result.success()) { } ``` -### socket.connect(String, Number) -> Result +### socket.connect(String, Number) -> Result This will connect to a socket on a given host and IP. Returns a Result type and on success will unwrap to nil. @@ -74,7 +74,7 @@ if (!result.success()) { } ``` -### socket.listen(Number: backlog -> Optional) -> Result +### socket.listen(Number: backlog -> Optional) -> Result This will enable connections to be made. The backlog parameter specifies how many pending connections can be queued before they begin to get rejected. If left unspecified @@ -89,7 +89,7 @@ if (!result.success()) { } ``` -### socket.accept() -> Result +### socket.accept() -> Result This will accept incoming connections. The socket must be bound to an address an listening for incoming connections before `.accept()` can be used. @@ -101,7 +101,7 @@ var [client, address] = socket.accept().unwrap(); print(address); // 127.0.0.1 ``` -### socket.write(String) -> Result +### socket.write(String) -> Result This will write data to the remote client socket. Returns a Result type and on success will unwrap to a number (amount of chars written). @@ -114,7 +114,7 @@ if (!result.success()) { } ``` -### socket.recv(Number) -> Result +### socket.recv(Number) -> Result This will receive data from the client socket. The maximum amount of data to be read at a given time is specified by the argument passed to `recv()`. diff --git a/docs/docs/standard-lib/sqlite.md b/docs/docs/standard-lib/sqlite.md index 8566422f..8f87fde1 100644 --- a/docs/docs/standard-lib/sqlite.md +++ b/docs/docs/standard-lib/sqlite.md @@ -25,7 +25,7 @@ Note: Unlike SQLite and most other libraries, foreign keys **are** enabled by de import Sqlite; ``` -### Sqlite.connect(String: database) -> Result +### Sqlite.connect(String: database) -> Result This opens a connection to a SQLite database. Returns a Result type and on success wraps an abstract SQLite type. @@ -36,7 +36,7 @@ Sqlite.connect(":memory:").unwrap(); Sqlite.connect("my/database/file.db").unwrap(); ``` -### sqlite.execute(String: query, List: arguments -> Optional) -> Result +### sqlite.execute(String: query, List: arguments -> Optional) -> Result The `execute` method is ran on the abstract that is returned from `.connect` rather than the `Sqlite` module, hence the lower case `sqlite`. The `execute` method executes an SQL query and can return one of 3 values. diff --git a/docs/docs/standard-lib/system.md b/docs/docs/standard-lib/system.md index fd7ed7d3..880a3643 100644 --- a/docs/docs/standard-lib/system.md +++ b/docs/docs/standard-lib/system.md @@ -49,7 +49,7 @@ import System; | System.W_OK | Test for write permission. | | System.R_OK | Test for read permission. | -### System.mkdir(String, Number: mode -> Optional) -> Result +### System.mkdir(String, Number: mode -> Optional) -> Result Make directory. Returns a Result type and on success will unwrap nil. @@ -69,9 +69,9 @@ var System.mkdir(dir, S_IRWXU|S_IRGRP|S_IXGRP|S_IXOTH|S_IROTH); ``` -### System.access(String, Number) -> Result +### System.access(String, Number) -> Result -Check user's permissions for a file +Check user's permissions for a file. Returns a Result type and on success will unwrap nil. **Note:** This method and the F_OK\|X_OK\|W_OK\|R_OK constants are not available on windows systems. @@ -82,7 +82,7 @@ var F_OK = System.F_OK; System.access("/", F_OK); ``` -### System.rmdir(String) -> Result +### System.rmdir(String) -> Result Remove directory. @@ -92,7 +92,7 @@ Returns a Result type and on success will unwrap nil. System.rmdir(dir); ``` -### System.remove(String) -> Result +### System.remove(String) -> Result Delete a file from filesystem. @@ -162,7 +162,7 @@ Returns the effective group ID of the calling process as a number. System.getegid(); ``` -### System.getCWD() -> Result +### System.getCWD() -> Result Get the current working directory of the Dictu process. @@ -172,7 +172,7 @@ Returns a Result type and on success will unwrap a string. System.getCWD().unwrap(); // '/Some/Path/To/A/Directory' ``` -### System.setCWD(String) -> Result +### System.setCWD(String) -> Result Set current working directory of the Dictu process. @@ -257,7 +257,7 @@ Note: This is not available on Windows systems. System.uname(); ``` -### System.mkdirTemp(String: directory_template -> Optional) -> Result +### System.mkdirTemp(String: directory_template -> Optional) -> Result Makes a temporary directory. If an empty string is given, the temporary directory's name will be a random string created in the current working directory. If a string is passed in, the temporary directory will be created with that name in the current working directory. @@ -272,7 +272,7 @@ System.mkdirTemp().unwrap(); // "VOO16s" System.mkdirTemp("test_XXXXXX").unwrap(); // "test_0bL2qS" ``` -### System.copyFile(String: src, String: dst) -> Result +### System.copyFile(String: src, String: dst) -> Result Copies the contents from the source file to the destination file. diff --git a/docs/docs/standard-lib/uuid.md b/docs/docs/standard-lib/uuid.md index 5cc16796..4f724683 100644 --- a/docs/docs/standard-lib/uuid.md +++ b/docs/docs/standard-lib/uuid.md @@ -24,7 +24,7 @@ To make use of the UUID module an import is required. import UUID; ``` -### UUID.generate() -> Result +### UUID.generate() -> Result Returns a Result value with a string representation of the UUID on success or an Error on failure. This function attempts to use `/dev/urandom` if available but if it's not, it uses alterntive means of generating randomness. @@ -36,7 +36,7 @@ print(uuid); // a9c313d8-5bdb-4537-af9a-0b08be1387fb ``` -### UUID.generateRandom() -> Result +### UUID.generateRandom() -> Result Returns a Result value with a string representation of the UUID on success or an Error on failure. This function forces the use of the all-random UUID format. @@ -48,7 +48,7 @@ print(uuid); // 95356011-f08c-46d9-9335-d5b988682211 ``` -### UUID.generateTime() -> Result +### UUID.generateTime() -> Result Returns a Result value with a string representation of the UUID on success or an Error on failure. This function forces the use of the alternative algorithm which uses the current time and local MAC address (if available). From baa0f5586d53c88b7658c77a9f427343249a1d3a Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 10 Jan 2023 17:29:24 -0700 Subject: [PATCH 071/109] pr comments Signed-off-by: Brian Downs --- docs/docs/standard-lib/math.md | 40 +++++++++++++++++----------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/docs/standard-lib/math.md b/docs/docs/standard-lib/math.md index 0f7f2116..dcd04139 100644 --- a/docs/docs/standard-lib/math.md +++ b/docs/docs/standard-lib/math.md @@ -39,36 +39,36 @@ import Math; | Math.ln2 | The mathematical constant: 0.69314718055994 | | Math.ln10 | The mathematical constant: 2.30258509299404 | -### Math.min(Iterable) -> Number +### Math.min(...Number|List) -> Number -Return the smallest number within the iterable +Return the smallest number within the iterable. ```cs Math.min(1, 2, 3); // 1 Math.min([1, 2, 3]); // 1 ``` -### Math.max(Iterable) -> Number +### Math.max(...Number|List) -> Number -Return the largest number within the iterable +Return the largest number within the iterable. ```cs Math.max(1, 2, 3); // 3 Math.max([1, 2, 3]); // 3 ``` -### Math.average(Iterable) -> Number +### Math.average(...Number|List) -> Number -Return the average of the iterable +Return the average of the iterable. ```cs Math.average(1, 2, 3); // 2 Math.average([1, 2, 3]); // 2 ``` -### Math.sum(Iterable) -> Number +### Math.sum(...Number|List) -> Number -Return the sum of the iterable +Return the sum of the iterable. ```cs Math.sum(1, 2, 3); // 6 @@ -77,7 +77,7 @@ Math.sum([1, 2, 3]); // 6 ### Math.floor(Number) -> Number -Return the largest integer less than or equal to the given input +Return the largest integer less than or equal to the given input. ```cs Math.floor(17.4); // 17 @@ -87,7 +87,7 @@ Math.floor(17); // 17 ### Math.round(Number) -> Number -Round to the nearest integer +Round to the nearest integer. ```cs Math.round(17.4); // 17 @@ -97,7 +97,7 @@ Math.round(17.5); // 18 ### Math.ceil(Number) -> Number -Returns smallest integer greater than or equal to given input +Returns smallest integer greater than or equal to given input. ```cs Math.ceil(17.4); // 18 @@ -107,7 +107,7 @@ Math.ceil(17); // 17 ### Math.abs(Number) -> Number -Returns absolute value of a given number +Returns absolute value of a given number. ```cs Math.abs(-10); // 10 @@ -117,7 +117,7 @@ Math.abs(-10.5); // 10.5 ### Math.sqrt(Number) -> Number -Returns the square root of a given number +Returns the square root of a given number. ```cs Math.sqrt(25); // 5 @@ -126,7 +126,7 @@ Math.sqrt(100); // 10 ### Math.sin(Number) -> Number -Returns the sin value of a given number in radian +Returns the sin value of a given number in radian. ```cs Math.sin(1); // 0.8414 @@ -135,7 +135,7 @@ Math.sin(50); // -0.2623 ### Math.cos(Number) -> Number -Returns the cos value of a given number in radian +Returns the cos value of a given number in radian. ```cs Math.cos(1); // 0.5403 @@ -144,25 +144,25 @@ Math.cos(50); // 0.9649 ### Math.tan(Number) -> Number -Returns the tan value of a given number in radian +Returns the tan value of a given number in radian. ```cs Math.tan(1); // 1.5574 Math.tan(50); // -0.2719 ``` -### Math.gcd(Iterable) -> Number +### Math.gcd(...Number|List) -> Number -Return the greatest common divisor of the numbers within the iterable +Return the greatest common divisor of the numbers within the iterable. ```cs Math.gcd(32, 24, 12); // 4 Math.gcd([32, 24, 12]); // 4 ``` -### Math.lcm(Iterable) -> Number +### Math.lcm(...Number|List) -> Number -Return the least common multiple of the numbers within the iterable +Return the least common multiple of the numbers within the iterable. ```cs Math.lcm(32, 24, 12); // 96 From 51f22173c52bd5ebf057c5b1609ab1cd84dc47b6 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 10 Jan 2023 19:04:39 -0700 Subject: [PATCH 072/109] use nfpm to create packages Signed-off-by: Brian Downs --- {scripts => ops}/checkTests.du | 0 {scripts => ops}/generate.du | 0 ops/nfpm_amd64.yaml | 408 +++++++++++++++++++++++++++++++++ ops/pkgBuild.du | 44 ++++ 4 files changed, 452 insertions(+) rename {scripts => ops}/checkTests.du (100%) rename {scripts => ops}/generate.du (100%) create mode 100644 ops/nfpm_amd64.yaml create mode 100755 ops/pkgBuild.du diff --git a/scripts/checkTests.du b/ops/checkTests.du similarity index 100% rename from scripts/checkTests.du rename to ops/checkTests.du diff --git a/scripts/generate.du b/ops/generate.du similarity index 100% rename from scripts/generate.du rename to ops/generate.du diff --git a/ops/nfpm_amd64.yaml b/ops/nfpm_amd64.yaml new file mode 100644 index 00000000..12716169 --- /dev/null +++ b/ops/nfpm_amd64.yaml @@ -0,0 +1,408 @@ +# Name. (required) +name: dictu-lang + +# Architecture. (required) +# This will expand any env var you set in the field, e.g. version: ${GOARCH} +# The architecture is specified using Go nomenclature (GOARCH) and translated +# to the platform specific equivalent. In order to manually set the architecture +# to a platform specific value, use deb_arch, rpm_arch and apk_arch. +# Examples: `all`, `amd64`, `386`, `arm5`, `arm6`, `arm7`, `arm64`, `mips`, +# `mipsle`, `mips64le`, `ppc64le`, `s390` +arch: amd64 + +# Platform. +# This will expand any env var you set in the field, e.g. version: ${GOOS} +# This is only used by the rpm and deb packagers. +# Examples: `linux` (default), `darwin` +platform: linux + +# Version. (required) +# This will expand any env var you set in the field, e.g. version: ${SEMVER} +# Some package managers, like deb, require the version to start with a digit. +# Hence, you should not prefix the version with 'v'. +version: 0.26.0 + +# Version Schema allows you to specify how to parse the version string. +# Default is `semver` +# `semver` attempt to parse the version string as a valid semver version. +# The parser is lenient; it will strip a `v` prefix and will accept +# versions with fewer than 3 components, like `v1.2`. +# If parsing succeeds, then the version will be molded into a format +# compatible with the specific packager used. +# If parsing fails, then the version is used as-is. +# `none` skip trying to parse the version string and just use what is passed in +version_schema: semver + +# Version Epoch. +# A package with a higher version epoch will always be considered newer. +# See: https://www.debian.org/doc/debian-policy/ch-controlfields.html#epochs-should-be-used-sparingly +epoch: 2 + +# Version Prerelease. +# Default is extracted from `version` if it is semver compatible. +# This is appended to the `version`, e.g. `1.2.3+beta1`. If the `version` is +# semver compatible, then this replaces the prerelease component of the semver. +#prerelease: beta1 + +# Version Metadata (previously deb.metadata). +# Default is extracted from `version` if it is semver compatible. +# Setting metadata might interfere with version comparisons depending on the +# packager. If the `version` is semver compatible, then this replaces the +# version metadata component of the semver. +#version_metadata: git + +# Version Release, aka revision. +# This will expand any env var you set in the field, e.g. release: ${VERSION_RELEASE} +# This is appended to the `version` after `prerelease`. This should be +# incremented if you release an updated package of the same upstream version, +# and it should reset to 1 when bumping the version. +release: 1 + +# Section. +# This is only used by the deb packager. +# See: https://www.debian.org/doc/debian-policy/ch-archive.html#sections +section: default + +# Priority. +# Defaults to `optional` on deb +# Defaults to empty on rpm and apk +# See: https://www.debian.org/doc/debian-policy/ch-archive.html#priorities +priority: extra + +# Maintainer. (required) +# This will expand any env var you set in the field, e.g. maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}> +# Defaults to empty on rpm and apk +# Leaving the 'maintainer' field unset will not be allowed in a future version +maintainer: Brian Downs + +# Description. +# Defaults to `no description given`. +# Most packagers call for a one-line synopsis of the package. Some (like deb) +# also call for a multi-line description starting on the second line. +description: Dictu Programming Language + +# Vendor. +# This will expand any env var you set in the field, e.g. vendor: ${VENDOR} +# This is only used by the rpm packager. +vendor: Dictu-lang + +# Package's homepage. +homepage: https://dictu-lang.com/ + +# License. +license: MIT + +# Changelog YAML file, see: https://github.com/goreleaser/chglog +#changelog: "changelog.yaml" + +# Disables globbing for files, config_files, etc. +disable_globbing: false + +# Packages it replaces. (overridable) +# This will expand any env var you set in the field, e.g. ${REPLACE_BLA} +# the env var approach can be used to account for differences in platforms +# replaces: +# - foobar +# - ${REPLACE_BLA} + +# Packages it provides. (overridable) +# This will expand any env var you set in the field, e.g. ${PROVIDES_BLA} +# the env var approach can be used to account for differences in platforms +# provides: +# - bar +# - ${PROVIDES_BLA} + +# Dependencies. (overridable) +# This will expand any env var you set in the field, e.g. ${DEPENDS_NGINX} +# the env var approach can be used to account for differences in platforms +# e.g. rhel needs nginx >= 1:1.18 and deb needs nginx (>= 1.18.0) +# depends: +# - git +# - ${DEPENDS_NGINX} + +# Recommended packages. (overridable) +# This will expand any env var you set in the field, e.g. ${RECOMMENDS_BLA} +# the env var approach can be used to account for differences in platforms +# recommends: +# - golang +# - ${RECOMMENDS_BLA} + +# Suggested packages. (overridable) +# This will expand any env var you set in the field, e.g. ${SUGGESTS_BLA} +# the env var approach can be used to account for differences in platforms +# suggests: +# - bzr + +# Packages it conflicts with. (overridable) +# This will expand any env var you set in the field, e.g. ${CONFLICTS_BLA} +# the env var approach can be used to account for differences in platforms +# conflicts: +# - mercurial +# - ${CONFLICTS_BLA} + +# Contents to add to the package +# This can be binaries or any other files. +contents: + # Basic file that applies to all packagers + - src: ./dictu + dst: /usr/local/bin/dictu + + # Simple config file + # - src: path/to/local/foo.conf + # dst: /etc/foo.conf + # type: config + + # Select files with a glob (doesn't work if you set disable_globbing: true). + # If `src` is a glob, then the `dst` will be treated like a directory - even + # if it doesn't end with `/`, and even if the glob only matches one file. + # - src: path/to/local/*.1.gz + # dst: /usr/share/man/man1/ + + # Simple symlink at /usr/bin/foo which points to /sbin/foo, which is + # the same behaviour as `ln -s /sbin/foo /usr/bin/foo`. + # + # This also means that both "src" and "dst" are paths inside the package (or + # rather paths in the file system where the package will be installed) and + # not in the build environment. This is different from regular files where + # "src" is a path in the build environment. However, this convention results + # in "dst" always being the file that is created when installing the + # package. + # - src: /actual/path/to/foo + # dst: /usr/bin/foo + # type: symlink + + # Corresponds to `%config(noreplace)` if the packager is rpm, otherwise it + # is just a config file + # - src: path/to/local/bar.conf + # dst: /etc/bar.conf + # type: config|noreplace + + # These files are not actually present in the package, but the file names + # are added to the package header. From the RPM directives documentation: + # + # "There are times when a file should be owned by the package but not + # installed - log files and state files are good examples of cases you might + # desire this to happen." + # + # "The way to achieve this is to use the %ghost directive. By adding this + # directive to the line containing a file, RPM will know about the ghosted + # file, but will not add it to the package." + # + # For non rpm packages ghost files are ignored at this time. + # - dst: /etc/casper.conf + # type: ghost + # - dst: /var/log/boo.log + # type: ghost + + # You can use the packager field to add files that are unique to a specific + # packager + # - src: path/to/rpm/file.conf + # dst: /etc/file.conf + # type: config|noreplace + # packager: rpm + # - src: path/to/deb/file.conf + # dst: /etc/file.conf + # type: config|noreplace + # packager: deb + # - src: path/to/apk/file.conf + # dst: /etc/file.conf + # type: config|noreplace + # packager: apk + + # Sometimes it is important to be able to set the mtime, mode, owner, or group for a file + # that differs from what is on the local build system at build time. The owner (if different + # than 'root') has to be always specified manually in 'file_info' as it will not be copied + # from the 'src' file. + # - src: ./dictu + # dst: /usr/share/foo + # file_info: + # # Make sure that the mode is specified in octal, e.g. 0644 instead of 644. + # mode: 0755 + # owner: notRoot + # group: notRoot + + # Using the type 'dir', empty directories can be created. When building RPMs, however, this + # type has another important purpose: Claiming ownership of that folder. This is important + # because when upgrading or removing an RPM package, only the directories for which it has + # claimed ownership are removed. However, you should not claim ownership of a folder that + # is created by the distro or a dependency of your package. + # A directory in the build environment can optionally be provided in the 'src' field in + # order copy mtime and mode from that directory without having to specify it manually. + # - dst: /some/dir + # type: dir + # file_info: + # mode: 0700 + +# Scripts to run at specific stages. (overridable) +# scripts: +# preinstall: ./scripts/preinstall.sh +# postinstall: ./scripts/postinstall.sh +# preremove: ./scripts/preremove.sh +# postremove: ./scripts/postremove.sh + +# All fields above marked as `overridable` can be overridden for a given +# package format in this section. +# overrides: +# # The depends override can for example be used to provide version +# # constraints for dependencies where different package formats use different +# # versions or for dependencies that are named differently. +# deb: +# depends: +# - baz (>= 1.2.3-0) +# - some-lib-dev +# # ... +# rpm: +# depends: +# - baz >= 1.2.3-0 +# - some-lib-devel +# # ... +# apk: +# # ... +# archlinux: +# depends: +# - baz +# - some-lib + +# Custom configuration applied only to the RPM packager. +rpm: + # rpm specific architecture name that overrides "arch" without performing any + # replacements. + # rpm_arch: ia64 + + # # RPM specific scripts. + # scripts: + # # The pretrans script runs before all RPM package transactions / stages. + # pretrans: ./scripts/pretrans.sh + # # The posttrans script runs after all RPM package transactions / stages. + # posttrans: ./scripts/posttrans.sh + + # The package group. This option is deprecated by most distros + # but required by old distros like CentOS 5 / EL 5 and earlier. + group: Unspecified + + # The package summary. This is, by default, the first line of the + # description, but can be explicitly provided here. + summary: Dictu Programming Language + + # The packager is used to identify the organization that actually packaged + # the software, as opposed to the author of the software. + # `maintainer` will be used as fallback if not specified. + # This will expand any env var you set in the field, e.g. packager: ${PACKAGER} + packager: GoReleaser + + # Compression algorithm (gzip (default), lzma or xz). + compression: lzma + + # The package is signed if a key_file is set + # signature: + # # PGP secret key (can also be ASCII-armored), the passphrase is taken + # # from the environment variable $NFPM_RPM_PASSPHRASE with a fallback + # # to $NFPM_PASSPHRASE. + # # This will expand any env var you set in the field, e.g. key_file: ${SIGNING_KEY_FILE} + # key_file: key.gpg + + # # PGP secret key id in hex format, if it is not set it will select the first subkey + # # that has the signing flag set. You may need to set this if you want to use the primary key as the signing key + # # or to support older versions of RPM < 4.13.0 which cannot validate a signed RPM that used a subkey to sign + # # This will expand any env var you set in the field, e.g. key_id: ${RPM_SIGNING_KEY_ID} + # key_id: bc8acdd415bd80b3 + +# Custom configuration applied only to the Deb packager. +deb: + # deb specific architecture name that overrides "arch" without performing any replacements. + # deb_arch: arm + + # # Custom deb special files. + # scripts: + # # Deb rules script. + # rules: foo.sh + + # # Deb templates file, when using debconf. + # templates: templates + + # # Deb config maintainer script for asking questions when using debconf. + # config: config + + # Custom deb triggers + # triggers: + # # register interest on a trigger activated by another package + # # (also available: interest_await, interest_noawait) + # interest: + # - some-trigger-name + + # # activate a trigger for another package + # # (also available: activate_await, activate_noawait) + # activate: + # - another-trigger-name + + # Packages which would break if this package would be installed. + # The installation of this package is blocked if `some-package` + # is already installed. + # breaks: + # - some-package + + # Compression algorithm (gzip (default), xz or none). + compression: xz + + # The package is signed if a key_file is set + # signature: + # # Signature method, either "dpkg-sig" or "debsign". + # # Defaults to "debsign" + # method: dpkg-sig + + # # PGP secret key (can also be ASCII-armored). The passphrase is taken + # # from the environment variable $NFPM_DEB_PASSPHRASE with a fallback + # # to $NFPM_PASSPHRASE. + # # This will expand any env var you set in the field, e.g. key_file: ${SIGNING_KEY_FILE} + # key_file: key.gpg + + # # The type describes the signers role, possible values are "origin", + # # "maint" and "archive". If unset, the type defaults to "origin". + # type: origin + + # # PGP secret key id in hex format, if it is not set it will select the first subkey + # # that has the signing flag set. You may need to set this if you want to use the primary key as the signing key + # # This will expand any env var you set in the field, e.g. key_id: ${DEB_SIGNING_KEY_ID} + # key_id: bc8acdd415bd80b3 + + # Additional fields for the control file. Empty fields are ignored. + fields: + Bugs: https://github.com/goreleaser/nfpm/issues + +apk: + # apk specific architecture name that overrides "arch" without performing any replacements. + # apk_arch: armhf + + # The package is signed if a key_file is set + # signature: + # # RSA private key in the PEM format. The passphrase is taken from + # # the environment variable $NFPM_APK_PASSPHRASE with a fallback + # # to $NFPM_PASSPHRASE. + # # This will expand any env var you set in the field, e.g. key_file: ${SIGNING_KEY_FILE} + # key_file: key.gpg + + # # The name of the signing key. When verifying a package, the signature + # # is matched to the public key store in /etc/apk/keys/.rsa.pub. + # # If unset, it defaults to the maintainer email address. + # key_name: origin + + # # APK does not use pgp keys, so the key_id field is ignored. + # key_id: ignored + +archlinux: + # This value is used to specify the name used to refer to a group + # of packages when building a split package. Defaults to name + # See: https://wiki.archlinux.org/title/PKGBUILD#pkgbase + #pkgbase: bar + + # The packager identifies the organization packaging the software + # rather than the developer. Defaults to "Unknown Packager". + packager: GoReleaser + + # Arch Linux specific scripts. + # scripts: + # # The preupgrade script runs before pacman upgrades the package + # preupgrade: ./scripts/preupgrade.sh + + # # The postupgrade script runs after pacman upgrades the package + # postupgrade: ./scripts/postupgrade.sh diff --git a/ops/pkgBuild.du b/ops/pkgBuild.du new file mode 100755 index 00000000..b070d3c2 --- /dev/null +++ b/ops/pkgBuild.du @@ -0,0 +1,44 @@ +#!/usr/local/bin/dictu + +import Env; +import Process; +import System; + + +const PKG_FORMATS = ["deb", "rpm", "apk"], + PKG_ARCHS = ["amd64"]; + +{ // main + print("Setting up environment..."); + + var semver = Env.get("SEMVER"); + if (not semver) { + const GIT_REV = Process.run(["git", "rev-parse", "--short", "HEAD"], true); + if (not GIT_REV.success()) { + print(GIT_REV.unwrapError()); + System.exit(1); + } + semver = GIT_REV.unwrap(); + semver = semver.strip(); + Env.set("SEMVER", semver); + } + + print("Building packages for version: {}".format(semver)); + + PKG_FORMATS.forEach(def(pkgFmt) => { + print("Creating {}...".format(pkgFmt)); + + Process.run(["nfpm", "pkg", "-f", "ops/nfpm_amd64.yaml", "--packager", pkgFmt, "--target", "build"], true).match( + def(result) => {}, + def(error) => { + print(error); + System.exit(1); + } + ); + }); + + print("Complete!"); + print("Packages can be found in ./build"); + + System.exit(0); +} From 361991c666dc1be77d69680010d9dbb0a5612c77 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 10 Jan 2023 19:14:25 -0700 Subject: [PATCH 073/109] update ref to scripts dir Signed-off-by: Brian Downs --- .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 566e6df3..5ad53d11 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,7 +17,7 @@ jobs: sudo apt-get install -y libcurl4-openssl-dev uuid-dev cmake -DCMAKE_BUILD_TYPE=Debug -DDISABLE_HTTP=1 -B ./build cmake --build ./build - ./dictu scripts/checkTests.du ci + ./dictu ops/checkTests.du ci test-ubuntu-cmake: name: Test on ${{ matrix.os }} runs-on: ${{ matrix.os }} From d6660515a57dd3069936b4e7fd03361767aaf158 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Thu, 12 Jan 2023 11:22:20 -0700 Subject: [PATCH 074/109] updates Signed-off-by: Brian Downs --- .github/workflows/main.yml | 160 +++++++-------- ops/nfpm.yaml | 122 +++++++++++ ops/nfpm_amd64.yaml | 408 ------------------------------------- ops/pkgBuild.du | 10 +- 4 files changed, 209 insertions(+), 491 deletions(-) create mode 100644 ops/nfpm.yaml delete mode 100644 ops/nfpm_amd64.yaml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5ad53d11..a407f78d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,86 +7,86 @@ on: - master jobs: - check-tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Make dictu and run checkTests - run: | - sudo apt-get update - sudo apt-get install -y libcurl4-openssl-dev uuid-dev - cmake -DCMAKE_BUILD_TYPE=Debug -DDISABLE_HTTP=1 -B ./build - cmake --build ./build - ./dictu ops/checkTests.du ci - test-ubuntu-cmake: - name: Test on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, ubuntu-20.04] + check-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Make dictu and run checkTests + run: | + sudo apt-get update + sudo apt-get install -y libcurl4-openssl-dev uuid-dev + cmake -DCMAKE_BUILD_TYPE=Debug -DDISABLE_HTTP=1 -B ./build + cmake --build ./build + ./dictu ops/checkTests.du ci + test-ubuntu-cmake: + name: Test on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, ubuntu-20.04] - steps: - - uses: actions/checkout@v2 - - name: Make dictu and run tests (No HTTP) - run: | - cmake -DCMAKE_BUILD_TYPE=Debug -DDISABLE_HTTP=1 -B ./build - cmake --build ./build - ./dictu tests/runTests.du ci | tee /dev/stderr | grep -q 'Total bytes lost: 0' - - name: Remove build directory - run: | - rm -rf build - - name: Make dictu and run tests (HTTP) - run: | - sudo apt-get update - sudo apt-get install -y libcurl4-openssl-dev uuid-dev - cmake -DCMAKE_BUILD_TYPE=Debug -B ./build - cmake --build ./build - ./dictu tests/runTests.du ci | tee /dev/stderr | grep -q 'Total bytes lost: 0' - test-mac-cmake: - name: Test on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [macOS-latest, macOS-11] + steps: + - uses: actions/checkout@v2 + - name: Make dictu and run tests (No HTTP) + run: | + cmake -DCMAKE_BUILD_TYPE=Debug -DDISABLE_HTTP=1 -B ./build + cmake --build ./build + ./dictu tests/runTests.du ci | tee /dev/stderr | grep -q 'Total bytes lost: 0' + - name: Remove build directory + run: | + rm -rf build + - name: Make dictu and run tests (HTTP) + run: | + sudo apt-get update + sudo apt-get install -y libcurl4-openssl-dev uuid-dev + cmake -DCMAKE_BUILD_TYPE=Debug -B ./build + cmake --build ./build + ./dictu tests/runTests.du ci | tee /dev/stderr | grep -q 'Total bytes lost: 0' + test-mac-cmake: + name: Test on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macOS-latest, macOS-11] - steps: - - uses: actions/checkout@v2 - - name: Make dictu and run tests (No HTTP) - run: | - cmake -DCMAKE_BUILD_TYPE=Debug -DDISABLE_HTTP=1 -B ./build - cmake --build ./build - ./dictu tests/runTests.du ci | tee /dev/stderr | grep -q 'Total bytes lost: 0' - - name: Remove build directory - run: | - rm -rf build - - name: Make dictu and run tests - run: | - cmake -DCMAKE_BUILD_TYPE=Debug -B ./build - cmake --build ./build - ./dictu tests/runTests.du ci | tee /dev/stderr | grep -q 'Total bytes lost: 0' - test-windows-cmake: - name: Test on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [windows-2022, windows-2019] + steps: + - uses: actions/checkout@v2 + - name: Make dictu and run tests (No HTTP) + run: | + cmake -DCMAKE_BUILD_TYPE=Debug -DDISABLE_HTTP=1 -B ./build + cmake --build ./build + ./dictu tests/runTests.du ci | tee /dev/stderr | grep -q 'Total bytes lost: 0' + - name: Remove build directory + run: | + rm -rf build + - name: Make dictu and run tests + run: | + cmake -DCMAKE_BUILD_TYPE=Debug -B ./build + cmake --build ./build + ./dictu tests/runTests.du ci | tee /dev/stderr | grep -q 'Total bytes lost: 0' + test-windows-cmake: + name: Test on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-2022, windows-2019] - 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=1 -DDISABLE_HTTP=1 -DDISABLE_LINENOISE=1 -B build - cmake --build build - Debug\dictu.exe tests/runTests.du ci - run-examples: - name: Test Examples - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Make dictu and run examples - run: | - sudo apt-get update - sudo apt-get install -y libcurl4-openssl-dev uuid-dev - cmake -DCMAKE_BUILD_TYPE=Debug -B ./build - cmake --build ./build - ./dictu examples/runExamples.du ci | tee /dev/stderr | grep -q 'Total bytes lost: 0' + 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=1 -DDISABLE_HTTP=1 -DDISABLE_LINENOISE=1 -B build + cmake --build build + Debug\dictu.exe tests/runTests.du ci + run-examples: + name: Test Examples + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Make dictu and run examples + run: | + sudo apt-get update + sudo apt-get install -y libcurl4-openssl-dev uuid-dev + cmake -DCMAKE_BUILD_TYPE=Debug -B ./build + cmake --build ./build + ./dictu examples/runExamples.du ci | tee /dev/stderr | grep -q 'Total bytes lost: 0' diff --git a/ops/nfpm.yaml b/ops/nfpm.yaml new file mode 100644 index 00000000..ea640771 --- /dev/null +++ b/ops/nfpm.yaml @@ -0,0 +1,122 @@ +name: dictu-lang + +# Architecture. (required) +# This will expand any env var you set in the field, e.g. version: ${GOARCH} +# The architecture is specified using Go nomenclature (GOARCH) and translated +# to the platform specific equivalent. In order to manually set the architecture +# to a platform specific value, use deb_arch, rpm_arch and apk_arch. +# Examples: `all`, `amd64`, `386`, `arm5`, `arm6`, `arm7`, `arm64`, `mips`, +# `mipsle`, `mips64le`, `ppc64le`, `s390` +arch: amd64 + +# Platform. +# This will expand any env var you set in the field, e.g. version: ${GOOS} +# This is only used by the rpm and deb packagers. +# Examples: `linux` (default), `darwin` +platform: linux + +# Version. (required) +# This will expand any env var you set in the field, e.g. version: ${SEMVER} +# Some package managers, like deb, require the version to start with a digit. +# Hence, you should not prefix the version with 'v'. +version: 0.26.0 + +# Version Schema allows you to specify how to parse the version string. +# Default is `semver` +# `semver` attempt to parse the version string as a valid semver version. +# The parser is lenient; it will strip a `v` prefix and will accept +# versions with fewer than 3 components, like `v1.2`. +# If parsing succeeds, then the version will be molded into a format +# compatible with the specific packager used. +# If parsing fails, then the version is used as-is. +# `none` skip trying to parse the version string and just use what is passed in +version_schema: semver + +# Version Epoch. +# A package with a higher version epoch will always be considered newer. +# See: https://www.debian.org/doc/debian-policy/ch-controlfields.html#epochs-should-be-used-sparingly +epoch: 2 + +# Version Release, aka revision. +# This will expand any env var you set in the field, e.g. release: ${VERSION_RELEASE} +# This is appended to the `version` after `prerelease`. This should be +# incremented if you release an updated package of the same upstream version, +# and it should reset to 1 when bumping the version. +release: 1 + +# Section. +# This is only used by the deb packager. +# See: https://www.debian.org/doc/debian-policy/ch-archive.html#sections +section: default + +# Priority. +# Defaults to `optional` on deb +# Defaults to empty on rpm and apk +# See: https://www.debian.org/doc/debian-policy/ch-archive.html#priorities +priority: extra + +# Maintainer. (required) +# This will expand any env var you set in the field, e.g. maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}> +# Defaults to empty on rpm and apk +# Leaving the 'maintainer' field unset will not be allowed in a future version +maintainer: Brian Downs + +# Description. +# Defaults to `no description given`. +# Most packagers call for a one-line synopsis of the package. Some (like deb) +# also call for a multi-line description starting on the second line. +description: Dictu Programming Language + +# Vendor. +# This will expand any env var you set in the field, e.g. vendor: ${VENDOR} +# This is only used by the rpm packager. +vendor: Dictu-lang + +# Package's homepage. +homepage: https://dictu-lang.com/ + +license: MIT + +# Changelog YAML file, see: https://github.com/goreleaser/chglog +#changelog: "changelog.yaml" + +# Disables globbing for files, config_files, etc. +disable_globbing: false + +contents: + - src: ./dictu + dst: /usr/local/bin/dictu + +rpm: + # The package group. This option is deprecated by most distros + # but required by old distros like CentOS 5 / EL 5 and earlier. + group: Unspecified + + # The package summary. This is, by default, the first line of the + # description, but can be explicitly provided here. + summary: Dictu Programming Language + + # The packager is used to identify the organization that actually packaged + # the software, as opposed to the author of the software. + # `maintainer` will be used as fallback if not specified. + # This will expand any env var you set in the field, e.g. packager: ${PACKAGER} + packager: GoReleaser + + # Compression algorithm (gzip (default), lzma or xz). + compression: lzma + +deb: + # Compression algorithm (gzip (default), xz or none). + compression: xz + + # Additional fields for the control file. Empty fields are ignored. + fields: + Bugs: https://github.com/goreleaser/nfpm/issues + +apk: + +archlinux: + # The packager identifies the organization packaging the software + # rather than the developer. Defaults to "Unknown Packager". + packager: GoReleaser + diff --git a/ops/nfpm_amd64.yaml b/ops/nfpm_amd64.yaml deleted file mode 100644 index 12716169..00000000 --- a/ops/nfpm_amd64.yaml +++ /dev/null @@ -1,408 +0,0 @@ -# Name. (required) -name: dictu-lang - -# Architecture. (required) -# This will expand any env var you set in the field, e.g. version: ${GOARCH} -# The architecture is specified using Go nomenclature (GOARCH) and translated -# to the platform specific equivalent. In order to manually set the architecture -# to a platform specific value, use deb_arch, rpm_arch and apk_arch. -# Examples: `all`, `amd64`, `386`, `arm5`, `arm6`, `arm7`, `arm64`, `mips`, -# `mipsle`, `mips64le`, `ppc64le`, `s390` -arch: amd64 - -# Platform. -# This will expand any env var you set in the field, e.g. version: ${GOOS} -# This is only used by the rpm and deb packagers. -# Examples: `linux` (default), `darwin` -platform: linux - -# Version. (required) -# This will expand any env var you set in the field, e.g. version: ${SEMVER} -# Some package managers, like deb, require the version to start with a digit. -# Hence, you should not prefix the version with 'v'. -version: 0.26.0 - -# Version Schema allows you to specify how to parse the version string. -# Default is `semver` -# `semver` attempt to parse the version string as a valid semver version. -# The parser is lenient; it will strip a `v` prefix and will accept -# versions with fewer than 3 components, like `v1.2`. -# If parsing succeeds, then the version will be molded into a format -# compatible with the specific packager used. -# If parsing fails, then the version is used as-is. -# `none` skip trying to parse the version string and just use what is passed in -version_schema: semver - -# Version Epoch. -# A package with a higher version epoch will always be considered newer. -# See: https://www.debian.org/doc/debian-policy/ch-controlfields.html#epochs-should-be-used-sparingly -epoch: 2 - -# Version Prerelease. -# Default is extracted from `version` if it is semver compatible. -# This is appended to the `version`, e.g. `1.2.3+beta1`. If the `version` is -# semver compatible, then this replaces the prerelease component of the semver. -#prerelease: beta1 - -# Version Metadata (previously deb.metadata). -# Default is extracted from `version` if it is semver compatible. -# Setting metadata might interfere with version comparisons depending on the -# packager. If the `version` is semver compatible, then this replaces the -# version metadata component of the semver. -#version_metadata: git - -# Version Release, aka revision. -# This will expand any env var you set in the field, e.g. release: ${VERSION_RELEASE} -# This is appended to the `version` after `prerelease`. This should be -# incremented if you release an updated package of the same upstream version, -# and it should reset to 1 when bumping the version. -release: 1 - -# Section. -# This is only used by the deb packager. -# See: https://www.debian.org/doc/debian-policy/ch-archive.html#sections -section: default - -# Priority. -# Defaults to `optional` on deb -# Defaults to empty on rpm and apk -# See: https://www.debian.org/doc/debian-policy/ch-archive.html#priorities -priority: extra - -# Maintainer. (required) -# This will expand any env var you set in the field, e.g. maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}> -# Defaults to empty on rpm and apk -# Leaving the 'maintainer' field unset will not be allowed in a future version -maintainer: Brian Downs - -# Description. -# Defaults to `no description given`. -# Most packagers call for a one-line synopsis of the package. Some (like deb) -# also call for a multi-line description starting on the second line. -description: Dictu Programming Language - -# Vendor. -# This will expand any env var you set in the field, e.g. vendor: ${VENDOR} -# This is only used by the rpm packager. -vendor: Dictu-lang - -# Package's homepage. -homepage: https://dictu-lang.com/ - -# License. -license: MIT - -# Changelog YAML file, see: https://github.com/goreleaser/chglog -#changelog: "changelog.yaml" - -# Disables globbing for files, config_files, etc. -disable_globbing: false - -# Packages it replaces. (overridable) -# This will expand any env var you set in the field, e.g. ${REPLACE_BLA} -# the env var approach can be used to account for differences in platforms -# replaces: -# - foobar -# - ${REPLACE_BLA} - -# Packages it provides. (overridable) -# This will expand any env var you set in the field, e.g. ${PROVIDES_BLA} -# the env var approach can be used to account for differences in platforms -# provides: -# - bar -# - ${PROVIDES_BLA} - -# Dependencies. (overridable) -# This will expand any env var you set in the field, e.g. ${DEPENDS_NGINX} -# the env var approach can be used to account for differences in platforms -# e.g. rhel needs nginx >= 1:1.18 and deb needs nginx (>= 1.18.0) -# depends: -# - git -# - ${DEPENDS_NGINX} - -# Recommended packages. (overridable) -# This will expand any env var you set in the field, e.g. ${RECOMMENDS_BLA} -# the env var approach can be used to account for differences in platforms -# recommends: -# - golang -# - ${RECOMMENDS_BLA} - -# Suggested packages. (overridable) -# This will expand any env var you set in the field, e.g. ${SUGGESTS_BLA} -# the env var approach can be used to account for differences in platforms -# suggests: -# - bzr - -# Packages it conflicts with. (overridable) -# This will expand any env var you set in the field, e.g. ${CONFLICTS_BLA} -# the env var approach can be used to account for differences in platforms -# conflicts: -# - mercurial -# - ${CONFLICTS_BLA} - -# Contents to add to the package -# This can be binaries or any other files. -contents: - # Basic file that applies to all packagers - - src: ./dictu - dst: /usr/local/bin/dictu - - # Simple config file - # - src: path/to/local/foo.conf - # dst: /etc/foo.conf - # type: config - - # Select files with a glob (doesn't work if you set disable_globbing: true). - # If `src` is a glob, then the `dst` will be treated like a directory - even - # if it doesn't end with `/`, and even if the glob only matches one file. - # - src: path/to/local/*.1.gz - # dst: /usr/share/man/man1/ - - # Simple symlink at /usr/bin/foo which points to /sbin/foo, which is - # the same behaviour as `ln -s /sbin/foo /usr/bin/foo`. - # - # This also means that both "src" and "dst" are paths inside the package (or - # rather paths in the file system where the package will be installed) and - # not in the build environment. This is different from regular files where - # "src" is a path in the build environment. However, this convention results - # in "dst" always being the file that is created when installing the - # package. - # - src: /actual/path/to/foo - # dst: /usr/bin/foo - # type: symlink - - # Corresponds to `%config(noreplace)` if the packager is rpm, otherwise it - # is just a config file - # - src: path/to/local/bar.conf - # dst: /etc/bar.conf - # type: config|noreplace - - # These files are not actually present in the package, but the file names - # are added to the package header. From the RPM directives documentation: - # - # "There are times when a file should be owned by the package but not - # installed - log files and state files are good examples of cases you might - # desire this to happen." - # - # "The way to achieve this is to use the %ghost directive. By adding this - # directive to the line containing a file, RPM will know about the ghosted - # file, but will not add it to the package." - # - # For non rpm packages ghost files are ignored at this time. - # - dst: /etc/casper.conf - # type: ghost - # - dst: /var/log/boo.log - # type: ghost - - # You can use the packager field to add files that are unique to a specific - # packager - # - src: path/to/rpm/file.conf - # dst: /etc/file.conf - # type: config|noreplace - # packager: rpm - # - src: path/to/deb/file.conf - # dst: /etc/file.conf - # type: config|noreplace - # packager: deb - # - src: path/to/apk/file.conf - # dst: /etc/file.conf - # type: config|noreplace - # packager: apk - - # Sometimes it is important to be able to set the mtime, mode, owner, or group for a file - # that differs from what is on the local build system at build time. The owner (if different - # than 'root') has to be always specified manually in 'file_info' as it will not be copied - # from the 'src' file. - # - src: ./dictu - # dst: /usr/share/foo - # file_info: - # # Make sure that the mode is specified in octal, e.g. 0644 instead of 644. - # mode: 0755 - # owner: notRoot - # group: notRoot - - # Using the type 'dir', empty directories can be created. When building RPMs, however, this - # type has another important purpose: Claiming ownership of that folder. This is important - # because when upgrading or removing an RPM package, only the directories for which it has - # claimed ownership are removed. However, you should not claim ownership of a folder that - # is created by the distro or a dependency of your package. - # A directory in the build environment can optionally be provided in the 'src' field in - # order copy mtime and mode from that directory without having to specify it manually. - # - dst: /some/dir - # type: dir - # file_info: - # mode: 0700 - -# Scripts to run at specific stages. (overridable) -# scripts: -# preinstall: ./scripts/preinstall.sh -# postinstall: ./scripts/postinstall.sh -# preremove: ./scripts/preremove.sh -# postremove: ./scripts/postremove.sh - -# All fields above marked as `overridable` can be overridden for a given -# package format in this section. -# overrides: -# # The depends override can for example be used to provide version -# # constraints for dependencies where different package formats use different -# # versions or for dependencies that are named differently. -# deb: -# depends: -# - baz (>= 1.2.3-0) -# - some-lib-dev -# # ... -# rpm: -# depends: -# - baz >= 1.2.3-0 -# - some-lib-devel -# # ... -# apk: -# # ... -# archlinux: -# depends: -# - baz -# - some-lib - -# Custom configuration applied only to the RPM packager. -rpm: - # rpm specific architecture name that overrides "arch" without performing any - # replacements. - # rpm_arch: ia64 - - # # RPM specific scripts. - # scripts: - # # The pretrans script runs before all RPM package transactions / stages. - # pretrans: ./scripts/pretrans.sh - # # The posttrans script runs after all RPM package transactions / stages. - # posttrans: ./scripts/posttrans.sh - - # The package group. This option is deprecated by most distros - # but required by old distros like CentOS 5 / EL 5 and earlier. - group: Unspecified - - # The package summary. This is, by default, the first line of the - # description, but can be explicitly provided here. - summary: Dictu Programming Language - - # The packager is used to identify the organization that actually packaged - # the software, as opposed to the author of the software. - # `maintainer` will be used as fallback if not specified. - # This will expand any env var you set in the field, e.g. packager: ${PACKAGER} - packager: GoReleaser - - # Compression algorithm (gzip (default), lzma or xz). - compression: lzma - - # The package is signed if a key_file is set - # signature: - # # PGP secret key (can also be ASCII-armored), the passphrase is taken - # # from the environment variable $NFPM_RPM_PASSPHRASE with a fallback - # # to $NFPM_PASSPHRASE. - # # This will expand any env var you set in the field, e.g. key_file: ${SIGNING_KEY_FILE} - # key_file: key.gpg - - # # PGP secret key id in hex format, if it is not set it will select the first subkey - # # that has the signing flag set. You may need to set this if you want to use the primary key as the signing key - # # or to support older versions of RPM < 4.13.0 which cannot validate a signed RPM that used a subkey to sign - # # This will expand any env var you set in the field, e.g. key_id: ${RPM_SIGNING_KEY_ID} - # key_id: bc8acdd415bd80b3 - -# Custom configuration applied only to the Deb packager. -deb: - # deb specific architecture name that overrides "arch" without performing any replacements. - # deb_arch: arm - - # # Custom deb special files. - # scripts: - # # Deb rules script. - # rules: foo.sh - - # # Deb templates file, when using debconf. - # templates: templates - - # # Deb config maintainer script for asking questions when using debconf. - # config: config - - # Custom deb triggers - # triggers: - # # register interest on a trigger activated by another package - # # (also available: interest_await, interest_noawait) - # interest: - # - some-trigger-name - - # # activate a trigger for another package - # # (also available: activate_await, activate_noawait) - # activate: - # - another-trigger-name - - # Packages which would break if this package would be installed. - # The installation of this package is blocked if `some-package` - # is already installed. - # breaks: - # - some-package - - # Compression algorithm (gzip (default), xz or none). - compression: xz - - # The package is signed if a key_file is set - # signature: - # # Signature method, either "dpkg-sig" or "debsign". - # # Defaults to "debsign" - # method: dpkg-sig - - # # PGP secret key (can also be ASCII-armored). The passphrase is taken - # # from the environment variable $NFPM_DEB_PASSPHRASE with a fallback - # # to $NFPM_PASSPHRASE. - # # This will expand any env var you set in the field, e.g. key_file: ${SIGNING_KEY_FILE} - # key_file: key.gpg - - # # The type describes the signers role, possible values are "origin", - # # "maint" and "archive". If unset, the type defaults to "origin". - # type: origin - - # # PGP secret key id in hex format, if it is not set it will select the first subkey - # # that has the signing flag set. You may need to set this if you want to use the primary key as the signing key - # # This will expand any env var you set in the field, e.g. key_id: ${DEB_SIGNING_KEY_ID} - # key_id: bc8acdd415bd80b3 - - # Additional fields for the control file. Empty fields are ignored. - fields: - Bugs: https://github.com/goreleaser/nfpm/issues - -apk: - # apk specific architecture name that overrides "arch" without performing any replacements. - # apk_arch: armhf - - # The package is signed if a key_file is set - # signature: - # # RSA private key in the PEM format. The passphrase is taken from - # # the environment variable $NFPM_APK_PASSPHRASE with a fallback - # # to $NFPM_PASSPHRASE. - # # This will expand any env var you set in the field, e.g. key_file: ${SIGNING_KEY_FILE} - # key_file: key.gpg - - # # The name of the signing key. When verifying a package, the signature - # # is matched to the public key store in /etc/apk/keys/.rsa.pub. - # # If unset, it defaults to the maintainer email address. - # key_name: origin - - # # APK does not use pgp keys, so the key_id field is ignored. - # key_id: ignored - -archlinux: - # This value is used to specify the name used to refer to a group - # of packages when building a split package. Defaults to name - # See: https://wiki.archlinux.org/title/PKGBUILD#pkgbase - #pkgbase: bar - - # The packager identifies the organization packaging the software - # rather than the developer. Defaults to "Unknown Packager". - packager: GoReleaser - - # Arch Linux specific scripts. - # scripts: - # # The preupgrade script runs before pacman upgrades the package - # preupgrade: ./scripts/preupgrade.sh - - # # The postupgrade script runs after pacman upgrades the package - # postupgrade: ./scripts/postupgrade.sh diff --git a/ops/pkgBuild.du b/ops/pkgBuild.du index b070d3c2..f50a02d0 100755 --- a/ops/pkgBuild.du +++ b/ops/pkgBuild.du @@ -1,5 +1,10 @@ #!/usr/local/bin/dictu +/* + * This script is used in the CI process to create RPMs, debs, and apks when + * new releases/tags are created. + */ + import Env; import Process; import System; @@ -18,8 +23,7 @@ const PKG_FORMATS = ["deb", "rpm", "apk"], print(GIT_REV.unwrapError()); System.exit(1); } - semver = GIT_REV.unwrap(); - semver = semver.strip(); + semver = GIT_REV.unwrap().strip(); Env.set("SEMVER", semver); } @@ -29,7 +33,7 @@ const PKG_FORMATS = ["deb", "rpm", "apk"], print("Creating {}...".format(pkgFmt)); Process.run(["nfpm", "pkg", "-f", "ops/nfpm_amd64.yaml", "--packager", pkgFmt, "--target", "build"], true).match( - def(result) => {}, + def(_) => _, def(error) => { print(error); System.exit(1); From b057dffd86a90dd25a84b32de3c27e5e53405598 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Thu, 12 Jan 2023 11:26:47 -0700 Subject: [PATCH 075/109] missed pr review comments Signed-off-by: Brian Downs --- docs/docs/collections/lists.md | 2 +- docs/docs/error-handling.md | 6 +++--- docs/docs/files.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/docs/collections/lists.md b/docs/docs/collections/lists.md index 39395105..0fe222d2 100644 --- a/docs/docs/collections/lists.md +++ b/docs/docs/collections/lists.md @@ -300,7 +300,7 @@ print([1, 2, 3, 4, 5, 6].find(def (item) => item % 2 == 0, 2)); // 4 print([1, 2, 3, 4, 5, 6].find(def (item) => item % 2 == 0, 2, 3)); // nil ``` -### list.findIndex(Func, Number: start -> Optional, Number: end -> Optional) +### list.findIndex(Func, Number: start -> Optional, Number: end -> Optional) -> Number To find a single item within a list we use `.findIndex()`. Find will search through each item in the list and as soon as the callback returns a truthy value, the index at which the item that satisfied the callback is returned, if none of the items satisfy the callback diff --git a/docs/docs/error-handling.md b/docs/docs/error-handling.md index a821cd40..6a23fcd8 100644 --- a/docs/docs/error-handling.md +++ b/docs/docs/error-handling.md @@ -80,7 +80,7 @@ Check if a Result type is in a SUCCESS state, returns a boolean. "number".toNumber().success(); // false ``` -### .match(Func: success, Func: error) +### .match(Func: success, Func: error) -> value `.match` takes two callbacks that are ran depending upon the status of the result type. The callbacks passed to match must both have one parameter each, on success the unwrapped value is passed as the first argument and on @@ -96,7 +96,7 @@ var number = "10".toNumber().match( } ); -print(Number); // 10 +print(number); // 10 var number = "number".toNumber().match( def (result) => result, @@ -109,7 +109,7 @@ var number = "number".toNumber().match( print(number); ``` -### .matchWrap(Func: success, Func: error) +### .matchWrap(Func: success, Func: error) -> Result `.matchWrap` is exactly the same as `.wrap` however, the value returned from either callback function is implicitly wrapped back up into a Result object. This allows us to easily deal diff --git a/docs/docs/files.md b/docs/docs/files.md index 62d30eee..d9a86935 100644 --- a/docs/docs/files.md +++ b/docs/docs/files.md @@ -77,7 +77,7 @@ with("test.txt", "r") { Another method which may come in useful when reading files is `seek()`. `seek()` allows you to move the file cursor so you can re-read a file, for example, without closing the file and reopening. -### Seek(Number, Number: from -> Optional) +### file.Seek(Number, Number: from -> Optional) Both arguments passed to seek need to be of numeric value, however the `from` argument is optional. The first argument (offset) is the amount of characters you wish to move from the cursor position (negative offset for seeking backwards). The second argument (from) is for controlling where the cursor will be within the file, options are 0, 1 or 2. From f237ebe1885c6c58f4c4e415b69c4102b9f01af4 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Sun, 22 Jan 2023 21:47:53 -0700 Subject: [PATCH 076/109] add list splice Signed-off-by: Brian Downs --- docs/docs/collections/lists.md | 13 ++++++++++++- src/vm/datatypes/lists/list-source.h | 8 ++++++++ src/vm/datatypes/lists/list.du | 10 +++++++++- tests/lists/import.du | 29 ++++++++++++++-------------- tests/lists/splice.du | 20 +++++++++++++++++++ 5 files changed, 64 insertions(+), 16 deletions(-) create mode 100644 tests/lists/splice.du diff --git a/docs/docs/collections/lists.md b/docs/docs/collections/lists.md index 0fe222d2..fa0f4ff3 100644 --- a/docs/docs/collections/lists.md +++ b/docs/docs/collections/lists.md @@ -312,4 +312,15 @@ Note: The first item to satisfy the callback is returned. print([1, 2, 3].findIndex(def (item) => item == 2)); // 1 print([1, 2, 3, 4, 5, 6].findIndex(def (item) => item % 2 == 0, 2)); // 3 print([1, 2, 3, 4, 5, 6].findIndex(def (item) => item % 2 == 0, 2, 3)); // nil -``` \ No newline at end of file +``` + +### list.Splice(Number, Number, List) -> List + +Splice provides a means of changing the contents of a list by removing or replacing existing elements and/or adding new elements in place. + +```cs +[1, 2, 3, 4, 5].splice(1, 0, [100]); // [1, 100, 2, 3, 4, 5] +[1, 2, 3, 4, 5].splice(1, 1, [100]); // [1, 100, 3, 4, 5] +[1, 2, 3, 4, 5].splice(1, 2, [100]); // [1, 100, 4, 5] +[1, 2, 3, 4, 5].splice(3, 0, [100, 101, 102]); // [1, 2, 3, 100, 101, 102, 4, 5] +``` diff --git a/src/vm/datatypes/lists/list-source.h b/src/vm/datatypes/lists/list-source.h index 8e1254f1..d58a0c32 100644 --- a/src/vm/datatypes/lists/list-source.h +++ b/src/vm/datatypes/lists/list-source.h @@ -58,4 +58,12 @@ " }\n" \ " }\n" \ "}\n" \ +"\n" \ +"def splice(list, index, count=0, items=[]) {\n" \ +" if (count == 0) {\n" \ +" return list[:index]+items+list[index:]; \n" \ +" }\n" \ +"\n" \ +" return list[:index]+items+list[index+count:];\n" \ +"}\n" \ diff --git a/src/vm/datatypes/lists/list.du b/src/vm/datatypes/lists/list.du index 88c9cabe..561d436f 100644 --- a/src/vm/datatypes/lists/list.du +++ b/src/vm/datatypes/lists/list.du @@ -57,4 +57,12 @@ def findIndex(list, func, start=0, end=list.len()) { return i; } } -} \ No newline at end of file +} + +def splice(list, index, count, items) { + if (count == 0) { + return list[:index]+items+list[index:]; + } + + return list[:index]+items+list[index+count:]; +} diff --git a/tests/lists/import.du b/tests/lists/import.du index 46aec85a..28ca9c1b 100644 --- a/tests/lists/import.du +++ b/tests/lists/import.du @@ -4,25 +4,26 @@ * General import file for all the list tests */ -import "subscript.du"; import "contains.du"; -import "push.du"; +import "copy.du"; import "extend.du"; +import "filter.du"; +import "find.du"; +import "findIndex.du"; +import "forEach.du"; import "insert.du"; -import "pop.du"; -import "remove.du"; -import "copy.du"; -import "slicing.du"; import "join.du"; import "len.du"; -import "toString.du"; -import "toBool.du"; -import "plusOperator.du"; -import "sort.du"; import "map.du"; -import "filter.du"; +import "plusOperator.du"; +import "pop.du"; +import "push.du"; import "reduce.du"; -import "forEach.du"; -import "find.du"; -import "findIndex.du"; +import "remove.du"; import "reverse.du"; +import "slicing.du"; +import "sort.du"; +import "splicing.du"; +import "subscript.du"; +import "toBool.du"; +import "toString.du"; diff --git a/tests/lists/splice.du b/tests/lists/splice.du new file mode 100644 index 00000000..4dcf35bf --- /dev/null +++ b/tests/lists/splice.du @@ -0,0 +1,20 @@ +/** + * splice.du + * + * Testing list splice + */ +from UnitTest import UnitTest; + +class TestListSplice < UnitTest { + testListSplice() { + const x = [1, 2, 3, 4, 5]; + + this.assertEquals(x.splice(1, 0, [100]), [1, 100, 2, 3, 4, 5]); + this.assertEquals(x.splice(1, 1, [100]), [1, 100, 3, 4, 5]); + this.assertEquals(x.splice(1, 2, [100]), [1, 100, 4, 5]); + + this.assertEquals(x.splice(3, 0, [100, 101, 102]), [1, 2, 3, 100, 101, 102, 4, 5]); + } +} + +TestListSplice().run(); From 27da6b8210995d41129f1e2c79bd067bfe08c1c9 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Sun, 22 Jan 2023 21:55:57 -0700 Subject: [PATCH 077/109] fix import name Signed-off-by: Brian Downs --- tests/lists/import.du | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lists/import.du b/tests/lists/import.du index 28ca9c1b..7bdd21e7 100644 --- a/tests/lists/import.du +++ b/tests/lists/import.du @@ -23,7 +23,7 @@ import "remove.du"; import "reverse.du"; import "slicing.du"; import "sort.du"; -import "splicing.du"; +import "splice.du"; import "subscript.du"; import "toBool.du"; import "toString.du"; From 5333f3dddc4aa5fa6e68f0d7941902d3d283288b Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Mon, 23 Jan 2023 09:02:19 -0700 Subject: [PATCH 078/109] pr remediations Signed-off-by: Brian Downs --- docs/docs/collections/lists.md | 4 ++-- src/vm/datatypes/lists/list-source.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/collections/lists.md b/docs/docs/collections/lists.md index fa0f4ff3..d2b29138 100644 --- a/docs/docs/collections/lists.md +++ b/docs/docs/collections/lists.md @@ -314,9 +314,9 @@ print([1, 2, 3, 4, 5, 6].findIndex(def (item) => item % 2 == 0, 2)); // 3 print([1, 2, 3, 4, 5, 6].findIndex(def (item) => item % 2 == 0, 2, 3)); // nil ``` -### list.Splice(Number, Number, List) -> List +### list.splice(Number, Number, List) -> List -Splice provides a means of changing the contents of a list by removing or replacing existing elements and/or adding new elements in place. +Splice provides a means of changing the contents of a list by removing or replacing existing elements and/or adding new elements and returns a new list reflecting the changes. ```cs [1, 2, 3, 4, 5].splice(1, 0, [100]); // [1, 100, 2, 3, 4, 5] diff --git a/src/vm/datatypes/lists/list-source.h b/src/vm/datatypes/lists/list-source.h index d58a0c32..8b1b3c93 100644 --- a/src/vm/datatypes/lists/list-source.h +++ b/src/vm/datatypes/lists/list-source.h @@ -59,7 +59,7 @@ " }\n" \ "}\n" \ "\n" \ -"def splice(list, index, count=0, items=[]) {\n" \ +"def splice(list, index, count, items) {\n" \ " if (count == 0) {\n" \ " return list[:index]+items+list[index:]; \n" \ " }\n" \ From ea389b6b6bb3b925bce226a93bba6cbb3017c35b Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Wed, 25 Jan 2023 00:13:14 +0000 Subject: [PATCH 079/109] Resolve issue with annotations --- src/vm/compiler.c | 5 ++--- src/vm/memory.c | 1 + src/vm/vm.c | 1 + src/vm/vm.h | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vm/compiler.c b/src/vm/compiler.c index a46b7bf9..ee2d914d 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -1613,14 +1613,13 @@ static void method(Compiler *compiler, bool private, Token *identifier, bool *ha continue; } - if (strcmp(AS_STRING(entry->key)->chars, "__annotatedMethodName") == 0) { + if (OBJ_VAL(vm->annotationString) == entry->key) { Value existingDict; dictGet(compiler->methodAnnotations, entry->key, &existingDict); ObjString *methodKey = copyString(vm, compiler->parser->previous.start, compiler->parser->previous.length); push(vm, OBJ_VAL(methodKey)); dictSet(vm, compiler->methodAnnotations, OBJ_VAL(methodKey), existingDict); - pop(vm); - dictDelete(vm, compiler->methodAnnotations, entry->key); + dictDelete(vm, compiler->methodAnnotations, OBJ_VAL(vm->annotationString)); break; } diff --git a/src/vm/memory.c b/src/vm/memory.c index f1ba1b07..b775cb8e 100644 --- a/src/vm/memory.c +++ b/src/vm/memory.c @@ -345,6 +345,7 @@ void collectGarbage(DictuVM *vm) { grayTable(vm, &vm->resultMethods); grayCompilerRoots(vm); grayObject(vm, (Obj *) vm->initString); + grayObject(vm, (Obj *) vm->annotationString); grayObject(vm, (Obj *) vm->replVar); // Traverse the references. diff --git a/src/vm/vm.c b/src/vm/vm.c index 71686161..798a1a6f 100644 --- a/src/vm/vm.c +++ b/src/vm/vm.c @@ -123,6 +123,7 @@ DictuVM *dictuInitVM(bool repl, int argc, char **argv) { vm->frames = ALLOCATE(vm, CallFrame, vm->frameCapacity); vm->initString = copyString(vm, "init", 4); + vm->annotationString = copyString(vm, "__annotatedMethodName", 21); // Native functions defineAllNatives(vm); diff --git a/src/vm/vm.h b/src/vm/vm.h index 1fb7aa19..cf6a67a9 100644 --- a/src/vm/vm.h +++ b/src/vm/vm.h @@ -40,6 +40,7 @@ struct _vm { Table instanceMethods; Table resultMethods; ObjString *initString; + ObjString *annotationString; ObjString *replVar; ObjUpvalue *openUpvalues; size_t bytesAllocated; From 00983b57bc2bf6e01d184117685c4c1070c9c8e5 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Wed, 25 Jan 2023 00:18:39 +0000 Subject: [PATCH 080/109] Use cached value when generating annotations --- src/vm/compiler.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/vm/compiler.c b/src/vm/compiler.c index ee2d914d..3f61bb06 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -1687,10 +1687,7 @@ static void parseMethodAnnotations(Compiler *compiler) { ObjDict *annotationDict = newDict(vm); push(vm, OBJ_VAL(annotationDict)); - ObjString *methodName = copyString(vm, "__annotatedMethodName", 21); - push(vm, OBJ_VAL(methodName)); - dictSet(vm, compiler->methodAnnotations, OBJ_VAL(methodName), OBJ_VAL(annotationDict)); - pop(vm); + dictSet(vm, compiler->methodAnnotations, OBJ_VAL(vm->annotationString), OBJ_VAL(annotationDict)); pop(vm); do { From 3b12d7584f2e09d97bcdd4c08e6cde9efba704bc Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Sun, 29 Jan 2023 19:00:59 -0700 Subject: [PATCH 081/109] new release Signed-off-by: Brian Downs --- .github/workflows/release.yml | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..8c4a177b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,40 @@ +on: + push: + tags: + - 'v*' + +name: Upload Release Asset + +jobs: + build: + name: Upload Release Asset + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Build project + run: | + sudo apt-get update + sudo apt-get install -y libcurl4-openssl-dev uuid-dev + cmake -DCMAKE_BUILD_TYPE=Debug -DDISABLE_HTTP=1 -B ./build + cmake --build ./build + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + draft: false + prerelease: false + - name: Upload Release Asset + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps + asset_path: ./my-artifact.zip + asset_name: my-artifact.zip + asset_content_type: application/zip From 30f217aa6c5f130a8c18f59c81f18b316feeb202 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Sun, 29 Jan 2023 19:04:43 -0700 Subject: [PATCH 082/109] new release Signed-off-by: Brian Downs --- .github/workflows/main.yml | 10 +++++----- .github/workflows/release.yml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a407f78d..0cf565a4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,7 +10,7 @@ jobs: check-tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Make dictu and run checkTests run: | sudo apt-get update @@ -26,7 +26,7 @@ jobs: os: [ubuntu-latest, ubuntu-20.04] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Make dictu and run tests (No HTTP) run: | cmake -DCMAKE_BUILD_TYPE=Debug -DDISABLE_HTTP=1 -B ./build @@ -50,7 +50,7 @@ jobs: os: [macOS-latest, macOS-11] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Make dictu and run tests (No HTTP) run: | cmake -DCMAKE_BUILD_TYPE=Debug -DDISABLE_HTTP=1 -B ./build @@ -72,7 +72,7 @@ jobs: os: [windows-2022, windows-2019] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Make dictu and run tests (No HTTP No Linenoise) run: | cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_SYSTEM_VERSION="10.0.18362.0" -DCICD=1 -DDISABLE_HTTP=1 -DDISABLE_LINENOISE=1 -B build @@ -82,7 +82,7 @@ jobs: name: Test Examples runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Make dictu and run examples run: | sudo apt-get update diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8c4a177b..3aa421bd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Build project run: | sudo apt-get update From c45b21593dac031551721a30c752c753ec53bf52 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Sun, 29 Jan 2023 19:09:02 -0700 Subject: [PATCH 083/109] update to new preferred format Signed-off-by: Brian Downs --- ops/pkgBuild.du | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ops/pkgBuild.du b/ops/pkgBuild.du index f50a02d0..6c81e737 100755 --- a/ops/pkgBuild.du +++ b/ops/pkgBuild.du @@ -1,4 +1,4 @@ -#!/usr/local/bin/dictu +#!/usr/bin/env dictu /* * This script is used in the CI process to create RPMs, debs, and apks when @@ -18,12 +18,12 @@ const PKG_FORMATS = ["deb", "rpm", "apk"], var semver = Env.get("SEMVER"); if (not semver) { - const GIT_REV = Process.run(["git", "rev-parse", "--short", "HEAD"], true); - if (not GIT_REV.success()) { - print(GIT_REV.unwrapError()); + const gitRev = Process.run(["git", "rev-parse", "--short", "HEAD"], true); + if (not gitRev.success()) { + print(gitRev.unwrapError()); System.exit(1); } - semver = GIT_REV.unwrap().strip(); + semver = gitRev.unwrap().strip(); Env.set("SEMVER", semver); } From c21b0b8799f12f0e8e7c60ab7383d3f5937ef9e3 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Thu, 2 Feb 2023 16:01:53 -0700 Subject: [PATCH 084/109] update names and artifacts Signed-off-by: Brian Downs --- .github/workflows/release.yml | 57 ++++++++++++++++------------------- ops/nfpm.yaml | 2 +- ops/pkgBuild.du | 4 +-- 3 files changed, 28 insertions(+), 35 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3aa421bd..d2f35852 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,40 +1,35 @@ on: push: tags: - - 'v*' + - '*' name: Upload Release Asset jobs: - build: - name: Upload Release Asset + release: + permissions: + contents: write runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v3 - - name: Build project - run: | - sudo apt-get update - sudo apt-get install -y libcurl4-openssl-dev uuid-dev - cmake -DCMAKE_BUILD_TYPE=Debug -DDISABLE_HTTP=1 -B ./build - cmake --build ./build - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.TOKEN }} - with: - tag_name: ${{ github.ref }} - release_name: Release ${{ github.ref }} - draft: false - prerelease: false - - name: Upload Release Asset - id: upload-release-asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps - asset_path: ./my-artifact.zip - asset_name: my-artifact.zip - asset_content_type: application/zip + - name: Checkout + uses: actions/checkout@v3 + - name: Build + run: | + sudo apt-get update + sudo apt-get install -y libcurl4-openssl-dev uuid-dev + cmake -DCMAKE_BUILD_TYPE=Release -B ./build + cmake --build ./build + - name: Package + run: | + ./dictu ops/pkgBuild.du + mv dictu dictu-linux-x86_64 + - name: Release + uses: SierraSoftworks/gh-releases@v1.0.7 + with: + token: ${{ secrets.TOKEN }} + overwrite: 'true' + files: | + dictu-linux-x86_64 + dictu-0.26.0-1_amd64.deb + dictu-0.26.0-1_x86_64.apk + dictu-0.26.0-1.x86_64.rpm diff --git a/ops/nfpm.yaml b/ops/nfpm.yaml index ea640771..d8a09500 100644 --- a/ops/nfpm.yaml +++ b/ops/nfpm.yaml @@ -1,4 +1,4 @@ -name: dictu-lang +name: dictu # Architecture. (required) # This will expand any env var you set in the field, e.g. version: ${GOARCH} diff --git a/ops/pkgBuild.du b/ops/pkgBuild.du index 6c81e737..4cf8d2c3 100755 --- a/ops/pkgBuild.du +++ b/ops/pkgBuild.du @@ -1,5 +1,3 @@ -#!/usr/bin/env dictu - /* * This script is used in the CI process to create RPMs, debs, and apks when * new releases/tags are created. @@ -32,7 +30,7 @@ const PKG_FORMATS = ["deb", "rpm", "apk"], PKG_FORMATS.forEach(def(pkgFmt) => { print("Creating {}...".format(pkgFmt)); - Process.run(["nfpm", "pkg", "-f", "ops/nfpm_amd64.yaml", "--packager", pkgFmt, "--target", "build"], true).match( + Process.run(["nfpm", "pkg", "-f", "ops/nfpm.yaml", "--packager", pkgFmt, "--target", "build"], true).match( def(_) => _, def(error) => { print(error); From 2a33cfbe96509024458afe134c6a29d30fe83400 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Thu, 2 Feb 2023 19:42:16 -0700 Subject: [PATCH 085/109] extend socket module Signed-off-by: Brian Downs --- .../docs/standard-lib/{sockets.md => socket.md} | 17 +++++++++++------ src/optionals/socket.c | 5 +++++ 2 files changed, 16 insertions(+), 6 deletions(-) rename docs/docs/standard-lib/{sockets.md => socket.md} (81%) diff --git a/docs/docs/standard-lib/sockets.md b/docs/docs/standard-lib/socket.md similarity index 81% rename from docs/docs/standard-lib/sockets.md rename to docs/docs/standard-lib/socket.md index f2613a98..02b56f8e 100644 --- a/docs/docs/standard-lib/sockets.md +++ b/docs/docs/standard-lib/socket.md @@ -26,12 +26,17 @@ import Socket; ### Constants -| Constant | Description | -| ------------------- | ------------------------------- | -| Socket.AF_INET | AF_INET protocol family | -| Socket.SOCK_STREAM | SOCK_STREAM protocol type | -| Socket.SOL_SOCKET | SOL_SOCKET option level | -| Socket.SO_REUSEADDR | SO_REUSEADDR allow socket reuse | +| Constant | Description | +| ---------------------- | --------------------------------------- | +| Socket.AF_INET | AF_INET protocol family | +| Socket.SOCK_STREAM | SOCK_STREAM protocol type | +| Socket.SOCK_DGRAM | SOCK_DGRAM protocol type | +| Socket.SOCK_RAW | SOCK_RAW protocol type | +| Socket.SOCK_MAXADDRLEN | Max address length | +| Socket.SOCK_RDM | Reliable datagram layer | +| Socket.SOCK_SEQPACKET | Sequenced, reliable, two-way connection | +| Socket.SOL_SOCKET | SOL_SOCKET option level | +| Socket.SO_REUSEADDR | SO_REUSEADDR allow socket reuse | ### Socket.create(Number: family, Number: type) -> Result diff --git a/src/optionals/socket.c b/src/optionals/socket.c index 9e6bef19..7229916c 100644 --- a/src/optionals/socket.c +++ b/src/optionals/socket.c @@ -354,6 +354,11 @@ Value createSocketModule(DictuVM *vm) { */ defineNativeProperty(vm, &module->values, "AF_INET", NUMBER_VAL(AF_INET)); defineNativeProperty(vm, &module->values, "SOCK_STREAM", NUMBER_VAL(SOCK_STREAM)); + defineNativeProperty(vm, &module->values, "SOCK_DGRAM", NUMBER_VAL(SOCK_DGRAM)); + defineNativeProperty(vm, &module->values, "SOCK_RAW", NUMBER_VAL(SOCK_RAW)); + defineNativeProperty(vm, &module->values, "SOCK_MAXADDRLEN", NUMBER_VAL(SOCK_MAXADDRLEN)); + defineNativeProperty(vm, &module->values, "SOCK_RDM", NUMBER_VAL(SOCK_RDM)); + defineNativeProperty(vm, &module->values, "SOCK_SEQPACKET", NUMBER_VAL(SOCK_SEQPACKET)); defineNativeProperty(vm, &module->values, "SOL_SOCKET", NUMBER_VAL(SOL_SOCKET)); defineNativeProperty(vm, &module->values, "SO_REUSEADDR", NUMBER_VAL(SO_REUSEADDR)); From e24a49869f484bbd577a33b86dabf865f51e0133 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Thu, 2 Feb 2023 19:48:45 -0700 Subject: [PATCH 086/109] remove maxaddrlen Signed-off-by: Brian Downs --- 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 7229916c..ff49f294 100644 --- a/src/optionals/socket.c +++ b/src/optionals/socket.c @@ -356,7 +356,7 @@ Value createSocketModule(DictuVM *vm) { defineNativeProperty(vm, &module->values, "SOCK_STREAM", NUMBER_VAL(SOCK_STREAM)); defineNativeProperty(vm, &module->values, "SOCK_DGRAM", NUMBER_VAL(SOCK_DGRAM)); defineNativeProperty(vm, &module->values, "SOCK_RAW", NUMBER_VAL(SOCK_RAW)); - defineNativeProperty(vm, &module->values, "SOCK_MAXADDRLEN", NUMBER_VAL(SOCK_MAXADDRLEN)); + //defineNativeProperty(vm, &module->values, "SOCK_MAXADDRLEN", NUMBER_VAL(SOCK_MAXADDRLEN)); defineNativeProperty(vm, &module->values, "SOCK_RDM", NUMBER_VAL(SOCK_RDM)); defineNativeProperty(vm, &module->values, "SOCK_SEQPACKET", NUMBER_VAL(SOCK_SEQPACKET)); defineNativeProperty(vm, &module->values, "SOL_SOCKET", NUMBER_VAL(SOL_SOCKET)); From f3fae58cd6cc3d1c0c5f652d238d37c078e10167 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Thu, 2 Feb 2023 19:53:22 -0700 Subject: [PATCH 087/109] remove rdm Signed-off-by: Brian Downs --- src/optionals/socket.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/optionals/socket.c b/src/optionals/socket.c index ff49f294..a227c66d 100644 --- a/src/optionals/socket.c +++ b/src/optionals/socket.c @@ -356,8 +356,6 @@ Value createSocketModule(DictuVM *vm) { defineNativeProperty(vm, &module->values, "SOCK_STREAM", NUMBER_VAL(SOCK_STREAM)); defineNativeProperty(vm, &module->values, "SOCK_DGRAM", NUMBER_VAL(SOCK_DGRAM)); defineNativeProperty(vm, &module->values, "SOCK_RAW", NUMBER_VAL(SOCK_RAW)); - //defineNativeProperty(vm, &module->values, "SOCK_MAXADDRLEN", NUMBER_VAL(SOCK_MAXADDRLEN)); - defineNativeProperty(vm, &module->values, "SOCK_RDM", NUMBER_VAL(SOCK_RDM)); defineNativeProperty(vm, &module->values, "SOCK_SEQPACKET", NUMBER_VAL(SOCK_SEQPACKET)); defineNativeProperty(vm, &module->values, "SOL_SOCKET", NUMBER_VAL(SOL_SOCKET)); defineNativeProperty(vm, &module->values, "SO_REUSEADDR", NUMBER_VAL(SO_REUSEADDR)); From 31a21ef53d2bf96191d68f252b23fc4022b4a2d5 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Thu, 2 Feb 2023 20:02:37 -0700 Subject: [PATCH 088/109] update docs Signed-off-by: Brian Downs --- docs/docs/standard-lib/socket.md | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/docs/docs/standard-lib/socket.md b/docs/docs/standard-lib/socket.md index 02b56f8e..d2fbb875 100644 --- a/docs/docs/standard-lib/socket.md +++ b/docs/docs/standard-lib/socket.md @@ -26,17 +26,15 @@ import Socket; ### Constants -| Constant | Description | -| ---------------------- | --------------------------------------- | -| Socket.AF_INET | AF_INET protocol family | -| Socket.SOCK_STREAM | SOCK_STREAM protocol type | -| Socket.SOCK_DGRAM | SOCK_DGRAM protocol type | -| Socket.SOCK_RAW | SOCK_RAW protocol type | -| Socket.SOCK_MAXADDRLEN | Max address length | -| Socket.SOCK_RDM | Reliable datagram layer | -| Socket.SOCK_SEQPACKET | Sequenced, reliable, two-way connection | -| Socket.SOL_SOCKET | SOL_SOCKET option level | -| Socket.SO_REUSEADDR | SO_REUSEADDR allow socket reuse | +| Constant | Description | +| --------------------- | --------------------------------------- | +| Socket.AF_INET | AF_INET protocol family | +| Socket.SOCK_STREAM | SOCK_STREAM protocol type | +| Socket.SOCK_DGRAM | SOCK_DGRAM protocol type | +| Socket.SOCK_RAW | SOCK_RAW protocol type | +| Socket.SOCK_SEQPACKET | Sequenced, reliable, two-way connection | +| Socket.SOL_SOCKET | SOL_SOCKET option level | +| Socket.SO_REUSEADDR | SO_REUSEADDR allow socket reuse | ### Socket.create(Number: family, Number: type) -> Result From 68592e79ef4697e2dc19a123043671f2b32ba837 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 3 Feb 2023 10:24:00 -0700 Subject: [PATCH 089/109] make versions dynamic Signed-off-by: Brian Downs --- .github/workflows/release.yml | 6 +++--- ops/nfpm.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d2f35852..dbe15584 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,6 +30,6 @@ jobs: overwrite: 'true' files: | dictu-linux-x86_64 - dictu-0.26.0-1_amd64.deb - dictu-0.26.0-1_x86_64.apk - dictu-0.26.0-1.x86_64.rpm + dictu_${{ github.ref_name }}_amd64.deb + dictu_${{ github.ref_name }}_x86_64.apk + dictu_${{ github.ref_name }}.x86_64.rpm diff --git a/ops/nfpm.yaml b/ops/nfpm.yaml index d8a09500..498d75dd 100644 --- a/ops/nfpm.yaml +++ b/ops/nfpm.yaml @@ -19,7 +19,7 @@ platform: linux # This will expand any env var you set in the field, e.g. version: ${SEMVER} # Some package managers, like deb, require the version to start with a digit. # Hence, you should not prefix the version with 'v'. -version: 0.26.0 +version: ${GITHUB_REF_NAME} # Version Schema allows you to specify how to parse the version string. # Default is `semver` From cacaf92c2811cd92f7c8213b9b88c303c9bc586d Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 3 Feb 2023 11:20:25 -0700 Subject: [PATCH 090/109] add so_broadcast Signed-off-by: Brian Downs --- src/optionals/socket.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/optionals/socket.c b/src/optionals/socket.c index a227c66d..d7251d02 100644 --- a/src/optionals/socket.c +++ b/src/optionals/socket.c @@ -359,6 +359,7 @@ Value createSocketModule(DictuVM *vm) { defineNativeProperty(vm, &module->values, "SOCK_SEQPACKET", NUMBER_VAL(SOCK_SEQPACKET)); defineNativeProperty(vm, &module->values, "SOL_SOCKET", NUMBER_VAL(SOL_SOCKET)); defineNativeProperty(vm, &module->values, "SO_REUSEADDR", NUMBER_VAL(SO_REUSEADDR)); + defineNativeProperty(vm, &module->values, "SO_BROADCAST", NUMBER_VAL(SO_BROADCAST)); pop(vm); pop(vm); From 42439c1614c6bdabe101a371babc4e5e38b42fb1 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 3 Feb 2023 11:25:06 -0700 Subject: [PATCH 091/109] update docs Signed-off-by: Brian Downs --- docs/docs/standard-lib/socket.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/standard-lib/socket.md b/docs/docs/standard-lib/socket.md index d2fbb875..b8cd0451 100644 --- a/docs/docs/standard-lib/socket.md +++ b/docs/docs/standard-lib/socket.md @@ -35,6 +35,7 @@ import Socket; | Socket.SOCK_SEQPACKET | Sequenced, reliable, two-way connection | | Socket.SOL_SOCKET | SOL_SOCKET option level | | Socket.SO_REUSEADDR | SO_REUSEADDR allow socket reuse | +| Socket.SO_BROADCAST | Allow sending to dgram sockets | ### Socket.create(Number: family, Number: type) -> Result From 9418fc05a453b0e0b4114c2dbf6522f7ab44b824 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Sat, 4 Feb 2023 22:00:02 -0700 Subject: [PATCH 092/109] update file handling to account for binary Signed-off-by: Brian Downs --- docs/docs/files.md | 2 ++ src/vm/datatypes/files.c | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/docs/docs/files.md b/docs/docs/files.md index d9a86935..cfa2eb34 100644 --- a/docs/docs/files.md +++ b/docs/docs/files.md @@ -26,6 +26,8 @@ when you leave the with scope automatically. Note, the path when opening files i | --------- | -------------------------------------------------------------------------------------------------------------------------- | | r | Opens a file for reading, the file must exist already. | | w | Opens a file for writing, if a file does not exist one is created, else existing file is overwritten. | +| rb | Opens a file for reading binary, the file must exist already. | +| wb | Opens a file for writing in binary mode, if a file does not exist one is created, else existing file is overwritten. | | a | Opens a file for appending, if a file does not exist one is created, else appends text to the end of a file. | | r+ | Opens a file for updating (read + write), the file must exist already. | | w+ | Opens a file for updating (read + write), if a file does not exist one is created, else existing file is overwritten | diff --git a/src/vm/datatypes/files.c b/src/vm/datatypes/files.c index a4e9f658..c901cbc8 100644 --- a/src/vm/datatypes/files.c +++ b/src/vm/datatypes/files.c @@ -15,13 +15,19 @@ static Value writeFile(DictuVM *vm, int argCount, Value *args) { ObjFile *file = AS_FILE(args[0]); ObjString *string = AS_STRING(args[1]); - if (strcmp(file->openType, "r") == 0) { + if (strcmp(file->openType, "r") == 0 || strcmp(file->openType, "rb") == 0) { runtimeError(vm, "File is not writable!"); return EMPTY_VAL; } - int charsWrote = fprintf(file->file, "%s", string->chars); - fflush(file->file); + int charsWrote = 0; + + if (strcmp(file->openType, "wb") == 0) { + charsWrote = fwrite(string->chars, 1, string->length, file->file); + } else { + charsWrote = fprintf(file->file, "%s", string->chars); + fflush(file->file); + } return NUMBER_VAL(charsWrote); } @@ -82,7 +88,10 @@ static Value readFullFile(DictuVM *vm, int argCount, Value *args) { buffer = SHRINK_ARRAY(vm, buffer, char, fileSize + 1, bytesRead + 1); } - buffer[bytesRead] = '\0'; + if (strcmp(file->openType, "r") == 0) { + buffer[bytesRead] = '\0'; + } + return OBJ_VAL(takeString(vm, buffer, bytesRead)); } From 8a127b47578c1e151c7f3058c5459e0c9c22b223 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Sat, 4 Feb 2023 22:03:24 -0700 Subject: [PATCH 093/109] simplify logic Signed-off-by: Brian Downs --- src/vm/datatypes/files.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vm/datatypes/files.c b/src/vm/datatypes/files.c index c901cbc8..f6c1c9ee 100644 --- a/src/vm/datatypes/files.c +++ b/src/vm/datatypes/files.c @@ -88,7 +88,7 @@ static Value readFullFile(DictuVM *vm, int argCount, Value *args) { buffer = SHRINK_ARRAY(vm, buffer, char, fileSize + 1, bytesRead + 1); } - if (strcmp(file->openType, "r") == 0) { + if (strcmp(file->openType, "rb") != 0) { buffer[bytesRead] = '\0'; } From 7f3400e0198ec62fac1d8a15a73d8588183ce972 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 7 Feb 2023 15:40:17 -0700 Subject: [PATCH 094/109] move fflush outside of logic block Signed-off-by: Brian Downs --- src/vm/datatypes/files.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vm/datatypes/files.c b/src/vm/datatypes/files.c index f6c1c9ee..507d62f9 100644 --- a/src/vm/datatypes/files.c +++ b/src/vm/datatypes/files.c @@ -26,8 +26,8 @@ static Value writeFile(DictuVM *vm, int argCount, Value *args) { charsWrote = fwrite(string->chars, 1, string->length, file->file); } else { charsWrote = fprintf(file->file, "%s", string->chars); - fflush(file->file); } + fflush(file->file); return NUMBER_VAL(charsWrote); } From d0306212ffebfd28f35d3125e6a1c5aa4f231cec Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Tue, 7 Feb 2023 15:51:14 -0700 Subject: [PATCH 095/109] make sure string is null terminated Signed-off-by: Brian Downs --- src/vm/datatypes/files.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vm/datatypes/files.c b/src/vm/datatypes/files.c index 507d62f9..251d73db 100644 --- a/src/vm/datatypes/files.c +++ b/src/vm/datatypes/files.c @@ -88,9 +88,7 @@ static Value readFullFile(DictuVM *vm, int argCount, Value *args) { buffer = SHRINK_ARRAY(vm, buffer, char, fileSize + 1, bytesRead + 1); } - if (strcmp(file->openType, "rb") != 0) { - buffer[bytesRead] = '\0'; - } + buffer[bytesRead] = '\0'; return OBJ_VAL(takeString(vm, buffer, bytesRead)); } From 00d25e0a56d718377b5e5bd8e2e30a190a612c8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Silva?= Date: Mon, 6 Feb 2023 14:14:30 +0000 Subject: [PATCH 096/109] Win32 corrections and ability to cross-compile from Linux with MinGW. --- src/optionals/hashlib/bcrypt/bcrypt.c | 4 ++-- src/optionals/process.c | 4 ++-- src/optionals/uuid.c | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/optionals/hashlib/bcrypt/bcrypt.c b/src/optionals/hashlib/bcrypt/bcrypt.c index 50888bf0..4f0161ba 100644 --- a/src/optionals/hashlib/bcrypt/bcrypt.c +++ b/src/optionals/hashlib/bcrypt/bcrypt.c @@ -37,7 +37,7 @@ #include #ifdef _WIN32 -#include +#include #include #endif @@ -402,4 +402,4 @@ bcrypt_pass(const char *pass, const char *salt) return gencrypted; } -DEF_WEAK(bcrypt); \ No newline at end of file +DEF_WEAK(bcrypt); diff --git a/src/optionals/process.c b/src/optionals/process.c index a66b3944..85284603 100644 --- a/src/optionals/process.c +++ b/src/optionals/process.c @@ -103,7 +103,7 @@ static Value executeReturnOutput(DictuVM* vm, ObjList* argList) { CloseHandle(childOutWrite); FREE_ARRAY(vm, char, args, len); - int dwRead; + DWORD dwRead; int size = 1024; char* output = ALLOCATE(vm, char, size); char buffer[1024]; @@ -288,4 +288,4 @@ Value createProcessModule(DictuVM* vm) { pop(vm); return OBJ_VAL(module); -} \ No newline at end of file +} diff --git a/src/optionals/uuid.c b/src/optionals/uuid.c index 6e3201c2..f0e3da6e 100644 --- a/src/optionals/uuid.c +++ b/src/optionals/uuid.c @@ -1,10 +1,10 @@ #include "uuid.h" +#ifndef _WIN32 static uuid_t uuid; #define UUID_STRING_LEN 37 -#ifndef _WIN32 static Value uuidGenerateNative(DictuVM *vm, int argCount, Value *args) { UNUSED(args); From f6a75e7562c4d78e93791e8ddb151ab570467a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Silva?= Date: Mon, 6 Feb 2023 14:27:07 +0000 Subject: [PATCH 097/109] Replaced included copy of linenoise with https://github.com/jamsilva/linenoise/tree/alt14. --- src/CMakeLists.txt | 5 +- src/cli/CMakeLists.txt | 12 +- src/cli/encodings/utf8.c | 535 ------- src/cli/encodings/utf8.h | 55 - src/cli/linenoise.c | 1356 ----------------- src/cli/linenoise.h | 84 -- src/cli/linenoise/linenoise-win32.c | 395 +++++ src/cli/linenoise/linenoise.c | 2094 +++++++++++++++++++++++++++ src/cli/linenoise/linenoise.h | 149 ++ src/cli/linenoise/stringbuf.c | 173 +++ src/cli/linenoise/stringbuf.h | 137 ++ src/cli/linenoise/utf8.c | 275 ++++ src/cli/linenoise/utf8.h | 107 ++ src/cli/main.c | 8 +- 14 files changed, 3343 insertions(+), 2042 deletions(-) delete mode 100755 src/cli/encodings/utf8.c delete mode 100755 src/cli/encodings/utf8.h delete mode 100755 src/cli/linenoise.c delete mode 100755 src/cli/linenoise.h create mode 100644 src/cli/linenoise/linenoise-win32.c create mode 100644 src/cli/linenoise/linenoise.c create mode 100644 src/cli/linenoise/linenoise.h create mode 100644 src/cli/linenoise/stringbuf.c create mode 100644 src/cli/linenoise/stringbuf.h create mode 100644 src/cli/linenoise/utf8.c create mode 100644 src/cli/linenoise/utf8.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6dbd1fd7..d4c4686e 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|utf8).c") -list(FILTER headers EXCLUDE REGEX "(linenoise|utf8).h") +list(FILTER sources EXCLUDE REGEX "(main|stringbuf|linenoise(-win32)?|utf8).c") +list(FILTER headers EXCLUDE REGEX "(linenoise|stringbuf|utf8).h") find_library(SQLITE_LIB SQLite3) set(THREADS) @@ -45,6 +45,7 @@ if(WIN32) # ws2_32 is required for winsock2.h to work correctly list(APPEND libraries ws2_32 bcrypt) else() + list(FILTER sources EXCLUDE REGEX "linenoise-win32.c") list(APPEND libraries m) endif() diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index e86031ae..9f229b0b 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -1,13 +1,19 @@ -set(DICTU_CLI_SRC main.c linenoise.c linenoise.h encodings/utf8.c encodings/utf8.h) +set(DICTU_CLI_SRC main.c linenoise/linenoise.c linenoise/linenoise.h linenoise/stringbuf.c linenoise/stringbuf.h linenoise/utf8.c linenoise/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((NOT WIN32) OR DISABLE_LINENOISE) + list(FILTER DICTU_CLI_SRC EXCLUDE REGEX "linenoise-win32.c") +endif() + if(DISABLE_LINENOISE) - list(FILTER DICTU_CLI_SRC EXCLUDE REGEX "(linenoise|utf8)\.(c|h)") + list(FILTER DICTU_CLI_SRC EXCLUDE REGEX "(linenoise|stringbuf|utf8)\.(c|h)") add_compile_definitions(DISABLE_LINENOISE) +else() + add_compile_definitions(USE_UTF8) endif() add_executable(dictu ${DICTU_CLI_SRC}) target_include_directories(dictu PUBLIC ${INCLUDE_DIR}) -target_link_libraries(dictu dictu_api_static) \ No newline at end of file +target_link_libraries(dictu dictu_api_static) diff --git a/src/cli/encodings/utf8.c b/src/cli/encodings/utf8.c deleted file mode 100755 index adda52d7..00000000 --- a/src/cli/encodings/utf8.c +++ /dev/null @@ -1,535 +0,0 @@ -/* 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 deleted file mode 100755 index d401bc86..00000000 --- a/src/cli/encodings/utf8.h +++ /dev/null @@ -1,55 +0,0 @@ -/* 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 deleted file mode 100755 index eabe6ba6..00000000 --- a/src/cli/linenoise.c +++ /dev/null @@ -1,1356 +0,0 @@ -/* linenoise.c -- 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-2016, 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. - * - * ------------------------------------------------------------------------ - * - * References: - * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html - * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html - * - * Todo list: - * - Filter bogus Ctrl+ combinations. - * - Win32 support - * - * Bloat: - * - History search like Ctrl+r in readline? - * - * List of escape sequences used by this program, we do everything just - * with three sequences. In order to be so cheap we may have some - * flickering effect with some slow terminal, but the lesser sequences - * the more compatible. - * - * EL (Erase Line) - * Sequence: ESC [ n K - * Effect: if n is 0 or missing, clear from cursor to end of line - * Effect: if n is 1, clear from beginning of line to cursor - * Effect: if n is 2, clear entire line - * - * CUF (CUrsor Forward) - * Sequence: ESC [ n C - * Effect: moves cursor forward n chars - * - * CUB (CUrsor Backward) - * Sequence: ESC [ n D - * Effect: moves cursor backward n chars - * - * The following is used to get the terminal width if getting - * the width with the TIOCGWINSZ ioctl fails - * - * DSR (Device Status Report) - * Sequence: ESC [ 6 n - * Effect: reports the current cusor position as ESC [ n ; m R - * where n is the row and m is the column - * - * When multi line mode is enabled, we also use an additional escape - * sequence. However multi line editing is disabled by default. - * - * CUU (Cursor Up) - * Sequence: ESC [ n A - * Effect: moves cursor up of n chars. - * - * CUD (Cursor Down) - * Sequence: ESC [ n B - * Effect: moves cursor down of n chars. - * - * When linenoiseClearScreen() is called, two additional escape sequences - * are used in order to clear the screen and position the cursor at home - * position. - * - * CUP (Cursor position) - * Sequence: ESC [ H - * Effect: moves the cursor to upper left corner - * - * ED (Erase display) - * Sequence: ESC [ 2 J - * Effect: clear the whole screen - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "linenoise.h" - -#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. */ -static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; -static int history_len = 0; -static char **history = NULL; - -/* The linenoiseState structure represents the state during line editing. - * We pass this state to functions implementing specific editing - * functionalities. */ -struct linenoiseState { - int ifd; /* Terminal stdin file descriptor. */ - int ofd; /* Terminal stdout file descriptor. */ - char *buf; /* Edited line buffer. */ - size_t buflen; /* Edited line buffer size. */ - const char *prompt; /* Prompt to display. */ - size_t plen; /* Prompt length. */ - size_t pos; /* Current 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) */ - int history_index; /* The history index we are currently editing. */ -}; - -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 */ -}; - -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; -} - -/* Return true if the terminal name is in the list of terminals we know are - * not able to understand basic escape sequences. */ -static int isUnsupportedTerm(void) { - char *term = getenv("TERM"); - int j; - - if (term == NULL) return 0; - for (j = 0; unsupported_term[j]; j++) - if (!strcasecmp(term,unsupported_term[j])) return 1; - return 0; -} - -/* Raw mode: 1960 magic shit. */ -static int enableRawMode(int fd) { - struct termios raw; - - if (!isatty(STDIN_FILENO)) goto fatal; - if (!atexit_registered) { - atexit(linenoiseAtExit); - atexit_registered = 1; - } - if (tcgetattr(fd,&orig_termios) == -1) goto fatal; - - raw = orig_termios; /* modify the original mode */ - /* input modes: no break, no CR to NL, no parity check, no strip char, - * no start/stop output control. */ - raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); - /* output modes - disable post processing */ - raw.c_oflag &= ~(OPOST); - /* control modes - set 8 bit chars */ - raw.c_cflag |= (CS8); - /* local modes - choing off, canonical off, no extended functions, - * no signal chars (^Z,^C) */ - raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); - /* control chars - set return condition: min number of bytes and timer. - * We want read to return every single byte, without timeout. */ - raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ - - /* put terminal in raw mode after flushing */ - if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal; - rawmode = 1; - return 0; - -fatal: - errno = ENOTTY; - return -1; -} - -static void disableRawMode(int fd) { - /* Don't even check the return value as it's too late. */ - if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1) - rawmode = 0; -} - -/* Use the ESC [6n escape sequence to query the horizontal cursor position - * and return it. On error -1 is returned, on success the position of the - * cursor. */ -static int getCursorPosition(int ifd, int ofd) { - char buf[32]; - int cols, rows; - unsigned int i = 0; - - /* Report cursor location */ - if (write(ofd, "\x1b[6n", 4) != 4) return -1; - - /* Read the response: ESC [ rows ; cols R */ - while (i < sizeof(buf)-1) { - if (read(ifd,buf+i,1) != 1) break; - if (buf[i] == 'R') break; - i++; - } - buf[i] = '\0'; - - /* Parse it. */ - if (buf[0] != ESC || buf[1] != '[') return -1; - if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1; - return cols; -} - -/* Try to get the number of columns in the current terminal, or assume 80 - * if it fails. */ -static int getColumns(int ifd, int ofd) { - struct winsize ws; - - if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { - /* ioctl() failed. Try to query the terminal itself. */ - int start, cols; - - /* Get the initial position so we can restore it later. */ - start = getCursorPosition(ifd,ofd); - if (start == -1) goto failed; - - /* Go to right margin and get position. */ - if (write(ofd,"\x1b[999C",6) != 6) goto failed; - cols = getCursorPosition(ifd,ofd); - if (cols == -1) goto failed; - - /* Restore position. */ - if (cols > start) { - char seq[32]; - snprintf(seq,32,"\x1b[%dD",cols-start); - if (write(ofd,seq,strlen(seq)) == -1) { - /* Can't recover... */ - } - } - return cols; - } else { - return ws.ws_col; - } - -failed: - return 80; -} - -/* Clear the screen. Used to handle ctrl+l */ -void linenoiseClearScreen(void) { - if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) { - /* nothing to do, just to avoid warning. */ - } -} - -/* Beep, used for completion when there is nothing to complete or when all - * the choices were already shown. */ -static void linenoiseBeep(void) { - fprintf(stderr, "\x7"); - fflush(stderr); -} - -/* ============================== Completion ================================ */ - -/* Free a list of completion option populated by linenoiseAddCompletion(). */ -static void freeCompletions(linenoiseCompletions *lc) { - size_t i; - for (i = 0; i < lc->len; i++) - free(lc->cvec[i]); - if (lc->cvec != NULL) - free(lc->cvec); -} - -/* This is an helper function for linenoiseEdit() and is called when the - * user types the key in order to complete the string currently in the - * input. - * - * The state of the editing is encapsulated into the pointed linenoiseState - * structure as described in the structure definition. */ -static int completeLine(struct linenoiseState *ls, char *cbuf, size_t cbuf_len, int *c) { - linenoiseCompletions lc = { 0, NULL }; - int nread = 0, nwritten; - *c = 0; - - completionCallback(ls->buf,&lc); - if (lc.len == 0) { - linenoiseBeep(); - } else { - size_t stop = 0, i = 0; - - while(!stop) { - /* Show completion or original buffer */ - if (i < lc.len) { - struct linenoiseState saved = *ls; - - ls->len = ls->pos = strlen(lc.cvec[i]); - ls->buf = lc.cvec[i]; - refreshLine(ls); - ls->len = saved.len; - ls->pos = saved.pos; - ls->buf = saved.buf; - } else { - refreshLine(ls); - } - - nread = readCode(ls->ifd,cbuf,cbuf_len,c); - if (nread <= 0) { - freeCompletions(&lc); - *c = -1; - return nread; - } - - switch(*c) { - case 9: /* tab */ - i = (i+1) % (lc.len+1); - if (i == lc.len) linenoiseBeep(); - break; - case 27: /* escape */ - /* Re-show original buffer */ - if (i < lc.len) refreshLine(ls); - stop = 1; - break; - default: - /* Update buffer and return */ - if (i < lc.len) { - nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]); - ls->len = ls->pos = nwritten; - } - stop = 1; - break; - } - } - } - - freeCompletions(&lc); - return nread; -} - -/* Register a callback function to be called for tab-completion. */ -void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { - completionCallback = fn; -} - -/* Register a hits function to be called to show hits to the user at the - * right of the prompt. */ -void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) { - hintsCallback = fn; -} - -/* Register a function to free the hints returned by the hints callback - * registered with linenoiseSetHintsCallback(). */ -void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) { - freeHintsCallback = fn; -} - -/* This function is used by the callback function registered by the user - * in order to add completion options given the input string when the - * user typed . See the example.c source code for a very easy to - * understand example. */ -void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { - size_t len = strlen(str); - char *copy, **cvec; - - copy = malloc(len+1); - if (copy == NULL) return; - memcpy(copy,str,len+1); - cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1)); - if (cvec == NULL) { - free(copy); - return; - } - lc->cvec = cvec; - lc->cvec[lc->len++] = copy; -} - -/* =========================== Line editing ================================= */ - -/* We define a very simple "append buffer" structure, that is an heap - * allocated string where we can append to. This is useful in order to - * write all the escape sequences in a buffer and flush them to the standard - * output in a single call, to avoid flickering effects. */ -struct abuf { - char *b; - int len; -}; - -static void abInit(struct abuf *ab) { - ab->b = NULL; - ab->len = 0; -} - -static void abAppend(struct abuf *ab, const char *s, int len) { - char *new = realloc(ab->b,ab->len+len); - - if (new == NULL) return; - memcpy(new+ab->len,s,len); - ab->b = new; - ab->len += len; -} - -static void abFree(struct abuf *ab) { - free(ab->b); -} - -/* Helper of refreshSingleLine() and refreshMultiLine() to show hints - * to the right of the prompt. */ -void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int pcollen) { - char seq[64]; - 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-collen; - if (hintlen > hintmaxlen) hintlen = hintmaxlen; - if (bold == 1 && color == -1) color = 37; - if (color != -1 || bold != 0) - snprintf(seq,64,"\033[%d;%d;49m",bold,color); - else - seq[0] = '\0'; - abAppend(ab,seq,strlen(seq)); - abAppend(ab,hint,hintlen); - if (color != -1 || bold != 0) - abAppend(ab,"\033[0m",4); - /* Call the function to free the hint returned. */ - if (freeHintsCallback) freeHintsCallback(hint); - } - } -} - -/* 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 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((pcollen+columnPos(buf,len,pos)) >= l->cols) { - int chlen = nextCharLen(buf,len,0,NULL); - buf += chlen; - len -= chlen; - pos -= chlen; - } - while (pcollen+columnPos(buf,len,len) > l->cols) { - len -= prevCharLen(buf,len,len,NULL); - } - - abInit(&ab); - /* Cursor to left edge */ - snprintf(seq,64,"\r"); - abAppend(&ab,seq,strlen(seq)); - /* Write the prompt and the current buffer content */ - abAppend(&ab,l->prompt,strlen(l->prompt)); - if (maskmode == 1) { - while (len--) abAppend(&ab,"*",1); - } else { - abAppend(&ab,buf,len); - } - /* Show hits if any. */ - 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)(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); -} - -/* Multi 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 refreshMultiLine(struct linenoiseState *l) { - char seq[64]; - 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; - int fd = l->ofd, j; - struct abuf ab; - - /* Update maxrows if needed. */ - if (rows > (int)l->maxrows) l->maxrows = rows; - - /* First step: clear all the lines used before. To do so start by - * 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)); - 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,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 && - (colpos2+pcollen) % l->cols == 0) - { - lndebug(""); - abAppend(&ab,"\n",1); - snprintf(seq,64,"\r"); - abAppend(&ab,seq,strlen(seq)); - rows++; - if (rows > (int)l->maxrows) l->maxrows = rows; - } - - /* Move cursor to right position. */ - 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 = (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)); - - lndebug("\n"); - l->oldcolpos = colpos2; - - if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ - abFree(&ab); -} - -/* Calls the two low level functions refreshSingleLine() or - * refreshMultiLine() according to the selected mode. */ -static void refreshLine(struct linenoiseState *l) { - if (mlmode) - refreshMultiLine(l); - else - refreshSingleLine(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, const char *cbuf, int clen) { - if (l->len+clen <= l->buflen) { - if (l->len == l->pos) { - memcpy(&l->buf[l->pos],cbuf,clen); - l->pos+=clen; - l->len+=clen;; - l->buf[l->len] = '\0'; - 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 (maskmode == 1) { - static const char d = '*'; - if (write(l->ofd,&d,1) == -1) return -1; - } else { - if (write(l->ofd,cbuf,clen) == -1) return -1; - } - } else { - refreshLine(l); - } - } else { - 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); - } - } - return 0; -} - -/* Move cursor on the left. */ -void linenoiseEditMoveLeft(struct linenoiseState *l) { - if (l->pos > 0) { - l->pos -= prevCharLen(l->buf,l->len,l->pos,NULL); - refreshLine(l); - } -} - -/* Move cursor on the right. */ -void linenoiseEditMoveRight(struct linenoiseState *l) { - if (l->pos != l->len) { - l->pos += nextCharLen(l->buf,l->len,l->pos,NULL); - refreshLine(l); - } -} - -/* Move cursor to the start of the line. */ -void linenoiseEditMoveHome(struct linenoiseState *l) { - if (l->pos != 0) { - l->pos = 0; - refreshLine(l); - } -} - -/* Move cursor to the end of the line. */ -void linenoiseEditMoveEnd(struct linenoiseState *l) { - if (l->pos != l->len) { - l->pos = l->len; - refreshLine(l); - } -} - -/* Substitute the currently edited line with the next or previous history - * entry as specified by 'dir'. */ -#define LINENOISE_HISTORY_NEXT 0 -#define LINENOISE_HISTORY_PREV 1 -void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) { - if (history_len > 1) { - /* Update the current history entry before to - * overwrite it with the next one. */ - free(history[history_len - 1 - l->history_index]); - history[history_len - 1 - l->history_index] = strdup(l->buf); - /* Show the new entry */ - l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; - if (l->history_index < 0) { - l->history_index = 0; - return; - } else if (l->history_index >= history_len) { - l->history_index = history_len-1; - return; - } - strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen); - l->buf[l->buflen-1] = '\0'; - l->len = l->pos = strlen(l->buf); - refreshLine(l); - } -} - -/* Delete the character at the right of the cursor without altering the cursor - * position. Basically this is what happens with the "Delete" keyboard key. */ -void linenoiseEditDelete(struct linenoiseState *l) { - if (l->len > 0 && l->pos < 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); - } -} - -/* Backspace implementation. */ -void linenoiseEditBackspace(struct linenoiseState *l) { - if (l->pos > 0 && l->len > 0) { - 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 previosu word, maintaining the cursor at the start of the - * current word. */ -void linenoiseEditDeletePrevWord(struct linenoiseState *l) { - size_t old_pos = l->pos; - size_t diff; - - while (l->pos > 0 && l->buf[l->pos-1] == ' ') - l->pos--; - while (l->pos > 0 && l->buf[l->pos-1] != ' ') - l->pos--; - diff = old_pos - l->pos; - memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1); - l->len -= diff; - refreshLine(l); -} - -/* This function is the core of the line editing capability of linenoise. - * It expects 'fd' to be already in "raw mode" so that every key pressed - * will be returned ASAP to read(). - * - * The resulting string is put into 'buf' when the user type enter, or - * when ctrl+d is typed. - * - * The function returns the length of the current buffer. */ -static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) -{ - struct linenoiseState l; - - /* Populate the linenoise state that we pass to functions implementing - * specific editing functionalities. */ - l.ifd = stdin_fd; - l.ofd = stdout_fd; - l.buf = buf; - l.buflen = buflen; - l.prompt = prompt; - l.plen = strlen(prompt); - l.oldcolpos = l.pos = 0; - l.len = 0; - l.cols = getColumns(stdin_fd, stdout_fd); - l.maxrows = 0; - l.history_index = 0; - - /* Buffer starts empty. */ - l.buf[0] = '\0'; - l.buflen--; /* Make sure there is always space for the nulterm */ - - /* The latest history entry is always our current buffer, that - * initially is just an empty string. */ - linenoiseHistoryAdd(""); - - if (write(l.ofd,prompt,l.plen) == -1) return -1; - while(1) { - int c; - char cbuf[32]; // large enough for any encoding? - int nread; - char seq[3]; - - 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) { - nread = completeLine(&l,cbuf,sizeof(cbuf),&c); - /* Return on errors */ - if (c < 0) return l.len; - /* Read next character when 0 */ - if (c == 0) continue; - } - - switch(c) { - 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]); - 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 { - 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; - } - } - } - - /* 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; -} - -/* This special mode is used by linenoise in order to print scan codes - * on screen for debugging / development purposes. It is implemented - * by the linenoise_example program using the --keycodes option. */ -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"); - if (enableRawMode(STDIN_FILENO) == -1) return; - memset(quit,' ',4); - while(1) { - char c; - int nread; - - nread = read(STDIN_FILENO,&c,1); - if (nread <= 0) continue; - memmove(quit,quit+1,sizeof(quit)-1); /* shift string to left. */ - quit[sizeof(quit)-1] = c; /* Insert current char on the right. */ - if (memcmp(quit,"quit",sizeof(quit)) == 0) break; - - printf("'%c' %02x (%d) (type quit to exit)\n", - isprint((int)c) ? c : '?', (int)c, (int)c); - printf("\r"); /* Go left edge manually, we are in raw mode. */ - fflush(stdout); - } - disableRawMode(STDIN_FILENO); -} - -/* This function calls the line editing function linenoiseEdit() using - * the STDIN file descriptor set in raw mode. */ -static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) { - int count; - - if (buflen == 0) { - errno = EINVAL; - return -1; - } - - if (enableRawMode(STDIN_FILENO) == -1) return -1; - count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt); - disableRawMode(STDIN_FILENO); - printf("\n"); - return count; -} - -/* This function is called when linenoise() is called with the standard - * input file descriptor not attached to a TTY. So for example when the - * program using linenoise is called in pipe or with a file redirected - * to its standard input. In this case, we want to be able to return the - * line regardless of its length (by default we are limited to 4k). */ -static char *linenoiseNoTTY(void) { - char *line = NULL; - size_t len = 0, maxlen = 0; - - while(1) { - if (len == maxlen) { - if (maxlen == 0) maxlen = 16; - maxlen *= 2; - char *oldval = line; - line = realloc(line,maxlen); - if (line == NULL) { - if (oldval) free(oldval); - return NULL; - } - } - int c = fgetc(stdin); - if (c == EOF || c == '\n') { - if (c == EOF && len == 0) { - free(line); - return NULL; - } else { - line[len] = '\0'; - return line; - } - } else { - line[len] = c; - len++; - } - } -} - -/* The high level function that is the main API of the linenoise library. - * This function checks if the terminal has basic capabilities, just checking - * for a blacklist of stupid terminals, and later either calls the line - * editing function or uses dummy fgets() so that you will be able to type - * something even in the most desperate of the conditions. */ -char *linenoise(const char *prompt) { - char buf[LINENOISE_MAX_LINE]; - int count; - - if (!isatty(STDIN_FILENO)) { - /* Not a tty: read from file / pipe. In this mode we don't want any - * limit to the line size, so we call a function to handle that. */ - return linenoiseNoTTY(); - } else if (isUnsupportedTerm()) { - size_t len; - - printf("%s",prompt); - fflush(stdout); - if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL; - len = strlen(buf); - while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) { - len--; - buf[len] = '\0'; - } - return strdup(buf); - } else { - count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt); - if (count == -1) return NULL; - return strdup(buf); - } -} - -/* This is just a wrapper the user may want to call in order to make sure - * the linenoise returned buffer is freed with the same allocator it was - * created with. Useful when the main program is using an alternative - * allocator. */ -void linenoiseFree(void *ptr) { - free(ptr); -} - -/* ================================ History ================================= */ - -/* Free the history, but does not reset it. Only used when we have to - * exit() to avoid memory leaks are reported by valgrind & co. */ -static void freeHistory(void) { - if (history) { - int j; - - for (j = 0; j < history_len; j++) - free(history[j]); - free(history); - } -} - -/* At exit we'll try to fix the terminal to the initial conditions. */ -static void linenoiseAtExit(void) { - disableRawMode(STDIN_FILENO); - freeHistory(); -} - -/* This is the API call to add a new entry in the linenoise history. - * It uses a fixed array of char pointers that are shifted (memmoved) - * when the history max length is reached in order to remove the older - * entry and make room for the new one, so it is not exactly suitable for huge - * histories, but will work well for a few hundred of entries. - * - * Using a circular buffer is smarter, but a bit more complex to handle. */ -int linenoiseHistoryAdd(const char *line) { - char *linecopy; - - if (history_max_len == 0) return 0; - - /* Initialization on first call. */ - if (history == NULL) { - history = malloc(sizeof(char*)*history_max_len); - if (history == NULL) return 0; - memset(history,0,(sizeof(char*)*history_max_len)); - } - - /* Don't add duplicated lines. */ - if (history_len && !strcmp(history[history_len-1], line)) return 0; - - /* Add an heap allocated copy of the line in the history. - * If we reached the max length, remove the older line. */ - linecopy = strdup(line); - if (!linecopy) return 0; - if (history_len == history_max_len) { - free(history[0]); - memmove(history,history+1,sizeof(char*)*(history_max_len-1)); - history_len--; - } - history[history_len] = linecopy; - history_len++; - return 1; -} - -/* Set the maximum length for the history. This function can be called even - * if there is already some history, the function will make sure to retain - * just the latest 'len' elements if the new history length value is smaller - * than the amount of items already inside the history. */ -int linenoiseHistorySetMaxLen(int len) { - char **new; - - if (len < 1) return 0; - if (history) { - int tocopy = history_len; - - new = malloc(sizeof(char*)*len); - if (new == NULL) return 0; - - /* If we can't copy everything, free the elements we'll not use. */ - if (len < tocopy) { - int j; - - for (j = 0; j < tocopy-len; j++) free(history[j]); - tocopy = len; - } - memset(new,0,sizeof(char*)*len); - memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy); - free(history); - history = new; - } - history_max_len = len; - if (history_len > history_max_len) - history_len = history_max_len; - return 1; -} - -/* Save the history in the specified file. On success 0 is returned - * otherwise -1 is returned. */ -int linenoiseHistorySave(const char *filename) { - mode_t old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO); - FILE *fp; - int j; - - fp = fopen(filename,"w"); - umask(old_umask); - if (fp == NULL) return -1; - chmod(filename,S_IRUSR|S_IWUSR); - for (j = 0; j < history_len; j++) - fprintf(fp,"%s\n",history[j]); - fclose(fp); - return 0; -} - -/* Load the history from the specified file. If the file does not exist - * zero is returned and no operation is performed. - * - * If the file exists and the operation succeeded 0 is returned, otherwise - * on error -1 is returned. */ -int linenoiseHistoryLoad(const char *filename) { - FILE *fp = fopen(filename,"r"); - char buf[LINENOISE_MAX_LINE]; - - if (fp == NULL) return -1; - - while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) { - char *p; - - p = strchr(buf,'\r'); - if (!p) p = strchr(buf,'\n'); - if (p) *p = '\0'; - linenoiseHistoryAdd(buf); - } - fclose(fp); - return 0; -} \ No newline at end of file diff --git a/src/cli/linenoise.h b/src/cli/linenoise.h deleted file mode 100755 index ceca1eb2..00000000 --- a/src/cli/linenoise.h +++ /dev/null @@ -1,84 +0,0 @@ -/* linenoise.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_H -#define __LINENOISE_H - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct linenoiseCompletions { - size_t len; - char **cvec; -} linenoiseCompletions; - -typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); -typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold); -typedef void(linenoiseFreeHintsCallback)(void *); -void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); -void linenoiseSetHintsCallback(linenoiseHintsCallback *); -void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *); -void linenoiseAddCompletion(linenoiseCompletions *, const char *); - -char *linenoise(const char *prompt); -void linenoiseFree(void *ptr); -int linenoiseHistoryAdd(const char *line); -int linenoiseHistorySetMaxLen(int len); -int linenoiseHistorySave(const char *filename); -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 -} -#endif - -#endif /* __LINENOISE_H */ diff --git a/src/cli/linenoise/linenoise-win32.c b/src/cli/linenoise/linenoise-win32.c new file mode 100644 index 00000000..bd488996 --- /dev/null +++ b/src/cli/linenoise/linenoise-win32.c @@ -0,0 +1,395 @@ + +/* this code is not standalone + * it is included into linenoise.c + * for windows. + * It is deliberately kept separate so that + * applications that have no need for windows + * support can omit this + */ +static DWORD orig_consolemode = 0; + +static int flushOutput(struct current *current); +static void outputNewline(struct current *current); + +static void refreshStart(struct current *current) +{ + (void)current; +} + +static void refreshEnd(struct current *current) +{ + (void)current; +} + +static void refreshStartChars(struct current *current) +{ + assert(current->output == NULL); + /* We accumulate all output here */ + current->output = sb_alloc(); +#ifdef USE_UTF8 + current->ubuflen = 0; +#endif +} + +static void refreshNewline(struct current *current) +{ + DRL(""); + outputNewline(current); +} + +static void refreshEndChars(struct current *current) +{ + assert(current->output); + flushOutput(current); + sb_free(current->output); + current->output = NULL; +} + +static int enableRawMode(struct current *current) { + DWORD n; + INPUT_RECORD irec; + + current->outh = GetStdHandle(STD_OUTPUT_HANDLE); + current->inh = GetStdHandle(STD_INPUT_HANDLE); + + if (!PeekConsoleInput(current->inh, &irec, 1, &n)) { + return -1; + } + if (getWindowSize(current) != 0) { + return -1; + } + if (GetConsoleMode(current->inh, &orig_consolemode)) { + SetConsoleMode(current->inh, ENABLE_PROCESSED_INPUT); + } + return 0; +} + +static void disableRawMode(struct current *current) +{ + SetConsoleMode(current->inh, orig_consolemode); +} + +void linenoiseClearScreen(void) +{ + /* XXX: This is ugly. Should just have the caller pass a handle */ + struct current current; + + current.outh = GetStdHandle(STD_OUTPUT_HANDLE); + + if (getWindowSize(¤t) == 0) { + COORD topleft = { 0, 0 }; + DWORD n; + + FillConsoleOutputCharacter(current.outh, ' ', + current.cols * current.rows, topleft, &n); + FillConsoleOutputAttribute(current.outh, + FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN, + current.cols * current.rows, topleft, &n); + SetConsoleCursorPosition(current.outh, topleft); + } +} + +static void cursorToLeft(struct current *current) +{ + COORD pos; + DWORD n; + + pos.X = 0; + pos.Y = (SHORT)current->y; + + FillConsoleOutputAttribute(current->outh, + FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN, current->cols, pos, &n); + current->x = 0; +} + +#ifdef USE_UTF8 +static void flush_ubuf(struct current *current) +{ + COORD pos; + DWORD nwritten; + pos.Y = (SHORT)current->y; + pos.X = (SHORT)current->x; + SetConsoleCursorPosition(current->outh, pos); + WriteConsoleW(current->outh, current->ubuf, current->ubuflen, &nwritten, 0); + current->x += current->ubufcols; + current->ubuflen = 0; + current->ubufcols = 0; +} + +static void add_ubuf(struct current *current, int ch) +{ + /* This code originally by: Author: Mark E. Davis, 1994. */ + static const int halfShift = 10; /* used for shifting by 10 bits */ + + static const DWORD halfBase = 0x0010000UL; + static const DWORD halfMask = 0x3FFUL; + + #define UNI_SUR_HIGH_START 0xD800 + #define UNI_SUR_HIGH_END 0xDBFF + #define UNI_SUR_LOW_START 0xDC00 + #define UNI_SUR_LOW_END 0xDFFF + + #define UNI_MAX_BMP 0x0000FFFF + + if (ch > UNI_MAX_BMP) { + /* convert from unicode to utf16 surrogate pairs + * There is always space for one extra word in ubuf + */ + ch -= halfBase; + current->ubuf[current->ubuflen++] = (WORD)((ch >> halfShift) + UNI_SUR_HIGH_START); + current->ubuf[current->ubuflen++] = (WORD)((ch & halfMask) + UNI_SUR_LOW_START); + } + else { + current->ubuf[current->ubuflen++] = ch; + } + current->ubufcols += utf8_width(ch); + if (current->ubuflen >= UBUF_MAX_CHARS) { + flush_ubuf(current); + } +} +#endif + +static int flushOutput(struct current *current) +{ + const char *pt = sb_str(current->output); + int len = sb_len(current->output); + +#ifdef USE_UTF8 + /* convert utf8 in current->output into utf16 in current->ubuf + */ + while (len) { + int ch; + int n = utf8_tounicode(pt, &ch); + + pt += n; + len -= n; + + add_ubuf(current, ch); + } + flush_ubuf(current); +#else + DWORD nwritten; + COORD pos; + + pos.Y = (SHORT)current->y; + pos.X = (SHORT)current->x; + + SetConsoleCursorPosition(current->outh, pos); + WriteConsoleA(current->outh, pt, len, &nwritten, 0); + + current->x += len; +#endif + + sb_clear(current->output); + + return 0; +} + +static int outputChars(struct current *current, const char *buf, int len) +{ + if (len < 0) { + len = strlen(buf); + } + assert(current->output); + + sb_append_len(current->output, buf, len); + + return 0; +} + +static void outputNewline(struct current *current) +{ + /* On the last row output a newline to force a scroll */ + if (current->y + 1 == current->rows) { + outputChars(current, "\n", 1); + } + flushOutput(current); + current->x = 0; + current->y++; +} + +static void setOutputHighlight(struct current *current, const int *props, int nprops) +{ + int colour = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN; + int bold = 0; + int reverse = 0; + int i; + + for (i = 0; i < nprops; i++) { + switch (props[i]) { + case 0: + colour = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN; + bold = 0; + reverse = 0; + break; + case 1: + bold = FOREGROUND_INTENSITY; + break; + case 7: + reverse = 1; + break; + case 30: + colour = 0; + break; + case 31: + colour = FOREGROUND_RED; + break; + case 32: + colour = FOREGROUND_GREEN; + break; + case 33: + colour = FOREGROUND_RED | FOREGROUND_GREEN; + break; + case 34: + colour = FOREGROUND_BLUE; + break; + case 35: + colour = FOREGROUND_RED | FOREGROUND_BLUE; + break; + case 36: + colour = FOREGROUND_BLUE | FOREGROUND_GREEN; + break; + case 37: + colour = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN; + break; + } + } + + flushOutput(current); + + if (reverse) { + SetConsoleTextAttribute(current->outh, BACKGROUND_INTENSITY); + } + else { + SetConsoleTextAttribute(current->outh, colour | bold); + } +} + +static void eraseEol(struct current *current) +{ + COORD pos; + DWORD n; + + pos.X = (SHORT) current->x; + pos.Y = (SHORT) current->y; + + FillConsoleOutputCharacter(current->outh, ' ', current->cols - current->x, pos, &n); +} + +static void setCursorXY(struct current *current) +{ + COORD pos; + + pos.X = (SHORT) current->x; + pos.Y = (SHORT) current->y; + + SetConsoleCursorPosition(current->outh, pos); +} + + +static void setCursorPos(struct current *current, int x) +{ + current->x = x; + setCursorXY(current); +} + +static void cursorUp(struct current *current, int n) +{ + current->y -= n; + setCursorXY(current); +} + +static void cursorDown(struct current *current, int n) +{ + current->y += n; + setCursorXY(current); +} + +static int fd_read(struct current *current) +{ + DWORD n; + INPUT_RECORD irec; + KEY_EVENT_RECORD evrec; + BOOL altgr; + + while (1) { + if (WaitForSingleObject(current->inh, INFINITE) != WAIT_OBJECT_0) return -1; + if (!ReadConsoleInputW(current->inh, &irec, 1, &n)) return -1; + if (!n) return 0; + + if (irec.EventType == KEY_EVENT && irec.Event.KeyEvent.bKeyDown) { + evrec = irec.Event.KeyEvent; + altgr = evrec.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED); + + if (evrec.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) && !altgr) { + + /* Ctrl+Key */ + switch (evrec.uChar.UnicodeChar) { + case 'I': + case 'M': + case 'O': + case 'Q': + case 'S': + case 'X': /* These are unsupported */ + break; + default: + if (evrec.uChar.UnicodeChar >= 'A' && evrec.uChar.UnicodeChar <= 'Z') + return ctrl(evrec.uChar.UnicodeChar); + } + } + else { + switch (evrec.wVirtualKeyCode) { + case VK_RETURN: + return '\n'; + case VK_ESCAPE: + return SPECIAL_ESCAPE; + case VK_LEFT: + return SPECIAL_LEFT; + case VK_RIGHT: + return SPECIAL_RIGHT; + case VK_UP: + return SPECIAL_UP; + case VK_DOWN: + return SPECIAL_DOWN; + case VK_BACK: + return SPECIAL_BACKSPACE; + case VK_INSERT: + return SPECIAL_INSERT; + case VK_DELETE: + return SPECIAL_DELETE; + case VK_HOME: + return SPECIAL_HOME; + case VK_END: + return SPECIAL_END; + case VK_PRIOR: + return SPECIAL_PAGE_UP; + case VK_NEXT: + return SPECIAL_PAGE_DOWN; + default: +#ifndef USE_UTF8 + if (isascii(evrec.uChar.UnicodeChar)) +#endif + return evrec.uChar.UnicodeChar; + } + } + } + } + return -1; +} + +static int getWindowSize(struct current *current) +{ + CONSOLE_SCREEN_BUFFER_INFO info; + if (!GetConsoleScreenBufferInfo(current->outh, &info)) { + return -1; + } + current->cols = info.dwSize.X; + current->rows = info.dwSize.Y; + if (current->cols <= 0 || current->rows <= 0) { + current->cols = 80; + return -1; + } + current->y = info.dwCursorPosition.Y; + current->x = info.dwCursorPosition.X; + return 0; +} diff --git a/src/cli/linenoise/linenoise.c b/src/cli/linenoise/linenoise.c new file mode 100644 index 00000000..95fd824f --- /dev/null +++ b/src/cli/linenoise/linenoise.c @@ -0,0 +1,2094 @@ +/* linenoise.c -- 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/msteveb/linenoise + * (forked from 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, Salvatore Sanfilippo + * Copyright (c) 2010, Pieter Noordhuis + * Copyright (c) 2011, Steve Bennett + * + * 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. + * + * ------------------------------------------------------------------------ + * + * References: + * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html + * + * Bloat: + * - Completion? + * + * Unix/termios + * ------------ + * List of escape sequences used by this program, we do everything just + * a few sequences. In order to be so cheap we may have some + * flickering effect with some slow terminal, but the lesser sequences + * the more compatible. + * + * EL (Erase Line) + * Sequence: ESC [ 0 K + * Effect: clear from cursor to end of line + * + * CUF (CUrsor Forward) + * Sequence: ESC [ n C + * Effect: moves cursor forward n chars + * + * CR (Carriage Return) + * Sequence: \r + * Effect: moves cursor to column 1 + * + * The following are used to clear the screen: ESC [ H ESC [ 2 J + * This is actually composed of two sequences: + * + * cursorhome + * Sequence: ESC [ H + * Effect: moves the cursor to upper left corner + * + * ED2 (Clear entire screen) + * Sequence: ESC [ 2 J + * Effect: clear the whole screen + * + * == For highlighting control characters, we also use the following two == + * SO (enter StandOut) + * Sequence: ESC [ 7 m + * Effect: Uses some standout mode such as reverse video + * + * SE (Standout End) + * Sequence: ESC [ 0 m + * Effect: Exit standout mode + * + * == Only used if TIOCGWINSZ fails == + * DSR/CPR (Report cursor position) + * Sequence: ESC [ 6 n + * Effect: reports current cursor position as ESC [ NNN ; MMM R + * + * == Only used in multiline mode == + * CUU (Cursor Up) + * Sequence: ESC [ n A + * Effect: moves cursor up n chars. + * + * CUD (Cursor Down) + * Sequence: ESC [ n B + * Effect: moves cursor down n chars. + * + * win32/console + * ------------- + * If __MINGW32__ is defined, the win32 console API is used. + * This could probably be made to work for the msvc compiler too. + * This support based in part on work by Jon Griffiths. + */ + +#ifdef _WIN32 /* Windows platform, either MinGW or Visual Studio (MSVC) */ +#include +#include +#define USE_WINCONSOLE +#ifdef __MINGW32__ +#define HAVE_UNISTD_H +#endif +#else +#include +#include +#include +#define USE_TERMIOS +#define HAVE_UNISTD_H +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) && !defined(__MINGW32__) +/* Microsoft headers don't like old POSIX names */ +#define strdup _strdup +#define snprintf _snprintf +#endif + +#include "linenoise.h" +#ifndef STRINGBUF_H +#include "stringbuf.h" +#endif +#ifndef UTF8_UTIL_H +#include "utf8.h" +#endif + +#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 + +#define TAB_WIDTH 8 + +/* ctrl('A') -> 0x01 */ +#define ctrl(C) ((C) - '@') +/* meta('a') -> 0xe1 */ +#define meta(C) (-(C)) + +/* Use -ve numbers here to co-exist with normal unicode chars */ +enum { + SPECIAL_NONE, + /* don't use -1 here since that indicates error */ + SPECIAL_UP = ctrl('P'), + SPECIAL_DOWN = ctrl('N'), + SPECIAL_LEFT = ctrl('B'), + SPECIAL_RIGHT = ctrl('F'), + SPECIAL_HOME = ctrl('A'), + SPECIAL_END = ctrl('E'), + SPECIAL_ESCAPE = 27, + SPECIAL_BACKSPACE = 127, + SPECIAL_FAILURE = -1, + SPECIAL_DELETE = -2, + SPECIAL_INSERT = -3, + SPECIAL_PAGE_UP = -4, + SPECIAL_PAGE_DOWN = -5, +}; + +static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; +static int history_len = 0; +static int history_index = 0; +static char **history = NULL; + +/* Structure to contain the status of the current (being edited) line */ +struct current { + stringbuf *buf; /* Current buffer. Always null terminated */ + int pos; /* Cursor position, measured in chars */ + int cols; /* Size of the window, in chars */ + int nrows; /* How many rows are being used in multiline mode (>= 1) */ + int rpos; /* The current row containing the cursor - multiline mode only */ + int colsright; /* refreshLine() cached cols for insert_char() optimisation */ + int colsleft; /* refreshLine() cached cols for remove_char() optimisation */ + const char *prompt; + stringbuf *capture; /* capture buffer, or NULL for none. Always null terminated */ + stringbuf *output; /* used only during refreshLine() - output accumulator */ +#if defined(USE_TERMIOS) + int fd; /* Terminal fd */ +#elif defined(USE_WINCONSOLE) + HANDLE outh; /* Console output handle */ + HANDLE inh; /* Console input handle */ + int rows; /* Screen rows */ + int x; /* Current column during output */ + int y; /* Current row */ +#ifdef USE_UTF8 + #define UBUF_MAX_CHARS 132 + WORD ubuf[UBUF_MAX_CHARS + 1]; /* Accumulates utf16 output - one extra for final surrogate pairs */ + int ubuflen; /* length used in ubuf */ + int ubufcols; /* how many columns are represented by the chars in ubuf? */ +#endif +#endif +}; + +static int fd_read(struct current *current); +static int getWindowSize(struct current *current); +static void cursorDown(struct current *current, int n); +static void cursorUp(struct current *current, int n); +static void eraseEol(struct current *current); +static void refreshLine(struct current *current); +static void refreshLineAlt(struct current *current, const char *prompt, const char *buf, int cursor_pos); +static void setCursorPos(struct current *current, int x); +static void setOutputHighlight(struct current *current, const int *props, int nprops); +static void set_current(struct current *current, const char *str); + +static int fd_isatty(struct current *current) +{ +#ifdef USE_TERMIOS + return isatty(current->fd); +#else + (void)current; + return 0; +#endif +} + +void linenoiseHistoryFree(void) { + if (history) { + int j; + + for (j = 0; j < history_len; j++) + free(history[j]); + free(history); + history = NULL; + history_len = 0; + } +} + +typedef enum { + EP_START, /* looking for ESC */ + EP_ESC, /* looking for [ */ + EP_DIGITS, /* parsing digits */ + EP_PROPS, /* parsing digits or semicolons */ + EP_END, /* ok */ + EP_ERROR, /* error */ +} ep_state_t; + +struct esc_parser { + ep_state_t state; + int props[5]; /* properties are stored here */ + int maxprops; /* size of the props[] array */ + int numprops; /* number of properties found */ + int termchar; /* terminator char, or 0 for any alpha */ + int current; /* current (partial) property value */ +}; + +/** + * Initialise the escape sequence parser at *parser. + * + * If termchar is 0 any alpha char terminates ok. Otherwise only the given + * char terminates successfully. + * Run the parser state machine with calls to parseEscapeSequence() for each char. + */ +static void initParseEscapeSeq(struct esc_parser *parser, int termchar) +{ + parser->state = EP_START; + parser->maxprops = sizeof(parser->props) / sizeof(*parser->props); + parser->numprops = 0; + parser->current = 0; + parser->termchar = termchar; +} + +/** + * Pass character 'ch' into the state machine to parse: + * 'ESC' '[' (';' )* + * + * The first character must be ESC. + * Returns the current state. The state machine is done when it returns either EP_END + * or EP_ERROR. + * + * On EP_END, the "property/attribute" values can be read from parser->props[] + * of length parser->numprops. + */ +static int parseEscapeSequence(struct esc_parser *parser, int ch) +{ + switch (parser->state) { + case EP_START: + parser->state = (ch == '\x1b') ? EP_ESC : EP_ERROR; + break; + case EP_ESC: + parser->state = (ch == '[') ? EP_DIGITS : EP_ERROR; + break; + case EP_PROPS: + if (ch == ';') { + parser->state = EP_DIGITS; +donedigits: + if (parser->numprops + 1 < parser->maxprops) { + parser->props[parser->numprops++] = parser->current; + parser->current = 0; + } + break; + } + /* fall through */ + case EP_DIGITS: + if (ch >= '0' && ch <= '9') { + parser->current = parser->current * 10 + (ch - '0'); + parser->state = EP_PROPS; + break; + } + /* must be terminator */ + if (parser->termchar != ch) { + if (parser->termchar != 0 || !((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))) { + parser->state = EP_ERROR; + break; + } + } + parser->state = EP_END; + goto donedigits; + case EP_END: + parser->state = EP_ERROR; + break; + case EP_ERROR: + break; + } + return parser->state; +} + +/*#define DEBUG_REFRESHLINE*/ + +#ifdef DEBUG_REFRESHLINE +#define DRL(ARGS...) fprintf(dfh, ARGS) +static FILE *dfh; + +static void DRL_CHAR(int ch) +{ + if (ch < ' ') { + DRL("^%c", ch + '@'); + } + else if (ch > 127) { + DRL("\\u%04x", ch); + } + else { + DRL("%c", ch); + } +} +static void DRL_STR(const char *str) +{ + while (*str) { + int ch; + int n = utf8_tounicode(str, &ch); + str += n; + DRL_CHAR(ch); + } +} +#else +#define DRL(...) +#define DRL_CHAR(ch) +#define DRL_STR(str) +#endif + +#if defined(USE_WINCONSOLE) +#include "linenoise-win32.c" +#endif + +#if defined(USE_TERMIOS) +static void outputChars(struct current *current, const char *buf, int len); +static void linenoiseAtExit(void); +static struct termios orig_termios; /* in order to restore at exit */ +static int rawmode = 0; /* for atexit() function to check if restore is needed*/ +static int atexit_registered = 0; /* register atexit just 1 time */ + +static const char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; + +static int isUnsupportedTerm(void) { + char *term = getenv("TERM"); + + if (term) { + int j; + for (j = 0; unsupported_term[j]; j++) { + if (strcmp(term, unsupported_term[j]) == 0) { + return 1; + } + } + } + return 0; +} + +static int enableRawMode(struct current *current) { + struct termios raw; + + current->fd = STDIN_FILENO; + current->cols = 0; + + if (!isatty(current->fd) || isUnsupportedTerm() || + tcgetattr(current->fd, &orig_termios) == -1) { +fatal: + errno = ENOTTY; + return -1; + } + + if (!atexit_registered) { + atexit(linenoiseAtExit); + atexit_registered = 1; + } + + raw = orig_termios; /* modify the original mode */ + /* input modes: no break, no CR to NL, no parity check, no strip char, + * no start/stop output control. */ + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + /* output modes - actually, no need to disable post processing */ + /*raw.c_oflag &= ~(OPOST);*/ + /* control modes - set 8 bit chars */ + raw.c_cflag |= (CS8); + /* local modes - choing off, canonical off, no extended functions, + * no signal chars (^Z,^C) */ + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + /* control chars - set return condition: min number of bytes and timer. + * We want read to return every single byte, without timeout. */ + raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ + + /* put terminal in raw mode after flushing */ + if (tcsetattr(current->fd,TCSADRAIN,&raw) < 0) { + goto fatal; + } + + outputChars(current, "\x1b[g", -1); + rawmode = 1; + return 0; +} + +static void disableRawMode(struct current *current) { + /* Don't even check the return value as it's too late. */ + if (rawmode && tcsetattr(current->fd,TCSADRAIN,&orig_termios) != -1) + rawmode = 0; +} + +/* At exit we'll try to fix the terminal to the initial conditions. */ +static void linenoiseAtExit(void) { + if (rawmode) { + tcsetattr(STDIN_FILENO, TCSADRAIN, &orig_termios); + } + linenoiseHistoryFree(); +} + +/* gcc/glibc insists that we care about the return code of write! + * Clarification: This means that a void-cast like "(void) (EXPR)" + * does not work. + */ +#define IGNORE_RC(EXPR) if (EXPR) {} + +/** + * Output bytes directly, or accumulate output (if current->output is set) + */ +static void outputChars(struct current *current, const char *buf, int len) +{ + if (len < 0) { + len = strlen(buf); + } + if (current->output) { + sb_append_len(current->output, buf, len); + } + else { + IGNORE_RC(write(current->fd, buf, len)); + } +} + +/* Like outputChars, but using printf-style formatting + */ +static void outputFormatted(struct current *current, const char *format, ...) +{ + va_list args; + char buf[64]; + int n; + + va_start(args, format); + n = vsnprintf(buf, sizeof(buf), format, args); + /* This will never happen because we are sure to use outputFormatted() only for short sequences */ + assert(n < (int)sizeof(buf)); + va_end(args); + outputChars(current, buf, n); +} + +static void cursorToLeft(struct current *current) +{ + outputChars(current, "\r", -1); +} + +static void setOutputHighlight(struct current *current, const int *props, int nprops) +{ + outputChars(current, "\x1b[", -1); + while (nprops--) { + outputFormatted(current, "%d%c", *props, (nprops == 0) ? 'm' : ';'); + props++; + } +} + +static void eraseEol(struct current *current) +{ + outputChars(current, "\x1b[0K", -1); +} + +static void setCursorPos(struct current *current, int x) +{ + if (x == 0) { + cursorToLeft(current); + } + else { + outputFormatted(current, "\r\x1b[%dC", x); + } +} + +static void cursorUp(struct current *current, int n) +{ + if (n) { + outputFormatted(current, "\x1b[%dA", n); + } +} + +static void cursorDown(struct current *current, int n) +{ + if (n) { + outputFormatted(current, "\x1b[%dB", n); + } +} + +void linenoiseClearScreen(void) +{ + IGNORE_RC(write(STDOUT_FILENO, "\x1b[H\x1b[2J", 7)); +} + +/** + * Reads a char from 'fd', waiting at most 'timeout' milliseconds. + * + * A timeout of -1 means to wait forever. + * + * Returns -1 if no char is received within the time or an error occurs. + */ +static int fd_read_char(int fd, int timeout) +{ + struct pollfd p; + unsigned char c; + + p.fd = fd; + p.events = POLLIN; + + if (poll(&p, 1, timeout) == 0) { + /* timeout */ + return -1; + } + if (read(fd, &c, 1) != 1) { + return -1; + } + return c; +} + +/** + * Reads a complete utf-8 character + * and returns the unicode value, or -1 on error. + */ +static int fd_read(struct current *current) +{ +#ifdef USE_UTF8 + char buf[MAX_UTF8_LEN]; + int n; + int i; + int c; + + if (read(current->fd, &buf[0], 1) != 1) { + return -1; + } + n = utf8_charlen(buf[0]); + if (n < 1) { + return -1; + } + for (i = 1; i < n; i++) { + if (read(current->fd, &buf[i], 1) != 1) { + return -1; + } + } + /* decode and return the character */ + utf8_tounicode(buf, &c); + return c; +#else + return fd_read_char(current->fd, -1); +#endif +} + + +/** + * Stores the current cursor column in '*cols'. + * Returns 1 if OK, or 0 if failed to determine cursor pos. + */ +static int queryCursor(struct current *current, int* cols) +{ + struct esc_parser parser; + int ch; + + /* Should not be buffering this output, it needs to go immediately */ + assert(current->output == NULL); + + /* control sequence - report cursor location */ + outputChars(current, "\x1b[6n", -1); + + /* Parse the response: ESC [ rows ; cols R */ + initParseEscapeSeq(&parser, 'R'); + while ((ch = fd_read_char(current->fd, 100)) > 0) { + switch (parseEscapeSequence(&parser, ch)) { + default: + continue; + case EP_END: + if (parser.numprops == 2 && parser.props[1] < 1000) { + *cols = parser.props[1]; + return 1; + } + break; + case EP_ERROR: + break; + } + /* failed */ + break; + } + return 0; +} + +/** + * Updates current->cols with the current window size (width) + */ +static int getWindowSize(struct current *current) +{ + struct winsize ws; + + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col != 0) { + current->cols = ws.ws_col; + return 0; + } + + /* Failed to query the window size. Perhaps we are on a serial terminal. + * Try to query the width by sending the cursor as far to the right + * and reading back the cursor position. + * Note that this is only done once per call to linenoise rather than + * every time the line is refreshed for efficiency reasons. + * + * In more detail, we: + * (a) request current cursor position, + * (b) move cursor far right, + * (c) request cursor position again, + * (d) at last move back to the old position. + * This gives us the width without messing with the externally + * visible cursor position. + */ + + if (current->cols == 0) { + int here; + + /* If anything fails => default 80 */ + current->cols = 80; + + /* (a) */ + if (queryCursor (current, &here)) { + /* (b) */ + setCursorPos(current, 999); + + /* (c). Note: If (a) succeeded, then (c) should as well. + * For paranoia we still check and have a fallback action + * for (d) in case of failure.. + */ + if (queryCursor (current, ¤t->cols)) { + /* (d) Reset the cursor back to the original location. */ + if (current->cols > here) { + setCursorPos(current, here); + } + } + } + } + + return 0; +} + +/** + * If SPECIAL_ESCAPE was received, reads subsequent + * chars to determine if this is a known special key. + * + * Returns SPECIAL_NONE if unrecognised, or -1 if EOF. + * + * If no additional char is received within a short time, + * SPECIAL_ESCAPE is returned. + */ +static int check_special(int fd) +{ + int c = fd_read_char(fd, 50); + int c2; + + if (c < 0) { + return SPECIAL_ESCAPE; + } + else if (c >= 'a' && c <= 'z') { + /* esc-a => meta-a */ + return meta(c); + } + + c2 = fd_read_char(fd, 50); + if (c2 < 0) { + return c2; + } + if (c == '[' || c == 'O') { + /* Potential arrow key */ + switch (c2) { + case 'A': + return SPECIAL_UP; + case 'B': + return SPECIAL_DOWN; + case 'C': + return SPECIAL_RIGHT; + case 'D': + return SPECIAL_LEFT; + case 'F': + return SPECIAL_END; + case 'H': + return SPECIAL_HOME; + } + } + if (c == '[' && c2 >= '1' && c2 <= '8') { + /* extended escape */ + c = fd_read_char(fd, 50); + if (c == '~') { + switch (c2) { + case '2': + return SPECIAL_INSERT; + case '3': + return SPECIAL_DELETE; + case '5': + return SPECIAL_PAGE_UP; + case '6': + return SPECIAL_PAGE_DOWN; + case '7': + return SPECIAL_HOME; + case '8': + return SPECIAL_END; + } + } + while (c != -1 && c != '~') { + /* .e.g \e[12~ or '\e[11;2~ discard the complete sequence */ + c = fd_read_char(fd, 50); + } + } + + return SPECIAL_NONE; +} +#endif + +static void clearOutputHighlight(struct current *current) +{ + int nohighlight = 0; + setOutputHighlight(current, &nohighlight, 1); +} + +static void outputControlChar(struct current *current, char ch) +{ + int reverse = 7; + setOutputHighlight(current, &reverse, 1); + outputChars(current, "^", 1); + outputChars(current, &ch, 1); + clearOutputHighlight(current); +} + +#ifndef utf8_getchars +static int utf8_getchars(char *buf, int c) +{ +#ifdef USE_UTF8 + return utf8_fromunicode(buf, c); +#else + *buf = c; + return 1; +#endif +} +#endif + +/** + * Returns the unicode character at the given offset, + * or -1 if none. + */ +static int get_char(struct current *current, int pos) +{ + if (pos >= 0 && pos < sb_chars(current->buf)) { + int c; + int i = utf8_index(sb_str(current->buf), pos); + (void)utf8_tounicode(sb_str(current->buf) + i, &c); + return c; + } + return -1; +} + +static int char_display_width(int ch) +{ + if (ch < ' ') { + /* control chars take two positions */ + return 2; + } + else { + return utf8_width(ch); + } +} + +#ifndef NO_COMPLETION +static linenoiseCompletionCallback *completionCallback = NULL; +static void *completionUserdata = NULL; +static int showhints = 1; +static linenoiseHintsCallback *hintsCallback = NULL; +static linenoiseFreeHintsCallback *freeHintsCallback = NULL; +static void *hintsUserdata = NULL; + +static void beep() { +#ifdef USE_TERMIOS + fprintf(stderr, "\x7"); + fflush(stderr); +#endif +} + +static void freeCompletions(linenoiseCompletions *lc) { + size_t i; + for (i = 0; i < lc->len; i++) + free(lc->cvec[i]); + free(lc->cvec); +} + +static int completeLine(struct current *current) { + linenoiseCompletions lc = { 0, NULL }; + int c = 0; + + completionCallback(sb_str(current->buf),&lc,completionUserdata); + if (lc.len == 0) { + beep(); + } else { + size_t stop = 0, i = 0; + + while(!stop) { + /* Show completion or original buffer */ + if (i < lc.len) { + int chars = utf8_strlen(lc.cvec[i], -1); + refreshLineAlt(current, current->prompt, lc.cvec[i], chars); + } else { + refreshLine(current); + } + + c = fd_read(current); + if (c == -1) { + break; + } + + switch(c) { + case '\t': /* tab */ + i = (i+1) % (lc.len+1); + if (i == lc.len) beep(); + break; + case SPECIAL_ESCAPE: /* escape */ + /* Re-show original buffer */ + if (i < lc.len) { + refreshLine(current); + } + stop = 1; + break; + default: + /* Update buffer and return */ + if (i < lc.len) { + set_current(current,lc.cvec[i]); + } + stop = 1; + break; + } + } + } + + freeCompletions(&lc); + return c; /* Return last read character */ +} + +/* Register a callback function to be called for tab-completion. + Returns the prior callback so that the caller may (if needed) + restore it when done. */ +linenoiseCompletionCallback * linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn, void *userdata) { + linenoiseCompletionCallback * old = completionCallback; + completionCallback = fn; + completionUserdata = userdata; + return old; +} + +void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { + lc->cvec = (char **)realloc(lc->cvec,sizeof(char*)*(lc->len+1)); + lc->cvec[lc->len++] = strdup(str); +} + +void linenoiseSetHintsCallback(linenoiseHintsCallback *callback, void *userdata) +{ + hintsCallback = callback; + hintsUserdata = userdata; +} + +void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *callback) +{ + freeHintsCallback = callback; +} + +#endif + + +static const char *reduceSingleBuf(const char *buf, int availcols, int *cursor_pos) +{ + /* We have availcols columns available. + * If necessary, strip chars off the front of buf until *cursor_pos + * fits within availcols + */ + int needcols = 0; + int pos = 0; + int new_cursor_pos = *cursor_pos; + const char *pt = buf; + + DRL("reduceSingleBuf: availcols=%d, cursor_pos=%d\n", availcols, *cursor_pos); + + while (*pt) { + int ch; + int n = utf8_tounicode(pt, &ch); + pt += n; + + needcols += char_display_width(ch); + + /* If we need too many cols, strip + * chars off the front of buf to make it fit. + * We keep 3 extra cols to the right of the cursor. + * 2 for possible wide chars, 1 for the last column that + * can't be used. + */ + while (needcols >= availcols - 3) { + n = utf8_tounicode(buf, &ch); + buf += n; + needcols -= char_display_width(ch); + DRL_CHAR(ch); + + /* and adjust the apparent cursor position */ + new_cursor_pos--; + + if (buf == pt) { + /* can't remove more than this */ + break; + } + } + + if (pos++ == *cursor_pos) { + break; + } + + } + DRL(""); + DRL_STR(buf); + DRL("\nafter reduce, needcols=%d, new_cursor_pos=%d\n", needcols, new_cursor_pos); + + /* Done, now new_cursor_pos contains the adjusted cursor position + * and buf points to he adjusted start + */ + *cursor_pos = new_cursor_pos; + return buf; +} + +static int mlmode = 0; + +void linenoiseSetMultiLine(int enableml) +{ + mlmode = enableml; +} + +/* Helper of refreshSingleLine() and refreshMultiLine() to show hints + * to the right of the prompt. + * Returns 1 if a hint was shown, or 0 if not + * If 'display' is 0, does no output. Just returns the appropriate return code. + */ +static int refreshShowHints(struct current *current, const char *buf, int availcols, int display) +{ + int rc = 0; + if (showhints && hintsCallback && availcols > 0) { + int bold = 0; + int color = -1; + char *hint = hintsCallback(buf, &color, &bold, hintsUserdata); + if (hint) { + rc = 1; + if (display) { + const char *pt; + if (bold == 1 && color == -1) color = 37; + if (bold || color > 0) { + int props[3] = { bold, color, 49 }; /* bold, color, fgnormal */ + setOutputHighlight(current, props, 3); + } + DRL("", bold, color); + pt = hint; + while (*pt) { + int ch; + int n = utf8_tounicode(pt, &ch); + int width = char_display_width(ch); + + if (width >= availcols) { + DRL(""); + break; + } + DRL_CHAR(ch); + + availcols -= width; + outputChars(current, pt, n); + pt += n; + } + if (bold || color > 0) { + clearOutputHighlight(current); + } + /* Call the function to free the hint returned. */ + if (freeHintsCallback) freeHintsCallback(hint, hintsUserdata); + } + } + } + return rc; +} + +#ifdef USE_TERMIOS +static void refreshStart(struct current *current) +{ + /* We accumulate all output here */ + assert(current->output == NULL); + current->output = sb_alloc(); +} + +static void refreshEnd(struct current *current) +{ + /* Output everything at once */ + IGNORE_RC(write(current->fd, sb_str(current->output), sb_len(current->output))); + sb_free(current->output); + current->output = NULL; +} + +static void refreshStartChars(struct current *current) +{ + (void)current; +} + +static void refreshNewline(struct current *current) +{ + DRL(""); + outputChars(current, "\n", 1); +} + +static void refreshEndChars(struct current *current) +{ + (void)current; +} +#endif + +static void refreshLineAlt(struct current *current, const char *prompt, const char *buf, int cursor_pos) +{ + int i; + const char *pt; + int displaycol; + int displayrow; + int visible; + int currentpos; + int notecursor; + int cursorcol = 0; + int cursorrow = 0; + int hint; + struct esc_parser parser; + +#ifdef DEBUG_REFRESHLINE + dfh = fopen("linenoise.debuglog", "a"); +#endif + + /* Should intercept SIGWINCH. For now, just get the size every time */ + getWindowSize(current); + + refreshStart(current); + + DRL("wincols=%d, cursor_pos=%d, nrows=%d, rpos=%d\n", current->cols, cursor_pos, current->nrows, current->rpos); + + /* Here is the plan: + * (a) move the the bottom row, going down the appropriate number of lines + * (b) move to beginning of line and erase the current line + * (c) go up one line and do the same, until we have erased up to the first row + * (d) output the prompt, counting cols and rows, taking into account escape sequences + * (e) output the buffer, counting cols and rows + * (e') when we hit the current pos, save the cursor position + * (f) move the cursor to the saved cursor position + * (g) save the current cursor row and number of rows + */ + + /* (a) - The cursor is currently at row rpos */ + cursorDown(current, current->nrows - current->rpos - 1); + DRL("", current->nrows - current->rpos - 1); + + /* (b), (c) - Erase lines upwards until we get to the first row */ + for (i = 0; i < current->nrows; i++) { + if (i) { + DRL(""); + cursorUp(current, 1); + } + DRL(""); + cursorToLeft(current); + eraseEol(current); + } + DRL("\n"); + + /* (d) First output the prompt. control sequences don't take up display space */ + pt = prompt; + displaycol = 0; /* current display column */ + displayrow = 0; /* current display row */ + visible = 1; + + refreshStartChars(current); + + while (*pt) { + int width; + int ch; + int n = utf8_tounicode(pt, &ch); + + if (visible && ch == SPECIAL_ESCAPE) { + /* The start of an escape sequence, so not visible */ + visible = 0; + initParseEscapeSeq(&parser, 'm'); + DRL(""); + } + + if (ch == '\n' || ch == '\r') { + /* treat both CR and NL the same and force wrap */ + refreshNewline(current); + displaycol = 0; + displayrow++; + } + else { + width = visible * utf8_width(ch); + + displaycol += width; + if (displaycol >= current->cols) { + /* need to wrap to the next line because of newline or if it doesn't fit + * XXX this is a problem in single line mode + */ + refreshNewline(current); + displaycol = width; + displayrow++; + } + + DRL_CHAR(ch); +#ifdef USE_WINCONSOLE + if (visible) { + outputChars(current, pt, n); + } +#else + outputChars(current, pt, n); +#endif + } + pt += n; + + if (!visible) { + switch (parseEscapeSequence(&parser, ch)) { + case EP_END: + visible = 1; + setOutputHighlight(current, parser.props, parser.numprops); + DRL("", parser.numprops); + break; + case EP_ERROR: + DRL(""); + visible = 1; + break; + } + } + } + + /* Now we are at the first line with all lines erased */ + DRL("\nafter prompt: displaycol=%d, displayrow=%d\n", displaycol, displayrow); + + + /* (e) output the buffer, counting cols and rows */ + if (mlmode == 0) { + /* In this mode we may need to trim chars from the start of the buffer until the + * cursor fits in the window. + */ + pt = reduceSingleBuf(buf, current->cols - displaycol, &cursor_pos); + } + else { + pt = buf; + } + + currentpos = 0; + notecursor = -1; + + while (*pt) { + int ch; + int n = utf8_tounicode(pt, &ch); + int width; + + if(ch == '\t') { + width = TAB_WIDTH - (displaycol % TAB_WIDTH); + } + else { + width = char_display_width(ch); + } + + if (currentpos == cursor_pos) { + /* (e') wherever we output this character is where we want the cursor */ + notecursor = 1; + } + + if (displaycol + width >= current->cols) { + if (mlmode == 0) { + /* In single line mode stop once we print as much as we can on one line */ + DRL(""); + break; + } + /* need to wrap to the next line since it doesn't fit */ + refreshNewline(current); + displaycol = 0; + displayrow++; + } + + if (notecursor == 1) { + /* (e') Save this position as the current cursor position */ + cursorcol = displaycol; + cursorrow = displayrow; + notecursor = 0; + DRL(""); + } + + displaycol += width; + + if (ch != '\t' && ch < ' ') { + outputControlChar(current, ch + '@'); + } + else { + outputChars(current, pt, n); + } + DRL_CHAR(ch); + if (width != 1) { + DRL("", width); + } + + pt += n; + currentpos++; + } + + /* If we didn't see the cursor, it is at the current location */ + if (notecursor) { + DRL(""); + cursorcol = displaycol; + cursorrow = displayrow; + } + + DRL("\nafter buf: displaycol=%d, displayrow=%d, cursorcol=%d, cursorrow=%d\n", displaycol, displayrow, cursorcol, cursorrow); + + /* (f) show hints */ + hint = refreshShowHints(current, buf, current->cols - displaycol, 1); + + /* Remember how many many cols are available for insert optimisation */ + if (prompt == current->prompt && hint == 0) { + current->colsright = current->cols - displaycol; + current->colsleft = displaycol; + } + else { + /* Can't optimise */ + current->colsright = 0; + current->colsleft = 0; + } + DRL("\nafter hints: colsleft=%d, colsright=%d\n\n", current->colsleft, current->colsright); + + refreshEndChars(current); + + /* (g) move the cursor to the correct place */ + cursorUp(current, displayrow - cursorrow); + setCursorPos(current, cursorcol); + + /* (h) Update the number of rows if larger, but never reduce this */ + if (displayrow >= current->nrows) { + current->nrows = displayrow + 1; + } + /* And remember the row that the cursor is on */ + current->rpos = cursorrow; + + refreshEnd(current); + +#ifdef DEBUG_REFRESHLINE + fclose(dfh); +#endif +} + +static void refreshLine(struct current *current) +{ + refreshLineAlt(current, current->prompt, sb_str(current->buf), current->pos); +} + +static void set_current(struct current *current, const char *str) +{ + sb_clear(current->buf); + sb_append(current->buf, str); + current->pos = sb_chars(current->buf); +} + +/** + * Removes the char at 'pos'. + * + * Returns 1 if the line needs to be refreshed, 2 if not + * and 0 if nothing was removed + */ +static int remove_char(struct current *current, int pos) +{ + if (pos >= 0 && pos < sb_chars(current->buf)) { + int offset = utf8_index(sb_str(current->buf), pos); + int nbytes = utf8_index(sb_str(current->buf) + offset, 1); + int rc = 1; + + /* Now we try to optimise in the simple but very common case that: + * - outputChars() can be used directly (not win32) + * - we are removing the char at EOL + * - the buffer is not empty + * - there are columns available to the left + * - the char being deleted is not a wide or utf-8 character + * - no hints are being shown + */ + if (current->output && current->pos == pos + 1 && current->pos == sb_chars(current->buf) && pos > 0) { +#ifdef USE_UTF8 + /* Could implement utf8_prev_len() but simplest just to not optimise this case */ + char last = sb_str(current->buf)[offset]; +#else + char last = 0; +#endif + if (current->colsleft > 0 && (last & 0x80) == 0) { + /* Have cols on the left and not a UTF-8 char or continuation */ + /* Yes, can optimise */ + current->colsleft--; + current->colsright++; + rc = 2; + } + } + + sb_delete(current->buf, offset, nbytes); + + if (current->pos > pos) { + current->pos--; + } + if (rc == 2) { + if (refreshShowHints(current, sb_str(current->buf), current->colsright, 0)) { + /* A hint needs to be shown, so can't optimise after all */ + rc = 1; + } + else { + /* optimised output */ + outputChars(current, "\b \b", 3); + } + } + return rc; + return 1; + } + return 0; +} + +/** + * Insert 'ch' at position 'pos' + * + * Returns 1 if the line needs to be refreshed, 2 if not + * and 0 if nothing was inserted (no room) + */ +static int insert_char(struct current *current, int pos, int ch) +{ + if (pos >= 0 && pos <= sb_chars(current->buf)) { + char buf[MAX_UTF8_LEN + 1]; + int offset = utf8_index(sb_str(current->buf), pos); + int n = utf8_getchars(buf, ch); + int rc = 1; + + /* null terminate since sb_insert() requires it */ + buf[n] = 0; + + /* Now we try to optimise in the simple but very common case that: + * - outputChars() can be used directly (not win32) + * - we are inserting at EOL + * - there are enough columns available + * - no hints are being shown + */ + if (current->output && pos == current->pos && pos == sb_chars(current->buf)) { + int width = char_display_width(ch); + if (current->colsright > width) { + /* Yes, can optimise */ + current->colsright -= width; + current->colsleft -= width; + rc = 2; + } + } + sb_insert(current->buf, offset, buf); + if (current->pos >= pos) { + current->pos++; + } + if (rc == 2) { + if (refreshShowHints(current, sb_str(current->buf), current->colsright, 0)) { + /* A hint needs to be shown, so can't optimise after all */ + rc = 1; + } + else { + /* optimised output */ + outputChars(current, buf, n); + } + } + return rc; + } + return 0; +} + +/** + * Captures up to 'n' characters starting at 'pos' for the cut buffer. + * + * This replaces any existing characters in the cut buffer. + */ +static void capture_chars(struct current *current, int pos, int nchars) +{ + if (pos >= 0 && (pos + nchars - 1) < sb_chars(current->buf)) { + int offset = utf8_index(sb_str(current->buf), pos); + int nbytes = utf8_index(sb_str(current->buf) + offset, nchars); + + if (nbytes > 0) { + if (current->capture) { + sb_clear(current->capture); + } + else { + current->capture = sb_alloc(); + } + sb_append_len(current->capture, sb_str(current->buf) + offset, nbytes); + } + } +} + +/** + * Removes up to 'n' characters at cursor position 'pos'. + * + * Returns 0 if no chars were removed or non-zero otherwise. + */ +static int remove_chars(struct current *current, int pos, int n) +{ + int removed = 0; + + /* First save any chars which will be removed */ + capture_chars(current, pos, n); + + while (n-- && remove_char(current, pos)) { + removed++; + } + return removed; +} +/** + * Inserts the characters (string) 'chars' at the cursor position 'pos'. + * + * Returns 0 if no chars were inserted or non-zero otherwise. + */ +static int insert_chars(struct current *current, int pos, const char *chars) +{ + int inserted = 0; + + while (*chars) { + int ch; + int n = utf8_tounicode(chars, &ch); + if (insert_char(current, pos, ch) == 0) { + break; + } + inserted++; + pos++; + chars += n; + } + return inserted; +} + +static int skip_space_nonspace(struct current *current, int dir, int check_is_space) +{ + int moved = 0; + int checkoffset = (dir < 0) ? -1 : 0; + int limit = (dir < 0) ? 0 : sb_chars(current->buf); + while (current->pos != limit && (get_char(current, current->pos + checkoffset) == ' ') == check_is_space) { + current->pos += dir; + moved++; + } + return moved; +} + +static int skip_space(struct current *current, int dir) +{ + return skip_space_nonspace(current, dir, 1); +} + +static int skip_nonspace(struct current *current, int dir) +{ + return skip_space_nonspace(current, dir, 0); +} + +static void set_history_index(struct current *current, int new_index) +{ + if (history_len > 1) { + /* Update the current history entry before to + * overwrite it with the next one. */ + free(history[history_len - 1 - history_index]); + history[history_len - 1 - history_index] = strdup(sb_str(current->buf)); + /* Show the new entry */ + history_index = new_index; + if (history_index < 0) { + history_index = 0; + } else if (history_index >= history_len) { + history_index = history_len - 1; + } else { + set_current(current, history[history_len - 1 - history_index]); + refreshLine(current); + } + } +} + +/** + * Returns the keycode to process, or 0 if none. + */ +static int reverseIncrementalSearch(struct current *current) +{ + /* Display the reverse-i-search prompt and process chars */ + char rbuf[50]; + char rprompt[80]; + int rchars = 0; + int rlen = 0; + int searchpos = history_len - 1; + int c; + + rbuf[0] = 0; + while (1) { + int n = 0; + const char *p = NULL; + int skipsame = 0; + int searchdir = -1; + + snprintf(rprompt, sizeof(rprompt), "(reverse-i-search)'%s': ", rbuf); + refreshLineAlt(current, rprompt, sb_str(current->buf), current->pos); + c = fd_read(current); + if (c == ctrl('H') || c == SPECIAL_BACKSPACE) { + if (rchars) { + int p_ind = utf8_index(rbuf, --rchars); + rbuf[p_ind] = 0; + rlen = strlen(rbuf); + } + continue; + } +#ifdef USE_TERMIOS + if (c == SPECIAL_ESCAPE) { + c = check_special(current->fd); + } +#endif + if (c == SPECIAL_UP) { + /* Search for the previous (earlier) match */ + if (searchpos > 0) { + searchpos--; + } + skipsame = 1; + } + else if (c == SPECIAL_DOWN) { + /* Search for the next (later) match */ + if (searchpos < history_len) { + searchpos++; + } + searchdir = 1; + skipsame = 1; + } + else if (c == SPECIAL_UP) { + /* Exit Ctrl-R mode and go to the previous history line from the current search pos */ + set_history_index(current, history_len - searchpos); + c = 0; + break; + } + else if (c == SPECIAL_DOWN) { + /* Exit Ctrl-R mode and go to the next history line from the current search pos */ + set_history_index(current, history_len - searchpos - 2); + c = 0; + break; + } + else if (c >= ' ' && c <= '~') { + /* >= here to allow for null terminator */ + if (rlen >= (int)sizeof(rbuf) - MAX_UTF8_LEN) { + continue; + } + + n = utf8_getchars(rbuf + rlen, c); + rlen += n; + rchars++; + rbuf[rlen] = 0; + + /* Adding a new char resets the search location */ + searchpos = history_len - 1; + } + else { + /* Exit from incremental search mode */ + break; + } + + /* Now search through the history for a match */ + for (; searchpos >= 0 && searchpos < history_len; searchpos += searchdir) { + p = strstr(history[searchpos], rbuf); + if (p) { + /* Found a match */ + if (skipsame && strcmp(history[searchpos], sb_str(current->buf)) == 0) { + /* But it is identical, so skip it */ + continue; + } + /* Copy the matching line and set the cursor position */ + history_index = history_len - 1 - searchpos; + set_current(current,history[searchpos]); + current->pos = utf8_strlen(history[searchpos], p - history[searchpos]); + break; + } + } + if (!p && n) { + /* No match, so don't add it */ + rchars--; + rlen -= n; + rbuf[rlen] = 0; + } + } + if (c == ctrl('G') || c == ctrl('C')) { + /* ctrl-g terminates the search with no effect */ + set_current(current, ""); + history_index = 0; + c = 0; + } + else if (c == ctrl('J')) { + /* ctrl-j terminates the search leaving the buffer in place */ + history_index = 0; + c = 0; + } + /* Go process the char normally */ + refreshLine(current); + return c; +} + +static int linenoiseEdit(struct current *current) { + history_index = 0; + + refreshLine(current); + + while(1) { + int c = fd_read(current); + +#ifndef NO_COMPLETION + /* 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 == '\t' && current->pos == sb_chars(current->buf) && completionCallback != NULL) { + c = completeLine(current); + } +#endif + if (c == ctrl('R')) { + /* reverse incremental search will provide an alternative keycode or 0 for none */ + c = reverseIncrementalSearch(current); + /* go on to process the returned char normally */ + } + +#ifdef USE_TERMIOS + if (c == SPECIAL_ESCAPE) { /* escape sequence */ + c = check_special(current->fd); + } +#endif + if (c == -1) { + /* Return on errors */ + return sb_len(current->buf); + } + + switch(c) { + case SPECIAL_NONE: + break; + case '\r': /* enter/CR */ + case '\n': /* LF */ + history_len--; + free(history[history_len]); + current->pos = sb_chars(current->buf); + if (mlmode || hintsCallback) { + showhints = 0; + refreshLine(current); + showhints = 1; + } + return sb_len(current->buf); + case ctrl('C'): /* ctrl-c */ + errno = EAGAIN; + return -1; + case ctrl('Z'): /* ctrl-z */ +#ifdef SIGTSTP + /* send ourselves SIGSUSP */ + disableRawMode(current); + raise(SIGTSTP); + /* and resume */ + enableRawMode(current); + refreshLine(current); +#endif + continue; + case SPECIAL_BACKSPACE: + case ctrl('H'): + if (remove_char(current, current->pos - 1) == 1) { + refreshLine(current); + } + break; + case ctrl('D'): /* ctrl-d */ + if (sb_len(current->buf) == 0) { + /* Empty line, so EOF */ + history_len--; + free(history[history_len]); + return -1; + } + /* Otherwise fall through to delete char to right of cursor */ + /* fall-thru */ + case SPECIAL_DELETE: + if (remove_char(current, current->pos) == 1) { + refreshLine(current); + } + break; + case SPECIAL_INSERT: + /* Ignore. Expansion Hook. + * Future possibility: Toggle Insert/Overwrite Modes + */ + break; + case meta('b'): /* meta-b, move word left */ + if (skip_nonspace(current, -1)) { + refreshLine(current); + } + else if (skip_space(current, -1)) { + skip_nonspace(current, -1); + refreshLine(current); + } + break; + case meta('f'): /* meta-f, move word right */ + if (skip_space(current, 1)) { + refreshLine(current); + } + else if (skip_nonspace(current, 1)) { + skip_space(current, 1); + refreshLine(current); + } + break; + case ctrl('W'): /* ctrl-w, delete word at left. save deleted chars */ + /* eat any spaces on the left */ + { + int pos = current->pos; + while (pos > 0 && get_char(current, pos - 1) == ' ') { + pos--; + } + + /* now eat any non-spaces on the left */ + while (pos > 0 && get_char(current, pos - 1) != ' ') { + pos--; + } + + if (remove_chars(current, pos, current->pos - pos)) { + refreshLine(current); + } + } + break; + case ctrl('T'): /* ctrl-t */ + if (current->pos > 0 && current->pos <= sb_chars(current->buf)) { + /* If cursor is at end, transpose the previous two chars */ + int fixer = (current->pos == sb_chars(current->buf)); + c = get_char(current, current->pos - fixer); + remove_char(current, current->pos - fixer); + insert_char(current, current->pos - 1, c); + refreshLine(current); + } + break; + case ctrl('V'): /* ctrl-v */ + /* Insert the ^V first */ + if (insert_char(current, current->pos, c)) { + refreshLine(current); + /* Now wait for the next char. Can insert anything except \0 */ + c = fd_read(current); + + /* Remove the ^V first */ + remove_char(current, current->pos - 1); + if (c > 0) { + /* Insert the actual char, can't be error or null */ + insert_char(current, current->pos, c); + } + refreshLine(current); + } + break; + case SPECIAL_LEFT: + if (current->pos > 0) { + current->pos--; + refreshLine(current); + } + break; + case SPECIAL_RIGHT: + if (current->pos < sb_chars(current->buf)) { + current->pos++; + refreshLine(current); + } + break; + case SPECIAL_PAGE_UP: /* move to start of history */ + set_history_index(current, history_len - 1); + break; + case SPECIAL_PAGE_DOWN: /* move to 0 == end of history, i.e. current */ + set_history_index(current, 0); + break; + case SPECIAL_UP: + set_history_index(current, history_index + 1); + break; + case SPECIAL_DOWN: + set_history_index(current, history_index - 1); + break; + case SPECIAL_HOME: + current->pos = 0; + refreshLine(current); + break; + case SPECIAL_END: + current->pos = sb_chars(current->buf); + refreshLine(current); + break; + case ctrl('U'): /* Ctrl+u, delete to beginning of line, save deleted chars. */ + if (remove_chars(current, 0, current->pos)) { + refreshLine(current); + } + break; + case ctrl('K'): /* Ctrl+k, delete from current to end of line, save deleted chars. */ + if (remove_chars(current, current->pos, sb_chars(current->buf) - current->pos)) { + refreshLine(current); + } + break; + case ctrl('Y'): /* Ctrl+y, insert saved chars at current position */ + if (current->capture && insert_chars(current, current->pos, sb_str(current->capture))) { + refreshLine(current); + } + break; + case ctrl('L'): /* Ctrl+L, clear screen */ + linenoiseClearScreen(); + /* Force recalc of window size for serial terminals */ + current->cols = 0; + current->rpos = 0; + refreshLine(current); + break; + default: + if (c >= meta('a') && c <= meta('z')) { + /* Don't insert meta chars that are not bound */ + break; + } + /* Only tab is allowed without ^V */ + if (c == '\t' || c >= ' ') { + if (insert_char(current, current->pos, c) == 1) { + refreshLine(current); + } + } + break; + } + } + return sb_len(current->buf); +} + +int linenoiseColumns(void) +{ + struct current current; + current.output = NULL; + enableRawMode (¤t); + getWindowSize (¤t); + disableRawMode (¤t); + return current.cols; +} + +/** + * Reads a line from the file handle (without the trailing NL or CRNL) + * and returns it in a stringbuf. + * Returns NULL if no characters are read before EOF or error. + * + * Note that the character count will *not* be correct for lines containing + * utf8 sequences. Do not rely on the character count. + */ +static stringbuf *sb_getline(FILE *fh) +{ + stringbuf *sb = sb_alloc(); + int c; + int n = 0; + + while ((c = getc(fh)) != EOF) { + char ch; + n++; + if (c == '\r') { + /* CRLF -> LF */ + continue; + } + if (c == '\n' || c == '\r') { + break; + } + ch = c; + /* ignore the effect of character count for partial utf8 sequences */ + sb_append_len(sb, &ch, 1); + } + if (n == 0 || sb->data == NULL) { + sb_free(sb); + return NULL; + } + return sb; +} + +char *linenoiseWithInitial(const char *prompt, const char *initial) +{ + int count; + struct current current; + stringbuf *sb; + + memset(¤t, 0, sizeof(current)); + + if (enableRawMode(¤t) == -1) { + printf("%s", prompt); + fflush(stdout); + sb = sb_getline(stdin); + if (sb && !fd_isatty(¤t)) { + printf("%s\n", sb_str(sb)); + fflush(stdout); + } + } + else { + current.buf = sb_alloc(); + current.pos = 0; + current.nrows = 1; + current.prompt = prompt; + + /* The latest history entry is always our current buffer */ + linenoiseHistoryAdd(initial); + set_current(¤t, initial); + + count = linenoiseEdit(¤t); + + disableRawMode(¤t); + printf("\n"); + + sb_free(current.capture); + if (count == -1) { + sb_free(current.buf); + return NULL; + } + sb = current.buf; + } + return sb ? sb_to_string(sb) : NULL; +} + +char *linenoise(const char *prompt) +{ + return linenoiseWithInitial(prompt, ""); +} + +/* Using a circular buffer is smarter, but a bit more complex to handle. */ +static int linenoiseHistoryAddAllocated(char *line) { + + if (history_max_len == 0) { +notinserted: + free(line); + return 0; + } + if (history == NULL) { + history = (char **)calloc(sizeof(char*), history_max_len); + } + + /* do not insert duplicate lines into history */ + if (history_len > 0 && strcmp(line, history[history_len - 1]) == 0) { + goto notinserted; + } + + if (history_len == history_max_len) { + free(history[0]); + memmove(history,history+1,sizeof(char*)*(history_max_len-1)); + history_len--; + } + history[history_len] = line; + history_len++; + return 1; +} + +int linenoiseHistoryAdd(const char *line) { + return linenoiseHistoryAddAllocated(strdup(line)); +} + +int linenoiseHistoryGetMaxLen(void) { + return history_max_len; +} + +int linenoiseHistorySetMaxLen(int len) { + char **newHistory; + + if (len < 1) return 0; + if (history) { + int tocopy = history_len; + + newHistory = (char **)calloc(sizeof(char*), len); + + /* If we can't copy everything, free the elements we'll not use. */ + if (len < tocopy) { + int j; + + for (j = 0; j < tocopy-len; j++) free(history[j]); + tocopy = len; + } + memcpy(newHistory,history+(history_len-tocopy), sizeof(char*)*tocopy); + free(history); + history = newHistory; + } + history_max_len = len; + if (history_len > history_max_len) + history_len = history_max_len; + return 1; +} + +/* Save the history in the specified file. On success 0 is returned + * otherwise -1 is returned. */ +int linenoiseHistorySave(const char *filename) { + FILE *fp = fopen(filename,"w"); + int j; + + if (fp == NULL) return -1; + for (j = 0; j < history_len; j++) { + const char *str = history[j]; + /* Need to encode backslash, nl and cr */ + while (*str) { + if (*str == '\\') { + fputs("\\\\", fp); + } + else if (*str == '\n') { + fputs("\\n", fp); + } + else if (*str == '\r') { + fputs("\\r", fp); + } + else { + fputc(*str, fp); + } + str++; + } + fputc('\n', fp); + } + + fclose(fp); + return 0; +} + +/* Load the history from the specified file. + * + * If the file does not exist or can't be opened, no operation is performed + * and -1 is returned. + * Otherwise 0 is returned. + */ +int linenoiseHistoryLoad(const char *filename) { + FILE *fp = fopen(filename,"r"); + stringbuf *sb; + + if (fp == NULL) return -1; + + while ((sb = sb_getline(fp)) != NULL) { + /* Take the stringbuf and decode backslash escaped values */ + char *buf = sb_to_string(sb); + char *dest = buf; + const char *src; + + for (src = buf; *src; src++) { + char ch = *src; + + if (ch == '\\') { + src++; + if (*src == 'n') { + ch = '\n'; + } + else if (*src == 'r') { + ch = '\r'; + } else { + ch = *src; + } + } + *dest++ = ch; + } + *dest = 0; + + linenoiseHistoryAddAllocated(buf); + } + fclose(fp); + return 0; +} + +/* Provide access to the history buffer. + * + * If 'len' is not NULL, the length is stored in *len. + */ +char **linenoiseHistory(int *len) { + if (len) { + *len = history_len; + } + return history; +} diff --git a/src/cli/linenoise/linenoise.h b/src/cli/linenoise/linenoise.h new file mode 100644 index 00000000..8842de3a --- /dev/null +++ b/src/cli/linenoise/linenoise.h @@ -0,0 +1,149 @@ +/* linenoise.h -- 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, Salvatore Sanfilippo + * Copyright (c) 2010, 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_H +#define __LINENOISE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef NO_COMPLETION +typedef struct linenoiseCompletions { + size_t len; + char **cvec; +} linenoiseCompletions; + +/* + * The callback type for tab completion handlers. + */ +typedef void(linenoiseCompletionCallback)(const char *prefix, linenoiseCompletions *comp, void *userdata); + +/* + * Sets the current tab completion handler and returns the previous one, or NULL + * if no prior one has been set. + */ +linenoiseCompletionCallback * linenoiseSetCompletionCallback(linenoiseCompletionCallback *comp, void *userdata); + +/* + * Adds a copy of the given string to the given completion list. The copy is owned + * by the linenoiseCompletions object. + */ +void linenoiseAddCompletion(linenoiseCompletions *comp, const char *str); + +typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold, void *userdata); +typedef void(linenoiseFreeHintsCallback)(void *hint, void *userdata); +void linenoiseSetHintsCallback(linenoiseHintsCallback *callback, void *userdata); +void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *callback); + +#endif + +/* + * Prompts for input using the given string as the input + * prompt. Returns when the user has tapped ENTER or (on an empty + * line) EOF (Ctrl-D on Unix, Ctrl-Z on Windows). Returns either + * a copy of the entered string (for ENTER) or NULL (on EOF). The + * caller owns the returned string and must eventually free() it. + */ +char *linenoise(const char *prompt); + +/** + * Like linenoise() but starts with an initial buffer. + */ +char *linenoiseWithInitial(const char *prompt, const char *initial); + +/** + * Clear the screen. + */ +void linenoiseClearScreen(void); + +/* + * Adds a copy of the given line of the command history. + */ +int linenoiseHistoryAdd(const char *line); + +/* + * Sets the maximum length of the command history, in lines. + * If the history is currently longer, it will be trimmed, + * retaining only the most recent entries. If len is 0 or less + * then this function does nothing. + */ +int linenoiseHistorySetMaxLen(int len); + +/* + * Returns the current maximum length of the history, in lines. + */ +int linenoiseHistoryGetMaxLen(void); + +/* + * Saves the current contents of the history to the given file. + * Returns 0 on success. + */ +int linenoiseHistorySave(const char *filename); + +/* + * Replaces the current history with the contents + * of the given file. Returns 0 on success. + */ +int linenoiseHistoryLoad(const char *filename); + +/* + * Frees all history entries, clearing the history. + */ +void linenoiseHistoryFree(void); + +/* + * Returns a pointer to the list of history entries, writing its + * length to *len if len is not NULL. The memory is owned by linenoise + * and must not be freed. + */ +char **linenoiseHistory(int *len); + +/* + * Returns the number of display columns in the current terminal. + */ +int linenoiseColumns(void); + +/** + * Enable or disable multiline mode (disabled by default) + */ +void linenoiseSetMultiLine(int enableml); + +#ifdef __cplusplus +} +#endif + +#endif /* __LINENOISE_H */ diff --git a/src/cli/linenoise/stringbuf.c b/src/cli/linenoise/stringbuf.c new file mode 100644 index 00000000..2b9fe706 --- /dev/null +++ b/src/cli/linenoise/stringbuf.c @@ -0,0 +1,173 @@ +/** + * resizable string buffer + * + * (c) 2017-2020 Steve Bennett + * + * See utf8.c for licence details. + */ +#include +#include +#include +#include +#include + +#ifndef STRINGBUF_H +#include "stringbuf.h" +#endif +#ifdef USE_UTF8 +#ifndef UTF8_UTIL_H +#include "utf8.h" +#endif +#endif + +#define SB_INCREMENT 200 + +stringbuf *sb_alloc(void) +{ + stringbuf *sb = (stringbuf *)malloc(sizeof(*sb)); + sb->remaining = 0; + sb->last = 0; +#ifdef USE_UTF8 + sb->chars = 0; +#endif + sb->data = NULL; + + return(sb); +} + +void sb_free(stringbuf *sb) +{ + if (sb) { + free(sb->data); + } + free(sb); +} + +static void sb_realloc(stringbuf *sb, int newlen) +{ + sb->data = (char *)realloc(sb->data, newlen); + sb->remaining = newlen - sb->last; +} + +void sb_append(stringbuf *sb, const char *str) +{ + sb_append_len(sb, str, strlen(str)); +} + +void sb_append_len(stringbuf *sb, const char *str, int len) +{ + if (sb->remaining < len + 1) { + sb_realloc(sb, sb->last + len + 1 + SB_INCREMENT); + } + memcpy(sb->data + sb->last, str, len); + sb->data[sb->last + len] = 0; + + sb->last += len; + sb->remaining -= len; +#ifdef USE_UTF8 + sb->chars += utf8_strlen(str, len); +#endif +} + +char *sb_to_string(stringbuf *sb) +{ + if (sb->data == NULL) { + /* Return an allocated empty string, not null */ + return strdup(""); + } + else { + /* Just return the data and free the stringbuf structure */ + char *pt = sb->data; + free(sb); + return pt; + } +} + +/* Insert and delete operations */ + +/* Moves up all the data at position 'pos' and beyond by 'len' bytes + * to make room for new data + * + * Note: Does *not* update sb->chars + */ +static void sb_insert_space(stringbuf *sb, int pos, int len) +{ + assert(pos <= sb->last); + + /* Make sure there is enough space */ + if (sb->remaining < len) { + sb_realloc(sb, sb->last + len + SB_INCREMENT); + } + /* Now move it up */ + memmove(sb->data + pos + len, sb->data + pos, sb->last - pos); + sb->last += len; + sb->remaining -= len; + /* And null terminate */ + sb->data[sb->last] = 0; +} + +/** + * Move down all the data from pos + len, effectively + * deleting the data at position 'pos' of length 'len' + */ +static void sb_delete_space(stringbuf *sb, int pos, int len) +{ + assert(pos < sb->last); + assert(pos + len <= sb->last); + +#ifdef USE_UTF8 + sb->chars -= utf8_strlen(sb->data + pos, len); +#endif + + /* Now move it up */ + memmove(sb->data + pos, sb->data + pos + len, sb->last - pos - len); + sb->last -= len; + sb->remaining += len; + /* And null terminate */ + sb->data[sb->last] = 0; +} + +void sb_insert(stringbuf *sb, int index, const char *str) +{ + if (index >= sb->last) { + /* Inserting after the end of the list appends. */ + sb_append(sb, str); + } + else { + int len = strlen(str); + + sb_insert_space(sb, index, len); + memcpy(sb->data + index, str, len); +#ifdef USE_UTF8 + sb->chars += utf8_strlen(str, len); +#endif + } +} + +/** + * Delete the bytes at index 'index' for length 'len' + * Has no effect if the index is past the end of the list. + */ +void sb_delete(stringbuf *sb, int index, int len) +{ + if (index < sb->last) { + char *pos = sb->data + index; + if (len < 0) { + len = sb->last; + } + + sb_delete_space(sb, pos - sb->data, len); + } +} + +void sb_clear(stringbuf *sb) +{ + if (sb->data) { + /* Null terminate */ + sb->data[0] = 0; + sb->last = 0; +#ifdef USE_UTF8 + sb->chars = 0; +#endif + } +} diff --git a/src/cli/linenoise/stringbuf.h b/src/cli/linenoise/stringbuf.h new file mode 100644 index 00000000..8ac6c9a6 --- /dev/null +++ b/src/cli/linenoise/stringbuf.h @@ -0,0 +1,137 @@ +#ifndef STRINGBUF_H +#define STRINGBUF_H +/** + * resizable string buffer + * + * (c) 2017-2020 Steve Bennett + * + * See utf8.c for licence details. + */ +#ifdef __cplusplus +extern "C" { +#endif + +/** @file + * A stringbuf is a resizing, null terminated string buffer. + * + * The buffer is reallocated as necessary. + * + * In general it is *not* OK to call these functions with a NULL pointer + * unless stated otherwise. + * + * If USE_UTF8 is defined, supports utf8. + */ + +/** + * The stringbuf structure should not be accessed directly. + * Use the functions below. + */ +typedef struct { + int remaining; /**< Allocated, but unused space */ + int last; /**< Index of the null terminator (and thus the length of the string) */ +#ifdef USE_UTF8 + int chars; /**< Count of characters */ +#endif + char *data; /**< Allocated memory containing the string or NULL for empty */ +} stringbuf; + +/** + * Allocates and returns a new stringbuf with no elements. + */ +stringbuf *sb_alloc(void); + +/** + * Frees a stringbuf. + * It is OK to call this with NULL. + */ +void sb_free(stringbuf *sb); + +/** + * Returns an allocated copy of the stringbuf + */ +stringbuf *sb_copy(stringbuf *sb); + +/** + * Returns the byte length of the buffer. + * + * Returns 0 for both a NULL buffer and an empty buffer. + */ +static inline int sb_len(stringbuf *sb) { + return sb->last; +} + +/** + * Returns the utf8 character length of the buffer. + * + * Returns 0 for both a NULL buffer and an empty buffer. + */ +static inline int sb_chars(stringbuf *sb) { +#ifdef USE_UTF8 + return sb->chars; +#else + return sb->last; +#endif +} + +/** + * Appends a null terminated string to the stringbuf + */ +void sb_append(stringbuf *sb, const char *str); + +/** + * Like sb_append() except does not require a null terminated string. + * The length of 'str' is given as 'len' + * + * Note that in utf8 mode, characters will *not* be counted correctly + * if a partial utf8 sequence is added with sb_append_len() + */ +void sb_append_len(stringbuf *sb, const char *str, int len); + +/** + * Returns a pointer to the null terminated string in the buffer. + * + * Note this pointer only remains valid until the next modification to the + * string buffer. + * + * The returned pointer can be used to update the buffer in-place + * as long as care is taken to not overwrite the end of the buffer. + */ +static inline char *sb_str(const stringbuf *sb) +{ + return sb->data; +} + +/** + * Inserts the given string *before* (zero-based) byte 'index' in the stringbuf. + * If index is past the end of the buffer, the string is appended, + * just like sb_append() + */ +void sb_insert(stringbuf *sb, int index, const char *str); + +/** + * Delete 'len' bytes in the string at the given index. + * + * Any bytes past the end of the buffer are ignored. + * The buffer remains null terminated. + * + * If len is -1, deletes to the end of the buffer. + */ +void sb_delete(stringbuf *sb, int index, int len); + +/** + * Clear to an empty buffer. + */ +void sb_clear(stringbuf *sb); + +/** + * Return an allocated copy of buffer and frees 'sb'. + * + * If 'sb' is empty, returns an allocated copy of "". + */ +char *sb_to_string(stringbuf *sb); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/cli/linenoise/utf8.c b/src/cli/linenoise/utf8.c new file mode 100644 index 00000000..75f07085 --- /dev/null +++ b/src/cli/linenoise/utf8.c @@ -0,0 +1,275 @@ +/** + * UTF-8 utility functions + * + * (c) 2010-2019 Steve Bennett + * + * 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 OWNER 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 +#include +#include +#ifndef UTF8_UTIL_H +#include "utf8.h" +#endif + +#ifdef USE_UTF8 +int utf8_fromunicode(char *p, unsigned uc) +{ + if (uc <= 0x7f) { + *p = uc; + return 1; + } + else if (uc <= 0x7ff) { + *p++ = 0xc0 | ((uc & 0x7c0) >> 6); + *p = 0x80 | (uc & 0x3f); + return 2; + } + else if (uc <= 0xffff) { + *p++ = 0xe0 | ((uc & 0xf000) >> 12); + *p++ = 0x80 | ((uc & 0xfc0) >> 6); + *p = 0x80 | (uc & 0x3f); + return 3; + } + /* Note: We silently truncate to 21 bits here: 0x1fffff */ + else { + *p++ = 0xf0 | ((uc & 0x1c0000) >> 18); + *p++ = 0x80 | ((uc & 0x3f000) >> 12); + *p++ = 0x80 | ((uc & 0xfc0) >> 6); + *p = 0x80 | (uc & 0x3f); + return 4; + } +} + +int utf8_charlen(int c) +{ + if ((c & 0x80) == 0) { + return 1; + } + if ((c & 0xe0) == 0xc0) { + return 2; + } + if ((c & 0xf0) == 0xe0) { + return 3; + } + if ((c & 0xf8) == 0xf0) { + return 4; + } + /* Invalid sequence */ + return -1; +} + +int utf8_strlen(const char *str, int bytelen) +{ + int charlen = 0; + if (bytelen < 0) { + bytelen = strlen(str); + } + while (bytelen > 0) { + int c; + int l = utf8_tounicode(str, &c); + charlen++; + str += l; + bytelen -= l; + } + return charlen; +} + +int utf8_strwidth(const char *str, int charlen) +{ + int width = 0; + while (charlen) { + int c; + int l = utf8_tounicode(str, &c); + width += utf8_width(c); + str += l; + charlen--; + } + return width; +} + +int utf8_index(const char *str, int index) +{ + const char *s = str; + while (index--) { + int c; + s += utf8_tounicode(s, &c); + } + return s - str; +} + +int utf8_tounicode(const char *str, int *uc) +{ + unsigned const char *s = (unsigned const char *)str; + + if (s[0] < 0xc0) { + *uc = s[0]; + return 1; + } + if (s[0] < 0xe0) { + if ((s[1] & 0xc0) == 0x80) { + *uc = ((s[0] & ~0xc0) << 6) | (s[1] & ~0x80); + if (*uc >= 0x80) { + return 2; + } + /* Otherwise this is an invalid sequence */ + } + } + else if (s[0] < 0xf0) { + if (((str[1] & 0xc0) == 0x80) && ((str[2] & 0xc0) == 0x80)) { + *uc = ((s[0] & ~0xe0) << 12) | ((s[1] & ~0x80) << 6) | (s[2] & ~0x80); + if (*uc >= 0x800) { + return 3; + } + /* Otherwise this is an invalid sequence */ + } + } + else if (s[0] < 0xf8) { + if (((str[1] & 0xc0) == 0x80) && ((str[2] & 0xc0) == 0x80) && ((str[3] & 0xc0) == 0x80)) { + *uc = ((s[0] & ~0xf0) << 18) | ((s[1] & ~0x80) << 12) | ((s[2] & ~0x80) << 6) | (s[3] & ~0x80); + if (*uc >= 0x10000) { + return 4; + } + /* Otherwise this is an invalid sequence */ + } + } + + /* Invalid sequence, so just return the byte */ + *uc = *s; + return 1; +} + +struct utf8range { + int lower; /* lower inclusive */ + int upper; /* upper exclusive */ +}; + +/* From http://unicode.org/Public/UNIDATA/UnicodeData.txt */ +static const struct utf8range unicode_range_combining[] = { + { 0x0300, 0x0370 }, { 0x0483, 0x048a }, { 0x0591, 0x05d0 }, { 0x0610, 0x061b }, + { 0x064b, 0x0660 }, { 0x0670, 0x0671 }, { 0x06d6, 0x06dd }, { 0x06df, 0x06e5 }, + { 0x06e7, 0x06ee }, { 0x0711, 0x0712 }, { 0x0730, 0x074d }, { 0x07a6, 0x07b1 }, + { 0x07eb, 0x07f4 }, { 0x0816, 0x0830 }, { 0x0859, 0x085e }, { 0x08d4, 0x0904 }, + { 0x093a, 0x0958 }, { 0x0962, 0x0964 }, { 0x0981, 0x0985 }, { 0x09bc, 0x09ce }, + { 0x09d7, 0x09dc }, { 0x09e2, 0x09e6 }, { 0x0a01, 0x0a05 }, { 0x0a3c, 0x0a59 }, + { 0x0a70, 0x0a72 }, { 0x0a75, 0x0a85 }, { 0x0abc, 0x0ad0 }, { 0x0ae2, 0x0ae6 }, + { 0x0afa, 0x0b05 }, { 0x0b3c, 0x0b5c }, { 0x0b62, 0x0b66 }, { 0x0b82, 0x0b83 }, + { 0x0bbe, 0x0bd0 }, { 0x0bd7, 0x0be6 }, { 0x0c00, 0x0c05 }, { 0x0c3e, 0x0c58 }, + { 0x0c62, 0x0c66 }, { 0x0c81, 0x0c85 }, { 0x0cbc, 0x0cde }, { 0x0ce2, 0x0ce6 }, + { 0x0d00, 0x0d05 }, { 0x0d3b, 0x0d4e }, { 0x0d57, 0x0d58 }, { 0x0d62, 0x0d66 }, + { 0x0d82, 0x0d85 }, { 0x0dca, 0x0de6 }, { 0x0df2, 0x0df4 }, { 0x0e31, 0x0e32 }, + { 0x0e34, 0x0e3f }, { 0x0e47, 0x0e4f }, { 0x0eb1, 0x0eb2 }, { 0x0eb4, 0x0ebd }, + { 0x0ec8, 0x0ed0 }, { 0x0f18, 0x0f1a }, { 0x0f35, 0x0f3a }, { 0x0f3e, 0x0f40 }, + { 0x0f71, 0x0f88 }, { 0x0f8d, 0x0fbe }, { 0x0fc6, 0x0fc7 }, { 0x102b, 0x103f }, + { 0x1056, 0x105a }, { 0x105e, 0x1065 }, { 0x1067, 0x106e }, { 0x1071, 0x1075 }, + { 0x1082, 0x1090 }, { 0x109a, 0x109e }, { 0x135d, 0x1360 }, { 0x1712, 0x1720 }, + { 0x1732, 0x1735 }, { 0x1752, 0x1760 }, { 0x1772, 0x1780 }, { 0x17b4, 0x17d4 }, + { 0x17dd, 0x17e0 }, { 0x180b, 0x180e }, { 0x1885, 0x1887 }, { 0x18a9, 0x18aa }, + { 0x1920, 0x1940 }, { 0x1a17, 0x1a1e }, { 0x1a55, 0x1a80 }, { 0x1ab0, 0x1b05 }, + { 0x1b34, 0x1b45 }, { 0x1b6b, 0x1b74 }, { 0x1b80, 0x1b83 }, { 0x1ba1, 0x1bae }, + { 0x1be6, 0x1bfc }, { 0x1c24, 0x1c3b }, { 0x1cd0, 0x1ce9 }, { 0x1ced, 0x1cee }, + { 0x1cf2, 0x1cf5 }, { 0x1cf7, 0x1d00 }, { 0x1dc0, 0x1e00 }, { 0x20d0, 0x2100 }, + { 0x2cef, 0x2cf2 }, { 0x2d7f, 0x2d80 }, { 0x2de0, 0x2e00 }, { 0x302a, 0x3030 }, + { 0x3099, 0x309b }, { 0xa66f, 0xa67e }, { 0xa69e, 0xa6a0 }, { 0xa6f0, 0xa6f2 }, + { 0xa802, 0xa803 }, { 0xa806, 0xa807 }, { 0xa80b, 0xa80c }, { 0xa823, 0xa828 }, + { 0xa880, 0xa882 }, { 0xa8b4, 0xa8ce }, { 0xa8e0, 0xa8f2 }, { 0xa926, 0xa92e }, + { 0xa947, 0xa95f }, { 0xa980, 0xa984 }, { 0xa9b3, 0xa9c1 }, { 0xa9e5, 0xa9e6 }, + { 0xaa29, 0xaa40 }, { 0xaa43, 0xaa44 }, { 0xaa4c, 0xaa50 }, { 0xaa7b, 0xaa7e }, + { 0xaab0, 0xaab5 }, { 0xaab7, 0xaab9 }, { 0xaabe, 0xaac2 }, { 0xaaeb, 0xaaf0 }, + { 0xaaf5, 0xab01 }, { 0xabe3, 0xabf0 }, { 0xfb1e, 0xfb1f }, { 0xfe00, 0xfe10 }, + { 0xfe20, 0xfe30 }, +}; + +/* From http://unicode.org/Public/UNIDATA/EastAsianWidth.txt */ +static const struct utf8range unicode_range_wide[] = { + { 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 }, { 0x3001, 0x303e }, + { 0x3041, 0x3096 }, { 0x3099, 0x30ff }, { 0x3105, 0x312e }, { 0x3131, 0x318e }, + { 0x3190, 0x31ba }, { 0x31c0, 0x31e3 }, { 0x31f0, 0x321e }, { 0x3220, 0x3247 }, + { 0x3250, 0x32fe }, { 0x3300, 0x4dbf }, { 0x4e00, 0xa48c }, { 0xa490, 0xa4c6 }, + { 0xa960, 0xa97c }, { 0xac00, 0xd7a3 }, { 0xf900, 0xfaff }, { 0xfe10, 0xfe19 }, + { 0xfe30, 0xfe52 }, { 0xfe54, 0xfe66 }, { 0xfe68, 0xfe6b }, { 0x16fe0, 0x16fe1 }, + { 0x17000, 0x187ec }, { 0x18800, 0x18af2 }, { 0x1b000, 0x1b11e }, { 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 }, { 0x1f6eb, 0x1f6ec }, { 0x1f6f4, 0x1f6f8 }, + { 0x1f910, 0x1f93e }, { 0x1f940, 0x1f94c }, { 0x1f950, 0x1f96b }, { 0x1f980, 0x1f997 }, + { 0x1f9c0, 0x1f9c0 }, { 0x1f9d0, 0x1f9e6 }, { 0x20000, 0x2fffd }, { 0x30000, 0x3fffd }, +}; + +#define ARRAYSIZE(A) sizeof(A) / sizeof(*(A)) + +static int cmp_range(const void *key, const void *cm) +{ + const struct utf8range *range = (const struct utf8range *)cm; + int ch = *(int *)key; + if (ch < range->lower) { + return -1; + } + if (ch >= range->upper) { + return 1; + } + return 0; +} + +static int utf8_in_range(const struct utf8range *range, int num, int ch) +{ + const struct utf8range *r = + bsearch(&ch, range, num, sizeof(*range), cmp_range); + + if (r) { + return 1; + } + return 0; +} + +int utf8_width(int ch) +{ + /* short circuit for common case */ + if (isascii(ch)) { + return 1; + } + if (utf8_in_range(unicode_range_combining, ARRAYSIZE(unicode_range_combining), ch)) { + return 0; + } + if (utf8_in_range(unicode_range_wide, ARRAYSIZE(unicode_range_wide), ch)) { + return 2; + } + return 1; +} +#endif diff --git a/src/cli/linenoise/utf8.h b/src/cli/linenoise/utf8.h new file mode 100644 index 00000000..dd9c94ec --- /dev/null +++ b/src/cli/linenoise/utf8.h @@ -0,0 +1,107 @@ +#ifndef UTF8_UTIL_H +#define UTF8_UTIL_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * UTF-8 utility functions + * + * (c) 2010-2019 Steve Bennett + * + * See utf8.c for licence details. + */ + +#ifndef USE_UTF8 +#include + +#define MAX_UTF8_LEN 1 + +/* No utf-8 support. 1 byte = 1 char */ +#define utf8_strlen(S, B) ((B) < 0 ? (int)strlen(S) : (B)) +#define utf8_strwidth(S, B) utf8_strlen((S), (B)) +#define utf8_tounicode(S, CP) (*(CP) = (unsigned char)*(S), 1) +#define utf8_index(C, I) (I) +#define utf8_charlen(C) 1 + #define utf8_width(C) 1 + +#else + +#define MAX_UTF8_LEN 4 + +/** + * Converts the given unicode codepoint (0 - 0x1fffff) to utf-8 + * and stores the result at 'p'. + * + * Returns the number of utf-8 characters + */ +int utf8_fromunicode(char *p, unsigned uc); + +/** + * Returns the length of the utf-8 sequence starting with 'c'. + * + * Returns 1-4, or -1 if this is not a valid start byte. + * + * Note that charlen=4 is not supported by the rest of the API. + */ +int utf8_charlen(int c); + +/** + * Returns the number of characters in the utf-8 + * string of the given byte length. + * + * Any bytes which are not part of an valid utf-8 + * sequence are treated as individual characters. + * + * The string *must* be null terminated. + * + * Does not support unicode code points > \u1fffff + */ +int utf8_strlen(const char *str, int bytelen); + +/** + * Calculates the display width of the first 'charlen' characters in 'str'. + * See utf8_width() + */ +int utf8_strwidth(const char *str, int charlen); + +/** + * Returns the byte index of the given character in the utf-8 string. + * + * The string *must* be null terminated. + * + * This will return the byte length of a utf-8 string + * if given the char length. + */ +int utf8_index(const char *str, int charindex); + +/** + * Returns the unicode codepoint corresponding to the + * utf-8 sequence 'str'. + * + * Stores the result in *uc and returns the number of bytes + * consumed. + * + * If 'str' is null terminated, then an invalid utf-8 sequence + * at the end of the string will be returned as individual bytes. + * + * If it is not null terminated, the length *must* be checked first. + * + * Does not support unicode code points > \u1fffff + */ +int utf8_tounicode(const char *str, int *uc); + +/** + * Returns the width (in characters) of the given unicode codepoint. + * This is 1 for normal letters and 0 for combining characters and 2 for wide characters. + */ +int utf8_width(int ch); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/cli/main.c b/src/cli/main.c index 4972cc13..09c291e5 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -12,8 +12,7 @@ #include "argparse.h" #ifndef DISABLE_LINENOISE -#include "linenoise.h" -#include "encodings/utf8.h" +#include "linenoise/linenoise.h" static bool replCountBraces(char *line) { int leftBraces = 0; @@ -71,11 +70,6 @@ static void repl(DictuVM *vm) { #ifndef DISABLE_LINENOISE - linenoiseSetEncodingFunctions( - linenoiseUtf8PrevCharLen, - linenoiseUtf8NextCharLen, - linenoiseUtf8ReadCode); - linenoiseHistoryLoad("history.txt"); while((line = linenoise(">>> ")) != NULL) { From 77ed6e0a856cda3931b89bd90d76f2bbe31e1039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Silva?= Date: Mon, 6 Feb 2023 14:38:53 +0000 Subject: [PATCH 098/109] Removed option to build without linenoise. --- .github/workflows/main.yml | 6 ++--- README.md | 14 +---------- src/cli/CMakeLists.txt | 12 ++-------- src/cli/main.c | 48 -------------------------------------- 4 files changed, 6 insertions(+), 74 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0cf565a4..040b92a3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -71,11 +71,11 @@ jobs: matrix: os: [windows-2022, windows-2019] - steps: + steps: - uses: actions/checkout@v3 - - name: Make dictu and run tests (No HTTP No Linenoise) + - name: Make dictu and run tests (No HTTP) run: | - cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_SYSTEM_VERSION="10.0.18362.0" -DCICD=1 -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 -B build cmake --build build Debug\dictu.exe tests/runTests.du ci run-examples: diff --git a/README.md b/README.md index 261cd66f..941137ef 100644 --- a/README.md +++ b/README.md @@ -75,25 +75,13 @@ $ cmake --build ./build $ ./dictu ``` -#### Compiling without linenoise -[Linenoise](https://github.com/antirez/linenoise) is used within Dictu to enhance the REPL, however it does not build on windows -systems so a simpler REPL solution is used. - -```bash -$ git clone -b master https://github.com/dictu-lang/Dictu.git -$ cd Dictu -$ cmake -DCMAKE_BUILD_TYPE=Release -DDISABLE_LINENOISE=1 -B ./build -$ cmake --build ./build -$ ./build/Dictu -``` - ### Docker Installation Refer to [Dictu Docker](https://github.com/dictu-lang/Dictu/blob/develop/Docker/README.md) ### FreeBSD Installation -For a full installation, make sure `curl` and `linenoise` are installed. They can be installed from the commands below: +For a full installation, make sure `curl` is installed. It can be installed from the commands below: ```bash $ pkg install -y curl linenoise-ng diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 9f229b0b..588a3ff3 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -1,19 +1,11 @@ set(DICTU_CLI_SRC main.c linenoise/linenoise.c linenoise/linenoise.h linenoise/stringbuf.c linenoise/stringbuf.h linenoise/utf8.c linenoise/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((NOT WIN32) OR DISABLE_LINENOISE) +if(NOT WIN32) list(FILTER DICTU_CLI_SRC EXCLUDE REGEX "linenoise-win32.c") endif() -if(DISABLE_LINENOISE) - list(FILTER DICTU_CLI_SRC EXCLUDE REGEX "(linenoise|stringbuf|utf8)\.(c|h)") - - add_compile_definitions(DISABLE_LINENOISE) -else() - add_compile_definitions(USE_UTF8) -endif() - +add_compile_definitions(USE_UTF8) add_executable(dictu ${DICTU_CLI_SRC}) target_include_directories(dictu PUBLIC ${INCLUDE_DIR}) target_link_libraries(dictu dictu_api_static) diff --git a/src/cli/main.c b/src/cli/main.c index 09c291e5..80d43499 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -11,7 +11,6 @@ #include "../include/dictu_include.h" #include "argparse.h" -#ifndef DISABLE_LINENOISE #include "linenoise/linenoise.h" static bool replCountBraces(char *line) { @@ -62,14 +61,10 @@ static bool replCountQuotes(char *line) { return singleQuotes % 2 == 0 && doubleQuotes % 2 == 0; } -#endif static void repl(DictuVM *vm) { printf(DICTU_STRING_VERSION); char *line; - - #ifndef DISABLE_LINENOISE - linenoiseHistoryLoad("history.txt"); while((line = linenoise(">>> ")) != NULL) { @@ -109,49 +104,6 @@ static void repl(DictuVM *vm) { free(line); free(fullLine); } - #else - #define BUFFER_SIZE 8 - line = calloc(BUFFER_SIZE, sizeof(char)); - if (line == NULL) { - printf("Unable to allocate memory\n"); - exit(71); - } - size_t lineLength = 0; - size_t lineMemory = BUFFER_SIZE; - - while (true) { - printf(">>> "); - - char buffer[BUFFER_SIZE]; - while (fgets(buffer, BUFFER_SIZE, stdin) != NULL) { - while (lineLength + BUFFER_SIZE > lineMemory) { - lineMemory *= 2; - line = realloc(line, lineMemory); - if (line == NULL) { - printf("Unable to allocate memory\n"); - exit(71); - } - } - strcat(line, buffer); - lineLength += BUFFER_SIZE; - if (strlen(buffer) != BUFFER_SIZE - 1 || buffer[BUFFER_SIZE-2] == '\n') { - break; - } - } - - if (line[0] == '\0') { - printf("\n"); - break; - } - - dictuInterpret(vm, "repl", line); - lineLength = 0; - line[0] = '\0'; - } - - #undef BUFFER_SIZE - free(line); - #endif } static char *readFile(const char *path) { From 376f90b060ce78bf4b7d50353d87219998aec633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Silva?= Date: Tue, 7 Feb 2023 13:17:54 +0000 Subject: [PATCH 099/109] Fixed multiline inputs in the REPL and improved string literal and brace matching. --- src/cli/main.c | 103 ++++++++++++++++++++++++++----------------------- 1 file changed, 55 insertions(+), 48 deletions(-) diff --git a/src/cli/main.c b/src/cli/main.c index 80d43499..8bdaf95d 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -13,53 +13,59 @@ #include "linenoise/linenoise.h" -static bool replCountBraces(char *line) { - int leftBraces = 0; - int rightBraces = 0; - bool inString = false; - - for (int i = 0; line[i]; i++) { - if (line[i] == '\'' || line[i] == '"') { - inString = !inString; - } - - if (inString) { - continue; - } - - if (line[i] == '{') { - leftBraces++; - } else if (line[i] == '}') { - rightBraces++; +static int matchStringLiteral(char* line, int i) +{ + char quote = line[i]; + + if (quote != '\'' && quote != '"') { + return i; // not at beginning of string, return same index + } + + while (line[++i]) { + if (line[i] == '\\') { + char skipped = line[++i]; + + if (skipped == '\0') { + return -1; // string not closed + } + } else if (line[i] == quote) { + return i; // return index of last character of string } } - - return leftBraces == rightBraces; + + return -1; // string not closed } -static bool replCountQuotes(char *line) { - int singleQuotes = 0; - int doubleQuotes = 0; - char quote = '\0'; +static bool matchBraces(char *line) { + int braceLevel = 0; for (int i = 0; line[i]; i++) { - if (line[i] == '\'' && quote != '"') { - singleQuotes++; - - if (quote == '\0') { - quote = '\''; - } - } else if (line[i] == '"' && quote != '\'') { - doubleQuotes++; - if (quote == '\0') { - quote = '"'; - } - } else if (line[i] == '\\') { - line++; + i = matchStringLiteral(line, i); + + if (i == -1) { + return false; + } + + if (line[i] == '\0') { + break; + } else if (line[i] == '{') { + braceLevel++; + } else if (line[i] == '}') { + braceLevel--; + } + + if (braceLevel < 0) { + return true; // closed brace before opening, end line now } } - return singleQuotes % 2 == 0 && doubleQuotes % 2 == 0; + return braceLevel == 0; +} + +static void memcpyAndAppendNul(char* dst, char* src, int len) +{ + memcpy(dst, src, len); + dst[len] = '\0'; } static void repl(DictuVM *vm) { @@ -68,14 +74,14 @@ static void repl(DictuVM *vm) { linenoiseHistoryLoad("history.txt"); while((line = linenoise(">>> ")) != NULL) { - int fullLineLength = strlen(line); - char *fullLine = malloc(sizeof(char) * (fullLineLength + 1)); - snprintf(fullLine, fullLineLength + 1, "%s", line); + int statementLength = strlen(line); + char *statement = malloc(sizeof(char) * (statementLength + 1)); + memcpyAndAppendNul(statement, line, statementLength); linenoiseHistoryAdd(line); linenoiseHistorySave("history.txt"); - while (!replCountBraces(fullLine) || !replCountQuotes(fullLine)) { + while (!matchBraces(statement)) { free(line); line = linenoise("... "); @@ -84,25 +90,26 @@ static void repl(DictuVM *vm) { } int lineLength = strlen(line); - char *temp = realloc(fullLine, sizeof(char) * fullLineLength + lineLength); + statement[statementLength++] = '\n'; // keep newline characters between lines + char *temp = realloc(statement, sizeof(char) * (statementLength + lineLength + 1)); if (temp == NULL) { printf("Unable to allocate memory\n"); exit(71); } - fullLine = temp; - memcpy(fullLine + fullLineLength, line, lineLength); - fullLineLength += lineLength; + statement = temp; + memcpyAndAppendNul(statement + statementLength, line, lineLength); + statementLength += lineLength; linenoiseHistoryAdd(line); linenoiseHistorySave("history.txt"); } - dictuInterpret(vm, "repl", fullLine); + dictuInterpret(vm, "repl", statement); free(line); - free(fullLine); + free(statement); } } From 97cc49a6418987278c331edb4bb3005890783f36 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 10 Feb 2023 19:09:34 -0700 Subject: [PATCH 100/109] add optional arg to readline for buffer size Signed-off-by: Brian Downs --- docs/docs/files.md | 2 +- src/vm/datatypes/files.c | 21 ++++++++++++++++----- tests/files/readLine.du | 10 ++++++++++ 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/docs/docs/files.md b/docs/docs/files.md index d9a86935..e001ec39 100644 --- a/docs/docs/files.md +++ b/docs/docs/files.md @@ -55,7 +55,7 @@ with("test.txt", "w") { ### Reading files -There are two methods available when reading files: `read()` and `readLine()`. `read()` reads the entire file, and returns its content as a string. `readLine()` will read the file up to a new line character. +There are two methods available when reading files: `read()` and `readLine()`. `read()` reads the entire file, and returns its content as a string. `readLine()` will read the file up to a new line character. It takes an optional arugment for the buffer read size. ```cs // Read entire file diff --git a/src/vm/datatypes/files.c b/src/vm/datatypes/files.c index a4e9f658..79dcc0a1 100644 --- a/src/vm/datatypes/files.c +++ b/src/vm/datatypes/files.c @@ -1,6 +1,8 @@ #include "files.h" #include "../memory.h" +#define READLINE_BUFFER_SIZE 4096 + static Value writeFile(DictuVM *vm, int argCount, Value *args) { if (argCount != 1) { runtimeError(vm, "write() takes 1 argument (%d given)", argCount); @@ -87,16 +89,25 @@ static Value readFullFile(DictuVM *vm, int argCount, Value *args) { } static Value readLineFile(DictuVM *vm, int argCount, Value *args) { - if (argCount != 0) { - runtimeError(vm, "readLine() takes no arguments (%d given)", argCount); - return EMPTY_VAL; + int readLineBufferSize = READLINE_BUFFER_SIZE; + + if (argCount > 0) { + // runtimeError(vm, "readLine() takes no arguments (%d given)", argCount); + // return EMPTY_VAL; + + if (!IS_NUMBER(args[1])) { + runtimeError(vm, "readLine() argument must be a number"); + return EMPTY_VAL; + } + + readLineBufferSize = AS_NUMBER(args[1]); } // TODO: This could be better - char line[4096]; + char line[readLineBufferSize]; ObjFile *file = AS_FILE(args[0]); - if (fgets(line, 4096, file->file) != NULL) { + if (fgets(line, readLineBufferSize, file->file) != NULL) { int lineLength = strlen(line); // Remove newline char if (line[lineLength - 1] == '\n') { diff --git a/tests/files/readLine.du b/tests/files/readLine.du index 936126dc..563f5ca4 100644 --- a/tests/files/readLine.du +++ b/tests/files/readLine.du @@ -15,6 +15,16 @@ class TestFileReadline < UnitTest { } } } + + testFileReadlineWithGivenBufferSize() { + with("tests/files/read.txt", "r") { + var line; + while ((line = file.readLine(16)) != nil) { + // Check readline works with empty lines as well + this.assertTruthy(line == "Dictu is great!" or line == ""); + } + } + } } TestFileReadline().run(); \ No newline at end of file From c1ef73e2f8d84326eb925dc535151c8f7f84c596 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 10 Feb 2023 19:23:53 -0700 Subject: [PATCH 101/109] update arg handling Signed-off-by: Brian Downs --- src/vm/datatypes/files.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vm/datatypes/files.c b/src/vm/datatypes/files.c index 79dcc0a1..656c5508 100644 --- a/src/vm/datatypes/files.c +++ b/src/vm/datatypes/files.c @@ -90,11 +90,12 @@ static Value readFullFile(DictuVM *vm, int argCount, Value *args) { static Value readLineFile(DictuVM *vm, int argCount, Value *args) { int readLineBufferSize = READLINE_BUFFER_SIZE; + if (argCount > 1) { + runtimeError(vm, "readLine() takes at most 1 argument (%d given)", argCount); + return EMPTY_VAL; + } - if (argCount > 0) { - // runtimeError(vm, "readLine() takes no arguments (%d given)", argCount); - // return EMPTY_VAL; - + if (argCount == 1) { if (!IS_NUMBER(args[1])) { runtimeError(vm, "readLine() argument must be a number"); return EMPTY_VAL; From e31c87cc14b79322be76130ea7cae8397e10fb9b Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 10 Feb 2023 19:42:29 -0700 Subject: [PATCH 102/109] account for windows Signed-off-by: Brian Downs --- src/vm/datatypes/files.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/vm/datatypes/files.c b/src/vm/datatypes/files.c index 656c5508..52ebce86 100644 --- a/src/vm/datatypes/files.c +++ b/src/vm/datatypes/files.c @@ -89,12 +89,13 @@ static Value readFullFile(DictuVM *vm, int argCount, Value *args) { } static Value readLineFile(DictuVM *vm, int argCount, Value *args) { - int readLineBufferSize = READLINE_BUFFER_SIZE; if (argCount > 1) { runtimeError(vm, "readLine() takes at most 1 argument (%d given)", argCount); return EMPTY_VAL; } + int readLineBufferSize = READLINE_BUFFER_SIZE; + if (argCount == 1) { if (!IS_NUMBER(args[1])) { runtimeError(vm, "readLine() argument must be a number"); @@ -104,8 +105,11 @@ static Value readLineFile(DictuVM *vm, int argCount, Value *args) { readLineBufferSize = AS_NUMBER(args[1]); } - // TODO: This could be better - char line[readLineBufferSize]; +#ifdef _WIN32 + char line[] = ALLOCATE(vm, char,readLineBufferSize); +#else + char line[readLineBufferSize]; +#endif ObjFile *file = AS_FILE(args[0]); if (fgets(line, readLineBufferSize, file->file) != NULL) { @@ -118,6 +122,10 @@ static Value readLineFile(DictuVM *vm, int argCount, Value *args) { return OBJ_VAL(copyString(vm, line, lineLength)); } +#ifdef _WIN32 + FREE(vm, char, line); +#endif + return NIL_VAL; } From b03acfe89258eb77e7898fc7d40dd8fdded163c4 Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 10 Feb 2023 19:46:41 -0700 Subject: [PATCH 103/109] account for windows Signed-off-by: Brian Downs --- src/vm/datatypes/files.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vm/datatypes/files.c b/src/vm/datatypes/files.c index 52ebce86..fc34a918 100644 --- a/src/vm/datatypes/files.c +++ b/src/vm/datatypes/files.c @@ -1,8 +1,6 @@ #include "files.h" #include "../memory.h" -#define READLINE_BUFFER_SIZE 4096 - static Value writeFile(DictuVM *vm, int argCount, Value *args) { if (argCount != 1) { runtimeError(vm, "write() takes 1 argument (%d given)", argCount); @@ -94,7 +92,7 @@ static Value readLineFile(DictuVM *vm, int argCount, Value *args) { return EMPTY_VAL; } - int readLineBufferSize = READLINE_BUFFER_SIZE; + int readLineBufferSize = 4096; if (argCount == 1) { if (!IS_NUMBER(args[1])) { @@ -106,7 +104,7 @@ static Value readLineFile(DictuVM *vm, int argCount, Value *args) { } #ifdef _WIN32 - char line[] = ALLOCATE(vm, char,readLineBufferSize); + char line[] = ALLOCATE(vm, char, readLineBufferSize); #else char line[readLineBufferSize]; #endif From 3ad9e8212ee60ecef0afc43c22c88f9f84bdd24e Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Fri, 10 Feb 2023 19:53:05 -0700 Subject: [PATCH 104/109] account for windows Signed-off-by: Brian Downs --- src/vm/datatypes/files.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vm/datatypes/files.c b/src/vm/datatypes/files.c index fc34a918..dd6ed5c1 100644 --- a/src/vm/datatypes/files.c +++ b/src/vm/datatypes/files.c @@ -104,7 +104,7 @@ static Value readLineFile(DictuVM *vm, int argCount, Value *args) { } #ifdef _WIN32 - char line[] = ALLOCATE(vm, char, readLineBufferSize); + char *line = ALLOCATE(vm, char, readLineBufferSize); #else char line[readLineBufferSize]; #endif From 641c9f20d06ef7d9d521a7a426119e5aa728fc7c Mon Sep 17 00:00:00 2001 From: Brian Downs Date: Sat, 11 Feb 2023 16:16:12 -0700 Subject: [PATCH 105/109] add docker build and push to release actions Signed-off-by: Brian Downs --- .github/workflows/release.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dbe15584..856c1680 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,3 +33,15 @@ jobs: dictu_${{ github.ref_name }}_amd64.deb dictu_${{ github.ref_name }}_x86_64.apk dictu_${{ github.ref_name }}.x86_64.rpm + - name: Docker Hub Login + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v4 + with: + push: true + tags: dictulang/dictu:${{ github.ref_name }} + file: ./Docker/DictuUbuntuDockerfile + context: . From d6288a505833392ef338c867cb19584395f0c72e Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Wed, 15 Feb 2023 12:01:30 +0000 Subject: [PATCH 106/109] Couple of changes to buffer size --- docs/docs/files.md | 12 +++++++++++- src/vm/datatypes/files.c | 2 +- tests/files/readLine.du | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/docs/docs/files.md b/docs/docs/files.md index e001ec39..1ec1cb93 100644 --- a/docs/docs/files.md +++ b/docs/docs/files.md @@ -55,7 +55,9 @@ with("test.txt", "w") { ### Reading files -There are two methods available when reading files: `read()` and `readLine()`. `read()` reads the entire file, and returns its content as a string. `readLine()` will read the file up to a new line character. It takes an optional arugment for the buffer read size. +There are two methods available when reading files: `read()` and `readLine()`. `read()` reads the entire file, and returns its content as a string. `readLine()` will read the file up to a new line character. + +`readline()` can also take an optional argument for the buffer size. ```cs // Read entire file @@ -73,6 +75,14 @@ with("test.txt", "r") { print(line); } } + +with("test.txt", "r") { + var line; + // When you reach the end of the file, nil is returned + while((line = file.readLine(10)) != nil) { + print(line); // Will only read 10 + } +} ``` Another method which may come in useful when reading files is `seek()`. `seek()` allows you to move the file cursor so you can re-read a file, for example, without closing the file and reopening. diff --git a/src/vm/datatypes/files.c b/src/vm/datatypes/files.c index dd6ed5c1..3e39ec9a 100644 --- a/src/vm/datatypes/files.c +++ b/src/vm/datatypes/files.c @@ -100,7 +100,7 @@ static Value readLineFile(DictuVM *vm, int argCount, Value *args) { return EMPTY_VAL; } - readLineBufferSize = AS_NUMBER(args[1]); + readLineBufferSize = AS_NUMBER(args[1]) + 1; } #ifdef _WIN32 diff --git a/tests/files/readLine.du b/tests/files/readLine.du index 563f5ca4..8feb1e64 100644 --- a/tests/files/readLine.du +++ b/tests/files/readLine.du @@ -24,6 +24,24 @@ class TestFileReadline < UnitTest { this.assertTruthy(line == "Dictu is great!" or line == ""); } } + + with("tests/files/read.txt", "r") { + this.assertTruthy(file.readLine(1) == "D"); + this.assertTruthy(file.readLine(1) == "i"); + this.assertTruthy(file.readLine(1) == "c"); + this.assertTruthy(file.readLine(1) == "t"); + this.assertTruthy(file.readLine(1) == "u"); + this.assertTruthy(file.readLine(1) == " "); + this.assertTruthy(file.readLine(1) == "i"); + this.assertTruthy(file.readLine(1) == "s"); + this.assertTruthy(file.readLine(1) == " "); + this.assertTruthy(file.readLine(1) == "g"); + this.assertTruthy(file.readLine(1) == "r"); + this.assertTruthy(file.readLine(1) == "e"); + this.assertTruthy(file.readLine(1) == "a"); + this.assertTruthy(file.readLine(1) == "t"); + this.assertTruthy(file.readLine(1) == "!"); + } } } From 34814dc651c1c458ab0a6b13e80d60cfa5929580 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Thu, 16 Feb 2023 18:08:32 +0000 Subject: [PATCH 107/109] Fix github actions config --- .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 040b92a3..176ae514 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -71,7 +71,7 @@ jobs: matrix: os: [windows-2022, windows-2019] - steps: + steps: - uses: actions/checkout@v3 - name: Make dictu and run tests (No HTTP) run: | From e1bd8535bcdd9e13fb576f2f5a2669bcd5958ab6 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Fri, 17 Feb 2023 17:35:47 +0000 Subject: [PATCH 108/109] Bump version to 0.27.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 a634b5ac..c9f5d175 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.26.0" +version: "0.27.0" github_username: dictu-lang search_enabled: true diff --git a/src/include/dictu_include.h b/src/include/dictu_include.h index 5461a4a6..b9a695ad 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 "26" +#define DICTU_MINOR_VERSION "27" #define DICTU_PATCH_VERSION "0" #define DICTU_STRING_VERSION "Dictu Version: " DICTU_MAJOR_VERSION "." DICTU_MINOR_VERSION "." DICTU_PATCH_VERSION "\n" From 5c575db5599403f03313bd999c7645e10264b301 Mon Sep 17 00:00:00 2001 From: Jason_000 Date: Sat, 18 Feb 2023 20:21:08 +0000 Subject: [PATCH 109/109] Unindent YAML one level --- .github/workflows/release.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 856c1680..23d24cb5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,15 +33,15 @@ jobs: dictu_${{ github.ref_name }}_amd64.deb dictu_${{ github.ref_name }}_x86_64.apk dictu_${{ github.ref_name }}.x86_64.rpm - - name: Docker Hub Login - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push - uses: docker/build-push-action@v4 - with: - push: true - tags: dictulang/dictu:${{ github.ref_name }} - file: ./Docker/DictuUbuntuDockerfile - context: . + - name: Docker Hub Login + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v4 + with: + push: true + tags: dictulang/dictu:${{ github.ref_name }} + file: ./Docker/DictuUbuntuDockerfile + context: .