diff --git a/src/core/compiler.c b/src/core/compiler.c index 4725b84..4b30d55 100644 --- a/src/core/compiler.c +++ b/src/core/compiler.c @@ -136,6 +136,7 @@ typedef enum { TK_WHILE, // while TK_FOR, // for TK_IF, // if + TK_ELIF, // elif TK_ELSE, // else TK_BREAK, // break TK_CONTINUE, // continue @@ -200,6 +201,7 @@ static _Keyword _keywords[] = { { "while", 5, TK_WHILE }, { "for", 3, TK_FOR }, { "if", 2, TK_IF }, + { "elif", 4, TK_ELIF }, { "else", 4, TK_ELSE }, { "break", 5, TK_BREAK }, { "continue", 8, TK_CONTINUE }, @@ -1267,7 +1269,8 @@ static void skipNewLines(Compiler* compiler) { matchLine(compiler); } -// Match semi collon, multiple new lines or peek 'end', 'else' keywords. +// Match semi collon, multiple new lines or peek 'end', 'else', 'elif' +// keywords. static bool matchEndStatement(Compiler* compiler) { if (match(compiler, TK_SEMICOLLON)) { skipNewLines(compiler); @@ -1278,7 +1281,9 @@ static bool matchEndStatement(Compiler* compiler) { // In the below statement we don't require any new lines or semicolons. // 'if cond then stmnt1 else if cond2 then stmnt2 else stmnt3 end' - if (peek(compiler) == TK_END || peek(compiler) == TK_ELSE) + if (peek(compiler) == TK_END + || peek(compiler) == TK_ELSE + || peek(compiler) == TK_ELIF) return true; return false; @@ -1639,6 +1644,7 @@ GrammarRule rules[] = { // Prefix Infix Infix Precedence /* TK_WHILE */ NO_RULE, /* TK_FOR */ NO_RULE, /* TK_IF */ NO_RULE, + /* TK_ELIF */ NO_RULE, /* TK_ELSE */ NO_RULE, /* TK_BREAK */ NO_RULE, /* TK_CONTINUE */ NO_RULE, @@ -2864,7 +2870,7 @@ static void compileBlockBody(Compiler* compiler, BlockType type) { _TokenType next = peek(compiler); while (!(next == TK_END || next == TK_EOF || - ((type == BLOCK_IF) && (next == TK_ELSE)))) { + ((type == BLOCK_IF) && (next == TK_ELSE || next == TK_ELIF)))) { compileStatement(compiler); skipNewLines(compiler); @@ -3024,7 +3030,7 @@ static void compileExpression(Compiler* compiler) { parsePrecedence(compiler, PREC_LOWEST); } -static void compileIfStatement(Compiler* compiler, bool else_if) { +static void compileIfStatement(Compiler* compiler, bool elif) { skipNewLines(compiler); compileExpression(compiler); //< Condition. @@ -3033,40 +3039,36 @@ static void compileIfStatement(Compiler* compiler, bool else_if) { compileBlockBody(compiler, BLOCK_IF); - if (match(compiler, TK_ELSE)) { + if (match(compiler, TK_ELIF)) { + // Jump pass else. + emitOpcode(compiler, OP_JUMP); + int exit_jump = emitShort(compiler, 0xffff); //< Will be patched. - if (match(compiler, TK_IF)) { //< Compile 'else if'. - // Jump pass else. - emitOpcode(compiler, OP_JUMP); - int exit_jump = emitShort(compiler, 0xffff); //< Will be patched. + // if (false) jump here. + patchJump(compiler, ifpatch); - // if (false) jump here. - patchJump(compiler, ifpatch); + compilerEnterBlock(compiler); + compileIfStatement(compiler, true); + compilerExitBlock(compiler); - compilerEnterBlock(compiler); - compileIfStatement(compiler, true); - compilerExitBlock(compiler); + patchJump(compiler, exit_jump); - patchJump(compiler, exit_jump); + } else if (match(compiler, TK_ELSE)) { + // Jump pass else. + emitOpcode(compiler, OP_JUMP); + int exit_jump = emitShort(compiler, 0xffff); //< Will be patched. - } else { //< Compile 'else'. - - // Jump pass else. - emitOpcode(compiler, OP_JUMP); - int exit_jump = emitShort(compiler, 0xffff); //< Will be patched. - - patchJump(compiler, ifpatch); - compileBlockBody(compiler, BLOCK_ELSE); - patchJump(compiler, exit_jump); - } + patchJump(compiler, ifpatch); + compileBlockBody(compiler, BLOCK_ELSE); + patchJump(compiler, exit_jump); } else { patchJump(compiler, ifpatch); } - // 'else if' will not consume the 'end' keyword as it'll be leaved to be - // consumed by it's 'if'. - if (!else_if) { + // elif will not consume the 'end' keyword as it'll be leaved to be consumed + // by it's 'if'. + if (!elif) { skipNewLines(compiler); consume(compiler, TK_END, "Expected 'end' after statement end."); } diff --git a/src/core/core.c b/src/core/core.c index 7178b38..a036c7d 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -237,13 +237,6 @@ static inline bool _callBinaryOpMethod(PKVM* vm, Var self, Var other, /* CORE BUILTIN FUNCTIONS */ /*****************************************************************************/ -DEF(coreTypeName, - "type_name(value:var) -> string\n" - "Returns the type name of the of the value.") { - - RET(VAR_OBJ(newString(vm, varTypeName(ARG(1))))); -} - DEF(coreHelp, "help([fn:Closure]) -> null\n" "It'll print the docstring the object and return.") { @@ -545,7 +538,6 @@ static void initializeBuiltinFunctions(PKVM* vm) { initializeBuiltinFN(vm, &vm->builtins_funcs[vm->builtins_count++], name, \ (int)strlen(name), argc, fn, DOCSTRING(fn)); // General functions. - INITIALIZE_BUILTIN_FN("type_name", coreTypeName, 1); INITIALIZE_BUILTIN_FN("help", coreHelp, -1); INITIALIZE_BUILTIN_FN("assert", coreAssert, -1); INITIALIZE_BUILTIN_FN("bin", coreBin, 1); @@ -642,31 +634,6 @@ DEF(stdLangDebugBreak, } #endif -DEF(stdLangWrite, - "write(...) -> null\n" - "Write function, just like print function but it wont put space between" - "args and write a new line at the end.") { - - // If the host application doesn't provide any write function, discard the - // output. - if (vm->config.stdout_write == NULL) return; - - String* str; //< Will be cleaned by garbage collector; - - for (int i = 1; i <= ARGC; i++) { - Var arg = ARG(i); - // If it's already a string don't allocate a new string instead use it. - if (IS_OBJ_TYPE(arg, OBJ_STRING)) { - str = (String*)AS_OBJ(arg); - } else { - str = varToString(vm, ARG(1), false); - if (str == NULL) RET(VAR_NULL); - } - - vm->config.stdout_write(vm, str->data); - } -} - static void initializeCoreModules(PKVM* vm) { #define MODULE_ADD_FN(module, name, fn, argc) \ moduleAddFunctionInternal(vm, module, name, fn, argc, DOCSTRING(fn)) @@ -680,7 +647,6 @@ static void initializeCoreModules(PKVM* vm) { NEW_MODULE(lang, "lang"); MODULE_ADD_FN(lang, "gc", stdLangGC, 0); MODULE_ADD_FN(lang, "disas", stdLangDisas, 1); - MODULE_ADD_FN(lang, "write", stdLangWrite, -1); #ifdef DEBUG MODULE_ADD_FN(lang, "debug_break", stdLangDebugBreak, 0); #endif @@ -764,6 +730,18 @@ static void _ctorFiber(PKVM* vm) { #define SELF (vm->fiber->self) +DEF(_objTypeName, + "Object.typename() -> String\n" + "Returns the type name of the object.") { + RET(VAR_OBJ(newString(vm, varTypeName(SELF)))); +} + +DEF(_objRepr, + "Object.repr() -> String\n" + "Returns the repr string of the object.") { + RET(VAR_OBJ(varToString(vm, SELF, true))); +} + DEF(_numberTimes, "Number.times(f:fn)\n" "Iterate the function [f] n times. Here n is the integral value of the " @@ -1166,6 +1144,9 @@ static void initializePrimitiveClasses(PKVM* vm) { } while (false) // TODO: write docs. + ADD_METHOD(PK_OBJECT, "typename", _objTypeName, 0); + ADD_METHOD(PK_OBJECT, "repr", _objRepr, 0); + ADD_METHOD(PK_NUMBER, "times", _numberTimes, 1); ADD_METHOD(PK_NUMBER, "isint", _numberIsint, 0); ADD_METHOD(PK_NUMBER, "isbyte", _numberIsbyte, 0); @@ -1465,6 +1446,26 @@ Var varSubtract(PKVM* vm, Var v1, Var v2, bool inplace) { Var varMultiply(PKVM* vm, Var v1, Var v2, bool inplace) { CHECK_NUMERIC_OP(*); CHECK_INST_BINARY_OP("*"); + + if (IS_OBJ_TYPE(v1, OBJ_STRING)) { + String* left = (String*) AS_OBJ(v1); + int64_t right; + if (isInteger(v2, &right)) { + if (left->length == 0) return VAR_OBJ(left); + if (right == 0) return VAR_OBJ(newString(vm, "")); + + String* str = newStringLength(vm, "", left->length * (uint32_t) right); + char* buff = str->data; + for (int i = 0; i < (int) right; i++) { + memcpy(buff, left->data, left->length); + buff += left->length; + } + ASSERT(buff == str->data + str->length, OOPS); + str->hash = utilHashString(str->data); + return VAR_OBJ(str); + } + } + UNSUPPORTED_BINARY_OP("*"); return VAR_NULL; } diff --git a/src/core/public.c b/src/core/public.c index 122f6ce..afac536 100644 --- a/src/core/public.c +++ b/src/core/public.c @@ -6,6 +6,8 @@ // This file contains all the pocketlang public function implementations. +#include + #ifndef PK_AMALGAMATED #include #include "core.h" @@ -78,7 +80,7 @@ static void stdoutWrite(PKVM* vm, const char* text); static char* stdinRead(PKVM* vm); static char* loadScript(PKVM* vm, const char* path); -PK_PUBLIC void* pkRealloc(PKVM* vm, void* ptr, size_t size) { +void* pkRealloc(PKVM* vm, void* ptr, size_t size) { ASSERT(vm->config.realloc_fn != NULL, "PKVM's allocator was NULL."); #if TRACE_MEMORY void* newptr = vm->config.realloc_fn(ptr, size, vm->config.user_data); @@ -553,7 +555,7 @@ bool pkCheckArgcRange(PKVM* vm, int argc, int min, int max) { // FIXME: If the user needs just the boolean value of the object, they should // use pkGetSlotBool(). -PK_PUBLIC bool pkValidateSlotBool(PKVM* vm, int slot, bool* value) { +bool pkValidateSlotBool(PKVM* vm, int slot, bool* value) { CHECK_FIBER_EXISTS(vm); VALIDATE_SLOT_INDEX(slot); @@ -567,7 +569,7 @@ PK_PUBLIC bool pkValidateSlotBool(PKVM* vm, int slot, bool* value) { return true; } -PK_PUBLIC bool pkValidateSlotNumber(PKVM* vm, int slot, double* value) { +bool pkValidateSlotNumber(PKVM* vm, int slot, double* value) { CHECK_FIBER_EXISTS(vm); VALIDATE_SLOT_INDEX(slot); @@ -581,7 +583,23 @@ PK_PUBLIC bool pkValidateSlotNumber(PKVM* vm, int slot, double* value) { return true; } -PK_PUBLIC bool pkValidateSlotString(PKVM* vm, int slot, const char** value, +bool pkValidateSlotInteger(PKVM* vm, int slot, int32_t* value) { + CHECK_FIBER_EXISTS(vm); + VALIDATE_SLOT_INDEX(slot); + + double n; + if (!pkValidateSlotNumber(vm, slot, &n)) return false; + + if (floor(n) != n) { + VM_SET_ERROR(vm, newString(vm, "Expected an integer got float.")); + return false; + } + + if (value) *value = (int32_t) n; + return true; +} + +bool pkValidateSlotString(PKVM* vm, int slot, const char** value, uint32_t* length) { CHECK_FIBER_EXISTS(vm); VALIDATE_SLOT_INDEX(slot); @@ -608,7 +626,7 @@ bool pkValidateSlotType(PKVM* vm, int slot, PkVarType type) { return true; } -PK_PUBLIC bool pkValidateSlotInstanceOf(PKVM* vm, int slot, int cls) { +bool pkValidateSlotInstanceOf(PKVM* vm, int slot, int cls) { CHECK_FIBER_EXISTS(vm); VALIDATE_SLOT_INDEX(slot); VALIDATE_SLOT_INDEX(cls); @@ -719,7 +737,7 @@ void pkSetSlotString(PKVM* vm, int index, const char* value) { SET_SLOT(index, VAR_OBJ(newString(vm, value))); } -PK_PUBLIC void pkSetSlotStringLength(PKVM* vm, int index, +void pkSetSlotStringLength(PKVM* vm, int index, const char* value, uint32_t length) { CHECK_FIBER_EXISTS(vm); VALIDATE_SLOT_INDEX(index); @@ -751,7 +769,7 @@ bool pkSetAttribute(PKVM* vm, int instance, const char* name, int value) { return !VM_HAS_ERROR(vm); } -PK_PUBLIC bool pkGetAttribute(PKVM* vm, int instance, const char* name, +bool pkGetAttribute(PKVM* vm, int instance, const char* name, int index) { CHECK_FIBER_EXISTS(vm); CHECK_ARG_NULL(name); @@ -830,7 +848,7 @@ bool pkCallFunction(PKVM* vm, int fn, int argc, int argv, int ret) { return false; } -PK_PUBLIC bool pkCallMethod(PKVM* vm, int instance, const char* method, +bool pkCallMethod(PKVM* vm, int instance, const char* method, int argc, int argv, int ret) { CHECK_FIBER_EXISTS(vm); CHECK_ARG_NULL(method); @@ -928,7 +946,7 @@ static char* stdinRead(PKVM* vm) { pkByteBufferInit(&buff); char c; do { - c = (char)fgetc(stdin); + c = (char) fgetc(stdin); if (c == '\n') break; pkByteBufferWrite(&buff, vm, (uint8_t)c); } while (c != EOF); @@ -936,6 +954,7 @@ static char* stdinRead(PKVM* vm) { char* str = pkRealloc(vm, NULL, buff.count); memcpy(str, buff.data, buff.count); + pkByteBufferClear(&buff, vm); return str; } diff --git a/src/include/pocketlang.h b/src/include/pocketlang.h index 8106c0b..1d311af 100644 --- a/src/include/pocketlang.h +++ b/src/include/pocketlang.h @@ -98,6 +98,11 @@ typedef void (*pkWriteFn) (PKVM* vm, const char* text); // string. typedef char* (*pkReadFn) (PKVM* vm); +// A generic function thiat could be used by the PKVM to signal something to +// the host application. The first argument is depend on the callback it's +// registered. +typedef void (*pkSignalFn) (void*); + // Load and return the script. Called by the compiler to fetch initial source // code and source for import statements. Return NULL to indicate failure to // load. Otherwise the string **must** be allocated with pkRealloc() and @@ -170,10 +175,12 @@ struct PkConfiguration { // pointer is NULL it defaults to the VM's realloc(), free() wrappers. pkReallocFn realloc_fn; + // I/O callbacks. pkWriteFn stderr_write; pkWriteFn stdout_write; pkReadFn stdin_read; + // Import system callbacks. pkResolvePathFn resolve_path_fn; pkLoadScriptFn load_script_fn; @@ -291,6 +298,10 @@ PK_PUBLIC bool pkValidateSlotBool(PKVM* vm, int slot, bool* value); // if not set a runtime error. PK_PUBLIC bool pkValidateSlotNumber(PKVM* vm, int slot, double* value); +// Helper function to check if the argument at the [slot] is an a whold number +// and if not set a runtime error. +PK_PUBLIC bool pkValidateSlotInteger(PKVM* vm, int slot, int32_t* value); + // Helper function to check if the argument at the [slot] slot is String and // if not set a runtime error. PK_PUBLIC bool pkValidateSlotString(PKVM* vm, int slot, diff --git a/src/libs/std_io.c b/src/libs/std_io.c index f0a410f..416b373 100644 --- a/src/libs/std_io.c +++ b/src/libs/std_io.c @@ -4,10 +4,56 @@ * Distributed Under The MIT License */ +#include + #ifndef PK_AMALGAMATED #include "libs.h" +#include "../core/value.h" #endif +DEF(_ioWrite, + "io.write(stream:var, bytes:String) -> null\n" + "Warning: the function is subjected to be changed anytime soon.\n" + "Write [bytes] string to the stream. stream should be any of io.stdin, " + "io.stdout, io.stderr.") { + + double stream; + if (!pkValidateSlotNumber(vm, 1, &stream)) return; + + if (stream != 0 && stream != 1 && stream != 2) { + pkSetRuntimeErrorFmt(vm, "Invalid stream (%g). Only use any of io.stdin, " + "io.stdout, io.stderr.", stream); + return; + } + + const char* bytes; + uint32_t length; + if (!pkValidateSlotString(vm, 2, &bytes, &length)) return; + + switch ((int) stream) { + case 0: + pkSetRuntimeError(vm, "Cannot write to stdin."); + return; + + // TODO: If the string contain null bytes it won't print anything after + // that, not sure if that needs to be fixed. + case 1: + fprintf(stdout, "%s", bytes); + return; + + case 2: + fprintf(stderr, "%s", bytes); + return; + } +} + +DEF(_ioFlush, + "io.flush() -> null\n" + "Warning: the function is subjected to be changed anytime soon.\n" + "Flush stdout buffer.\n") { + fflush(stdout); +} + /*****************************************************************************/ /* FILE CLASS */ /*****************************************************************************/ @@ -22,13 +68,26 @@ // 'a+' | write to end | create new | typedef enum { FMODE_NONE = 0, + FMODE_READ = (1 << 0), FMODE_WRITE = (1 << 1), FMODE_APPEND = (1 << 2), + _FMODE_EXT = (1 << 3), + _FMODE_BIN = (1 << 4), + FMODE_READ_EXT = (_FMODE_EXT | FMODE_READ), FMODE_WRITE_EXT = (_FMODE_EXT | FMODE_WRITE), FMODE_APPEND_EXT = (_FMODE_EXT | FMODE_APPEND), + + FMODE_READ_BIN = (_FMODE_BIN | FMODE_READ), + FMODE_WRITE_BIN = (_FMODE_BIN | FMODE_READ), + FMODE_APPEND_BIN = (_FMODE_BIN | FMODE_READ), + + FMODE_READ_BIN_EXT = (_FMODE_BIN | FMODE_READ_EXT), + FMODE_WRITE_BIN_EXT = (_FMODE_BIN | FMODE_WRITE_EXT), + FMODE_APPEND_BIN_EXT = (_FMODE_BIN | FMODE_APPEND_EXT), + } FileAccessMode; typedef struct { @@ -49,9 +108,15 @@ void* _newFile(PKVM* vm) { void _deleteFile(PKVM* vm, void* ptr) { File* file = (File*)ptr; if (!file->closed) { + ASSERT(file->fp != NULL, OOPS); if (fclose(file->fp) != 0) { /* TODO: error! */ } file->closed = true; + file->fp = NULL; + + } else { + ASSERT(file->fp == NULL, OOPS); } + pkRealloc(vm, file, 0); } @@ -73,15 +138,24 @@ DEF(_fileOpen, "") { if (argc == 2) { if (!pkValidateSlotString(vm, 2, &mode_str, NULL)) return; - // Check if the mode string is valid, and update the mode value. + // TODO: I should properly parser this. do { if (strcmp(mode_str, "r") == 0) { mode = FMODE_READ; break; } if (strcmp(mode_str, "w") == 0) { mode = FMODE_WRITE; break; } if (strcmp(mode_str, "a") == 0) { mode = FMODE_APPEND; break; } + if (strcmp(mode_str, "r+") == 0) { mode = FMODE_READ_EXT; break; } if (strcmp(mode_str, "w+") == 0) { mode = FMODE_WRITE_EXT; break; } if (strcmp(mode_str, "a+") == 0) { mode = FMODE_APPEND_EXT; break; } + if (strcmp(mode_str, "rb") == 0) { mode = FMODE_READ_BIN; break; } + if (strcmp(mode_str, "wb") == 0) { mode = FMODE_WRITE_BIN; break; } + if (strcmp(mode_str, "ab") == 0) { mode = FMODE_APPEND_BIN; break; } + + if (strcmp(mode_str, "rb+") == 0) { mode = FMODE_READ_BIN_EXT; break; } + if (strcmp(mode_str, "wb+") == 0) { mode = FMODE_WRITE_BIN_EXT; break; } + if (strcmp(mode_str, "ab+") == 0) { mode = FMODE_APPEND_BIN_EXT; break; } + // TODO: (fmt, ...) va_arg for runtime error public api. // If we reached here, that means it's an invalid mode string. pkSetRuntimeError(vm, "Invalid mode string."); @@ -89,14 +163,10 @@ DEF(_fileOpen, "") { } while (false); } - // This TODO is just a blockade from running the bellow code, complete the - // native interface and test before removing it. - TODO; - FILE* fp = fopen(path, mode_str); if (fp != NULL) { - File* self = (File*)pkGetSelf(vm); + File* self = (File*) pkGetSelf(vm); self->fp = fp; self->mode = mode; self->closed = false; @@ -107,36 +177,145 @@ DEF(_fileOpen, "") { } DEF(_fileRead, "") { - // This TODO is just a blockade from running the bellow code, complete the - // native interface and test before removing it. - TODO; - File* file = (File*)pkGetSelf(vm); + int argc = pkGetArgc(vm); + if (!pkCheckArgcRange(vm, argc, 0, 1)) return; + + // If count == -1, that means read all bytes. + long count = -1; + + if (argc == 1) { + // Not using pkValidateInteger since it's supports 32 bit int range. + // but we need a long. + double count_; + if (!pkValidateSlotNumber(vm, 1, &count_)) return; + if (floor(count_) != count_) { + pkSetRuntimeError(vm, "Expected an integer."); + return; + } + if (count_ < 0 && count_ != -1) { + pkSetRuntimeError(vm, "Read bytes count should be either > 0 or == -1."); + return; + } + count = (long) count_; + } + + File* file = (File*) pkGetSelf(vm); if (file->closed) { pkSetRuntimeError(vm, "Cannot read from a closed file."); return; } - if ((file->mode != FMODE_READ) && ((_FMODE_EXT & file->mode) == 0)) { + if (!(file->mode & FMODE_READ) && !(_FMODE_EXT & file->mode)) { pkSetRuntimeError(vm, "File is not readable."); return; } - // TODO: this is temporary. + if (count == -1) { + // Get the source length. In windows the ftell will includes the cariage + // return when using ftell with fseek. But that's not an issue since + // we'll be allocating more memory than needed for fread(). + long current = ftell(file->fp); + fseek(file->fp, 0, SEEK_END); + count = ftell(file->fp); + fseek(file->fp, current, SEEK_SET); + } - char buff[2048]; - size_t read = fread((void*)buff, sizeof(char), sizeof(buff), file->fp); - (void) read; - pkSetSlotString(vm, 0, (const char*)buff); + // Allocate string + 1 for the NULL terminator. + char* buff = pkRealloc(vm, NULL, (size_t) count + 1); + ASSERT(buff != NULL, "pkRealloc failed."); + + clearerr(file->fp); + size_t read = fread(buff, sizeof(char), count, file->fp); + + if (ferror(file->fp)) { + pkSetRuntimeErrorFmt(vm, "An error occured at C.fread() - '%s'.", + strerror(errno)); + goto L_done; + } + + bool is_read_failed = read > count; + if (!is_read_failed) buff[read] = '\0'; + + // If EOF is already reached it won't read anymore bytes. + if (read == 0) { + pkSetSlotStringLength(vm, 0, "", 0); + goto L_done; + } + + if (is_read_failed) { + pkSetRuntimeError(vm, "C.fread() failed."); + goto L_done; + } + + // TODO: maybe I should check if the [read] length is larger for uint32_t. + pkSetSlotStringLength(vm, 0, buff, (uint32_t) read); + goto L_done; + +L_done: + pkRealloc(vm, buff, 0); + return; + +} + +// Note that fgetline is not standard in older version of C. so we're defining +// something similler. +DEF(_fileGetLine, "") { + File* file = (File*) pkGetSelf(vm); + + if (file->closed) { + pkSetRuntimeError(vm, "Cannot read from a closed file."); + return; + } + + if (!(file->mode & FMODE_READ) && !(_FMODE_EXT & file->mode)) { + pkSetRuntimeError(vm, "File is not readable."); + return; + } + + if (file->mode & _FMODE_BIN) { + pkSetRuntimeError(vm, "Cannot getline binary files."); + return; + } + + // FIXME: + // I'm not sure this is an efficient method to read a line using fgetc + // and a dynamic buffer. Consider fgets() or maybe find '\n' in file + // and use pkRealloc() with size using ftell() or something. + + pkByteBuffer buff; + pkByteBufferInit(&buff); + char c; + do { + c = (char) fgetc(file->fp); + + // End of file or error. + if (c == EOF) { + if (ferror(file->fp)) { + pkSetRuntimeErrorFmt(vm, "Error while reading line - '%s'.", + strerror(errno)); + goto L_done; + } + break; // EOF is reached. + } + + pkByteBufferWrite(&buff, vm, (uint8_t) c); + if (c == '\n') break; + + } while (true); + + // A null byte '\0' will be added by pocketlang. + pkSetSlotStringLength(vm, 0, buff.data, buff.count); + +L_done: + pkByteBufferClear(&buff, vm); + return; } DEF(_fileWrite, "") { - // This TODO is just a blockade from running the bellow code, complete the - // native interface and test before removing it. - TODO; - File* file = (File*)pkGetSelf(vm); + File* file = (File*) pkGetSelf(vm); const char* text; uint32_t length; if (!pkValidateSlotString(vm, 1, &text, &length)) return; @@ -145,22 +324,28 @@ DEF(_fileWrite, "") { return; } - if ((file->mode != FMODE_WRITE) && ((_FMODE_EXT & file->mode) == 0)) { + if (!(file->mode & FMODE_WRITE) + && !(file->mode & FMODE_APPEND) + && !(file->mode & _FMODE_EXT)) { pkSetRuntimeError(vm, "File is not writable."); return; } - fwrite(text, sizeof(char), (size_t)length, file->fp); + clearerr(file->fp); + fwrite(text, sizeof(char), (size_t) length, file->fp); + if (ferror(file->fp)) { + pkSetRuntimeErrorFmt(vm, "An error occureed at C.fwrite() - '%s'.", + strerror(errno)); + return; + } } DEF(_fileClose, "") { - // This TODO is just a blockade from running the bellow code, complete the - // native interface and test before removing it. - TODO; - File* file = (File*)pkGetSelf(vm); + File* file = (File*) pkGetSelf(vm); if (file->closed) { + ASSERT(file->fp == NULL, OOPS); pkSetRuntimeError(vm, "File already closed."); return; } @@ -168,17 +353,79 @@ DEF(_fileClose, "") { if (fclose(file->fp) != 0) { pkSetRuntimeError(vm, "fclose() failed!."); } + + file->fp = NULL; file->closed = true; } +DEF(_fileSeek, + "io.File.seek(offset:int, whence:int) -> null\n" + "") { + + int argc = pkGetArgc(vm); + if (!pkCheckArgcRange(vm, argc, 1, 2)) return; + + int32_t offset = 0, whence = 0; + if (!pkValidateSlotInteger(vm, 1, &offset)) return; + + if (argc == 2) { + if (!pkValidateSlotInteger(vm, 2, &whence)) return; + if (whence < 0 || whence > 2) { + pkSetRuntimeErrorFmt(vm, "Invalid whence value (%i).", whence); + return; + } + } + + File* file = (File*) pkGetSelf(vm); + + if (file->closed) { + pkSetRuntimeError(vm, "Cannot seek from a closed file."); + return; + } + + if (fseek(file->fp, offset, whence) != 0) { + pkSetRuntimeErrorFmt(vm, "An error occureed at C.fseek() - '%s'.", + strerror(errno)); + return; + } +} + +DEF(_fileTell, "") { + File* file = (File*) pkGetSelf(vm); + + if (file->closed) { + pkSetRuntimeError(vm, "Cannot tell from a closed file."); + return; + } + + // C.ftell() doesn't "throw" any error right? + pkSetSlotNumber(vm, 0, (double) ftell(file->fp)); +} + void registerModuleIO(PKVM* vm) { + PkHandle* io = pkNewModule(vm, "io"); + pkReserveSlots(vm, 2); + pkSetSlotHandle(vm, 0, io); // slot[0] = io + pkSetSlotNumber(vm, 1, 0); // slot[1] = 0 + pkSetAttribute(vm, 0, "stdin", 1); // slot[0].stdin = slot[1] + pkSetSlotNumber(vm, 1, 1); // slot[1] = 1 + pkSetAttribute(vm, 0, "stdout", 1); // slot[0].stdout = slot[1] + pkSetSlotNumber(vm, 1, 2); // slot[1] = 2 + pkSetAttribute(vm, 0, "stderr", 1); // slot[0].stderr = slot[1] + + pkModuleAddFunction(vm, io, "write", _ioWrite, 2); + pkModuleAddFunction(vm, io, "flush", _ioFlush, 0); + PkHandle* cls_file = pkNewClass(vm, "File", NULL, io, _newFile, _deleteFile); - pkClassAddMethod(vm, cls_file, "open", _fileOpen, -1); - pkClassAddMethod(vm, cls_file, "read", _fileRead, 0); - pkClassAddMethod(vm, cls_file, "write", _fileWrite, 1); - pkClassAddMethod(vm, cls_file, "close", _fileClose, 0); + pkClassAddMethod(vm, cls_file, "open", _fileOpen, -1); + pkClassAddMethod(vm, cls_file, "read", _fileRead, -1); + pkClassAddMethod(vm, cls_file, "write", _fileWrite, 1); + pkClassAddMethod(vm, cls_file, "getline", _fileGetLine, 0); + pkClassAddMethod(vm, cls_file, "close", _fileClose, 0); + pkClassAddMethod(vm, cls_file, "seek", _fileSeek, -1); + pkClassAddMethod(vm, cls_file, "tell", _fileTell, 0); pkReleaseHandle(vm, cls_file); pkRegisterModule(vm, io); diff --git a/src/libs/std_types.c b/src/libs/std_types.c index c1820b0..1a4c15e 100644 --- a/src/libs/std_types.c +++ b/src/libs/std_types.c @@ -36,6 +36,23 @@ void _bytebuffReserve(PKVM* vm) { pkByteBufferReserve(self, vm, (size_t) size); } +// buff.fill(data, count) +void _bytebuffFill(PKVM* vm) { + uint32_t n; + if (!pkValidateSlotInteger(vm, 1, &n)) return; + if (n < 0x00 || n > 0xff) { + pkSetRuntimeErrorFmt(vm, "Expected integer in range " + "0x00 to 0xff, got %i.", n); + return; + } + + double count; + if (!pkValidateSlotNumber(vm, 1, &count)) return; + + pkByteBuffer* self = pkGetSelf(vm); + pkByteBufferFill(self, vm, (uint8_t) n, (int) count); +} + void _bytebuffClear(PKVM* vm) { // TODO: Should I also zero or reduce the capacity? pkByteBuffer* self = pkGetSelf(vm); @@ -55,12 +72,8 @@ void _bytebuffWrite(PKVM* vm) { return; case PK_NUMBER: { - double n = pkGetSlotNumber(vm, 1); - if (floor(n) != n) { - pkSetRuntimeErrorFmt(vm, "Expected an integer, got float %g.", n); - return; - } - int64_t i = (int64_t) n; + uint32_t i; + if (!pkValidateSlotInteger(vm, 1, &i)) return; if (i < 0x00 || i > 0xff) { pkSetRuntimeErrorFmt(vm, "Expected integer in range " "0x00 to 0xff, got %i.", i); @@ -80,6 +93,10 @@ void _bytebuffWrite(PKVM* vm) { return; } + // TODO: + case PK_LIST: { + } + default: break; } @@ -144,6 +161,11 @@ void _bytebuffString(PKVM* vm) { pkSetSlotStringLength(vm, 0, self->data, self->count); } +void _bytebuffCount(PKVM* vm) { + pkByteBuffer* self = pkGetSelf(vm); + pkSetSlotNumber(vm, 0, self->count); +} + /*****************************************************************************/ /* VECTOR */ /*****************************************************************************/ @@ -244,12 +266,14 @@ void registerModuleTypes(PKVM* vm) { PkHandle* cls_byte_buffer = pkNewClass(vm, "ByteBuffer", NULL, types, _bytebuffNew, _bytebuffDelete); - pkClassAddMethod(vm, cls_byte_buffer, "[]", _bytebuffSubscriptGet, 1); - pkClassAddMethod(vm, cls_byte_buffer, "[]=", _bytebuffSubscriptSet, 2); + pkClassAddMethod(vm, cls_byte_buffer, "[]", _bytebuffSubscriptGet, 1); + pkClassAddMethod(vm, cls_byte_buffer, "[]=", _bytebuffSubscriptSet, 2); pkClassAddMethod(vm, cls_byte_buffer, "reserve", _bytebuffReserve, 1); - pkClassAddMethod(vm, cls_byte_buffer, "clear", _bytebuffClear, 0); - pkClassAddMethod(vm, cls_byte_buffer, "write", _bytebuffWrite, 1); - pkClassAddMethod(vm, cls_byte_buffer, "string", _bytebuffString, 0); + pkClassAddMethod(vm, cls_byte_buffer, "fill", _bytebuffFill, 2); + pkClassAddMethod(vm, cls_byte_buffer, "clear", _bytebuffClear, 0); + pkClassAddMethod(vm, cls_byte_buffer, "write", _bytebuffWrite, 1); + pkClassAddMethod(vm, cls_byte_buffer, "string", _bytebuffString, 0); + pkClassAddMethod(vm, cls_byte_buffer, "count", _bytebuffCount, 0); pkReleaseHandle(vm, cls_byte_buffer); // TODO: add move mthods. diff --git a/tests/examples/brainfuck.pk b/tests/examples/brainfuck.pk index 0f35837..de284e0 100644 --- a/tests/examples/brainfuck.pk +++ b/tests/examples/brainfuck.pk @@ -1,4 +1,4 @@ -from lang import write +import io ############################################################################### ## BRAINFUCK IMPLEMENTATION IN POCKETLANG ## @@ -31,6 +31,11 @@ def main() execute(hello_world) execute(triangle) ##execute(fibonacci) This will run endlessly (cannot run test). + io.flush() +end + +def write(msg) + io.write(io.stdout, msg) end ############################################################################### @@ -51,52 +56,52 @@ def execute(expr) if ptr >= mem.length then list_append(mem, 0) end ## Decrement the data pointer (to point to the next cell to the left). - else if c == '<' + elif c == '<' ptr -= 1 if ptr < 0 then assert(false, "ip < 0") end ## Increment (increase by one) the byte at the data pointer. - else if c == '+' + elif c == '+' if mem[ptr] == 255 then mem[ptr] = 0 else mem[ptr] += 1 end ## Decrement (decrease by one) the byte at the data pointer. - else if c == '-' + elif c == '-' if mem[ptr] == 0 then mem[ptr] = 255 else mem[ptr] -= 1 end ## output the byte at the data pointer. - else if c == '.' + elif c == '.' write(chr(mem[ptr])) - else if c == ',' + elif c == ',' assert(false, "Currently input isn't supported in pocketlang.") ## if the byte at the data pointer is zero, then instead of moving the ## instruction pointer forward to the next command, jump it forward to ## the command after the matching ] command. - else if c == '[' and mem[ptr] == 0 + elif c == '[' and mem[ptr] == 0 open = 0 while true i += 1 if expr[i] == ']' and open == 0 then break end if expr[i] == '[' then open += 1 - else if expr[i] == ']' then open -= 1 + elif expr[i] == ']' then open -= 1 end assert(open >= 0) end ## if the byte at the data pointer is nonzero, then instead of moving the ## instruction pointer forward to the next command, jump it back to the ## command after the matching [ command - else if c == ']' and mem[ptr] != 0 + elif c == ']' and mem[ptr] != 0 open = 0 while true i -= 1 if expr[i] == '[' and open == 0 then break end if expr[i] == ']' then open -= 1 - else if expr[i] == '[' then open += 1 + elif expr[i] == '[' then open += 1 end assert(open <= 0) end diff --git a/tests/examples/fizzbuzz.pk b/tests/examples/fizzbuzz.pk index 1ea6471..56bfcc4 100644 --- a/tests/examples/fizzbuzz.pk +++ b/tests/examples/fizzbuzz.pk @@ -2,8 +2,8 @@ for i in 1..100 if i%3 == 0 and i%5 == 0 then print('fizzbuzz') - else if i%3 == 0 then print('fizz') - else if i%5 == 0 then print('buzz') + elif i%3 == 0 then print('fizz') + elif i%5 == 0 then print('buzz') else print(i) end end diff --git a/tests/lang/basics.pk b/tests/lang/basics.pk index 5d9c503..c263d58 100644 --- a/tests/lang/basics.pk +++ b/tests/lang/basics.pk @@ -10,6 +10,10 @@ assert("testing" == "test" + "ing") assert("foo \ bar" == "foo bar") +assert('abc ' * 3 == 'abc abc abc ') +assert('' * 1000 == '') +assert('foo' * 0 == '') + assert(-0b10110010 == -178 and 0b11001010 == 202) assert(0b1111111111111111 == 65535) assert( diff --git a/tests/lang/class.pk b/tests/lang/class.pk index badff7f..effba31 100644 --- a/tests/lang/class.pk +++ b/tests/lang/class.pk @@ -119,7 +119,7 @@ class Path def /(other) if other is String return Path(self.path + "/" + other) - else if other is Path + elif other is Path return Path(self.path + "/" + other.path) else assert(false, "Invalid type") diff --git a/tests/lang/controlflow.pk b/tests/lang/controlflow.pk index 79cde3f..cdf5a40 100644 --- a/tests/lang/controlflow.pk +++ b/tests/lang/controlflow.pk @@ -7,21 +7,21 @@ if true then variable = 42 else unreachable() end assert(variable == 42, 'If statement failed.') if false then unreachable() -else if true then variable = null +elif true then variable = null else unreachable() end assert(variable == null) if false then unreachable() -else if false then unreachable() -else if false then unreachable() +elif false then unreachable() +elif false then unreachable() else variable = "changed" end assert(variable == "changed") if false then unreachable() -else if true +elif true if false unreachable() - else if true + elif true variable = 123 else unreachable() @@ -39,9 +39,9 @@ assert(variable == 1212) while true variable = 22 - if true then else if false then end + if true then elif false then end if false - else if true + elif true variable += 2300 end variable += 1 @@ -57,7 +57,7 @@ assert(sum == 54) val = 2 if val == 1 then val = null -else if val == 2 then val = 'foo' +elif val == 2 then val = 'foo' else val = null end assert(val == 'foo') @@ -66,7 +66,7 @@ val = 2 if val == 1 then val = null else if val == 2 then val = 'bar' - end ##< Need an extra end since 'else if' != 'else \n if'. + end ##< Need an extra end since 'elif' != 'else \n if'. end assert(val == 'bar') diff --git a/tests/lang/import.pk b/tests/lang/import.pk index 18a27ed..0420736 100644 --- a/tests/lang/import.pk +++ b/tests/lang/import.pk @@ -3,7 +3,7 @@ import lang import lang, path import lang as o, path as p -from lang import write +from io import write from time import sleep as s import basics diff --git a/tests/random/lisp_eval.pk b/tests/random/lisp_eval.pk index 3493668..3d1023a 100644 --- a/tests/random/lisp_eval.pk +++ b/tests/random/lisp_eval.pk @@ -44,27 +44,27 @@ def eval(expr, ind) end return [val, ind] - else if c == '+' + elif c == '+' r = binary_op(expr, ind) if not r[0] then return [null, -1] end return [r[1] + r[2], r[3]] - else if c == '-' + elif c == '-' r = binary_op(expr, ind) if not r[0] then return [null, -1] end return [r[1] - r[2], r[3]] - else if c == '*' + elif c == '*' r = binary_op(expr, ind) if not r[0] then return [null, -1] end return [r[1] * r[2], r[3]] - else if c == '/' + elif c == '/' r = binary_op(expr, ind) if not r[0] then return [null, -1] end return [r[1] / r[2], r[3]] - else if isnum(c) + elif isnum(c) val = ord(c) - ord('0') assert(0 <= val and val < 10) return [val, ind]