diff --git a/README.md b/README.md index 8303c9fc..8c5cbc45 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ # Dictu -*What is Dictu?* -Dictu is a very simple dynamically typed programming language -built upon the [craftinginterpreters tutorial](http://www.craftinginterpreters.com/contents.html). +*What is Dictu?* + +Dictu is a high-level dynamically typed, multi-paradigm, interpreted programming language. Dictu has a very familiar +C-style syntax along with taking inspiration from the family of languages surrounding it, such as Python and JavaScript. + +*What does Dictu mean?* -*What does Dictu mean?* Dictu means `simplistic` in Latin. ### Dictu documentation @@ -13,6 +15,39 @@ Documentation for Dictu can be found [here](https://dictu-lang.com/) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/ab84059049bd4ba7b7b8c1fcfaac4ea5)](https://app.codacy.com/manual/jasonhall96686/Dictu?utm_source=github.com&utm_medium=referral&utm_content=Jason2605/Dictu&utm_campaign=Badge_Grade_Dashboard) [![CI](https://github.com/Jason2605/Dictu/workflows/CI/badge.svg)](https://github.com/Jason2605/Dictu/actions) +## Example programs +```js +const guess = 10; + +while { + const userInput = input("Input your guess: ").toNumber().unwrap(); + if (userInput == guess) { + print("Well done!"); + break; + } else if (userInput < guess) { + print("Too low!"); + } else { + print("Too high!"); + } + + System.sleep(1); +} +``` + +```js +def fibonacci(num) { + if (num < 2) { + return num; + } + + return fibonacci(num - 2) + fibonacci(num - 1); +} + +print(fibonacci(10)); +``` + +More [here.](https://github.com/Jason2605/Dictu/tree/develop/examples) + ## Running Dictu Dictu requires that you have CMake installed and it is at least version 3.16.3. @@ -54,25 +89,11 @@ $ ./build/Dictu Refer to [Dictu Docker](https://github.com/dictu-lang/Dictu/blob/develop/Docker/README.md) +## Extensions -## Example program -```js -var userInput; -var guess = 10; +Dictu has a Visual Studio Code extension [here](https://marketplace.visualstudio.com/items?itemName=Dictu.dictuvsc) with the implementation located +in the [DictuVSC repo](https://github.com/dictu-lang/DictuVSC). -while { - userInput = input("Input your guess: ").toNumber().unwrap(); - if (userInput == guess) { - print("Well done!"); - break; - } else if (userInput < guess) { - print("Too low!"); - } else { - print("Too high!"); - } +## Credits - System.sleep(1); -} -``` - -More [here.](https://github.com/Jason2605/Dictu/tree/develop/examples) +This language was initially based on the very good [craftinginterpreters book](http://www.craftinginterpreters.com/contents.html), along with inspiration from [Wren](https://github.com/wren-lang/wren). diff --git a/docs/_config.yml b/docs/_config.yml index 5cd3f1a2..2b155bf7 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.17.0" +version: "0.18.0" github_username: dictu-lang search_enabled: true diff --git a/docs/docs/collections/lists.md b/docs/docs/collections/lists.md index 000974c2..612d0a97 100644 --- a/docs/docs/collections/lists.md +++ b/docs/docs/collections/lists.md @@ -178,6 +178,17 @@ myList.pop(0); // 1 print(myList); // [2] ``` +### list.reverse() + +To reverse a list we use `.reverse()`, this will reverse a list *in-place* (modifying the list) rather than generating a new list. + +```cs +const myList = [1, 2, 3, 4]; +myList.reverse(); + +print(myList); // [4, 3, 2, 1] +``` + ### Copying lists #### list.copy() @@ -272,4 +283,16 @@ By default the initial value for `.reduce()` is 0, however we can change this to ```cs print(["Dictu ", "is", " great!"].reduce(def (accumulate, element) => accumulate + element, "")); // 'Dictu is great!' +``` + +### list.find(func) + +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 +function then `nil` is returned. + +Note: The first item to satisfy the callback is returned. + +```cs +print([1, 2, 3].find(def (item) => item == 2)); // 2 ``` \ No newline at end of file diff --git a/docs/docs/enum.md b/docs/docs/enum.md new file mode 100644 index 00000000..5aaad198 --- /dev/null +++ b/docs/docs/enum.md @@ -0,0 +1,43 @@ +--- +layout: default +title: Enums +nav_order: 10 +--- + +# Enums +{: .no_toc } + +## Table of contents +{: .no_toc .text-delta } + +1. TOC +{:toc} + +--- + +## Enums + +Enums are a collection of constants which can be accessed via a name rather than +an index to document intent. Unlike other languages, enums in Dictu must be assigned +to a value when declaring the enum and no automatic value will be generated. + +```cs +enum Test { + a = 0, + b = 1, + c = 2 +} + +print(Test.a); // 0 +``` + +Enums in Dictu also do not care about the value being stored within the enum, so +if you wanted to create a heterogeneous enum, Dictu will not stop you. + +```cs +enum HeterogeneousEnum { + a = 0, + b = "string", + c = def () => 10 +} +``` \ No newline at end of file diff --git a/docs/docs/error-handling.md b/docs/docs/error-handling.md index 32adeb28..4b097152 100644 --- a/docs/docs/error-handling.md +++ b/docs/docs/error-handling.md @@ -1,7 +1,7 @@ --- layout: default title: Error Handling -nav_order: 12 +nav_order: 14 --- # Error Handling diff --git a/docs/docs/files.md b/docs/docs/files.md index 1e1131ed..83db62b9 100644 --- a/docs/docs/files.md +++ b/docs/docs/files.md @@ -1,7 +1,7 @@ --- layout: default title: Files -nav_order: 10 +nav_order: 11 --- # Files diff --git a/docs/docs/modules.md b/docs/docs/modules.md index 8f67a086..da30b2ab 100644 --- a/docs/docs/modules.md +++ b/docs/docs/modules.md @@ -1,7 +1,7 @@ --- layout: default title: Modules -nav_order: 11 +nav_order: 12 --- # Modules diff --git a/docs/docs/operators.md b/docs/docs/operators.md index 5bcc2cae..86efec2f 100644 --- a/docs/docs/operators.md +++ b/docs/docs/operators.md @@ -5,6 +5,16 @@ nav_order: 6 --- # Operators +{: .no_toc } + +## Table of contents +{: .no_toc .text-delta } + +1. TOC +{:toc} + +--- +## Operators | Operator | Description | Example | |:-------------|:---------------------------------------------------------------|:---------------------------| @@ -30,7 +40,7 @@ nav_order: 6 | ? | Ternary operator - See below | true ? 'value' : 'other' | | ?. | Optional chaining - See [classes](/docs/classes/#optional-chaining) | object?.someMethod() | -## Ternary Operator +### Ternary Operator The ternary operator is an operator which takes 3 operands and returns either the second or third depending on whether the first operand is truthy. @@ -40,4 +50,24 @@ print(value); // 'true!' var otherValue = 0 ? 'true!' : 'false!'; print(otherValue); // 'false!' -``` \ No newline at end of file +``` + +## Precedence + +Precedence table from highest to lowest, with all operators having a left-to-right associativity. + +| Operators | +| . () [] | +| ?. | +| ! - | +| \*\* | +| * / | +| \+ \- | +| & | +| ^ | +| \| | +| < > <= >= | +| == != | +| and | +| or | +| \= | \ No newline at end of file diff --git a/docs/docs/standard-lib.md b/docs/docs/standard-lib.md index a0f8a9a3..6fb1b125 100644 --- a/docs/docs/standard-lib.md +++ b/docs/docs/standard-lib.md @@ -1,7 +1,7 @@ --- layout: default title: Standard Library -nav_order: 13 +nav_order: 15 has_children: true --- diff --git a/docs/docs/standard-lib/http.md b/docs/docs/standard-lib/http.md index 9b197bcb..6d2442ab 100644 --- a/docs/docs/standard-lib/http.md +++ b/docs/docs/standard-lib/http.md @@ -24,19 +24,20 @@ To make use of the HTTP module an import is required. import HTTP; ``` -### HTTP.get(string, number: timeout -> optional) +### HTTP.get(string, list: headers -> optional, number: timeout -> optional) Sends a HTTP GET request to a given URL. Timeout is given in seconds. Returns a Result and unwraps to a dictionary upon success. ```cs HTTP.get("https://httpbin.org/get"); -HTTP.get("https://httpbin.org/get", 1); +HTTP.get("https://httpbin.org/get", ["Content-Type: application/json"]); +HTTP.get("https://httpbin.org/get", ["Content-Type: application/json"], 1); {"content": "...", "headers": ["...", "..."], "statusCode": 200} ``` -### HTTP.post(string, dictionary: postArgs -> optional, number: timeout -> optional) +### HTTP.post(string, dictionary: postArgs -> optional, list: headers -> optional, number: timeout -> optional) Sends a HTTP POST request to a given URL.Timeout is given in seconds. Returns a Result and unwraps to a dictionary upon success. @@ -44,12 +45,13 @@ Returns a Result and unwraps to a dictionary upon success. ```cs HTTP.post("https://httpbin.org/post"); HTTP.post("https://httpbin.org/post", {"test": 10}); -HTTP.post("https://httpbin.org/post", {"test": 10}, 1); +HTTP.post("https://httpbin.org/post", {"test": 10}, ["Content-Type: application/json"]); +HTTP.post("https://httpbin.org/post", {"test": 10}, ["Content-Type: application/json"], 1); ``` ### Response -Both HTTP.get() and HTTP.post() return a dictionary, or nil on error. +Both HTTP.get() and HTTP.post() return a Result that unwraps a dictionary on success, or nil on error. The dictionary returned has 3 keys, "content", "headers" and "statusCode". "content" is the actual content returned from the HTTP request as a string, "headers" is a list of all the response headers and "statusCode" is a number denoting the status code from the response diff --git a/docs/docs/standard-lib/math.md b/docs/docs/standard-lib/math.md index 1237618f..aade9a35 100644 --- a/docs/docs/standard-lib/math.md +++ b/docs/docs/standard-lib/math.md @@ -46,8 +46,8 @@ Math.min([1, 2, 3]); // 1 Return the largest number within the iterable ```cs -Math.min(1, 2, 3); // 3 -Math.min([1, 2, 3]); // 3 +Math.max(1, 2, 3); // 3 +Math.max([1, 2, 3]); // 3 ``` ### Math.average(iterable) diff --git a/docs/docs/standard-lib/path.md b/docs/docs/standard-lib/path.md index 6617cd2e..684ea25b 100644 --- a/docs/docs/standard-lib/path.md +++ b/docs/docs/standard-lib/path.md @@ -33,7 +33,7 @@ import Path; ### Path.basename(string) -Returns the basename of string. +Returns the basename of the path. ```cs Path.basename("/usr/bin"); // 'bin' @@ -41,7 +41,7 @@ Path.basename("/usr/bin"); // 'bin' ### Path.dirname(string) -Returns the directory name of string. +Returns the directory name of the path. ```cs Path.dirname("/usr/bin"); // '/usr' @@ -49,7 +49,7 @@ Path.dirname("/usr/bin"); // '/usr' ### Path.extname(string) -Returns the extension portion of string, including the dot. +Returns the extension portion of the path, including the dot. ```cs Path.extname("/tmp/t.ext"); // '.ext' @@ -58,7 +58,7 @@ Path.extname("/tmp/t"); // '' ### Path.isAbsolute(string) -Returns true if string is an absolute path or false otherwise. +Returns true if path is absolute, false otherwise. ```cs Path.isAbsolute("/usr"); // true @@ -83,22 +83,22 @@ 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) Checks whether a given path points to a directory or not. -**Note:** This is not available on windows systems yet. +**Note:** This is not available on windows systems. ```cs -Path.isdir("/usr/bin/"); //true +Path.isDir("/usr/bin/"); //true ``` -### Path.listdir(string) +### Path.listDir(string) Returns a list of strings containing the contents of the input path. **Note:** This function does not guarantee any ordering of the returned list. ```js -Path.listdir("/"); // ["bin", "dev", "home", "lib", ...] +Path.listDir("/"); // ["bin", "dev", "home", "lib", ...] ``` diff --git a/docs/docs/standard-lib/random.md b/docs/docs/standard-lib/random.md index d4434c89..61702e84 100644 --- a/docs/docs/standard-lib/random.md +++ b/docs/docs/standard-lib/random.md @@ -27,7 +27,7 @@ import Random; ### Random.random() -Return a random float between 0 and 1. +Return a random decimal between 0 and 1. ```cs Random.random(); // 0.314 @@ -36,7 +36,7 @@ Random.random(); // 0.271 ### Random.range(number: lowest, number: highest) -Returns a random integer between the lowest and highest inputs. +Returns a random integer between the lowest and highest inputs where both are inclusive. ```cs Random.range(1, 5); // 2 @@ -44,7 +44,7 @@ Random.range(1, 5); // 4 Random.range(0, 2); // 1 ``` -### Random.select(iterable) +### Random.select(list) Returns a value randomly selected from the list. diff --git a/docs/docs/standard-lib/system.md b/docs/docs/standard-lib/system.md index 27513103..a5a2f294 100644 --- a/docs/docs/standard-lib/system.md +++ b/docs/docs/standard-lib/system.md @@ -173,8 +173,8 @@ Set current working directory of the Dictu process. Returns a Result type and on success will unwrap nil. ```cs -if (System.setCWD('/') == -1) { - print ("failed to change directory"); +if (!System.setCWD('/').success()) { + print("failed to change directory"); } ``` diff --git a/docs/index.md b/docs/index.md index c6fefaae..b2c1e7dd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -17,16 +17,17 @@ Dictu is a simple, dynamically typed programming language. import HTTP; import JSON; -var response = HTTP.get("https://api.coindesk.com/v1/bpi/currentprice.json"); -if (response.success()) { - var data = JSON.parse(response.unwrap()["content"]).unwrap(); - print("${} per BTC".format(data["bpi"]["USD"]["rate"])); // $10,577.70 per BTC -} else { - print(response.unwrapError()); -} +HTTP.get("https://api.coindesk.com/v1/bpi/currentprice.json").match( + def (response) => { + const data = JSON.parse(response["content"]).unwrap(); + print("${} per BTC".format(data["bpi"]["USD"]["rate"])); // $10,577.70 per BTC + }, + def (error) => print(error) +); ``` -Dictu is a very small, simple, and dynamically typed programming language inspired by a [book by Robert Nystrom](http://www.craftinginterpreters.com/contents.html). Dictu builds upon the concepts within the book and adds more features to the language. +Dictu is a high-level dynamically typed, multi-paradigm, interpreted programming language. Dictu has a very familiar +C-style syntax along with taking inspiration from the family of languages surrounding it, such as Python and JavaScript. Dictu means simplistic in Latin. This is the aim of the language: to be as simplistic, organized, and logical as humanly possible. diff --git a/examples/README.md b/examples/README.md index 9b4ac554..551f723d 100644 --- a/examples/README.md +++ b/examples/README.md @@ -13,11 +13,8 @@ class Person { // Use the trait and inherit the methods use PrintNameTrait; - // Class constructor - init(name, age) { - this.name = name; - this.age = age; - } + // Class constructor, set name and age as public instance variables + init(var name, var age) {} // Define a new method printAge() { @@ -30,11 +27,8 @@ class Car { // Use the trait and inherit the methods use PrintNameTrait; - // Class constructor - init(name, model) { - this.name = name; - this.model = model; - } + // Class constructor, set name and model as public instance variables + init(var name, var model) {} // Define a new method printModel() { @@ -43,7 +37,7 @@ class Car { } // Instantiate a new Person object -var jason = Person("Jason", 500); +const jason = Person("Jason", 500); // Call the method defined in the trait jason.printName(); @@ -52,7 +46,7 @@ jason.printName(); jason.printAge(); // Instantiate a new Car object -var cayman = Car("Cayman", "Porsche"); +const cayman = Car("Cayman", "Porsche"); // Call the method defined in the trait cayman.printName(); @@ -85,7 +79,7 @@ if (amount > 0) { ## FizzBuzz ```js -for (var i = 1; i < 101; ++i) { +for (var i = 1; i < 101; i += 1) { if (i % 15 == 0) { print("FizzBuzz"); } else if (i % 3 == 0) { @@ -101,11 +95,10 @@ for (var i = 1; i < 101; ++i) { ## Guessing Game ```js -var guess = 10; +const guess = 10; while { - var userInput = input("Input your guess: ").toNumber(); - print(userInput); + const userInput = input("Input your guess: ").toNumber().unwrap(); if (userInput == guess) { print("Well done!"); break; diff --git a/examples/bubbleSort.du b/examples/bubbleSort.du index 9ada5010..99fd062b 100644 --- a/examples/bubbleSort.du +++ b/examples/bubbleSort.du @@ -1,17 +1,17 @@ def bubbleSort(list) { - var sortedList = list.copy(); + const sortedList = list.copy(); - for (var i = 0; i < sortedList.len(); i += 1) { - for (var j = 0; j < sortedList.len() - 1; ++j) { - if (sortedList[j] > sortedList[j+1]) { - var temp = sortedList[j+1]; - sortedList[j+1] = sortedList[j]; - sortedList[j] = temp; - } + for (var i = 0; i < sortedList.len(); i += 1) { + for (var j = 0; j < sortedList.len() - 1; j += 1) { + if (sortedList[j] > sortedList[j + 1]) { + var temp = sortedList[j + 1]; + sortedList[j + 1] = sortedList[j]; + sortedList[j] = temp; + } + } } - } - return sortedList; + return sortedList; } print(bubbleSort([2, 1, 3, 4, 10, 5, 6, 5, 100, -1])); // [-1, 1, 2, 3, 4, 5, 5, 6, 10, 100] diff --git a/examples/classes.du b/examples/classes.du index 9ca7846e..54c774c2 100644 --- a/examples/classes.du +++ b/examples/classes.du @@ -10,11 +10,8 @@ class Person { // Use the trait and inherit the methods use PrintNameTrait; - // Class constructor - init(name, age) { - this.name = name; - this.age = age; - } + // Class constructor, set name and age as public instance variables + init(var name, var age) {} // Define a new method printAge() { @@ -27,11 +24,8 @@ class Car { // Use the trait and inherit the methods use PrintNameTrait; - // Class constructor - init(name, model) { - this.name = name; - this.model = model; - } + // Class constructor, set name and model as public instance variables + init(var name, var model) {} // Define a new method printModel() { @@ -40,7 +34,7 @@ class Car { } // Instantiate a new Person object -var jason = Person("Jason", 500); +const jason = Person("Jason", 500); // Call the method defined in the trait jason.printName(); @@ -49,7 +43,7 @@ jason.printName(); jason.printAge(); // Instantiate a new Car object -var cayman = Car("Cayman", "Porsche"); +const cayman = Car("Cayman", "Porsche"); // Call the method defined in the trait cayman.printName(); diff --git a/examples/guessingGame.du b/examples/guessingGame.du index f2574834..6e20c9b0 100644 --- a/examples/guessingGame.du +++ b/examples/guessingGame.du @@ -1,7 +1,7 @@ -var guess = 10; +const guess = 10; while { - var userInput = input("Input your guess: ").toNumber().unwrap(); + const userInput = input("Input your guess: ").toNumber().unwrap(); if (userInput == guess) { print("Well done!"); break; diff --git a/examples/inheritance.du b/examples/inheritance.du index 86008f16..6d1bc927 100644 --- a/examples/inheritance.du +++ b/examples/inheritance.du @@ -24,8 +24,8 @@ class Cat < Animal { } // Initialize child class -var dog = Dog(); -var cat = Cat(); +const dog = Dog(); +const cat = Cat(); // Inherited methods/behaviour from parent class - Animal dog.eating(); diff --git a/examples/isPalindrome.du b/examples/isPalindrome.du index 68725faa..f0154d20 100644 --- a/examples/isPalindrome.du +++ b/examples/isPalindrome.du @@ -1,12 +1,12 @@ def isPalindrome(arg) { - var len = arg.len(); - for (var i = 0; i < len/2; i += 1) { - if (arg[i] != arg[len - 1 - i]) { - return false; + const len = arg.len(); + for (var i = 0; i < len / 2; i += 1) { + if (arg[i] != arg[len - 1 - i]) { + return false; + } + } - - } - return true; + return true; } print(isPalindrome("aba")); // true diff --git a/src/include/dictu_include.h b/src/include/dictu_include.h index de5b5a7d..f5e55b09 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 "17" +#define DICTU_MINOR_VERSION "18" #define DICTU_PATCH_VERSION "0" #define DICTU_STRING_VERSION "Dictu Version: " DICTU_MAJOR_VERSION "." DICTU_MINOR_VERSION "." DICTU_PATCH_VERSION "\n" diff --git a/src/optionals/http.c b/src/optionals/http.c index f6117b0e..66c89009 100644 --- a/src/optionals/http.c +++ b/src/optionals/http.c @@ -98,6 +98,25 @@ static char *dictToPostArgs(ObjDict *dict) { return ret; } +static bool setRequestHeaders(DictuVM *vm, struct curl_slist *list, CURL *curl, ObjList *headers) { + if (headers->values.count == 0) { + return true; + } + + for (int i = 0; i < headers->values.count; ++i) { + if (!IS_STRING(headers->values.values[i])) { + runtimeError(vm, "Headers list must only contain strings"); + return false; + } + + list = curl_slist_append(list, AS_CSTRING(headers->values.values[i])); + } + + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); + + return true; +} + static ObjDict *endRequest(DictuVM *vm, CURL *curl, Response response) { // Get status code curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response.statusCode); @@ -143,20 +162,31 @@ static ObjDict *endRequest(DictuVM *vm, CURL *curl, Response response) { } static Value get(DictuVM *vm, int argCount, Value *args) { - if (argCount != 1 && argCount != 2) { - runtimeError(vm, "get() takes 1 or 2 arguments (%d given).", argCount); + if (argCount < 0 || argCount > 3) { + runtimeError(vm, "get() takes between 1 and 3 arguments (%d given).", argCount); return EMPTY_VAL; } long timeout = 20; + ObjList *headers = NULL; - if (argCount == 2) { - if (!IS_NUMBER(args[1])) { + if (argCount == 3) { + if (!IS_NUMBER(args[2])) { runtimeError(vm, "Timeout passed to get() must be a number."); return EMPTY_VAL; } - timeout = AS_NUMBER(args[1]); + timeout = AS_NUMBER(args[2]); + argCount--; + } + + if (argCount == 2) { + if (!IS_LIST(args[1])) { + runtimeError(vm, "Headers passed to get() must be a list."); + return EMPTY_VAL; + } + + headers = AS_LIST(args[1]); } if (!IS_STRING(args[0])) { @@ -175,6 +205,15 @@ static Value get(DictuVM *vm, int argCount, Value *args) { createResponse(vm, &response); char *url = AS_CSTRING(args[0]); + struct curl_slist *list = NULL; + + if (headers) { + if (!setRequestHeaders(vm, list, curl, headers)) { + curl_slist_free_all(list); + return EMPTY_VAL; + } + } + curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout); curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "gzip"); @@ -186,6 +225,10 @@ static Value get(DictuVM *vm, int argCount, Value *args) { /* Perform the request, res will get the return code */ curlResponse = curl_easy_perform(curl); + if (headers) { + curl_slist_free_all(list); + } + /* Check for errors */ if (curlResponse != CURLE_OK) { /* always cleanup */ @@ -210,34 +253,45 @@ static Value get(DictuVM *vm, int argCount, Value *args) { } static Value post(DictuVM *vm, int argCount, Value *args) { - if (argCount != 1 && argCount != 2 && argCount != 3) { - runtimeError(vm, "post() takes between 1 and 3 arguments (%d given).", argCount); + if (argCount < 1 || argCount > 4) { + runtimeError(vm, "post() takes between 1 and 4 arguments (%d given).", argCount); return EMPTY_VAL; } long timeout = 20; - ObjDict *dict = NULL; + ObjDict *postValuesDict = NULL; + ObjString *postValueString = NULL; + ObjList *headers = NULL; - if (argCount == 3) { - if (!IS_NUMBER(args[2])) { + if (argCount == 4) { + if (!IS_NUMBER(args[3])) { runtimeError(vm, "Timeout passed to post() must be a number."); return EMPTY_VAL; } - if (!IS_DICT(args[1])) { - runtimeError(vm, "Post values passed to post() must be a dictionary."); + timeout = (long) AS_NUMBER(args[3]); + argCount--; + } + + if (argCount == 3) { + if (!IS_LIST(args[2])) { + runtimeError(vm, "Headers passed to post() must be a list."); return EMPTY_VAL; } - timeout = (long) AS_NUMBER(args[2]); - dict = AS_DICT(args[1]); - } else if (argCount == 2) { - if (!IS_DICT(args[1])) { - runtimeError(vm, "Post values passed to post() must be a dictionary."); + headers = AS_LIST(args[2]); + argCount--; + } + + if (argCount == 2) { + if (IS_DICT(args[1])) { + postValuesDict = AS_DICT(args[1]); + } else if (IS_STRING(args[1])) { + postValueString = AS_STRING(args[1]); + } else { + runtimeError(vm, "Post values passed to post() must be a dictionary or a string."); return EMPTY_VAL; } - - dict = AS_DICT(args[1]); } if (!IS_STRING(args[0])) { @@ -257,8 +311,19 @@ static Value post(DictuVM *vm, int argCount, Value *args) { char *url = AS_CSTRING(args[0]); char *postValue = ""; - if (dict != NULL) { - postValue = dictToPostArgs(dict); + struct curl_slist *list = NULL; + + if (headers) { + if (!setRequestHeaders(vm, list, curl, headers)) { + curl_slist_free_all(list); + return EMPTY_VAL; + } + } + + if (postValuesDict != NULL) { + postValue = dictToPostArgs(postValuesDict); + } else if (postValueString != NULL) { + postValue = postValueString->chars; } curl_easy_setopt(curl, CURLOPT_URL, url); @@ -273,7 +338,11 @@ static Value post(DictuVM *vm, int argCount, Value *args) { /* Perform the request, res will get the return code */ curlResponse = curl_easy_perform(curl); - if (dict != NULL) { + if (headers) { + curl_slist_free_all(list); + } + + if (postValuesDict != NULL) { free(postValue); } diff --git a/src/optionals/path.c b/src/optionals/path.c index 9cf0133d..4307d309 100644 --- a/src/optionals/path.c +++ b/src/optionals/path.c @@ -135,12 +135,12 @@ static Value existsNative(DictuVM *vm, int argCount, Value *args) { static Value isdirNative(DictuVM *vm, int argCount, Value *args) { if (argCount != 1) { - runtimeError(vm, "isdir() takes 1 argument (%d given)", argCount); + runtimeError(vm, "isDir() takes 1 argument (%d given)", argCount); return EMPTY_VAL; } if (!IS_STRING(args[0])) { - runtimeError(vm, "isdir() argument must be a string"); + runtimeError(vm, "isDir() argument must be a string"); return EMPTY_VAL; } @@ -155,9 +155,9 @@ static Value isdirNative(DictuVM *vm, int argCount, Value *args) { } -static Value listdirNative(DictuVM *vm, int argCount, Value *args) { +static Value listDirNative(DictuVM *vm, int argCount, Value *args) { if (argCount > 1) { - runtimeError(vm, "listdir() takes 0 or 1 arguments (%d given)", argCount); + runtimeError(vm, "listDir() takes 0 or 1 arguments (%d given)", argCount); return EMPTY_VAL; } @@ -166,7 +166,7 @@ static Value listdirNative(DictuVM *vm, int argCount, Value *args) { path = "."; } else { if (!IS_STRING(args[0])) { - runtimeError(vm, "listdir() argument must be a string"); + runtimeError(vm, "listDir() argument must be a string"); return EMPTY_VAL; } path = AS_CSTRING(args[0]); @@ -179,7 +179,7 @@ static Value listdirNative(DictuVM *vm, int argCount, Value *args) { int length = strlen(path) + 4; char *searchPath = ALLOCATE(vm, char, length); if (searchPath == NULL) { - runtimeError(vm, "Memory error on listdir()!"); + runtimeError(vm, "Memory error on listDir()!"); return EMPTY_VAL; } strcpy(searchPath, path); @@ -250,8 +250,8 @@ ObjModule *createPathModule(DictuVM *vm) { defineNative(vm, &module->values, "extname", extnameNative); defineNative(vm, &module->values, "dirname", dirnameNative); defineNative(vm, &module->values, "exists", existsNative); - defineNative(vm, &module->values, "isdir", isdirNative); - defineNative(vm, &module->values, "listdir", listdirNative); + defineNative(vm, &module->values, "isDir", isdirNative); + defineNative(vm, &module->values, "listDir", listDirNative); /** * Define Path properties diff --git a/src/optionals/system.c b/src/optionals/system.c index 109d3378..482f7213 100644 --- a/src/optionals/system.c +++ b/src/optionals/system.c @@ -250,9 +250,19 @@ static Value sleepNative(DictuVM *vm, int argCount, Value *args) { #ifdef _WIN32 Sleep(stopTime * 1000); +#elif _POSIX_C_SOURCE >= 199309L + struct timespec ts; + ts.tv_sec = stopTime; + ts.tv_nsec = fmod(stopTime, 1) * 1000000000; + nanosleep(&ts, NULL); #else - sleep(stopTime); + if (stopTime >= 1) + sleep(stopTime); + + // 1000000 = 1 second + usleep(fmod(stopTime, 1) * 1000000); #endif + return NIL_VAL; } diff --git a/src/vm/compiler.c b/src/vm/compiler.c index 5f8cd649..7c6515d1 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -150,6 +150,7 @@ static void initCompiler(Parser *parser, Compiler *compiler, Compiler *parent, F compiler->function = NULL; compiler->class = NULL; compiler->loop = NULL; + compiler->withBlock = false; if (parent != NULL) { compiler->class = parent->class; @@ -1434,6 +1435,7 @@ ParseRule rules[] = { {super_, NULL, PREC_NONE}, // TOKEN_SUPER {arrow, NULL, PREC_NONE}, // TOKEN_DEF {NULL, NULL, PREC_NONE}, // TOKEN_AS + {NULL, NULL, PREC_NONE}, // TOKEN_ENUM {NULL, NULL, PREC_NONE}, // TOKEN_IF {NULL, and_, PREC_AND}, // TOKEN_AND {NULL, NULL, PREC_NONE}, // TOKEN_ELSE @@ -1586,7 +1588,7 @@ static void parseClassBody(Compiler *compiler) { consume(compiler, TOKEN_IDENTIFIER, "Expect class variable name."); uint8_t name = identifierConstant(compiler, &compiler->parser->previous); - consume(compiler, TOKEN_EQUAL, "Expect '=' after expression."); + consume(compiler, TOKEN_EQUAL, "Expect '=' after class variable identifier."); expression(compiler); emitBytes(compiler, OP_SET_CLASS_VAR, name); @@ -1714,6 +1716,33 @@ static void traitDeclaration(Compiler *compiler) { endClassCompiler(compiler, &classCompiler); } +static void enumDeclaration(Compiler *compiler) { + consume(compiler, TOKEN_IDENTIFIER, "Expect enum name."); + + uint8_t nameConstant = identifierConstant(compiler, &compiler->parser->previous); + declareVariable(compiler, &compiler->parser->previous); + + emitBytes(compiler, OP_ENUM, nameConstant); + + consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' before enum body."); + + do { + if (check(compiler, TOKEN_RIGHT_BRACE)) { + error(compiler->parser, "Trailing comma in enum declaration"); + } + + consume(compiler, TOKEN_IDENTIFIER, "Expect enum value identifier."); + uint8_t name = identifierConstant(compiler, &compiler->parser->previous); + + consume(compiler, TOKEN_EQUAL, "Expect '=' after enum value identifier."); + expression(compiler); + emitBytes(compiler, OP_SET_ENUM_VALUE, name); + } while (match(compiler, TOKEN_COMMA)); + + consume(compiler, TOKEN_RIGHT_BRACE, "Expect '}' after enum body."); + defineVariable(compiler, nameConstant, false); +} + static void funDeclaration(Compiler *compiler) { uint8_t global = parseVariable(compiler, "Expect function name.", false); function(compiler, TYPE_FUNCTION, ACCESS_PUBLIC); @@ -1817,7 +1846,6 @@ static int getArgCount(uint8_t *code, const ValueArray constants, int ip) { case OP_IMPORT_END: case OP_USE: case OP_OPEN_FILE: - case OP_CLOSE_FILE: case OP_BREAK: case OP_BITWISE_AND: case OP_BITWISE_XOR: @@ -1850,6 +1878,7 @@ static int getArgCount(uint8_t *code, const ValueArray constants, int ip) { case OP_IMPORT: case OP_NEW_LIST: case OP_NEW_DICT: + case OP_CLOSE_FILE: return 1; case OP_DEFINE_OPTIONAL: @@ -2040,14 +2069,17 @@ static void ifStatement(Compiler *compiler) { } static void withStatement(Compiler *compiler) { + compiler->withBlock = true; consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'with'."); expression(compiler); consume(compiler, TOKEN_COMMA, "Expect comma"); expression(compiler); consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after 'with'."); + consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' before with body."); beginScope(compiler); + int fileIndex = compiler->localCount; Local *local = &compiler->locals[compiler->localCount++]; local->depth = compiler->scopeDepth; local->isUpvalue = false; @@ -2055,9 +2087,21 @@ static void withStatement(Compiler *compiler) { local->constant = true; emitByte(compiler, OP_OPEN_FILE); - statement(compiler); - emitByte(compiler, OP_CLOSE_FILE); + block(compiler); + emitBytes(compiler, OP_CLOSE_FILE, fileIndex); endScope(compiler); + compiler->withBlock = false; +} + +static void checkForFileHandle(Compiler *compiler) { + if (compiler->withBlock) { + Token token = syntheticToken("file"); + int local = resolveLocal(compiler, &token, true); + + if (local != -1) { + emitBytes(compiler, OP_CLOSE_FILE, local); + } + } } static void returnStatement(Compiler *compiler) { @@ -2066,6 +2110,7 @@ static void returnStatement(Compiler *compiler) { } if (match(compiler, TOKEN_SEMICOLON)) { + checkForFileHandle(compiler); emitReturn(compiler); } else { if (compiler->type == TYPE_INITIALIZER) { @@ -2074,6 +2119,8 @@ static void returnStatement(Compiler *compiler) { expression(compiler); consume(compiler, TOKEN_SEMICOLON, "Expect ';' after return value."); + + checkForFileHandle(compiler); emitByte(compiler, OP_RETURN); } } @@ -2287,6 +2334,8 @@ static void declaration(Compiler *compiler) { varDeclaration(compiler, false); } else if (match(compiler, TOKEN_CONST)) { varDeclaration(compiler, true); + } else if (match(compiler, TOKEN_ENUM)) { + enumDeclaration(compiler); } else { statement(compiler); } @@ -2382,7 +2431,10 @@ ObjFunction *compile(DictuVM *vm, ObjModule *module, const char *source) { ObjFunction *function = endCompiler(&compiler); - freeTable(vm, &vm->constants); + // If we're in the repl we need the constants to live for the entirety of the execution + if (!vm->repl) { + freeTable(vm, &vm->constants); + } // If there was a compile error, the code is not valid, so don't // create a function. diff --git a/src/vm/compiler.h b/src/vm/compiler.h index d97b953a..0e582fbc 100644 --- a/src/vm/compiler.h +++ b/src/vm/compiler.h @@ -20,7 +20,6 @@ typedef enum { PREC_FACTOR, // * / PREC_INDICES, // ** PREC_UNARY, // ! - - PREC_PREFIX, // ++ -- PREC_CHAIN, // ?. PREC_CALL, // . () [] PREC_PRIMARY @@ -101,6 +100,7 @@ typedef struct Compiler { Upvalue upvalues[UINT8_COUNT]; int scopeDepth; + bool withBlock; } Compiler; typedef void (*ParsePrefixFn)(Compiler *compiler, bool canAssign); diff --git a/src/vm/datatypes/files.c b/src/vm/datatypes/files.c index dbab930f..a4e9f658 100644 --- a/src/vm/datatypes/files.c +++ b/src/vm/datatypes/files.c @@ -63,13 +63,7 @@ static Value readFullFile(DictuVM *vm, int argCount, Value *args) { // Calculate file size fseek(file->file, 0L, SEEK_END); size_t fileSize = ftell(file->file); - rewind(file->file); - - // Reset cursor position - if (currentPosition < fileSize) { - fileSize -= currentPosition; - fseek(file->file, currentPosition, SEEK_SET); - } + fseek(file->file, currentPosition, SEEK_SET); char *buffer = ALLOCATE(vm, char, fileSize + 1); if (buffer == NULL) { diff --git a/src/vm/datatypes/lists/list-source.h b/src/vm/datatypes/lists/list-source.h index 63c1ba24..ddbcb72c 100644 --- a/src/vm/datatypes/lists/list-source.h +++ b/src/vm/datatypes/lists/list-source.h @@ -42,4 +42,12 @@ " func(list[i]);\n" \ " }\n" \ "}\n" \ +"\n" \ +"def find(list, func) {\n" \ +" for (var i = 0; i < list.len(); i += 1) {\n" \ +" if (func(list[i])) {\n" \ +" return list[i];\n" \ +" }\n" \ +" }\n" \ +"}\n" \ diff --git a/src/vm/datatypes/lists/list.du b/src/vm/datatypes/lists/list.du index 8d78b36e..c14ed152 100644 --- a/src/vm/datatypes/lists/list.du +++ b/src/vm/datatypes/lists/list.du @@ -41,4 +41,12 @@ def forEach(list, func) { for (var i = 0; i < list.len(); i += 1) { func(list[i]); } +} + +def find(list, func) { + for (var i = 0; i < list.len(); i += 1) { + if (func(list[i])) { + return list[i]; + } + } } \ No newline at end of file diff --git a/src/vm/datatypes/lists/lists.c b/src/vm/datatypes/lists/lists.c index e94d4378..42c432c2 100644 --- a/src/vm/datatypes/lists/lists.c +++ b/src/vm/datatypes/lists/lists.c @@ -376,6 +376,24 @@ static Value sortList(DictuVM *vm, int argCount, Value *args) { return NIL_VAL; } +static Value reverseList(DictuVM *vm, int argCount, Value *args) { + if (argCount != 0) { + runtimeError(vm, "reverse() takes no arguments (%d given)", argCount); + return EMPTY_VAL; + } + + ObjList* list = AS_LIST(args[0]); + int listLength = list->values.count; + + for (int i = 0; i < listLength / 2; i++) { + Value temp = list->values.values[i]; + list->values.values[i] = list->values.values[listLength - i - 1]; + list->values.values[listLength - i - 1] = temp; + } + + return NIL_VAL; +} + void declareListMethods(DictuVM *vm) { defineNative(vm, &vm->listMethods, "toString", toStringList); defineNative(vm, &vm->listMethods, "len", lenList); @@ -390,6 +408,7 @@ void declareListMethods(DictuVM *vm) { defineNative(vm, &vm->listMethods, "deepCopy", copyListDeep); defineNative(vm, &vm->listMethods, "toBool", boolNative); // Defined in util defineNative(vm, &vm->listMethods, "sort", sortList); + defineNative(vm, &vm->listMethods, "reverse", reverseList); dictuInterpret(vm, "List", DICTU_LIST_SOURCE); diff --git a/src/vm/debug.c b/src/vm/debug.c index 99f8bf1d..b034298d 100644 --- a/src/vm/debug.c +++ b/src/vm/debug.c @@ -268,12 +268,16 @@ int disassembleInstruction(Chunk *chunk, int offset) { return simpleInstruction("OP_END_CLASS", offset); case OP_METHOD: return constantInstruction("OP_METHOD", chunk, offset); + case OP_ENUM: + return constantInstruction("OP_ENUM", chunk, offset); + case OP_SET_ENUM_VALUE: + return constantInstruction("OP_SET_ENUM_VALUE", chunk, offset); case OP_USE: return constantInstruction("OP_USE", chunk, offset); case OP_OPEN_FILE: return constantInstruction("OP_OPEN_FILE", chunk, offset); case OP_CLOSE_FILE: - return simpleInstruction("OP_CLOSE_FILE", offset); + return constantInstruction("OP_CLOSE_FILE", chunk, offset); case OP_BREAK: return simpleInstruction("OP_BREAK", offset); default: diff --git a/src/vm/memory.c b/src/vm/memory.c index 8f9785f1..1d2204af 100644 --- a/src/vm/memory.c +++ b/src/vm/memory.c @@ -108,6 +108,13 @@ static void blackenObject(DictuVM *vm, Obj *object) { break; } + case OBJ_ENUM: { + ObjEnum *enumObj = (ObjEnum *) object; + grayObject(vm, (Obj *) enumObj->name); + grayTable(vm, &enumObj->values); + break; + } + case OBJ_CLOSURE: { ObjClosure *closure = (ObjClosure *) object; grayObject(vm, (Obj *) closure->function); @@ -201,6 +208,13 @@ void freeObject(DictuVM *vm, Obj *object) { break; } + case OBJ_ENUM: { + ObjEnum *enumObj = (ObjEnum *) object; + freeTable(vm, &enumObj->values); + FREE(vm, ObjEnum, object); + break; + } + case OBJ_CLOSURE: { ObjClosure *closure = (ObjClosure *) object; FREE_ARRAY(vm, ObjUpvalue*, closure->upvalues, closure->upvalueCount); diff --git a/src/vm/object.c b/src/vm/object.c index d231834f..033f10fc 100644 --- a/src/vm/object.c +++ b/src/vm/object.c @@ -71,6 +71,13 @@ ObjClass *newClass(DictuVM *vm, ObjString *name, ObjClass *superclass, ClassType return klass; } +ObjEnum *newEnum(DictuVM *vm, ObjString *name) { + ObjEnum *enumObj = ALLOCATE_OBJ(vm, ObjEnum , OBJ_ENUM); + enumObj->name = name; + initTable(&enumObj->values); + return enumObj; +} + ObjClosure *newClosure(DictuVM *vm, ObjFunction *function) { ObjUpvalue **upvalues = ALLOCATE(vm, ObjUpvalue*, function->upvalueCount); for (int i = 0; i < function->upvalueCount; i++) { @@ -256,11 +263,11 @@ char *listToString(Value value) { elementSize = strlen(element); } - if (elementSize > (size - listStringLength - 3)) { - if (elementSize > size * 2) { - size += elementSize * 2 + 3; + if (elementSize > (size - listStringLength - 6)) { + if (elementSize > size) { + size = size + elementSize * 2 + 6; } else { - size = size * 2 + 3; + size = size * 2 + 6; } char *newB = realloc(listString, sizeof(char) * size); @@ -513,6 +520,18 @@ char *objectToString(Value value) { return classToString(value); } + case OBJ_ENUM: { + ObjEnum *enumObj = AS_ENUM(value); + char *enumString = malloc(sizeof(char) * (enumObj->name->length + 8)); + memcpy(enumString, "name->chars, enumObj->name->length); + memcpy(enumString + 6 + enumObj->name->length, ">", 1); + + enumString[7 + enumObj->name->length] = '\0'; + + return enumString; + } + case OBJ_BOUND_METHOD: { ObjBoundMethod *method = AS_BOUND_METHOD(value); char *methodString; diff --git a/src/vm/object.h b/src/vm/object.h index 00a2e4cf..015f5972 100644 --- a/src/vm/object.h +++ b/src/vm/object.h @@ -15,6 +15,7 @@ #define AS_MODULE(value) ((ObjModule*)AS_OBJ(value)) #define AS_BOUND_METHOD(value) ((ObjBoundMethod*)AS_OBJ(value)) #define AS_CLASS(value) ((ObjClass*)AS_OBJ(value)) +#define AS_ENUM(value) ((ObjEnum*)AS_OBJ(value)) #define AS_CLOSURE(value) ((ObjClosure*)AS_OBJ(value)) #define AS_FUNCTION(value) ((ObjFunction*)AS_OBJ(value)) #define AS_INSTANCE(value) ((ObjInstance*)AS_OBJ(value)) @@ -33,6 +34,7 @@ #define IS_CLASS(value) isObjType(value, OBJ_CLASS) #define IS_DEFAULT_CLASS(value) isObjType(value, OBJ_CLASS) && AS_CLASS(value)->type == CLASS_DEFAULT #define IS_TRAIT(value) isObjType(value, OBJ_CLASS) && AS_CLASS(value)->type == CLASS_TRAIT +#define IS_ENUM(value) isObjType(value, OBJ_ENUM) #define IS_CLOSURE(value) isObjType(value, OBJ_CLOSURE) #define IS_FUNCTION(value) isObjType(value, OBJ_FUNCTION) #define IS_INSTANCE(value) isObjType(value, OBJ_INSTANCE) @@ -49,6 +51,7 @@ typedef enum { OBJ_MODULE, OBJ_BOUND_METHOD, OBJ_CLASS, + OBJ_ENUM, OBJ_CLOSURE, OBJ_FUNCTION, OBJ_INSTANCE, @@ -221,6 +224,12 @@ typedef struct sObjClass { ClassType type; } ObjClass; +typedef struct sObjEnum { + Obj obj; + ObjString *name; + Table values; +} ObjEnum; + typedef struct { Obj obj; ObjClass *klass; @@ -240,6 +249,8 @@ ObjBoundMethod *newBoundMethod(DictuVM *vm, Value receiver, ObjClosure *method); ObjClass *newClass(DictuVM *vm, ObjString *name, ObjClass *superclass, ClassType type); +ObjEnum *newEnum(DictuVM *vm, ObjString *name); + ObjClosure *newClosure(DictuVM *vm, ObjFunction *function); ObjFunction *newFunction(DictuVM *vm, ObjModule *module, FunctionType type, AccessLevel level); diff --git a/src/vm/opcodes.h b/src/vm/opcodes.h index bb5ff9fc..7bd4c986 100644 --- a/src/vm/opcodes.h +++ b/src/vm/opcodes.h @@ -56,6 +56,8 @@ OPCODE(CLASS) OPCODE(SUBCLASS) OPCODE(END_CLASS) OPCODE(METHOD) +OPCODE(ENUM) +OPCODE(SET_ENUM_VALUE) OPCODE(IMPORT) OPCODE(IMPORT_BUILTIN) OPCODE(IMPORT_BUILTIN_VARIABLE) diff --git a/src/vm/scanner.c b/src/vm/scanner.c index c7e1341a..a70bc9c2 100644 --- a/src/vm/scanner.c +++ b/src/vm/scanner.c @@ -175,7 +175,15 @@ static TokenType identifierType(Scanner *scanner) { case 'd': return checkKeyword(scanner, 1, 2, "ef", TOKEN_DEF); case 'e': - return checkKeyword(scanner, 1, 3, "lse", TOKEN_ELSE); + if (scanner->current - scanner->start > 1) { + switch (scanner->start[1]) { + case 'l': + return checkKeyword(scanner, 2, 2, "se", TOKEN_ELSE); + case 'n': + return checkKeyword(scanner, 2, 2, "um", TOKEN_ENUM); + } + } + break; case 'f': if (scanner->current - scanner->start > 1) { switch (scanner->start[1]) { diff --git a/src/vm/scanner.h b/src/vm/scanner.h index cba1de8e..33a7769f 100644 --- a/src/vm/scanner.h +++ b/src/vm/scanner.h @@ -33,8 +33,8 @@ typedef enum { // Keywords. TOKEN_CLASS, TOKEN_ABSTRACT, TOKEN_TRAIT, TOKEN_USE, TOKEN_STATIC, - TOKEN_PRIVATE, - TOKEN_THIS, TOKEN_SUPER, TOKEN_DEF, TOKEN_AS, + TOKEN_PRIVATE, TOKEN_THIS, TOKEN_SUPER, TOKEN_DEF, TOKEN_AS, + TOKEN_ENUM, TOKEN_IF, TOKEN_AND, TOKEN_ELSE, TOKEN_OR, TOKEN_VAR, TOKEN_CONST, TOKEN_TRUE, TOKEN_FALSE, TOKEN_NIL, TOKEN_FOR, TOKEN_WHILE, TOKEN_BREAK, diff --git a/src/vm/value.c b/src/vm/value.c index c3795f01..039f17ab 100644 --- a/src/vm/value.c +++ b/src/vm/value.c @@ -400,6 +400,9 @@ char *valueTypeToString(DictuVM *vm, Value value, int *length) { break; } + case OBJ_ENUM: { + CONVERT(enum, 4); + } case OBJ_MODULE: { CONVERT(module, 6); } diff --git a/src/vm/vm.c b/src/vm/vm.c index 0fc0fda4..99d9ad05 100644 --- a/src/vm/vm.c +++ b/src/vm/vm.c @@ -135,6 +135,10 @@ DictuVM *dictuInitVM(bool repl, int argc, char *argv[]) { } void dictuFreeVM(DictuVM *vm) { + if (vm->repl) { + freeTable(vm, &vm->constants); + } + freeTable(vm, &vm->modules); freeTable(vm, &vm->globals); freeTable(vm, &vm->constants); @@ -555,6 +559,18 @@ static bool invoke(DictuVM *vm, ObjString *name, int argCount) { return false; } + case OBJ_ENUM: { + ObjEnum *enumObj = AS_ENUM(receiver); + + Value value; + if (tableGet(&enumObj->values, name, &value)) { + return callValue(vm, value, argCount); + } + + runtimeError(vm, "'%s' enum has no property '%s'.", enumObj->name->chars, name->chars); + return false; + } + default: break; } @@ -947,70 +963,98 @@ static DictuInterpretResult run(DictuVM *vm) { } CASE_CODE(GET_PROPERTY): { - if (IS_INSTANCE(peek(vm, 0))) { - ObjInstance *instance = AS_INSTANCE(peek(vm, 0)); - ObjString *name = READ_STRING(); - Value value; - if (tableGet(&instance->publicFields, name, &value)) { - pop(vm); // Instance. - push(vm, value); - DISPATCH(); - } + Value receiver = peek(vm, 0); - if (bindMethod(vm, instance->klass, name)) { - DISPATCH(); - } - - // Check class for properties - ObjClass *klass = instance->klass; + if (!IS_OBJ(receiver)) { + RUNTIME_ERROR_TYPE("'%s' type has no properties", 0); + } - while (klass != NULL) { - if (tableGet(&klass->publicProperties, name, &value)) { + switch (getObjType(receiver)) { + case OBJ_INSTANCE: { + ObjInstance *instance = AS_INSTANCE(receiver); + ObjString *name = READ_STRING(); + Value value; + if (tableGet(&instance->publicFields, name, &value)) { pop(vm); // Instance. push(vm, value); DISPATCH(); } - klass = klass->superclass; + if (bindMethod(vm, instance->klass, name)) { + DISPATCH(); + } + + // Check class for properties + ObjClass *klass = instance->klass; + + while (klass != NULL) { + if (tableGet(&klass->publicProperties, name, &value)) { + pop(vm); // Instance. + push(vm, value); + DISPATCH(); + } + + klass = klass->superclass; + } + + if (tableGet(&instance->privateFields, name, &value)) { + RUNTIME_ERROR("Cannot access private property '%s' on '%s' instance.", name->chars, instance->klass->name->chars); + } + + RUNTIME_ERROR("'%s' instance has no property: '%s'.", instance->klass->name->chars, name->chars); } - if (tableGet(&instance->privateFields, name, &value)) { - RUNTIME_ERROR("Cannot access private property '%s' on '%s' instance.", name->chars, instance->klass->name->chars); + case OBJ_MODULE: { + ObjModule *module = AS_MODULE(receiver); + ObjString *name = READ_STRING(); + Value value; + if (tableGet(&module->values, name, &value)) { + pop(vm); // Module. + push(vm, value); + DISPATCH(); + } + + RUNTIME_ERROR("'%s' module has no property: '%s'.", module->name->chars, name->chars); } - RUNTIME_ERROR("'%s' instance has no property: '%s'.", instance->klass->name->chars, name->chars); - } else if (IS_MODULE(peek(vm, 0))) { - ObjModule *module = AS_MODULE(peek(vm, 0)); - ObjString *name = READ_STRING(); - Value value; - if (tableGet(&module->values, name, &value)) { - pop(vm); // Module. - push(vm, value); - DISPATCH(); + case OBJ_CLASS: { + ObjClass *klass = AS_CLASS(receiver); + // Used to keep a reference to the class for the runtime error below + ObjClass *klassStore = klass; + ObjString *name = READ_STRING(); + + Value value; + while (klass != NULL) { + if (tableGet(&klass->publicProperties, name, &value)) { + pop(vm); // Class. + push(vm, value); + DISPATCH(); + } + + klass = klass->superclass; + } + + RUNTIME_ERROR("'%s' class has no property: '%s'.", klassStore->name->chars, name->chars); } - RUNTIME_ERROR("'%s' module has no property: '%s'.", module->name->chars, name->chars); - } else if (IS_CLASS(peek(vm, 0))) { - ObjClass *klass = AS_CLASS(peek(vm, 0)); - // Used to keep a reference to the class for the runtime error below - ObjClass *klassStore = klass; - ObjString *name = READ_STRING(); + case OBJ_ENUM: { + ObjEnum *enumObj = AS_ENUM(receiver); + ObjString *name = READ_STRING(); + Value value; - Value value; - while (klass != NULL) { - if (tableGet(&klass->publicProperties, name, &value)) { - pop(vm); // Class. + if (tableGet(&enumObj->values, name, &value)) { + pop(vm); // Enum. push(vm, value); DISPATCH(); } - klass = klass->superclass; + RUNTIME_ERROR("'%s' enum has no property: '%s'.", enumObj->name->chars, name->chars); } - RUNTIME_ERROR("'%s' class has no property: '%s'.", klassStore->name->chars, name->chars); + default: { + RUNTIME_ERROR_TYPE("'%s' type has no properties", 0); + } } - - RUNTIME_ERROR_TYPE("'%s' type has no properties", 0); } CASE_CODE(GET_PRIVATE_PROPERTY): { @@ -1938,6 +1982,21 @@ static DictuInterpretResult run(DictuVM *vm) { defineMethod(vm, READ_STRING()); DISPATCH(); + CASE_CODE(ENUM): { + ObjEnum *enumObj = newEnum(vm, READ_STRING()); + push(vm, OBJ_VAL(enumObj)); + DISPATCH(); + } + + CASE_CODE(SET_ENUM_VALUE): { + Value value = peek(vm, 0); + ObjEnum *enumObj = AS_ENUM(peek(vm, 1)); + + tableSet(vm, &enumObj->values, READ_STRING(), value); + pop(vm); + DISPATCH(); + } + CASE_CODE(USE): { Value trait = peek(vm, 0); if (!IS_TRAIT(trait)) { @@ -1983,8 +2042,11 @@ static DictuInterpretResult run(DictuVM *vm) { } CASE_CODE(CLOSE_FILE): { - ObjFile *file = AS_FILE(peek(vm, 0)); - fclose(file->file); + uint8_t slot = READ_BYTE(); + Value file = frame->slots[slot]; + ObjFile *fileObject = AS_FILE(file); + fclose(fileObject->file); + DISPATCH(); } } diff --git a/tests/enum/enum.du b/tests/enum/enum.du new file mode 100644 index 00000000..499c60c9 --- /dev/null +++ b/tests/enum/enum.du @@ -0,0 +1,28 @@ +/** + * enum.du + * + * Testing enums + */ + +enum Test { + a = 1, + b = 2, + c = 3 +} + +assert(Test.a == 1); +assert(Test.b == 2); +assert(Test.c == 3); + +const func = def () => 10; + +enum HeterogeneousEnum { + a = 0, + b = "string", + c = func +} + +assert(HeterogeneousEnum.a == 0); +assert(HeterogeneousEnum.b == "string"); +assert(HeterogeneousEnum.c == func); +assert(HeterogeneousEnum.c() == 10); \ No newline at end of file diff --git a/tests/enum/import.du b/tests/enum/import.du new file mode 100644 index 00000000..16c5d788 --- /dev/null +++ b/tests/enum/import.du @@ -0,0 +1,7 @@ +/** + * import.du + * + * General import file for all the enum tests + */ + +import "enum.du"; \ No newline at end of file diff --git a/tests/http/get.du b/tests/http/get.du index a1a8e2cb..9e1400da 100644 --- a/tests/http/get.du +++ b/tests/http/get.du @@ -18,13 +18,22 @@ assert(response["headers"].len() > 0); // HTTPS result = HTTP.get("https://httpbin.org/get"); +assert(result.success()); +response = result.unwrap(); + +assert(response["statusCode"] == 200); +assert(response["content"].contains("headers")); +assert(response["headers"].len() > 0); +// Headers +result = HTTP.get("https://httpbin.org/get", ["Header: test"]); assert(result.success()); response = result.unwrap(); assert(response["statusCode"] == 200); assert(response["content"].contains("headers")); assert(response["headers"].len() > 0); +assert(response["headers"].contains('"Test": "header"')); response = HTTP.get("https://BAD_URL.test_for_error"); assert(response.success() == false); diff --git a/tests/http/post.du b/tests/http/post.du index e46f667d..40748c95 100644 --- a/tests/http/post.du +++ b/tests/http/post.du @@ -26,6 +26,18 @@ assert(response["headers"].len() > 0); assert(response["content"].contains("origin")); assert(response["content"].contains('"test": "10"')); + +// HTTPS +result = HTTP.post("https://httpbin.org/post", {"test": 10}, ["Test: header"]); +assert(result.success()); +var response = result.unwrap(); + +assert(response["statusCode"] == 200); +assert(response["headers"].len() > 0); +assert(response["headers"].contains('"Test": "header"')); +assert(response["content"].contains("origin")); +assert(response["content"].contains('"test": "10"')); + response = HTTP.post("https://BAD_URL.test_for_error", {"test": 10}); assert(response.success() == false); assert(response.unwrapError() == "Couldn't resolve host name"); diff --git a/tests/lists/find.du b/tests/lists/find.du new file mode 100644 index 00000000..4d53ad77 --- /dev/null +++ b/tests/lists/find.du @@ -0,0 +1,13 @@ +/** + * find.du + * + * Testing the list.find() method + * + * .find() runs a user defined function on each element in the list and returns the item in the + * list that satisfies the callback + */ + +const myList = [1, 2, 3, 4, 5]; + +assert(myList.find(def (item) => item == 3) == 3); +assert(myList.find(def (item) => item == 10) == nil); \ No newline at end of file diff --git a/tests/lists/forEach.du b/tests/lists/forEach.du index 1e1a542b..de2d7358 100644 --- a/tests/lists/forEach.du +++ b/tests/lists/forEach.du @@ -1,9 +1,9 @@ /** - * foreach.du + * forEach.du * - * Testing the list.foreach() method + * Testing the list.forEach() method * - * .foreach() runs a user defined function on each element in the list. + * .forEach() runs a user defined function on each element in the list. */ const myList = [1, 2, 3, 4, 5]; diff --git a/tests/lists/import.du b/tests/lists/import.du index 77f6ed55..441e87d2 100644 --- a/tests/lists/import.du +++ b/tests/lists/import.du @@ -23,3 +23,5 @@ import "map.du"; import "filter.du"; import "reduce.du"; import "forEach.du"; +import "find.du"; +import "reverse.du"; diff --git a/tests/lists/reverse.du b/tests/lists/reverse.du new file mode 100644 index 00000000..b30aadfb --- /dev/null +++ b/tests/lists/reverse.du @@ -0,0 +1,12 @@ +/** + * reverse.du + * + * Testing list.reverse() + */ + +const list = [1, 2, 3, 4]; +list.reverse(); + +assert(list != [1, 2, 3, 4]); +assert(list.len() == 4); +assert(list == [4, 3, 2, 1]); \ No newline at end of file diff --git a/tests/path/import.du b/tests/path/import.du index 06fd6eb5..8906e8f9 100644 --- a/tests/path/import.du +++ b/tests/path/import.du @@ -10,5 +10,5 @@ import "extname.du"; import "isAbsolute.du"; import "realpath.du"; import "exists.du"; -import "isdir.du"; -import "listdir.du"; \ No newline at end of file +import "isDir.du"; +import "listDir.du"; \ No newline at end of file diff --git a/tests/path/isDir.du b/tests/path/isDir.du new file mode 100644 index 00000000..f67ada09 --- /dev/null +++ b/tests/path/isDir.du @@ -0,0 +1,16 @@ +/** + * isDir.du + * + * Testing Path.isDir() + * + * Returns true if the given string is a path to a directory, else false. (Linux only) + */ +import Path; + +if (System.platform != "windows") { + assert(Path.isDir("/usr/bin") == true); + assert(Path.isDir("/home/") == true); +} + +assert(Path.isDir("tests") == true); +assert(Path.isDir("tests/runTests.du") == false); \ No newline at end of file diff --git a/tests/path/isdir.du b/tests/path/isdir.du deleted file mode 100644 index 98753746..00000000 --- a/tests/path/isdir.du +++ /dev/null @@ -1,16 +0,0 @@ -/** - * isAbsolute.du - * - * Testing Path.isdir() - * - * Returns true if the given string is a path to a directory, else false. (Linux only) - */ -import Path; - -if (System.platform != "windows") { - assert(Path.isdir("/usr/bin") == true); - assert(Path.isdir("/home/") == true); -} - -assert(Path.isdir("tests") == true); -assert(Path.isdir("tests/runTests.du") == false); \ No newline at end of file diff --git a/tests/path/listdir.du b/tests/path/listDir.du similarity index 74% rename from tests/path/listdir.du rename to tests/path/listDir.du index 1bc135e4..2217c348 100644 --- a/tests/path/listdir.du +++ b/tests/path/listDir.du @@ -1,12 +1,12 @@ /** - * dirname.du + * listDir.du * - * Testing Path.listdir() + * Testing Path.listDir() * */ import Path; -var dir_contents = Path.listdir("tests/path/test_dir"); +var dir_contents = Path.listDir("tests/path/test_dir"); var exp_dir_contents = ["test_file_1", "test_file_2", "test_file_3"]; for (var i = 0; i < exp_dir_contents.len(); i += 1) { var exp_inode = exp_dir_contents[i]; diff --git a/tests/runTests.du b/tests/runTests.du index c5923e28..05d73dae 100644 --- a/tests/runTests.du +++ b/tests/runTests.du @@ -11,6 +11,7 @@ import "operators/import.du"; import "loops/import.du"; import "functions/import.du"; import "classes/import.du"; +import "enum/import.du"; import "builtins/import.du"; import "files/import.du"; import "maths/import.du";