diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..3ba7a43e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +examples +.github +docs +README.md +LICENSE diff --git a/Docker/DictuAlpineDockerfile b/Docker/DictuAlpineDockerfile new file mode 100644 index 00000000..ca693fc9 --- /dev/null +++ b/Docker/DictuAlpineDockerfile @@ -0,0 +1,14 @@ +FROM alpine + +WORKDIR Dictu + +RUN apk add make curl-dev gcc libc-dev --no-cache + +COPY . . + +RUN make dictu \ + && cp dictu /usr/bin/ \ + && dictu tests/runTests.du \ + && rm -rf * + +CMD ["dictu"] diff --git a/Docker/DictuUbuntuDockerfile b/Docker/DictuUbuntuDockerfile new file mode 100644 index 00000000..9059b711 --- /dev/null +++ b/Docker/DictuUbuntuDockerfile @@ -0,0 +1,18 @@ +FROM ubuntu + +WORKDIR Dictu + +RUN apt update \ + && apt install -y --no-install-recommends build-essential \ + && apt-get update \ + && apt-get install -y --no-install-recommends libcurl4-gnutls-dev\ + && rm -rf /var/lib/apt/lists/* + +COPY . . + +RUN make dictu \ + && cp dictu /usr/bin/ \ + && dictu tests/runTests.du \ + && rm -rf * + +CMD ["dictu"] diff --git a/Docker/README.md b/Docker/README.md new file mode 100644 index 00000000..141ba5fb --- /dev/null +++ b/Docker/README.md @@ -0,0 +1,35 @@ +## Dictu Docker + +### Building the Docker image + +Make sure you have Docker installed on your system. Refer to [Docker installation](https://docs.docker.com/engine/install/). + +To build the Docker image of Dictu, clone the repository and change directory into `Dictu`. + +Run the following command from the **root of the project** i.e, the `Dictu` folder by default. + +To build the *Alpine* version of Dictu - + +```bash +$ docker build -t dictu:alpine -f Docker/DictuAlpineDockerfile . +``` + +To build the *Ubuntu* version of Dictu - + +```bash +$ docker build -t dictu:ubuntu -f Docker/DictuUbuntuDockerfile . +``` + +To start a REPL from this image, run - (Replace the tag with the appropriate version, i.e, alpine or ubuntu) + +```bash +$ docker run -it dictu:ubuntu +``` + +> The image built can be used in other docker images as a base image for running dictu files. Make sure you have the image built locally. + +```Dockerfile +FROM dictu:ubuntu +COPY example.du . +RUN dictu example.du +``` diff --git a/README.md b/README.md index 1aac2aec..96c716e8 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,11 @@ $ make dictu DISABLE_HTTP=1 $ ./dictu examples/guessingGame.du ``` +### Docker Installation + +Refer to [Dictu Docker](https://github.com/dictu-lang/Dictu/blob/develop/Docker/README.md) + + ## Example program ```js var userInput; diff --git a/c/common.h b/c/common.h index a8d65fee..698f60c7 100644 --- a/c/common.h +++ b/c/common.h @@ -5,6 +5,8 @@ #include #include +#define UNUSED(__x__) (void) __x__ + #define NAN_TAGGING #define DEBUG_PRINT_CODE #define DEBUG_TRACE_EXECUTION diff --git a/c/compiler.c b/c/compiler.c index 6d6f7db6..99e9d04b 100644 --- a/c/compiler.c +++ b/c/compiler.c @@ -48,7 +48,7 @@ static void advance(Parser *parser) { parser->previous = parser->current; for (;;) { - parser->current = scanToken(); + parser->current = scanToken(&parser->scanner); if (parser->current.type != TOKEN_ERROR) break; errorAtCurrent(parser, parser->current.start); @@ -672,7 +672,7 @@ static void number(Compiler *compiler, bool canAssign) { // We allocate the whole range for the worst case. // Also account for the null-byte. - char* buffer = (char *)malloc((compiler->parser->previous.length + 1) * sizeof(char)); + char* buffer = ALLOCATE(compiler->parser->vm, char, compiler->parser->previous.length + 1); char* current = buffer; // Strip it of any underscores. @@ -692,7 +692,7 @@ static void number(Compiler *compiler, bool canAssign) { emitConstant(compiler, NUMBER_VAL(value)); // Free the malloc'd buffer. - free(buffer); + FREE_ARRAY(compiler->parser->vm, char, buffer, compiler->parser->previous.length + 1); } static void or_(Compiler *compiler, bool canAssign) { @@ -781,13 +781,14 @@ static void string(Compiler *compiler, bool canAssign) { Parser *parser = compiler->parser; - char *string = malloc(sizeof(char) * parser->previous.length - 1); + char *string = ALLOCATE(parser->vm, char, parser->previous.length - 1); + memcpy(string, parser->previous.start + 1, parser->previous.length - 2); int length = parseString(string, parser->previous.length - 2); string[length] = '\0'; - emitConstant(compiler, OBJ_VAL(copyString(parser->vm, string, length))); - free(string); + emitConstant(compiler, OBJ_VAL(takeString(parser->vm, string, length))); + parser->vm->bytesAllocated -= parser->previous.length - 2 - length; } static void list(Compiler *compiler, bool canAssign) { @@ -1189,6 +1190,7 @@ ParseRule rules[] = { {NULL, NULL, PREC_NONE}, // TOKEN_WITH {NULL, NULL, PREC_NONE}, // TOKEN_EOF {NULL, NULL, PREC_NONE}, // TOKEN_IMPORT + {NULL, NULL, PREC_NONE}, // TOKEN_FROM {NULL, NULL, PREC_NONE}, // TOKEN_ERROR }; @@ -1276,6 +1278,11 @@ static void method(Compiler *compiler) { // Setup function and parse parameters beginFunction(compiler, &fnCompiler, TYPE_ABSTRACT); endCompiler(&fnCompiler); + + if (check(compiler, TOKEN_LEFT_BRACE)) { + error(compiler->parser, "Abstract methods can not have an implementation."); + return; + } } emitBytes(compiler, OP_METHOD, constant); @@ -1676,6 +1683,7 @@ static void importStatement(Compiler *compiler) { compiler->parser->previous.length - 2))); emitBytes(compiler, OP_IMPORT, importConstant); + emitByte(compiler, OP_POP); if (match(compiler, TOKEN_AS)) { uint8_t importName = parseVariable(compiler, "Expect import alias.", false); @@ -1683,7 +1691,9 @@ static void importStatement(Compiler *compiler) { defineVariable(compiler, importName, false); } } else { - uint8_t importName = parseVariable(compiler, "Expect import identifier.", false); + consume(compiler, TOKEN_IDENTIFIER, "Expect import identifier."); + uint8_t importName = identifierConstant(compiler, &compiler->parser->previous); + declareVariable(compiler, &compiler->parser->previous); int index = findBuiltinModule( (char *)compiler->parser->previous.start, @@ -1704,6 +1714,104 @@ static void importStatement(Compiler *compiler) { emitByte(compiler, OP_IMPORT_END); } +static void fromImportStatement(Compiler *compiler) { + if (match(compiler, TOKEN_STRING)) { + int importConstant = makeConstant(compiler, OBJ_VAL(copyString( + compiler->parser->vm, + compiler->parser->previous.start + 1, + compiler->parser->previous.length - 2))); + + consume(compiler, TOKEN_IMPORT, "Expect 'import' after import path."); + emitBytes(compiler, OP_IMPORT, importConstant); + emitByte(compiler, OP_POP); + + uint8_t variables[255]; + Token tokens[255]; + int varCount = 0; + + do { + consume(compiler, TOKEN_IDENTIFIER, "Expect variable name."); + tokens[varCount] = compiler->parser->previous; + variables[varCount] = identifierConstant(compiler, &compiler->parser->previous); + varCount++; + + if (varCount > 255) { + error(compiler->parser, "Cannot have more than 255 variables."); + } + } while (match(compiler, TOKEN_COMMA)); + + emitBytes(compiler, OP_IMPORT_FROM, varCount); + + for (int i = 0; i < varCount; ++i) { + emitByte(compiler, variables[i]); + } + + // This needs to be two separate loops as we need + // all the variables popped before defining. + if (compiler->scopeDepth == 0) { + for (int i = varCount - 1; i >= 0; --i) { + defineVariable(compiler, variables[i], false); + } + } else { + for (int i = 0; i < varCount; ++i) { + declareVariable(compiler, &tokens[i]); + defineVariable(compiler, 0, false); + } + } + + emitByte(compiler, OP_IMPORT_END); + } else { + consume(compiler, TOKEN_IDENTIFIER, "Expect import identifier."); + uint8_t importName = identifierConstant(compiler, &compiler->parser->previous); + + int index = findBuiltinModule( + (char *)compiler->parser->previous.start, + compiler->parser->previous.length + ); + + consume(compiler, TOKEN_IMPORT, "Expect 'import' after identifier"); + + if (index == -1) { + error(compiler->parser, "Unknown module"); + } + + uint8_t variables[255]; + Token tokens[255]; + int varCount = 0; + + do { + consume(compiler, TOKEN_IDENTIFIER, "Expect variable name."); + tokens[varCount] = compiler->parser->previous; + variables[varCount] = identifierConstant(compiler, &compiler->parser->previous); + varCount++; + + if (varCount > 255) { + error(compiler->parser, "Cannot have more than 255 variables."); + } + } while (match(compiler, TOKEN_COMMA)); + + emitBytes(compiler, OP_IMPORT_BUILTIN_VARIABLE, index); + emitBytes(compiler, importName, varCount); + + for (int i = 0; i < varCount; ++i) { + emitByte(compiler, variables[i]); + } + + if (compiler->scopeDepth == 0) { + for (int i = varCount - 1; i >= 0; --i) { + defineVariable(compiler, variables[i], false); + } + } else { + for (int i = 0; i < varCount; ++i) { + declareVariable(compiler, &tokens[i]); + defineVariable(compiler, 0, false); + } + } + } + + consume(compiler, TOKEN_SEMICOLON, "Expect ';' after import."); +} + static void whileStatement(Compiler *compiler) { Loop loop; loop.start = currentChunk(compiler)->count; @@ -1794,6 +1902,8 @@ static void statement(Compiler *compiler) { withStatement(compiler); } else if (match(compiler, TOKEN_IMPORT)) { importStatement(compiler); + } else if (match(compiler, TOKEN_FROM)) { + fromImportStatement(compiler); } else if (match(compiler, TOKEN_BREAK)) { breakStatement(compiler); } else if (match(compiler, TOKEN_WHILE)) { @@ -1808,8 +1918,8 @@ static void statement(Compiler *compiler) { if (check(compiler, TOKEN_RIGHT_BRACE)) { if (check(compiler, TOKEN_SEMICOLON)) { - backTrack(); - backTrack(); + backTrack(&parser->scanner); + backTrack(&parser->scanner); parser->current = previous; expressionStatement(compiler); return; @@ -1818,7 +1928,7 @@ static void statement(Compiler *compiler) { if (check(compiler, TOKEN_COLON)) { for (int i = 0; i < parser->current.length + parser->previous.length; ++i) { - backTrack(); + backTrack(&parser->scanner); } parser->current = previous; @@ -1828,7 +1938,7 @@ static void statement(Compiler *compiler) { // Reset the scanner to the previous position for (int i = 0; i < parser->current.length; ++i) { - backTrack(); + backTrack(&parser->scanner); } // Reset the parser @@ -1852,7 +1962,10 @@ ObjFunction *compile(VM *vm, ObjModule *module, const char *source) { parser.panicMode = false; parser.module = module; - initScanner(source); + Scanner scanner; + initScanner(&scanner, source); + parser.scanner = scanner; + Compiler compiler; initCompiler(&parser, &compiler, NULL, TYPE_TOP_LEVEL); diff --git a/c/compiler.h b/c/compiler.h index 9f1185eb..45bc8314 100644 --- a/c/compiler.h +++ b/c/compiler.h @@ -68,6 +68,7 @@ typedef struct Loop { typedef struct { VM *vm; + Scanner scanner; Token current; Token previous; bool hadError; diff --git a/c/datatypes/files.c b/c/datatypes/files.c index 98454800..81bd49d9 100644 --- a/c/datatypes/files.c +++ b/c/datatypes/files.c @@ -1,5 +1,4 @@ #include "files.h" -#include "../vm.h" #include "../memory.h" static Value writeFile(VM *vm, int argCount, Value *args) { @@ -72,7 +71,7 @@ static Value readFullFile(VM *vm, int argCount, Value *args) { fseek(file->file, currentPosition, SEEK_SET); } - char *buffer = (char *) malloc(fileSize + 1); + char *buffer = ALLOCATE(vm, char, fileSize + 1); if (buffer == NULL) { runtimeError(vm, "Not enough memory to read \"%s\".\n", file->path); return EMPTY_VAL; @@ -80,16 +79,13 @@ static Value readFullFile(VM *vm, int argCount, Value *args) { size_t bytesRead = fread(buffer, sizeof(char), fileSize, file->file); if (bytesRead < fileSize && !feof(file->file)) { - free(buffer); + FREE_ARRAY(vm, char, buffer, fileSize + 1); runtimeError(vm, "Could not read file \"%s\".\n", file->path); return EMPTY_VAL; } buffer[bytesRead] = '\0'; - Value ret = OBJ_VAL(copyString(vm, buffer, bytesRead)); - - free(buffer); - return ret; + return OBJ_VAL(takeString(vm, buffer, bytesRead)); } static Value readLineFile(VM *vm, int argCount, Value *args) { diff --git a/c/datatypes/instance.c b/c/datatypes/instance.c index 5a7ce047..dc3e4100 100644 --- a/c/datatypes/instance.c +++ b/c/datatypes/instance.c @@ -88,6 +88,33 @@ static Value setAttribute(VM *vm, int argCount, Value *args) { return NIL_VAL; } +static Value isInstance(VM *vm, int argCount, Value *args) { + if (argCount != 1) { + runtimeError(vm, "isInstance() takes 1 argument (%d given)", argCount); + return EMPTY_VAL; + } + + if (!IS_CLASS(args[1])) { + runtimeError(vm, "Argument passed to isInstance() must be a class"); + return EMPTY_VAL; + } + + ObjInstance *object = AS_INSTANCE(args[0]); + + ObjClass *klass = AS_CLASS(args[1]); + ObjClass *klassToFind = object->klass; + + while (klassToFind != NULL) { + if (klass == klassToFind) { + return BOOL_VAL(true); + } + + klassToFind = klassToFind->superclass; + } + + return BOOL_VAL(false); +} + static Value copyShallow(VM *vm, int argCount, Value *args) { if (argCount != 0) { runtimeError(vm, "copy() takes no arguments (%d given)", argCount); @@ -117,6 +144,7 @@ void declareInstanceMethods(VM *vm) { defineNative(vm, &vm->instanceMethods, "hasAttribute", hasAttribute); defineNative(vm, &vm->instanceMethods, "getAttribute", getAttribute); defineNative(vm, &vm->instanceMethods, "setAttribute", setAttribute); + defineNative(vm, &vm->instanceMethods, "isInstance", isInstance); defineNative(vm, &vm->instanceMethods, "copy", copyShallow); defineNative(vm, &vm->instanceMethods, "deepCopy", copyDeep); } diff --git a/c/datatypes/lists.c b/c/datatypes/lists.c index 04229ea5..3ec65d8b 100644 --- a/c/datatypes/lists.c +++ b/c/datatypes/lists.c @@ -223,7 +223,7 @@ static Value joinListItem(VM *vm, int argCount, Value *args) { char *output; char *fullString = NULL; - int index = 0; + int length = 0; int delimiterLength = strlen(delimiter); for (int j = 0; j < list->values.count - 1; ++j) { @@ -233,15 +233,16 @@ static Value joinListItem(VM *vm, int argCount, Value *args) { output = valueToString(list->values.values[j]); } int elementLength = strlen(output); - fullString = realloc(fullString, index + elementLength + delimiterLength + 1); - memcpy(fullString + index, output, elementLength); + fullString = GROW_ARRAY(vm, fullString, char, length, length + elementLength + delimiterLength); + + memcpy(fullString + length, output, elementLength); if (!IS_STRING(list->values.values[j])) { free(output); } - index += elementLength; - memcpy(fullString + index, delimiter, delimiterLength); - index += delimiterLength; + length += elementLength; + memcpy(fullString + length, delimiter, delimiterLength); + length += delimiterLength; } // Outside the loop as we do not want the append the delimiter on the last element @@ -252,19 +253,17 @@ static Value joinListItem(VM *vm, int argCount, Value *args) { } int elementLength = strlen(output); - fullString = realloc(fullString, index + elementLength + 1); - memcpy(fullString + index, output, elementLength); - index += elementLength; + fullString = GROW_ARRAY(vm, fullString, char, length, length + elementLength + 1); + memcpy(fullString + length, output, elementLength); + length += elementLength; - fullString[index] = '\0'; + fullString[length] = '\0'; if (!IS_STRING(list->values.values[list->values.count - 1])) { free(output); } - Value ret = OBJ_VAL(copyString(vm, fullString, index)); - free(fullString); - return ret; + return OBJ_VAL(takeString(vm, fullString, length)); } static Value copyListShallow(VM *vm, int argCount, Value *args) { diff --git a/c/datatypes/number.c b/c/datatypes/number.c index c45ecc94..2e47b2cd 100644 --- a/c/datatypes/number.c +++ b/c/datatypes/number.c @@ -1,7 +1,5 @@ #include "number.h" -#include "../vm.h" - -#include +#include "../memory.h" static Value toStringNumber(VM *vm, int argCount, Value *args) { if (argCount != 0) { @@ -12,17 +10,15 @@ static Value toStringNumber(VM *vm, int argCount, Value *args) { double number = AS_NUMBER(args[0]); int numberStringLength = snprintf(NULL, 0, "%.15g", number) + 1; - char *numberString = malloc(numberStringLength); + char *numberString = ALLOCATE(vm, char, numberStringLength); + if (numberString == NULL) { runtimeError(vm, "Memory error on toString()!"); return EMPTY_VAL; } snprintf(numberString, numberStringLength, "%.15g", number); - Value value = OBJ_VAL(copyString(vm, numberString, numberStringLength - 1)); - - free(numberString); - return value; + return OBJ_VAL(takeString(vm, numberString, numberStringLength - 1)); } void declareNumberMethods(VM *vm) { diff --git a/c/datatypes/strings.c b/c/datatypes/strings.c index 0fe69f33..7bad623e 100644 --- a/c/datatypes/strings.c +++ b/c/datatypes/strings.c @@ -1,5 +1,4 @@ #include "strings.h" -#include "../vm.h" #include "../memory.h" @@ -40,7 +39,7 @@ static Value formatString(VM *vm, int argCount, Value *args) { } int length = 0; - char **replaceStrings = malloc(argCount * sizeof(char *)); + char **replaceStrings = ALLOCATE(vm, char*, argCount); for (int j = 1; j < argCount + 1; j++) { Value value = args[j]; @@ -59,7 +58,7 @@ static Value formatString(VM *vm, int argCount, Value *args) { ObjString *string = AS_STRING(args[0]); int stringLen = string->length + 1; - char *tmp = malloc(stringLen); + char *tmp = ALLOCATE(vm, char, stringLen); char *tmpFree = tmp; memcpy(tmp, string->chars, stringLen); @@ -78,14 +77,14 @@ static Value formatString(VM *vm, int argCount, Value *args) { free(replaceStrings[i]); } - free(tmp); - free(replaceStrings); + FREE_ARRAY(vm, char, tmp , stringLen); + FREE_ARRAY(vm, char*, replaceStrings, argCount); return EMPTY_VAL; } int fullLength = string->length - count * 2 + length + 1; char *pos; - char *newStr = malloc(sizeof(char) * fullLength); + char *newStr = ALLOCATE(vm, char, fullLength); int stringLength = 0; for (int i = 0; i < argCount; ++i) { @@ -102,12 +101,11 @@ static Value formatString(VM *vm, int argCount, Value *args) { free(replaceStrings[i]); } - free(replaceStrings); + FREE_ARRAY(vm, char*, replaceStrings, argCount); memcpy(newStr + stringLength, tmp, strlen(tmp)); - ObjString *newString = copyString(vm, newStr, fullLength - 1); + ObjString *newString = takeString(vm, newStr, fullLength - 1); - free(newStr); - free(tmpFree); + FREE_ARRAY(vm, char, tmpFree, stringLen); return OBJ_VAL(newString); } @@ -125,9 +123,9 @@ static Value splitString(VM *vm, int argCount, Value *args) { ObjString *string = AS_STRING(args[0]); char *delimiter = AS_CSTRING(args[1]); - char *tmp = malloc(string->length + 1); + char *tmp = ALLOCATE(vm, char, string->length + 1); char *tmpFree = tmp; - memcpy(tmp, string->chars, string->length + 1); + memcpy(tmp, string->chars, string->length); tmp[string->length] = '\0'; int delimiterLength = strlen(delimiter); char *token; @@ -162,7 +160,7 @@ static Value splitString(VM *vm, int argCount, Value *args) { } pop(vm); - free(tmpFree); + FREE_ARRAY(vm, char, tmpFree, string->length + 1); return OBJ_VAL(list); } @@ -251,9 +249,10 @@ static Value replaceString(VM *vm, int argCount, Value *args) { int stringLen = strlen(string) + 1; // Make a copy of the string so we do not modify the original - char *tmp = malloc(stringLen); + char *tmp = ALLOCATE(vm, char, stringLen + 1); char *tmpFree = tmp; memcpy(tmp, string, stringLen); + tmp[stringLen] = '\0'; // Count the occurrences of the needle so we can determine the size // of the string we need to allocate @@ -266,13 +265,13 @@ static Value replaceString(VM *vm, int argCount, Value *args) { tmp = tmpFree; if (count == 0) { - free(tmpFree); + FREE_ARRAY(vm, char, tmpFree, stringLen + 1); return stringValue; } int length = strlen(tmp) - count * (len - replaceLen) + 1; char *pos; - char *newStr = malloc(sizeof(char) * length); + char *newStr = ALLOCATE(vm, char, length); int stringLength = 0; for (int i = 0; i < count; ++i) { @@ -288,10 +287,9 @@ static Value replaceString(VM *vm, int argCount, Value *args) { } memcpy(newStr + stringLength, tmp, strlen(tmp)); - ObjString *newString = copyString(vm, newStr, length - 1); + ObjString *newString = takeString(vm, newStr, length - 1); - free(newStr); - free(tmpFree); + FREE_ARRAY(vm, char, tmpFree, stringLen + 1); return OBJ_VAL(newString); } @@ -302,17 +300,14 @@ static Value lowerString(VM *vm, int argCount, Value *args) { } ObjString *string = AS_STRING(args[0]); - - char *temp = malloc(sizeof(char) * (string->length + 1)); + char *temp = ALLOCATE(vm, char, string->length + 1); for (int i = 0; string->chars[i]; i++) { temp[i] = tolower(string->chars[i]); } temp[string->length] = '\0'; - Value ret = OBJ_VAL(copyString(vm, temp, string->length)); - free(temp); - return ret; + return OBJ_VAL(takeString(vm, temp, string->length)); } static Value upperString(VM *vm, int argCount, Value *args) { @@ -322,17 +317,14 @@ static Value upperString(VM *vm, int argCount, Value *args) { } ObjString *string = AS_STRING(args[0]); - - char *temp = malloc(sizeof(char) * (string->length + 1)); + char *temp = ALLOCATE(vm, char, string->length + 1); for (int i = 0; string->chars[i]; i++) { temp[i] = toupper(string->chars[i]); } temp[string->length] = '\0'; - Value ret = OBJ_VAL(copyString(vm, temp, string->length)); - free(temp); - return ret; + return OBJ_VAL(takeString(vm, temp, string->length)); } static Value startsWithString(VM *vm, int argCount, Value *args) { @@ -381,7 +373,7 @@ static Value leftStripString(VM *vm, int argCount, Value *args) { ObjString *string = AS_STRING(args[0]); int i, count = 0; - char *temp = malloc(sizeof(char) * (string->length + 1)); + char *temp = ALLOCATE(vm, char, string->length + 1); for (i = 0; i < string->length; ++i) { if (!isspace(string->chars[i])) { @@ -390,10 +382,11 @@ static Value leftStripString(VM *vm, int argCount, Value *args) { count++; } + // We need to remove the stripped chars from the count + // Will be free'd at the call of takeString + vm->bytesAllocated -= count; memcpy(temp, string->chars + count, string->length - count); - Value ret = OBJ_VAL(copyString(vm, temp, string->length - count)); - free(temp); - return ret; + return OBJ_VAL(takeString(vm, temp, string->length - count)); } static Value rightStripString(VM *vm, int argCount, Value *args) { @@ -404,7 +397,7 @@ static Value rightStripString(VM *vm, int argCount, Value *args) { ObjString *string = AS_STRING(args[0]); int length; - char *temp = malloc(sizeof(char) * (string->length + 1)); + char *temp = ALLOCATE(vm, char, string->length + 1); for (length = string->length - 1; length > 0; --length) { if (!isspace(string->chars[length])) { @@ -412,10 +405,12 @@ static Value rightStripString(VM *vm, int argCount, Value *args) { } } + // We need to remove the stripped chars from the count + // Will be free'd at the call of takeString + vm->bytesAllocated -= string->length - length - 1; memcpy(temp, string->chars, length + 1); - Value ret = OBJ_VAL(copyString(vm, temp, length + 1)); - free(temp); - return ret; + temp[length + 1] = '\0'; + return OBJ_VAL(takeString(vm, temp, length + 1)); } static Value stripString(VM *vm, int argCount, Value *args) { diff --git a/c/debug.c b/c/debug.c index ecdda8f0..d474dd25 100644 --- a/c/debug.c +++ b/c/debug.c @@ -41,6 +41,16 @@ static int builtinImportInstruction(const char* name, Chunk* chunk, return offset + 3; } +static int builtinFromImportInstruction(const char* name, Chunk* chunk, + int offset) { + uint8_t module = chunk->code[offset + 2]; + uint8_t argCount = chunk->code[offset + 3]; + printf("%-16s '", name); + printValue(chunk->constants.values[module]); + printf("'\n"); + return offset + 3 + argCount; +} + static int classInstruction(const char* name, Chunk* chunk, int offset) { uint8_t type = chunk->code[offset + 1]; @@ -178,8 +188,12 @@ int disassembleInstruction(Chunk *chunk, int offset) { return constantInstruction("OP_IMPORT", chunk, offset); case OP_IMPORT_BUILTIN: return builtinImportInstruction("OP_IMPORT_BUILTIN", chunk, offset); + case OP_IMPORT_BUILTIN_VARIABLE: + return builtinFromImportInstruction("OP_IMPORT_BUILTIN_VARIABLE", chunk, offset); case OP_IMPORT_VARIABLE: return simpleInstruction("OP_IMPORT_VARIABLE", offset); + case OP_IMPORT_FROM: + return constantInstruction("OP_IMPORT_FROM", chunk, offset); case OP_IMPORT_END: return simpleInstruction("OP_IMPORT_END", offset); case OP_NEW_LIST: diff --git a/c/main.c b/c/main.c index bb4abcff..d3bc55e7 100644 --- a/c/main.c +++ b/c/main.c @@ -15,10 +15,10 @@ void cleanupSockets(void) { #endif #include "common.h" -#include "vm.h" +#include "memory.h" #include "util.h" -#define VERSION "Dictu Version: 0.11.0\n" +#define VERSION "Dictu Version: 0.12.0\n" #ifndef DISABLE_LINENOISE #include "linenoise.h" @@ -83,8 +83,9 @@ static void repl(VM *vm, int argc, const char *argv[]) { linenoiseHistoryLoad("history.txt"); while((line = linenoise(">>> ")) != NULL) { - char *fullLine = malloc(sizeof(char) * (strlen(line) + 1)); - snprintf(fullLine, strlen(line) + 1, "%s", line); + int fullLineLength = strlen(line); + char *fullLine = ALLOCATE(vm, char, fullLineLength + 1); + snprintf(fullLine, fullLineLength + 1, "%s", line); linenoiseHistoryAdd(line); linenoiseHistorySave("history.txt"); @@ -92,12 +93,14 @@ static void repl(VM *vm, int argc, const char *argv[]) { while (!replCountBraces(fullLine) || !replCountQuotes(fullLine)) { free(line); line = linenoise("... "); + int lineLength = strlen(line); if (line == NULL) { return; } - char *temp = realloc(fullLine, strlen(fullLine) + strlen(line) + 1); + + char *temp = GROW_ARRAY(vm, fullLine, char, fullLineLength, fullLineLength + lineLength); if (temp == NULL) { printf("Unable to allocate memory\n"); @@ -105,7 +108,8 @@ static void repl(VM *vm, int argc, const char *argv[]) { } fullLine = temp; - memcpy(fullLine + strlen(fullLine), line, strlen(line) + 1); + memcpy(fullLine + fullLineLength, line, lineLength); + fullLineLength += lineLength; linenoiseHistoryAdd(line); linenoiseHistorySave("history.txt"); @@ -114,7 +118,7 @@ static void repl(VM *vm, int argc, const char *argv[]) { interpret(vm, fullLine); free(line); - free(fullLine); + FREE_ARRAY(vm, char, fullLine, fullLineLength + 1); } #else #define BUFFER_SIZE 8 @@ -159,9 +163,15 @@ static void repl(VM *vm, int argc, const char *argv[]) { static void runFile(VM *vm, int argc, const char *argv[]) { UNUSED(argc); - char *source = readFile(argv[1]); + char *source = readFile(vm, argv[1]); + + if (source == NULL) { + fprintf(stderr, "Could not open file \"%s\".\n", argv[1]); + exit(74); + } + InterpretResult result = interpret(vm, source); - free(source); // [owner] + FREE_ARRAY(vm, char, source, strlen(source) + 1); if (result == INTERPRET_COMPILE_ERROR) exit(65); if (result == INTERPRET_RUNTIME_ERROR) exit(70); diff --git a/c/memory.h b/c/memory.h index 1bb068b7..7b4aa715 100644 --- a/c/memory.h +++ b/c/memory.h @@ -14,7 +14,7 @@ ((capacity) < 8 ? 8 : (capacity) * 2) #define SHRINK_CAPACITY(capacity) \ - ((capacity) < 16 ? 7 : (capacity) / 2) + ((capacity) < 16 ? 8 : (capacity) / 2) #define GROW_ARRAY(vm, previous, type, oldCount, count) \ (type*)reallocate(vm, previous, sizeof(type) * (oldCount), \ diff --git a/c/natives.c b/c/natives.c index c875adf6..0561b80d 100644 --- a/c/natives.c +++ b/c/natives.c @@ -97,7 +97,7 @@ static Value inputNative(VM *vm, int argCount, Value *args) { } uint64_t currentSize = 128; - char *line = malloc(currentSize); + char *line = ALLOCATE(vm, char, currentSize); if (line == NULL) { runtimeError(vm, "Memory error on input()!"); @@ -105,13 +105,14 @@ static Value inputNative(VM *vm, int argCount, Value *args) { } int c = EOF; - uint64_t i = 0; + uint64_t length = 0; while ((c = getchar()) != '\n' && c != EOF) { - line[i++] = (char) c; + line[length++] = (char) c; - if (i + 1 == currentSize) { + if (length + 1 == currentSize) { + int oldSize = currentSize; currentSize = GROW_CAPACITY(currentSize); - line = realloc(line, currentSize); + line = GROW_ARRAY(vm, line, char, oldSize, currentSize); if (line == NULL) { printf("Unable to allocate memory\n"); @@ -120,10 +121,10 @@ static Value inputNative(VM *vm, int argCount, Value *args) { } } - line[i] = '\0'; + line[length] = '\0'; - Value l = OBJ_VAL(copyString(vm, line, strlen(line))); - free(line); + Value l = OBJ_VAL(takeString(vm, line, length)); + vm->bytesAllocated -= currentSize - length - 1; return l; } diff --git a/c/opcodes.h b/c/opcodes.h index 01dbaa0b..49a8a2c9 100644 --- a/c/opcodes.h +++ b/c/opcodes.h @@ -53,7 +53,9 @@ OPCODE(END_CLASS) OPCODE(METHOD) OPCODE(IMPORT) OPCODE(IMPORT_BUILTIN) +OPCODE(IMPORT_BUILTIN_VARIABLE) OPCODE(IMPORT_VARIABLE) +OPCODE(IMPORT_FROM) OPCODE(IMPORT_END) OPCODE(USE) OPCODE(OPEN_FILE) diff --git a/c/optionals/c.c b/c/optionals/c.c index f971341a..0e918334 100644 --- a/c/optionals/c.c +++ b/c/optionals/c.c @@ -49,7 +49,7 @@ Value strerrorNative(VM *vm, int argCount, Value *args) { return strerrorGeneric(vm, error); } -void createCClass(VM *vm) { +void createCModule(VM *vm) { ObjString *name = copyString(vm, "C", 1); push(vm, OBJ_VAL(name)); ObjModule *module = newModule(vm, name); diff --git a/c/optionals/c.h b/c/optionals/c.h index fa9c272c..b085d719 100644 --- a/c/optionals/c.h +++ b/c/optionals/c.h @@ -35,7 +35,7 @@ #include "../vm.h" #include "../memory.h" -void createCClass(VM *vm); +void createCModule(VM *vm); #define MAX_ERROR_LEN 256 Value strerrorGeneric(VM *, int); diff --git a/c/optionals/datetime.c b/c/optionals/datetime.c index fb0030cd..a01d17ef 100644 --- a/c/optionals/datetime.c +++ b/c/optionals/datetime.c @@ -1,4 +1,3 @@ -#include #include #include "datetime.h" @@ -85,7 +84,7 @@ static Value strftimeNative(VM *vm, int argCount, Value *args) { struct tm tictoc; int len = (format->length > 128 ? format->length * 4 : 128); - char *point = malloc(len); + char *point = ALLOCATE(vm, char, len); if (point == NULL) { runtimeError(vm, "Memory error on strftime()!"); return EMPTY_VAL; @@ -99,36 +98,33 @@ static Value strftimeNative(VM *vm, int argCount, Value *args) { * there is a big enough buffer. */ - /** however is not guaranteed that 0 indicates a failure (`man strftime' says so). + /** however is not guaranteed that 0 indicates a failure (`man strftime' says so). * So we might want to catch up the eternal loop, by using a maximum iterator. */ - - ObjString *res; - int max_iterations = 8; // maximum 65536 bytes with the default 128 len, // more if the given string is > 128 int iterator = 0; while (strftime(point, sizeof(char) * len, fmt, &tictoc) == 0) { if (++iterator > max_iterations) { - res = copyString(vm, "", 0); - goto theend; + FREE_ARRAY(vm, char, point, len); + return OBJ_VAL(copyString(vm, "", 0)); } len *= 2; - point = realloc(point, len); + point = GROW_ARRAY(vm, point, char, len / 2, len); if (point == NULL) { runtimeError(vm, "Memory error on strftime()!"); return EMPTY_VAL; } } - res = copyString(vm, point, strlen(point)); + int length = strlen(point); -theend: - free(point); + // Account for the buffer created at the start + vm->bytesAllocated -= len - length - 1; - return OBJ_VAL(res); + return OBJ_VAL(takeString(vm, point, length)); } #ifdef HAS_STRPTIME @@ -157,7 +153,7 @@ static Value strptimeNative(VM *vm, int argCount, Value *args) { } #endif -ObjModule *createDatetimeClass(VM *vm) { +ObjModule *createDatetimeModule(VM *vm) { ObjString *name = copyString(vm, "Datetime", 8); push(vm, OBJ_VAL(name)); ObjModule *module = newModule(vm, name); @@ -166,12 +162,19 @@ ObjModule *createDatetimeClass(VM *vm) { /** * Define Datetime methods */ + defineNative(vm, &module->values, "strerror", strerrorNative); defineNative(vm, &module->values, "now", nowNative); defineNative(vm, &module->values, "nowUTC", nowUTCNative); defineNative(vm, &module->values, "strftime", strftimeNative); #ifdef HAS_STRPTIME defineNative(vm, &module->values, "strptime", strptimeNative); #endif + + /** + * Define Datetime properties + */ + defineNativeProperty(vm, &module->values, "errno", NUMBER_VAL(0)); + pop(vm); pop(vm); diff --git a/c/optionals/datetime.h b/c/optionals/datetime.h index 20a7fc89..2095a489 100644 --- a/c/optionals/datetime.h +++ b/c/optionals/datetime.h @@ -1,13 +1,17 @@ #ifndef dictu_datetime_h #define dictu_datetime_h +#ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE +#endif +#ifndef __USE_XOPEN #define __USE_XOPEN +#endif #include #include "optionals.h" -ObjModule *createDatetimeClass(VM *vm); +ObjModule *createDatetimeModule(VM *vm); #endif //dictu_datetime_h diff --git a/c/optionals/env.c b/c/optionals/env.c index ed85b911..3e47f0a4 100644 --- a/c/optionals/env.c +++ b/c/optionals/env.c @@ -68,7 +68,7 @@ static Value set(VM *vm, int argCount, Value *args) { return NUMBER_VAL(retval == 0 ? OK : NOTOK); } -ObjModule *createEnvClass(VM *vm) { +ObjModule *createEnvModule(VM *vm) { ObjString *name = copyString(vm, "Env", 3); push(vm, OBJ_VAL(name)); ObjModule *module = newModule(vm, name); @@ -80,6 +80,12 @@ ObjModule *createEnvClass(VM *vm) { defineNative(vm, &module->values, "strerror", strerrorNative); defineNative(vm, &module->values, "get", get); defineNative(vm, &module->values, "set", set); + + /** + * Define Env properties + */ + defineNativeProperty(vm, &module->values, "errno", NUMBER_VAL(0)); + pop(vm); pop(vm); diff --git a/c/optionals/env.h b/c/optionals/env.h index f1b5e329..f836ac86 100644 --- a/c/optionals/env.h +++ b/c/optionals/env.h @@ -7,6 +7,6 @@ #include "optionals.h" #include "../vm.h" -ObjModule *createEnvClass(VM *vm); +ObjModule *createEnvModule(VM *vm); #endif //dictu_env_h diff --git a/c/optionals/http.c b/c/optionals/http.c index 6dfc84cb..c49fecb5 100644 --- a/c/optionals/http.c +++ b/c/optionals/http.c @@ -27,8 +27,7 @@ static void createResponse(VM *vm, Response *response) { response->res = NULL; } -static size_t writeResponse(char *ptr, size_t size, size_t nmemb, void *data) -{ +static size_t writeResponse(char *ptr, size_t size, size_t nmemb, void *data) { Response *response = (Response *) data; size_t new_len = response->len + size * nmemb; response->res = GROW_ARRAY(response->vm, response->res, char, response->len, new_len + 1); @@ -43,8 +42,7 @@ static size_t writeResponse(char *ptr, size_t size, size_t nmemb, void *data) return size * nmemb; } -static size_t writeHeaders(char *ptr, size_t size, size_t nitems, void *data) -{ +static size_t writeHeaders(char *ptr, size_t size, size_t nitems, void *data) { Response *response = (Response *) data; // if nitems equals 2 its an empty header if (nitems != 2) { @@ -117,7 +115,7 @@ static char *dictToPostArgs(ObjDict *dict) { return ret; } -static ObjDict* endRequest(VM *vm, CURL *curl, Response response) { +static ObjDict *endRequest(VM *vm, CURL *curl, Response response) { // Get status code curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response.statusCode); ObjString *content = takeString(vm, response.res, response.len); @@ -125,10 +123,6 @@ static ObjDict* endRequest(VM *vm, CURL *curl, Response response) { // Push to stack to avoid GC push(vm, OBJ_VAL(content)); - /* always cleanup */ - curl_easy_cleanup(curl); - curl_global_cleanup(); - ObjDict *responseVal = initDict(vm); // Push to stack to avoid GC push(vm, OBJ_VAL(responseVal)); @@ -153,6 +147,10 @@ static ObjDict* endRequest(VM *vm, CURL *curl, Response response) { pop(vm); pop(vm); + /* always cleanup */ + curl_easy_cleanup(curl); + curl_global_cleanup(); + return responseVal; } @@ -201,6 +199,11 @@ static Value get(VM *vm, int argCount, Value *args) { /* Check for errors */ if (curlResponse != CURLE_OK) { + /* always cleanup */ + curl_easy_cleanup(curl); + curl_global_cleanup(); + pop(vm); + errno = curlResponse; SET_ERRNO(GET_SELF_CLASS); return NIL_VAL; @@ -209,6 +212,11 @@ static Value get(VM *vm, int argCount, Value *args) { return OBJ_VAL(endRequest(vm, curl, response)); } + /* always cleanup */ + curl_easy_cleanup(curl); + curl_global_cleanup(); + pop(vm); + errno = CURLE_FAILED_INIT; SET_ERRNO(GET_SELF_CLASS); return NIL_VAL; @@ -234,7 +242,7 @@ static Value post(VM *vm, int argCount, Value *args) { return EMPTY_VAL; } - timeout = (long)AS_NUMBER(args[2]); + timeout = (long) AS_NUMBER(args[2]); dict = AS_DICT(args[1]); } else if (argCount == 2) { if (!IS_DICT(args[1])) { @@ -282,6 +290,11 @@ static Value post(VM *vm, int argCount, Value *args) { } if (curlResponse != CURLE_OK) { + /* always cleanup */ + curl_easy_cleanup(curl); + curl_global_cleanup(); + pop(vm); + errno = curlResponse; SET_ERRNO(GET_SELF_CLASS); return NIL_VAL; @@ -290,12 +303,17 @@ static Value post(VM *vm, int argCount, Value *args) { return OBJ_VAL(endRequest(vm, curl, response)); } + /* always cleanup */ + curl_easy_cleanup(curl); + curl_global_cleanup(); + pop(vm); + errno = CURLE_FAILED_INIT; SET_ERRNO(GET_SELF_CLASS); return NIL_VAL; } -ObjModule *createHTTPClass(VM *vm) { +ObjModule *createHTTPModule(VM *vm) { ObjString *name = copyString(vm, "HTTP", 4); push(vm, OBJ_VAL(name)); ObjModule *module = newModule(vm, name); diff --git a/c/optionals/http.h b/c/optionals/http.h index 73af4304..26806449 100644 --- a/c/optionals/http.h +++ b/c/optionals/http.h @@ -10,13 +10,12 @@ typedef struct response { VM *vm; - char *res; ObjList *headers; + char *res; size_t len; - size_t headerLen; long statusCode; } Response; -ObjModule *createHTTPClass(VM *vm); +ObjModule *createHTTPModule(VM *vm); #endif //dictu_http_h \ No newline at end of file diff --git a/c/optionals/json.c b/c/optionals/json.c index c1954e53..f8f126a7 100644 --- a/c/optionals/json.c +++ b/c/optionals/json.c @@ -146,7 +146,7 @@ static Value parse(VM *vm, int argCount, Value *args) { return val; } -json_value* stringifyJson(Value value) { +json_value* stringifyJson(VM *vm, Value value) { if (IS_NIL(value)) { return json_null_new(); } else if (IS_BOOL(value)) { @@ -170,7 +170,7 @@ json_value* stringifyJson(Value value) { json_value *json = json_array_new(list->values.count); for (int i = 0; i < list->values.count; i++) { - json_array_push(json, stringifyJson(list->values.values[i])); + json_array_push(json, stringifyJson(vm, list->values.values[i])); } return json; @@ -202,7 +202,7 @@ json_value* stringifyJson(Value value) { json_object_push( json, key, - stringifyJson(entry->value) + stringifyJson(vm, entry->value) ); if (!IS_STRING(entry->key)) { @@ -240,7 +240,7 @@ static Value stringify(VM *vm, int argCount, Value *args) { indent = AS_NUMBER(args[1]); } - json_value *json = stringifyJson(args[0]); + json_value *json = stringifyJson(vm, args[0]); if (json == NULL) { errno = JSON_ENOSERIAL; @@ -255,15 +255,22 @@ static Value stringify(VM *vm, int argCount, Value *args) { indent }; - char *buf = malloc(json_measure_ex(json, default_opts)); + + int length = json_measure_ex(json, default_opts); + char *buf = ALLOCATE(vm, char, length); json_serialize_ex(buf, json, default_opts); - ObjString *string = copyString(vm, buf, strlen(buf)); - free(buf); + int actualLength = strlen(buf); + ObjString *string = takeString(vm, buf, actualLength); + + // json_measure_ex can produce a length larger than the actual string returned + // so we need to cater for this case + vm->bytesAllocated -= length - actualLength - 1; + json_builder_free(json); return OBJ_VAL(string); } -ObjModule *createJSONClass(VM *vm) { +ObjModule *createJSONModule(VM *vm) { ObjString *name = copyString(vm, "JSON", 4); push(vm, OBJ_VAL(name)); ObjModule *module = newModule(vm, name); diff --git a/c/optionals/json.h b/c/optionals/json.h index 87b6424f..667316be 100644 --- a/c/optionals/json.h +++ b/c/optionals/json.h @@ -6,6 +6,6 @@ #include "optionals.h" #include "../vm.h" -ObjModule *createJSONClass(VM *vm); +ObjModule *createJSONModule(VM *vm); #endif //dictu_json_h diff --git a/c/optionals/math.c b/c/optionals/math.c index 91d7f8d4..468f4ae0 100644 --- a/c/optionals/math.c +++ b/c/optionals/math.c @@ -179,15 +179,58 @@ static Value sqrtNative(VM *vm, int argCount, Value *args) { return NUMBER_VAL(sqrt(AS_NUMBER(args[0]))); } -ObjModule *createMathsClass(VM *vm) { +static Value sinNative(VM *vm, int argCount, Value *args) { + if (argCount != 1) { + runtimeError(vm, "sin() takes 1 argument (%d given).", argCount); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[0])) { + runtimeError(vm, "A non-number value passed to sin()"); + return EMPTY_VAL; + } + + return NUMBER_VAL(sin(AS_NUMBER(args[0]))); +} + +static Value cosNative(VM *vm, int argCount, Value *args) { + if (argCount != 1) { + runtimeError(vm, "cos() takes 1 argument (%d given).", argCount); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[0])) { + runtimeError(vm, "A non-number value passed to cos()"); + return EMPTY_VAL; + } + + return NUMBER_VAL(cos(AS_NUMBER(args[0]))); +} + +static Value tanNative(VM *vm, int argCount, Value *args) { + if (argCount != 1) { + runtimeError(vm, "tan() takes 1 argument (%d given).", argCount); + return EMPTY_VAL; + } + + if (!IS_NUMBER(args[0])) { + runtimeError(vm, "A non-number value passed to tan()"); + return EMPTY_VAL; + } + + return NUMBER_VAL(tan(AS_NUMBER(args[0]))); +} + +ObjModule *createMathsModule(VM *vm) { ObjString *name = copyString(vm, "Math", 4); push(vm, OBJ_VAL(name)); ObjModule *module = newModule(vm, name); push(vm, OBJ_VAL(module)); /** - * Define Math values + * Define Math methods */ + defineNative(vm, &module->values, "strerror", strerrorNative); defineNative(vm, &module->values, "average", averageNative); defineNative(vm, &module->values, "floor", floorNative); defineNative(vm, &module->values, "round", roundNative); @@ -197,10 +240,14 @@ ObjModule *createMathsClass(VM *vm) { defineNative(vm, &module->values, "min", minNative); defineNative(vm, &module->values, "sum", sumNative); defineNative(vm, &module->values, "sqrt", sqrtNative); + defineNative(vm, &module->values, "sin", sinNative); + defineNative(vm, &module->values, "cos", cosNative); + defineNative(vm, &module->values, "tan", tanNative); /** * Define Math properties */ + defineNativeProperty(vm, &module->values, "errno", NUMBER_VAL(0)); defineNativeProperty(vm, &module->values, "PI", NUMBER_VAL(3.14159265358979)); defineNativeProperty(vm, &module->values, "e", NUMBER_VAL(2.71828182845905)); pop(vm); diff --git a/c/optionals/math.h b/c/optionals/math.h index ececd1a0..0c7dc08a 100644 --- a/c/optionals/math.h +++ b/c/optionals/math.h @@ -6,6 +6,6 @@ #include "optionals.h" #include "../vm.h" -ObjModule *createMathsClass(VM *vm); +ObjModule *createMathsModule(VM *vm); #endif //dictu_math_h diff --git a/c/optionals/optionals.c b/c/optionals/optionals.c index 9f30f994..7c152ba6 100644 --- a/c/optionals/optionals.c +++ b/c/optionals/optionals.c @@ -1,15 +1,15 @@ #include "optionals.h" BuiltinModules modules[] = { - {"Math", &createMathsClass}, - {"Env", &createEnvClass}, - {"JSON", &createJSONClass}, - {"Path", &createPathClass}, - {"Datetime", &createDatetimeClass}, - {"Socket", &createSocketClass}, - {"Random", &createRandomClass}, + {"Math", &createMathsModule}, + {"Env", &createEnvModule}, + {"JSON", &createJSONModule}, + {"Path", &createPathModule}, + {"Datetime", &createDatetimeModule}, + {"Socket", &createSocketModule}, + {"Random", &createRandomModule}, #ifndef DISABLE_HTTP - {"HTTP", &createHTTPClass}, + {"HTTP", &createHTTPModule}, #endif {NULL, NULL} }; diff --git a/c/optionals/path.c b/c/optionals/path.c index a92bb0c4..a27dc43a 100644 --- a/c/optionals/path.c +++ b/c/optionals/path.c @@ -215,7 +215,8 @@ static Value listdirNative(VM *vm, int argCount, Value *args) { push(vm, OBJ_VAL(dir_contents)); #ifdef _WIN32 - char *searchPath = malloc(strlen(path) + 4); + int length = strlen(path) + 4; + char *searchPath = ALLOCATE(vm, char, length); if (searchPath == NULL) { runtimeError(vm, "Memory error on listdir()!"); return EMPTY_VAL; @@ -243,7 +244,7 @@ static Value listdirNative(VM *vm, int argCount, Value *args) { } while (FindNextFile(dir, &file) != 0); FindClose(dir); - free(searchPath); + FREE_ARRAY(vm, char, searchPath, length); #else struct dirent *dir; DIR *d; @@ -262,6 +263,8 @@ static Value listdirNative(VM *vm, int argCount, Value *args) { runtimeError(vm, "%s is not a path!", path); return EMPTY_VAL; } + + closedir(d); #endif pop(vm); @@ -269,7 +272,7 @@ static Value listdirNative(VM *vm, int argCount, Value *args) { return OBJ_VAL(dir_contents); } -ObjModule *createPathClass(VM *vm) { +ObjModule *createPathModule(VM *vm) { ObjString *name = copyString(vm, "Path", 4); push(vm, OBJ_VAL(name)); ObjModule *module = newModule(vm, name); @@ -280,9 +283,8 @@ ObjModule *createPathClass(VM *vm) { */ #ifdef HAS_REALPATH defineNative(vm, &module->values, "realpath", realpathNative); - defineNativeProperty(vm, &module->values, "errno", NUMBER_VAL(0)); - defineNative(vm, &module->values, "strerror", strerrorNative); // only realpath uses errno #endif + defineNative(vm, &module->values, "strerror", strerrorNative); // only realpath uses errno defineNative(vm, &module->values, "isAbsolute", isAbsoluteNative); defineNative(vm, &module->values, "basename", basenameNative); defineNative(vm, &module->values, "extname", extnameNative); @@ -291,6 +293,10 @@ ObjModule *createPathClass(VM *vm) { defineNative(vm, &module->values, "isdir", isdirNative); defineNative(vm, &module->values, "listdir", listdirNative); + /** + * Define Path properties + */ + defineNativeProperty(vm, &module->values, "errno", NUMBER_VAL(0)); defineNativeProperty(vm, &module->values, "delimiter", OBJ_VAL( copyString(vm, PATH_DELIMITER_AS_STRING, PATH_DELIMITER_STRLEN))); defineNativeProperty(vm, &module->values, "dirSeparator", OBJ_VAL( diff --git a/c/optionals/path.h b/c/optionals/path.h index a879ee62..07eb3e0a 100644 --- a/c/optionals/path.h +++ b/c/optionals/path.h @@ -41,6 +41,6 @@ #include "../vm.h" #include "../memory.h" -ObjModule *createPathClass(VM *vm); +ObjModule *createPathModule(VM *vm); #endif //dictu_path_h diff --git a/c/optionals/random.c b/c/optionals/random.c index ae9da8ad..0d79ffcb 100644 --- a/c/optionals/random.c +++ b/c/optionals/random.c @@ -67,7 +67,7 @@ static Value randomSelect(VM *vm, int argCount, Value *args) return args[index]; } -ObjModule *createRandomClass(VM *vm) +ObjModule *createRandomModule(VM *vm) { ObjString *name = copyString(vm, "Random", 6); push(vm, OBJ_VAL(name)); @@ -79,10 +79,16 @@ ObjModule *createRandomClass(VM *vm) /** * Define Random methods */ + defineNative(vm, &module->values, "strerror", strerrorNative); defineNative(vm, &module->values, "random", randomRandom); defineNative(vm, &module->values, "range", randomRange); defineNative(vm, &module->values, "select", randomSelect); + /** + * Define Random properties + */ + defineNativeProperty(vm, &module->values, "errno", NUMBER_VAL(0)); + pop(vm); pop(vm); diff --git a/c/optionals/random.h b/c/optionals/random.h index be86105e..176adf15 100644 --- a/c/optionals/random.h +++ b/c/optionals/random.h @@ -7,6 +7,6 @@ #include "optionals.h" #include "../vm.h" -ObjModule *createRandomClass(VM *vm); +ObjModule *createRandomModule(VM *vm); #endif //dictu_random_h diff --git a/c/optionals/socket.c b/c/optionals/socket.c index 54910588..ed90a48d 100644 --- a/c/optionals/socket.c +++ b/c/optionals/socket.c @@ -223,7 +223,7 @@ static Value setSocketOpt(VM *vm, int argCount, Value *args) { return TRUE_VAL; } -ObjModule *createSocketClass(VM *vm) { +ObjModule *createSocketModule(VM *vm) { ObjString *name = copyString(vm, "Socket", 6); push(vm, OBJ_VAL(name)); ObjModule *module = newModule(vm, name); @@ -245,7 +245,7 @@ ObjModule *createSocketClass(VM *vm) { defineNativeProperty(vm, &module->values, "SO_REUSEADDR", NUMBER_VAL(SO_REUSEADDR)); /** - * Setup socket object methods + * Setup Socket object methods */ defineNative(vm, &vm->socketMethods, "bind", bindSocket); defineNative(vm, &vm->socketMethods, "listen", listenSocket); diff --git a/c/optionals/socket.h b/c/optionals/socket.h index 4efd2164..062cc135 100644 --- a/c/optionals/socket.h +++ b/c/optionals/socket.h @@ -10,6 +10,6 @@ #include #endif -ObjModule *createSocketClass(VM *vm); +ObjModule *createSocketModule(VM *vm); #endif //dictu_socket_h diff --git a/c/optionals/system.c b/c/optionals/system.c index 3a2cc342..e61ea046 100644 --- a/c/optionals/system.c +++ b/c/optionals/system.c @@ -307,7 +307,7 @@ void initPlatform(VM *vm, Table *table) { #endif } -void createSystemClass(VM *vm, int argc, const char *argv[]) { +void createSystemModule(VM *vm, int argc, const char *argv[]) { ObjString *name = copyString(vm, "System", 6); push(vm, OBJ_VAL(name)); ObjModule *module = newModule(vm, name); diff --git a/c/optionals/system.h b/c/optionals/system.h index 47d0f30b..54ddce20 100644 --- a/c/optionals/system.h +++ b/c/optionals/system.h @@ -24,6 +24,6 @@ #include "../vm.h" #include "../memory.h" -void createSystemClass(VM *vm, int argc, const char *argv[]); +void createSystemModule(VM *vm, int argc, const char *argv[]); #endif //dictu_system_h diff --git a/c/scanner.c b/c/scanner.c index d32fa613..2138669b 100644 --- a/c/scanner.c +++ b/c/scanner.c @@ -1,23 +1,13 @@ -#include #include #include "common.h" #include "scanner.h" -typedef struct { - const char *start; - const char *current; - int line; - bool rawString; -} Scanner; - -Scanner scanner; - -void initScanner(const char *source) { - scanner.start = source; - scanner.current = source; - scanner.line = 1; - scanner.rawString = false; +void initScanner(Scanner *scanner, const char *source) { + scanner->start = source; + scanner->current = source; + scanner->line = 1; + scanner->rawString = false; } static bool isAlpha(char c) { @@ -30,92 +20,96 @@ static bool isDigit(char c) { return c >= '0' && c <= '9'; } -static bool isAtEnd() { - return *scanner.current == '\0'; +static bool isHexDigit(char c) { + return ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || (c == '_')); } -static char advance() { - scanner.current++; - return scanner.current[-1]; +static bool isAtEnd(Scanner *scanner) { + return *scanner->current == '\0'; } -static char peek() { - return *scanner.current; +static char advance(Scanner *scanner) { + scanner->current++; + return scanner->current[-1]; } -static char peekNext() { - if (isAtEnd()) return '\0'; - return scanner.current[1]; +static char peek(Scanner *scanner) { + return *scanner->current; } -static bool match(char expected) { - if (isAtEnd()) return false; - if (*scanner.current != expected) return false; +static char peekNext(Scanner *scanner) { + if (isAtEnd(scanner)) return '\0'; + return scanner->current[1]; +} - scanner.current++; +static bool match(Scanner *scanner, char expected) { + if (isAtEnd(scanner)) return false; + if (*scanner->current != expected) return false; + + scanner->current++; return true; } -static Token makeToken(TokenType type) { +static Token makeToken(Scanner *scanner, TokenType type) { Token token; token.type = type; - token.start = scanner.start; - token.length = (int) (scanner.current - scanner.start); - token.line = scanner.line; + token.start = scanner->start; + token.length = (int) (scanner->current - scanner->start); + token.line = scanner->line; return token; } -static Token errorToken(const char *message) { +static Token errorToken(Scanner *scanner, const char *message) { Token token; token.type = TOKEN_ERROR; token.start = message; token.length = (int) strlen(message); - token.line = scanner.line; + token.line = scanner->line; return token; } -static void skipWhitespace() { +static void skipWhitespace(Scanner *scanner) { for (;;) { - char c = peek(); + char c = peek(scanner); switch (c) { case ' ': case '\r': case '\t': - advance(); + advance(scanner); break; case '\n': - scanner.line++; - advance(); + scanner->line++; + advance(scanner); break; case '/': - if (peekNext() == '*') { + if (peekNext(scanner) == '*') { // Multiline comments - advance(); - advance(); + advance(scanner); + advance(scanner); while (true) { - while (peek() != '*' && !isAtEnd()) { - if ((c = advance()) == '\n') { - scanner.line++; + while (peek(scanner) != '*' && !isAtEnd(scanner)) { + if ((c = advance(scanner)) == '\n') { + scanner->line++; } } - if (isAtEnd()) + if (isAtEnd(scanner)) return; - if (peekNext() == '/') { + if (peekNext(scanner) == '/') { break; } - advance(); + advance(scanner); } - advance(); - advance(); - } else if (peekNext() == '/') { + advance(scanner); + advance(scanner); + } else if (peekNext(scanner) == '/') { // A comment goes until the end of the line. - while (peek() != '\n' && !isAtEnd()) advance(); + while (peek(scanner) != '\n' && !isAtEnd(scanner)) advance(scanner); } else { return; } @@ -127,51 +121,51 @@ static void skipWhitespace() { } } -static TokenType checkKeyword(int start, int length, +static TokenType checkKeyword(Scanner *scanner, int start, int length, const char *rest, TokenType type) { - if (scanner.current - scanner.start == start + length && - memcmp(scanner.start + start, rest, length) == 0) { + if (scanner->current - scanner->start == start + length && + memcmp(scanner->start + start, rest, length) == 0) { return type; } return TOKEN_IDENTIFIER; } -static TokenType identifierType() { - switch (scanner.start[0]) { +static TokenType identifierType(Scanner *scanner) { + switch (scanner->start[0]) { case 'a': - if (scanner.current - scanner.start > 1) { - switch (scanner.start[1]) { + if (scanner->current - scanner->start > 1) { + switch (scanner->start[1]) { case 'b': { - return checkKeyword(2, 6, "stract", TOKEN_ABSTRACT); + return checkKeyword(scanner, 2, 6, "stract", TOKEN_ABSTRACT); } case 'n': { - return checkKeyword(2, 1, "d", TOKEN_AND); + return checkKeyword(scanner, 2, 1, "d", TOKEN_AND); } case 's': { - return checkKeyword(2, 0, "", TOKEN_AS); + return checkKeyword(scanner, 2, 0, "", TOKEN_AS); } } } break; case 'b': - return checkKeyword(1, 4, "reak", TOKEN_BREAK); + return checkKeyword(scanner, 1, 4, "reak", TOKEN_BREAK); case 'c': - if (scanner.current - scanner.start > 1) { - switch (scanner.start[1]) { + if (scanner->current - scanner->start > 1) { + switch (scanner->start[1]) { case 'l': - return checkKeyword(2, 3, "ass", TOKEN_CLASS); + return checkKeyword(scanner, 2, 3, "ass", TOKEN_CLASS); case 'o': { // Skip second char // Skip third char - if (scanner.current - scanner.start > 3) { - switch (scanner.start[3]) { + if (scanner->current - scanner->start > 3) { + switch (scanner->start[3]) { case 't': - return checkKeyword(4, 4, "inue", TOKEN_CONTINUE); + return checkKeyword(scanner, 4, 4, "inue", TOKEN_CONTINUE); case 's': - return checkKeyword(4, 1, "t", TOKEN_CONST); + return checkKeyword(scanner, 4, 1, "t", TOKEN_CONST); } } } @@ -179,92 +173,94 @@ static TokenType identifierType() { } break; case 'd': - return checkKeyword(1, 2, "ef", TOKEN_DEF); + return checkKeyword(scanner, 1, 2, "ef", TOKEN_DEF); case 'e': - return checkKeyword(1, 3, "lse", TOKEN_ELSE); + return checkKeyword(scanner, 1, 3, "lse", TOKEN_ELSE); case 'f': - if (scanner.current - scanner.start > 1) { - switch (scanner.start[1]) { + if (scanner->current - scanner->start > 1) { + switch (scanner->start[1]) { case 'a': - return checkKeyword(2, 3, "lse", TOKEN_FALSE); + return checkKeyword(scanner, 2, 3, "lse", TOKEN_FALSE); case 'o': - return checkKeyword(2, 1, "r", TOKEN_FOR); + return checkKeyword(scanner, 2, 1, "r", TOKEN_FOR); + case 'r': + return checkKeyword(scanner, 2, 2, "om", TOKEN_FROM); } } break; case 'i': - if (scanner.current - scanner.start > 1) { - switch (scanner.start[1]) { + if (scanner->current - scanner->start > 1) { + switch (scanner->start[1]) { case 'f': - return checkKeyword(2, 0, "", TOKEN_IF); + return checkKeyword(scanner, 2, 0, "", TOKEN_IF); case 'm': - return checkKeyword(2, 4, "port", TOKEN_IMPORT); + return checkKeyword(scanner, 2, 4, "port", TOKEN_IMPORT); } } break; case 'n': - if (scanner.current - scanner.start > 1) { - switch (scanner.start[1]) { + if (scanner->current - scanner->start > 1) { + switch (scanner->start[1]) { case 'o': - return checkKeyword(2, 1, "t", TOKEN_BANG); + return checkKeyword(scanner, 2, 1, "t", TOKEN_BANG); case 'i': - return checkKeyword(2, 1, "l", TOKEN_NIL); + return checkKeyword(scanner, 2, 1, "l", TOKEN_NIL); } } break; case 'o': - return checkKeyword(1, 1, "r", TOKEN_OR); + return checkKeyword(scanner, 1, 1, "r", TOKEN_OR); case 'r': - if (scanner.current - scanner.start > 1) { - switch (scanner.start[1]) { + if (scanner->current - scanner->start > 1) { + switch (scanner->start[1]) { case 'e': - return checkKeyword(2, 4, "turn", TOKEN_RETURN); + return checkKeyword(scanner, 2, 4, "turn", TOKEN_RETURN); } } else { - if (scanner.start[1] == '"' || scanner.start[1] == '\'') { - scanner.rawString = true; + if (scanner->start[1] == '"' || scanner->start[1] == '\'') { + scanner->rawString = true; return TOKEN_R; } } break; case 's': - if (scanner.current - scanner.start > 1) { - switch (scanner.start[1]) { + if (scanner->current - scanner->start > 1) { + switch (scanner->start[1]) { case 'u': - return checkKeyword(2, 3, "per", TOKEN_SUPER); + return checkKeyword(scanner, 2, 3, "per", TOKEN_SUPER); case 't': - return checkKeyword(2, 4, "atic", TOKEN_STATIC); + return checkKeyword(scanner, 2, 4, "atic", TOKEN_STATIC); } } break; case 't': - if (scanner.current - scanner.start > 1) { - switch (scanner.start[1]) { + if (scanner->current - scanner->start > 1) { + switch (scanner->start[1]) { case 'h': - return checkKeyword(2, 2, "is", TOKEN_THIS); + return checkKeyword(scanner, 2, 2, "is", TOKEN_THIS); case 'r': - if (scanner.current - scanner.start > 2) { - switch (scanner.start[2]) { + if (scanner->current - scanner->start > 2) { + switch (scanner->start[2]) { case 'u': - return checkKeyword(3, 1, "e", TOKEN_TRUE); + return checkKeyword(scanner, 3, 1, "e", TOKEN_TRUE); case 'a': - return checkKeyword(3, 2, "it", TOKEN_TRAIT); + return checkKeyword(scanner, 3, 2, "it", TOKEN_TRAIT); } } } } break; case 'u': - return checkKeyword(1, 2, "se", TOKEN_USE); + return checkKeyword(scanner, 1, 2, "se", TOKEN_USE); case 'v': - return checkKeyword(1, 2, "ar", TOKEN_VAR); + return checkKeyword(scanner, 1, 2, "ar", TOKEN_VAR); case 'w': - if (scanner.current - scanner.start > 1) { - switch (scanner.start[1]) { + if (scanner->current - scanner->start > 1) { + switch (scanner->start[1]) { case 'h': - return checkKeyword(2, 3, "ile", TOKEN_WHILE); + return checkKeyword(scanner, 2, 3, "ile", TOKEN_WHILE); case 'i': - return checkKeyword(2, 2, "th", TOKEN_WITH); + return checkKeyword(scanner, 2, 2, "th", TOKEN_WITH); } } break; @@ -273,144 +269,168 @@ static TokenType identifierType() { return TOKEN_IDENTIFIER; } -static Token identifier() { - while (isAlpha(peek()) || isDigit(peek())) advance(); +static Token identifier(Scanner *scanner) { + while (isAlpha(peek(scanner)) || isDigit(peek(scanner))) advance(scanner); - return makeToken(identifierType()); + return makeToken(scanner,identifierType(scanner)); } -static Token number() { - while (isDigit(peek()) || peek() == '_') advance(); +static Token exponent(Scanner *scanner) { + // Consume the "e" + advance(scanner); + while (peek(scanner) == '_') advance(scanner); + if (peek(scanner) == '+' || peek(scanner) == '-') { + // Consume the "+ or -" + advance(scanner); + } + if (!isDigit(peek(scanner)) && peek(scanner) != '_') return errorToken(scanner, "Invalid exopnent literal"); + while (isDigit(peek(scanner)) || peek(scanner) == '_') advance(scanner); + return makeToken(scanner,TOKEN_NUMBER); +} +static Token number(Scanner *scanner) { + while (isDigit(peek(scanner)) || peek(scanner) == '_') advance(scanner); + if (peek(scanner) == 'e' || peek(scanner) == 'E') + return exponent(scanner); // Look for a fractional part. - if (peek() == '.' && isDigit(peekNext())) { + if (peek(scanner) == '.' && (isDigit(peekNext(scanner)))) { // Consume the "." - advance(); - - while (isDigit(peek()) || peek() == '_') advance(); + advance(scanner); + while (isDigit(peek(scanner)) || peek(scanner) == '_') advance(scanner); + if (peek(scanner) == 'e' || peek(scanner) == 'E') + return exponent(scanner); } + return makeToken(scanner,TOKEN_NUMBER); +} - return makeToken(TOKEN_NUMBER); +static Token hexNumber(Scanner *scanner) { + while (peek(scanner) == '_') advance(scanner); + if (peek(scanner) == '0')advance(scanner); + if ((peek(scanner) == 'x') || (peek(scanner) == 'X')) { + advance(scanner); + if (!isHexDigit(peek(scanner))) return errorToken(scanner, "Invalid hex literal"); + while (isHexDigit(peek(scanner))) advance(scanner); + return makeToken(scanner,TOKEN_NUMBER); + } else return number(scanner); } -static Token string(char stringToken) { - while (peek() != stringToken && !isAtEnd()) { - if (peek() == '\n') { - scanner.line++; - } else if (peek() == '\\' && !scanner.rawString) { - scanner.current++; +static Token string(Scanner *scanner, char stringToken) { + while (peek(scanner) != stringToken && !isAtEnd(scanner)) { + if (peek(scanner) == '\n') { + scanner->line++; + } else if (peek(scanner) == '\\' && !scanner->rawString) { + scanner->current++; } - advance(); + advance(scanner); } - - if (isAtEnd()) return errorToken("Unterminated string."); + if (isAtEnd(scanner)) return errorToken(scanner, "Unterminated string."); // The closing " or '. - advance(); - scanner.rawString = false; - return makeToken(TOKEN_STRING); + advance(scanner); + scanner->rawString = false; + return makeToken(scanner,TOKEN_STRING); } -void backTrack() { - scanner.current--; +void backTrack(Scanner *scanner) { + scanner->current--; } -Token scanToken() { - skipWhitespace(); +Token scanToken(Scanner *scanner) { + skipWhitespace(scanner); - scanner.start = scanner.current; + scanner->start = scanner->current; - if (isAtEnd()) return makeToken(TOKEN_EOF); + if (isAtEnd(scanner)) return makeToken(scanner, TOKEN_EOF); - char c = advance(); + char c = advance(scanner); - if (isAlpha(c)) return identifier(); - if (isDigit(c)) return number(); + if (isAlpha(c)) return identifier(scanner); + if (isDigit(c)) return hexNumber(scanner); switch (c) { case '(': - return makeToken(TOKEN_LEFT_PAREN); + return makeToken(scanner, TOKEN_LEFT_PAREN); case ')': - return makeToken(TOKEN_RIGHT_PAREN); + return makeToken(scanner, TOKEN_RIGHT_PAREN); case '{': - return makeToken(TOKEN_LEFT_BRACE); + return makeToken(scanner, TOKEN_LEFT_BRACE); case '}': - return makeToken(TOKEN_RIGHT_BRACE); + return makeToken(scanner, TOKEN_RIGHT_BRACE); case '[': - return makeToken(TOKEN_LEFT_BRACKET); + return makeToken(scanner, TOKEN_LEFT_BRACKET); case ']': - return makeToken(TOKEN_RIGHT_BRACKET); + return makeToken(scanner, TOKEN_RIGHT_BRACKET); case ';': - return makeToken(TOKEN_SEMICOLON); + return makeToken(scanner, TOKEN_SEMICOLON); case ':': - return makeToken(TOKEN_COLON); + return makeToken(scanner, TOKEN_COLON); case ',': - return makeToken(TOKEN_COMMA); + return makeToken(scanner, TOKEN_COMMA); case '.': - return makeToken(TOKEN_DOT); + return makeToken(scanner, TOKEN_DOT); case '/': { - if (match('=')) { - return makeToken(TOKEN_DIVIDE_EQUALS); + if (match(scanner, '=')) { + return makeToken(scanner, TOKEN_DIVIDE_EQUALS); } else { - return makeToken(TOKEN_SLASH); + return makeToken(scanner, TOKEN_SLASH); } } case '*': { - if (match('=')) { - return makeToken(TOKEN_MULTIPLY_EQUALS); - } else if (match('*')) { - return makeToken(TOKEN_STAR_STAR); + if (match(scanner, '=')) { + return makeToken(scanner, TOKEN_MULTIPLY_EQUALS); + } else if (match(scanner, '*')) { + return makeToken(scanner, TOKEN_STAR_STAR); } else { - return makeToken(TOKEN_STAR); + return makeToken(scanner, TOKEN_STAR); } } case '%': - return makeToken(TOKEN_PERCENT); + return makeToken(scanner, TOKEN_PERCENT); case '-': { - if (match('-')) { - return makeToken(TOKEN_MINUS_MINUS); - } else if (match('=')) { - return makeToken(TOKEN_MINUS_EQUALS); + if (match(scanner, '-')) { + return makeToken(scanner, TOKEN_MINUS_MINUS); + } else if (match(scanner, '=')) { + return makeToken(scanner, TOKEN_MINUS_EQUALS); } else { - return makeToken(TOKEN_MINUS); + return makeToken(scanner, TOKEN_MINUS); } } case '+': { - if (match('+')) { - return makeToken(TOKEN_PLUS_PLUS); - } else if (match('=')) { - return makeToken(TOKEN_PLUS_EQUALS); + if (match(scanner, '+')) { + return makeToken(scanner, TOKEN_PLUS_PLUS); + } else if (match(scanner, '=')) { + return makeToken(scanner, TOKEN_PLUS_EQUALS); } else { - return makeToken(TOKEN_PLUS); + return makeToken(scanner, TOKEN_PLUS); } } case '&': - return makeToken(match('=') ? TOKEN_AMPERSAND_EQUALS : TOKEN_AMPERSAND); + return makeToken(scanner, match(scanner, '=') ? TOKEN_AMPERSAND_EQUALS : TOKEN_AMPERSAND); case '^': - return makeToken(match('=') ? TOKEN_CARET_EQUALS : TOKEN_CARET); + return makeToken(scanner, match(scanner, '=') ? TOKEN_CARET_EQUALS : TOKEN_CARET); case '|': - return makeToken(match('=') ? TOKEN_PIPE_EQUALS : TOKEN_PIPE); + return makeToken(scanner, match(scanner, '=') ? TOKEN_PIPE_EQUALS : TOKEN_PIPE); case '!': - return makeToken(match('=') ? TOKEN_BANG_EQUAL : TOKEN_BANG); + return makeToken(scanner, match(scanner, '=') ? TOKEN_BANG_EQUAL : TOKEN_BANG); case '=': - if (match('=')) { - return makeToken(TOKEN_EQUAL_EQUAL); - } else if (match('>')) { - return makeToken(TOKEN_ARROW); + if (match(scanner, '=')) { + return makeToken(scanner, TOKEN_EQUAL_EQUAL); + } else if (match(scanner, '>')) { + return makeToken(scanner, TOKEN_ARROW); } else { - return makeToken(TOKEN_EQUAL); + return makeToken(scanner, TOKEN_EQUAL); } case '<': - return makeToken(match('=') ? TOKEN_LESS_EQUAL : TOKEN_LESS); + return makeToken(scanner, match(scanner, '=') ? TOKEN_LESS_EQUAL : TOKEN_LESS); case '>': - return makeToken(match('=') ? + return makeToken(scanner, match(scanner, '=') ? TOKEN_GREATER_EQUAL : TOKEN_GREATER); case '"': - return string('"'); + return string(scanner, '"'); case '\'': - return string('\''); + return string(scanner, '\''); } - return errorToken("Unexpected character."); + return errorToken(scanner, "Unexpected character."); } diff --git a/c/scanner.h b/c/scanner.h index 57500de3..4722f9b7 100644 --- a/c/scanner.h +++ b/c/scanner.h @@ -37,7 +37,7 @@ typedef enum { TOKEN_VAR, TOKEN_CONST, TOKEN_TRUE, TOKEN_FALSE, TOKEN_NIL, TOKEN_FOR, TOKEN_WHILE, TOKEN_BREAK, TOKEN_RETURN, TOKEN_CONTINUE, - TOKEN_WITH, TOKEN_EOF, TOKEN_IMPORT, + TOKEN_WITH, TOKEN_EOF, TOKEN_IMPORT, TOKEN_FROM, TOKEN_ERROR } TokenType; @@ -49,10 +49,17 @@ typedef struct { int line; } Token; -void initScanner(const char *source); +typedef struct { + const char *start; + const char *current; + int line; + bool rawString; +} Scanner; + +void initScanner(Scanner *scanner, const char *source); -void backTrack(); +void backTrack(Scanner *scanner); -Token scanToken(); +Token scanToken(Scanner *scanner); #endif diff --git a/c/table.c b/c/table.c index 5caf774d..31d5c181 100644 --- a/c/table.c +++ b/c/table.c @@ -113,6 +113,7 @@ bool tableSet(VM *vm, Table *table, ObjString *key, Value value) { } bool tableDelete(VM *vm, Table *table, ObjString *key) { + UNUSED(vm); if (table->count == 0) return false; int capacityMask = table->capacityMask; @@ -159,13 +160,6 @@ bool tableDelete(VM *vm, Table *table, ObjString *key) { entry = nextEntry; } - // TODO: Add constant for table load factor - if (table->count - 1 < table->capacityMask * 0.35) { - // Figure out the new table size. - capacityMask = SHRINK_CAPACITY(table->capacityMask); - adjustCapacity(vm, table, capacityMask); - } - return true; } diff --git a/c/util.c b/c/util.c index f99065af..64821066 100644 --- a/c/util.c +++ b/c/util.c @@ -3,19 +3,19 @@ #include #include "vm.h" +#include "memory.h" -char *readFile(const char *path) { +char *readFile(VM *vm, const char *path) { FILE *file = fopen(path, "rb"); if (file == NULL) { - fprintf(stderr, "Could not open file \"%s\".\n", path); - exit(74); + return NULL; } fseek(file, 0L, SEEK_END); size_t fileSize = ftell(file); rewind(file); - char *buffer = (char *) malloc(fileSize + 1); + char *buffer = ALLOCATE(vm, char, fileSize + 1); if (buffer == NULL) { fprintf(stderr, "Not enough memory to read \"%s\".\n", path); exit(74); diff --git a/c/util.h b/c/util.h index 571d4b51..bd559e88 100644 --- a/c/util.h +++ b/c/util.h @@ -3,7 +3,7 @@ #include "vm.h" -char *readFile(const char *path); +char *readFile(VM *vm, const char *path); void defineNative(VM *vm, Table *table, const char *name, NativeFn function); diff --git a/c/value.c b/c/value.c index aaf96f2f..e7433ff0 100644 --- a/c/value.c +++ b/c/value.c @@ -8,7 +8,7 @@ #include "vm.h" #define TABLE_MAX_LOAD 0.75 -#define TABLE_MIN_LOAD 0.35 +#define TABLE_MIN_LOAD 0.25 void initValueArray(ValueArray *array) { array->values = NULL; @@ -216,7 +216,7 @@ bool dictDelete(VM *vm, ObjDict *dict, Value key) { if (dict->count - 1 < dict->capacityMask * TABLE_MIN_LOAD) { // Figure out the new table size. - capacityMask = SHRINK_CAPACITY(dict->capacityMask); + capacityMask = SHRINK_CAPACITY(dict->capacityMask + 1) - 1; adjustDictCapacity(vm, dict, capacityMask); } @@ -318,7 +318,7 @@ bool setDelete(VM *vm, ObjSet *set, Value value) { if (set->count - 1 < set->capacityMask * TABLE_MIN_LOAD) { // Figure out the new table size. - int capacityMask = SHRINK_CAPACITY(set->capacityMask); + int capacityMask = SHRINK_CAPACITY(set->capacityMask + 1) - 1; adjustSetCapacity(vm, set, capacityMask); } diff --git a/c/vm.c b/c/vm.c index b160ef60..cef00917 100644 --- a/c/vm.c +++ b/c/vm.c @@ -145,10 +145,10 @@ VM *initVM(bool repl, const char *scriptName, int argc, const char *argv[]) { /** * Native classes which are not required to be - * imported. For imported natives see optionals.c + * imported. For imported modules see optionals.c */ - createSystemClass(vm, argc, argv); - createCClass(vm); + createSystemModule(vm, argc, argv); + createCModule(vm); return vm; } @@ -1104,10 +1104,17 @@ static InterpretResult run(VM *vm) { if (tableGet(&vm->modules, fileName, &moduleVal)) { ++vm->scriptNameCount; vm->lastModule = AS_MODULE(moduleVal); + push(vm, NIL_VAL); DISPATCH(); } - char *s = readFile(fileName->chars); + char *source = readFile(vm, fileName->chars); + + if (source == NULL) { + frame->ip = ip; + runtimeError(vm, "Could not open file \"%s\".", fileName->chars); + return INTERPRET_RUNTIME_ERROR; + } if (vm->scriptNameCapacity < vm->scriptNameCount + 2) { int oldCapacity = vm->scriptNameCapacity; @@ -1123,15 +1130,15 @@ static InterpretResult run(VM *vm) { vm->lastModule = module; push(vm, OBJ_VAL(module)); - ObjFunction *function = compile(vm, module, s); + ObjFunction *function = compile(vm, module, source); pop(vm); - free(s); + + FREE_ARRAY(vm, char, source, strlen(source) + 1); if (function == NULL) return INTERPRET_COMPILE_ERROR; push(vm, OBJ_VAL(function)); ObjClosure *closure = newClosure(vm, function); pop(vm); - push(vm, OBJ_VAL(closure)); frame->ip = ip; @@ -1161,11 +1168,61 @@ static InterpretResult run(VM *vm) { DISPATCH(); } + CASE_CODE(IMPORT_BUILTIN_VARIABLE): { + int index = READ_BYTE(); + ObjString *fileName = READ_STRING(); + int varCount = READ_BYTE(); + + Value moduleVal; + ObjModule *module; + + if (tableGet(&vm->modules, fileName, &moduleVal)) { + module = AS_MODULE(moduleVal); + } else { + module = importBuiltinModule(vm, index); + } + + for (int i = 0; i < varCount; i++) { + Value moduleVariable; + ObjString *variable = READ_STRING(); + + if (!tableGet(&module->values, variable, &moduleVariable)) { + frame->ip = ip; + runtimeError(vm, "%s can't be found in module %s", variable->chars, module->name->chars); + return INTERPRET_RUNTIME_ERROR; + } + + push(vm, moduleVariable); + } + + DISPATCH(); + } + CASE_CODE(IMPORT_VARIABLE): { push(vm, OBJ_VAL(vm->lastModule)); DISPATCH(); } + CASE_CODE(IMPORT_FROM): { + int varCount = READ_BYTE(); + + for (int i = 0; i < varCount; i++) { + Value moduleVariable; + ObjString *variable = READ_STRING(); + + if (!tableGet(&vm->lastModule->values, variable, &moduleVariable)) { + vm->scriptNameCount--; + frame->ip = ip; + runtimeError(vm, "%s can't be found in module %s", variable->chars, vm->lastModule->name->chars); + return INTERPRET_RUNTIME_ERROR; + } + + push(vm, moduleVariable); + } + + DISPATCH(); + } + CASE_CODE(IMPORT_END): { vm->scriptNameCount--; if (vm->scriptNameCount >= 0) { diff --git a/c/vm.h b/c/vm.h index d4c7b48b..2efa5f6e 100644 --- a/c/vm.h +++ b/c/vm.h @@ -64,8 +64,6 @@ typedef enum { #define OK 0 #define NOTOK -1 -#define UNUSED(__x__) (void) __x__ - VM *initVM(bool repl, const char *scriptName, int argc, const char *argv[]); void freeVM(VM *vm); diff --git a/docs/_config.yml b/docs/_config.yml index 7eeb72cb..c597151a 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.11.0" +version: "0.12.0" github_username: dictu-lang search_enabled: true diff --git a/docs/docs/classes.md b/docs/docs/classes.md index 55ca4170..a9080700 100644 --- a/docs/docs/classes.md +++ b/docs/docs/classes.md @@ -440,4 +440,33 @@ myObject.obj = Test(); // Reference to a mutable datatype myNewObject = myObject.deepCopy(); myNewObject.obj.x = 100; print(myObject.obj.x); // 10 +``` + +## Checking instance types + +### instance.isInstance(class) + +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 +classes, and we know subclasses have the type of their parent class, the same holds true for `isInstance()`. If the instance being checked +is passed it's parent class as an argument `isInstance()` will evaluate to true. + +```cs +class Test {} + +var obj = Test(); + +print(obj.isInstance(Test)); // true + +// Inheritance + +class Test {} +class AnotherTest < Test {} + +var testObj = Test(); +var anotherTestObj = AnotherTest(); + +testObj.isInstance(AnotherTest); // false +anotherTestObj.isInstance(AnotherTest); // true +anotherTestObj.isInstance(Test); // true ``` \ No newline at end of file diff --git a/docs/docs/datetime.md b/docs/docs/datetime.md index 93a65349..d55fe8d2 100644 --- a/docs/docs/datetime.md +++ b/docs/docs/datetime.md @@ -23,6 +23,12 @@ To make use of the Datetime module an import is required. import Datetime; ``` +### Constants + +| Constant | Description | +|----------------------|---------------------------------| +| Datetime.errno | Number of the last error | + ### Datetime.now() Returns a human readable locale datetime string. diff --git a/docs/docs/json.md b/docs/docs/json.md index ded55cfd..1dc2fd99 100644 --- a/docs/docs/json.md +++ b/docs/docs/json.md @@ -23,6 +23,16 @@ To make use of the JSON module an import is required. import JSON; ``` +### Constants + +| Constant | Description | +|----------------------|------------------------------------------------------------| +| JSON.errno | Number of the last error | +| JSON.ENULL | Error value when JSON object is nil | +| JSON.ENOTYPE | Error value when there's no corresponding data type | +| JSON.EINVAL | Error value when it's an invalid JSON Object | +| JSON.ENOSERIAL | Error value when the object is not serializable | + ### JSON.parse(string) Parses a JSON string and turns it into a valid Dictu datatype. diff --git a/docs/docs/math.md b/docs/docs/math.md index 45f242ea..53aa09b4 100644 --- a/docs/docs/math.md +++ b/docs/docs/math.md @@ -26,10 +26,11 @@ import Math; ### Constants -| Constant | Description | -|-----------|--------------------------------------------------------| -| Math.PI | The mathematical constant: 3.14159265358979 | -| Math.e | The mathematical constant: 2.71828182845905 | +| Constant | Description | +|--------------|--------------------------------------------------------| +| Math.errno | Number of the last error | +| Math.PI | The mathematical constant: 3.14159265358979 | +| Math.e | The mathematical constant: 2.71828182845905 | ### Math.min(iterable) @@ -115,3 +116,30 @@ Returns the square root of a given number Math.sqrt(25); // 5 Math.sqrt(100); // 10 ``` + +### Math.sin(number) + +Returns the sin value of a given number in radian + +```cs +Math.sin(1); // 0.8414 +Math.sin(50); // -0.2623 +``` + +### Math.cos(number) + +Returns the cos value of a given number in radian + +```cs +Math.cos(1); // 0.5403 +Math.cos(50); // 0.9649 +``` + +### Math.tan(number) + +Returns the tan value of a given number in radian + +```cs +Math.tan(1); // 1.5574 +Math.tan(50); // -0.2719 +``` diff --git a/docs/docs/modules.md b/docs/docs/modules.md index acb130ca..324b6ae9 100644 --- a/docs/docs/modules.md +++ b/docs/docs/modules.md @@ -33,6 +33,18 @@ import Math; Math.PI; // 3.14... ``` +If however you wish to import something specific from the module into the current scope you can use a `from` import, this +accepts a single identifier or multiple separated by a comma. + +```cs +from Math import PI; + +PI; // 3.14... + +// Import multiple things +from JSON import parse, stringify; +``` + #### User created scripts When you wish to import another Dictu script, you use the import keyword. This takes an optional identifier which will be the @@ -47,7 +59,7 @@ import "some/file.du"; import "some/file.du" as SomeModule; ``` -When importing a module with an identifer, you can access the top level module variables using the `.` operator, much +When importing a module with an identifier, you can access the top level module variables using the `.` operator, much like you would for a class. **some/file.du** @@ -69,4 +81,13 @@ print(SomeModule.test()); // "test" Once an module has been imported, it is stored within the VM, and is not executed again even if it is imported elsewhere. What happens is the module is "loaded" again, which means if it was to change in the time from import, it will not be changed -within your program even if re-importing the module. \ No newline at end of file +within your program even if re-importing the module. + +Same as importing specific items from a builtin module, `from` imports also work on user created files. + +```cs +from "some/file.du" import x, test; + +print(x); // 10 +print(test()); // "test" +``` \ No newline at end of file diff --git a/docs/docs/path.md b/docs/docs/path.md index b0f08ed5..38ac2900 100644 --- a/docs/docs/path.md +++ b/docs/docs/path.md @@ -27,9 +27,9 @@ import Path; | Constant | Description | |--------------------|--------------------------------------| +| Path.errno | Number of the last error | | Path.delimiter | System dependent path delimiter | | Path.dirSeparator | System dependent directory separator | -| Path.errno | Number of the last error (UNIX only) | ### Path.basename(string) diff --git a/docs/docs/random.md b/docs/docs/random.md index 34ea0a35..9b0c826d 100644 --- a/docs/docs/random.md +++ b/docs/docs/random.md @@ -24,6 +24,12 @@ To make use of the Random module an import is required. import Random; ``` +### Constants + +| Constant | Description | +|----------------------|---------------------------------| +| Random.errno | Number of the last error | + ### Random.random() Return a random float between 0 and 1. diff --git a/docs/docs/system.md b/docs/docs/system.md index 2ec78869..cdfbb289 100644 --- a/docs/docs/system.md +++ b/docs/docs/system.md @@ -21,8 +21,8 @@ nav_order: 12 | Constant | Description | |-----------------|---------------------------------------------------------------------------------------------------| -| System.argv | The list of command line arguments. The first element of the argv list is always the script name. | | System.errno | Number of the last error. | +| System.argv | The list of command line arguments. The first element of the argv list is always the script name. | | System.platform | This string identifies the underlying system platform. | | System.S_IRWXU | Read, write, and execute by owner. | | System.S_IRUSR | Read by owner. | diff --git a/examples/binarySearchTree.du b/examples/binarySearchTree.du new file mode 100644 index 00000000..9cc91975 --- /dev/null +++ b/examples/binarySearchTree.du @@ -0,0 +1,69 @@ +class treeNode { + init(key) { + this.key = key; + this.left = nil; + this.right = nil; + } + getKey() { + return this.key; + } +} + +class binarySearchTree { + init(key) { + this.root = treeNode(key); + } + insert(key) { + if (this.root == nil) { + this.root = treeNode(key); + } else { + var cur = this.root; + var prev = cur; + while (cur != nil) { + prev = cur; + if (cur.key < key) { + cur = cur.left; + } else if (cur.key > key){ + cur = cur.right; + } + } + if (prev != nil) { + if (prev.getKey() < key) { + prev.right = treeNode(key); + } else { + prev.left = treeNode(key); + } + } + } + } + search(key) { + if (this.root == nil) { + return false; + } + if (this.root.key == key) { + return true; + } + var cur = this.root; + var prev = cur; + while (cur != nil and cur.key != key) { + prev = cur; + if (cur.getKey() < key) { + cur = cur.right; + } else { + cur = cur.left; + } + } + if ( cur != nil and cur.getKey() == key) { + return true; + } + return false; + } +} + +var tree = binarySearchTree(10); +tree.insert(6); +tree.insert(9); +tree.insert(13); +tree.insert(15); +assert(tree.search(9)); +assert(not tree.search(1)); \ No newline at end of file diff --git a/examples/bubbleSort.du b/examples/bubbleSort.du new file mode 100644 index 00000000..70dd08fb --- /dev/null +++ b/examples/bubbleSort.du @@ -0,0 +1,17 @@ +def bubbleSort(list) { + var sortedList = list.copy(); + + for (var i = 0; i < sortedList.len(); ++i) { + 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; + } + } + } + + 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/design-patterns/factoryMethod.du b/examples/design-patterns/factoryMethod.du new file mode 100644 index 00000000..496638d8 --- /dev/null +++ b/examples/design-patterns/factoryMethod.du @@ -0,0 +1,35 @@ +class Factory { + /** + * Factory Function that are used to create IceCream object + */ + static createIceCream() { + return IceCream(); + } + + /** + * Factory Function that are used to create Chocolate object + */ + static createChocolate() { + return Chocolate(); + } +} + +class IceCream { + order() { + print("IceCream ordered"); + } +} + +class Chocolate { + order() { + print("Chocolate ordered"); + } +} + +// Factory methods are used to create objects without having to specify the exact class +var factory = Factory(); +var icecream = factory.createIceCream(); +var chocolate = factory.createChocolate(); + +icecream.order(); +chocolate.order(); diff --git a/examples/inheritance.du b/examples/inheritance.du new file mode 100644 index 00000000..86008f16 --- /dev/null +++ b/examples/inheritance.du @@ -0,0 +1,36 @@ +/** +* inheritance.du +*/ + +// Parent class - Animal +class Animal { + eating() { + print("Eating"); + } +} + +// Child class - Dog +class Dog < Animal { + bark() { + print("Barking"); + } +} + +// Child class - Cat +class Cat < Animal { + meow() { + print("Meowing"); + } +} + +// Initialize child class +var dog = Dog(); +var cat = Cat(); + +// Inherited methods/behaviour from parent class - Animal +dog.eating(); +cat.eating(); + +// Methods/behaviour specific to child class +dog.bark(); +cat.meow(); \ No newline at end of file diff --git a/examples/isPalindrome.du b/examples/isPalindrome.du new file mode 100644 index 00000000..f71a4d30 --- /dev/null +++ b/examples/isPalindrome.du @@ -0,0 +1,13 @@ +def isPalindrome(arg) { + var len = arg.len(); + for (var i = 0; i < len/2; ++i) { + if (arg[i] != arg[len - 1 - i]) { + return false; + } + + } + return true; +} + +print(isPalindrome("aba")); // true +print(isPalindrome("abc")); // false \ No newline at end of file diff --git a/tests/classes/import.du b/tests/classes/import.du index 2dd590c5..c99f12ca 100644 --- a/tests/classes/import.du +++ b/tests/classes/import.du @@ -13,4 +13,5 @@ import "tests/classes/copy.du"; import "tests/classes/toString.du"; import "tests/classes/abstract.du"; import "tests/classes/classVariables.du"; -import "tests/classes/parameters.du"; \ No newline at end of file +import "tests/classes/parameters.du"; +import "tests/classes/isInstance.du"; \ No newline at end of file diff --git a/tests/classes/isInstance.du b/tests/classes/isInstance.du new file mode 100644 index 00000000..1d62551e --- /dev/null +++ b/tests/classes/isInstance.du @@ -0,0 +1,42 @@ +/** + * isInstance.du + * + * Testing instance.isInstance() method + * + * .isInstance() returns a boolean based on whether the object is created from a given class / parent superclass + */ + +class Test {} + +assert(Test().isInstance(Test)); + +class AnotherTest < Test {} + +assert(AnotherTest().isInstance(Test)); +assert(AnotherTest().isInstance(AnotherTest)); +assert(!Test().isInstance(AnotherTest)); + +class Unrelated {} + +assert(!Test().isInstance(Unrelated)); +assert(!AnotherTest().isInstance(Unrelated)); + + +abstract class AbstractBase {} + +class ConcreteClass < AbstractBase {} + +assert(ConcreteClass().isInstance(AbstractBase)); +assert(ConcreteClass().isInstance(ConcreteClass)); + + +class FirstClass {} +class SecondClass < FirstClass {} +class ThirdClass < SecondClass {} + +assert(FirstClass().isInstance(FirstClass)); +assert(SecondClass().isInstance(FirstClass)); +assert(SecondClass().isInstance(SecondClass)); +assert(ThirdClass().isInstance(FirstClass)); +assert(ThirdClass().isInstance(SecondClass)); +assert(ThirdClass().isInstance(ThirdClass)); \ No newline at end of file diff --git a/tests/imports/class.du b/tests/imports/class.du new file mode 100644 index 00000000..e3e0962c --- /dev/null +++ b/tests/imports/class.du @@ -0,0 +1,11 @@ +class Test { + init() { + this.x = 10; + } +} + +class AnotherTest { + init() { + this.y = 10; + } +} \ No newline at end of file diff --git a/tests/imports/from.du b/tests/imports/from.du new file mode 100644 index 00000000..1b01ac69 --- /dev/null +++ b/tests/imports/from.du @@ -0,0 +1,25 @@ +/** + * from.du + * + * Testing the from import + * + */ + +from "tests/imports/class.du" import Test; + +assert(type(Test) == 'class'); +assert(Test().x == 10); + +from "tests/imports/class.du" import Test, AnotherTest; + +assert(type(Test) == 'class'); +assert(Test().x == 10); +assert(type(AnotherTest) == 'class'); +assert(AnotherTest().y == 10); + +import "tests/imports/middle-import.du" as MiddleImportModule; + +assert(type(MiddleImportModule.Test) == 'class'); +assert(MiddleImportModule.Test().x == 10); +assert(type(MiddleImportModule.AnotherTest) == 'class'); +assert(MiddleImportModule.AnotherTest().y == 10); \ No newline at end of file diff --git a/tests/imports/import.du b/tests/imports/import.du new file mode 100644 index 00000000..b64fd6d7 --- /dev/null +++ b/tests/imports/import.du @@ -0,0 +1,7 @@ +/** + * import.du + * + * General import file for all the import tests + */ + +import "tests/imports/from.du"; \ No newline at end of file diff --git a/tests/imports/middle-import.du b/tests/imports/middle-import.du new file mode 100644 index 00000000..4250b37a --- /dev/null +++ b/tests/imports/middle-import.du @@ -0,0 +1 @@ +from "tests/imports/class.du" import Test, AnotherTest; \ No newline at end of file diff --git a/tests/maths/maths.du b/tests/maths/maths.du index c5bfe823..d99b448a 100644 --- a/tests/maths/maths.du +++ b/tests/maths/maths.du @@ -29,6 +29,11 @@ assert(Math.sqrt(25) == 5); assert(Math.sqrt(100) == 10); assert(Math.sqrt(20) == 4.47213595499958); assert(Math.sqrt(100_00) == 1_00); +assert(Math.sin(1) > 0.84); +assert(Math.sin(1) < 0.845); +assert(Math.cos(0) == 1); +assert(Math.tan(1) > 1.5); +assert(Math.tan(1) < 1.6); assert(Math.PI == 3.14159265358979); assert(Math.e == 2.71828182845905); \ No newline at end of file diff --git a/tests/modules/from.du b/tests/modules/from.du new file mode 100644 index 00000000..b61cd9ba --- /dev/null +++ b/tests/modules/from.du @@ -0,0 +1,31 @@ +/** + * from.du + * + * Testing from imports + */ + +from "tests/variables/scope.du" import x; +assert(x == 10); + +{ + from "tests/variables/scope.du" import x; + assert(x == 10); + { + from "tests/variables/scope.du" import x; + assert(x == 10); + } +} + + +from JSON import parse; +assert(parse("10") == 10); + +{ + from JSON import parse; + assert(parse("10") == 10); + + { + from Math import PI; + assert(PI == 3.14159265358979); + } +} \ No newline at end of file diff --git a/tests/modules/functions.du b/tests/modules/functions.du index f4cb231e..d09d543c 100644 --- a/tests/modules/functions.du +++ b/tests/modules/functions.du @@ -39,3 +39,26 @@ assert(ArrowFuncModule.func9() == 30); assert(ArrowFuncModule.func9(10, 20) == 30); assert(ArrowFuncModule.func9(1.5, 2.5) == 4); assert(ArrowFuncModule.func9("Dictu ", "is great!") == "Dictu is great!"); + +/** + * Nested scope + */ +{ + { + import "tests/functions/arrow.du" as NestedArrowFuncModule; + assert(type(NestedArrowFuncModule.func) == "function"); + assert(NestedArrowFuncModule.func() == 10); + + assert(type(NestedArrowFuncModule.func9) == "function"); + assert(NestedArrowFuncModule.func9() == 30); + assert(NestedArrowFuncModule.func9(10, 20) == 30); + assert(NestedArrowFuncModule.func9(1.5, 2.5) == 4); + assert(NestedArrowFuncModule.func9("Dictu ", "is great!") == "Dictu is great!"); + { + { + import JSON; + assert(JSON.parse("10") == 10); + } + } + } +} \ No newline at end of file diff --git a/tests/modules/import.du b/tests/modules/import.du index f249b244..61e2c6b1 100644 --- a/tests/modules/import.du +++ b/tests/modules/import.du @@ -6,4 +6,5 @@ import "tests/modules/variables.du"; import "tests/modules/functions.du"; -import "tests/modules/classes.du"; \ No newline at end of file +import "tests/modules/classes.du"; +import "tests/modules/from.du"; \ No newline at end of file diff --git a/tests/modules/variables.du b/tests/modules/variables.du index f055f002..79312a68 100644 --- a/tests/modules/variables.du +++ b/tests/modules/variables.du @@ -5,6 +5,13 @@ */ import "tests/variables/scope.du" as ScopeModule; - assert(ScopeModule.x == 10); +{ + import "tests/variables/scope.du" as AnotherScopeModule; + assert(AnotherScopeModule.x == 10); + { + import "tests/variables/scope.du" as MoreScopeModule; + assert(MoreScopeModule.x == 10); + } +} diff --git a/tests/number/import.du b/tests/number/import.du index e9dc950b..dbda735f 100644 --- a/tests/number/import.du +++ b/tests/number/import.du @@ -6,3 +6,4 @@ import "tests/number/toString.du"; import "tests/number/toBool.du"; +import "tests/number/literals.du"; diff --git a/tests/number/literals.du b/tests/number/literals.du new file mode 100644 index 00000000..d4f4e5d3 --- /dev/null +++ b/tests/number/literals.du @@ -0,0 +1,13 @@ +/** + * literals.du + * + * Testing some alternate numeric literals + * + */ + +assert(0x2a == 42); +assert(0x342f == 13359); +assert(1e5 == 100000); +assert(1e+5 == 100000); +assert(1e-5 == 0.00001); +assert(2.5e3 == 2500); \ No newline at end of file diff --git a/tests/runTests.du b/tests/runTests.du index e79a0c6f..a062a1e9 100644 --- a/tests/runTests.du +++ b/tests/runTests.du @@ -25,6 +25,7 @@ if (isDefined("HTTP")) { } import "tests/modules/import.du"; +import "tests/imports/import.du"; // If we got here no runtime errors were thrown, therefore all tests passed. print("All tests passed successfully!");