diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 400701c..94242f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: run: | python3 tests/check.py - ## Compile and run test on linux system. + ## Compile and run test on linux. linux-build: runs-on: ubuntu-latest steps: @@ -24,7 +24,7 @@ jobs: run: | python3 tests/tests.py - ## Compile and run tests on windows system. + ## Compile and run tests on windows. windows-build: runs-on: windows-latest steps: @@ -36,7 +36,7 @@ jobs: run: | python3 tests/tests.py - ## Compile and run tests on macos system. + ## Compile and run tests on macos. macos-build: runs-on: macos-latest steps: diff --git a/README.md b/README.md index 308aa30..565efb0 100644 --- a/README.md +++ b/README.md @@ -63,13 +63,10 @@ directory. They were ran using a small python script in the test directory. ## Building From Source -See [build documentation](https://thakeenathees.github.io/pocketlang/getting-started-build-from-source.html#using-a-build-script) -for using an optional build script (Makefile, batch script for MSVC, SCons found in the `build/` directory). -It can be build from source easily without any dependencies, or additional -requirements except for a c99 compatible compiler. It can be compiled with the -following command. +It can be build from source easily without any dependencies, or additional requirements +except for a c99 compatible compiler. It can be compiled with the following command. -#### GCC +#### GCC / MinGw / Clang (alias with gcc) ``` gcc -o pocket cli/*.c src/*.c -Isrc/include -lm -Wno-int-to-pointer-cast ``` @@ -79,6 +76,22 @@ gcc -o pocket cli/*.c src/*.c -Isrc/include -lm -Wno-int-to-pointer-cast cl /Fepocket cli/*.c src/*.c /Isrc/include && rm *.obj ``` +#### Makefile +``` +make +``` +To run make file on windows with mingw, you require `make` and `find` unix command in your path. +Which you can get from [msys2](https://www.msys2.org/) or [cygwin](https://www.cygwin.com/). Run +`set PATH=;%PATH% && make`, this will override the system `find` command with +unix `find` for the current session, and run the `make` script. + +#### Windows batch script +``` +build +``` +You don't have to run the script from a Visual Studio .NET developer command prompt, It'll search +for the MSVS installation path and setup the build enviornment. + ### For other compiler/IDE 1. Create an empty project file / makefile. diff --git a/src/include/pocketlang.h b/src/include/pocketlang.h index aa258a3..8c0c11f 100644 --- a/src/include/pocketlang.h +++ b/src/include/pocketlang.h @@ -54,18 +54,6 @@ extern "C" { #define PK_PUBLIC #endif -// A convenient macro to define documentation of funcions. Use it to document -// your native functions. -// -// PK_DOC( -// "The function will print 'foo' on the console.", -// static void foo()) { -// printf("foo\n"); -// } -// -#define PK_DOC(doc, func) \ - /* TODO: static char __pkdoc__##func[] = doc;*/ func - // Name of the implicit function for a module. When a module is parsed all of // it's statements are wrapped around an implicit function with this name. #define PK_IMPLICIT_MAIN_NAME "$(SourceBody)" diff --git a/src/pk_common.h b/src/pk_common.h index 797cd24..c1bbbd1 100644 --- a/src/pk_common.h +++ b/src/pk_common.h @@ -80,10 +80,13 @@ #include //< Only needed here for ASSERT() macro and for release mode //< TODO; macro use this to print a crash report. -// This will terminate the compilation if the [condition] is false, because of -// 1/0 evaluated. Use this to check missing enums in switch, or check if an -// enum or macro has a specific value. (STATIC_ASSERT(ENUM_SUCCESS == 0)). -#define STATIC_ASSERT(condition) ( 1 / ((int)(condition)) ) +#define TOSTRING(x) #x +#define STRINGIFY(x) TOSTRING(x) + +// CONCAT_LINE(X) will result evaluvated X<__LINE__>. +#define __CONCAT_LINE_INTERNAL_R(a, b) a ## b +#define __CONCAT_LINE_INTERNAL_F(a, b) __CONCAT_LINE_INTERNAL_R(a, b) +#define CONCAT_LINE(X) __CONCAT_LINE_INTERNAL_F(X, __LINE__) // The internal assertion macro, this will print error and break regardless of // the build target (debug or release). Use ASSERT() for debug assertion and @@ -109,6 +112,11 @@ #define DEBUG_BREAK() __builtin_trap() #endif +// This will terminate the compilation if the [condition] is false, because of +// char _assertion_failure_<__LINE__>[-1] evaluated. +#define STATIC_ASSERT(condition) \ + static char CONCAT_LINE(_assertion_failure_)[2*!!(condition) - 1] + #define ASSERT(condition, message) __ASSERT(condition, message) #define ASSERT_INDEX(index, size) \ @@ -124,6 +132,8 @@ #else +#define STATIC_ASSERT(condition) NO_OP + #define DEBUG_BREAK() NO_OP #define ASSERT(condition, message) NO_OP #define ASSERT_INDEX(index, size) NO_OP @@ -149,9 +159,6 @@ #define TODO __ASSERT(false, "TODO: It hasn't implemented yet.") #define OOPS "Oops a bug!! report please." -#define TOSTRING(x) #x -#define STRINGIFY(x) TOSTRING(x) - // The formated string to convert double to string. It'll be with the minimum // length string representation of either a regular float or a scientific // notation (at most 15 decimal points). diff --git a/src/pk_compiler.c b/src/pk_compiler.c index e84a231..8c68642 100644 --- a/src/pk_compiler.c +++ b/src/pk_compiler.c @@ -92,6 +92,7 @@ typedef enum { TK_MINUSEQ, // -= TK_STAREQ, // *= TK_DIVEQ, // /= + //TK_MODEQ, // %= TK_ANDEQ, // &= TK_OREQ, // |= @@ -103,8 +104,6 @@ typedef enum { //TODO: //TK_SRIGHTEQ // >>= //TK_SLEFTEQ // <<= - //TK_MODEQ, // %= - //TK_XOREQ, // ^= // Keywords. TK_MODULE, // module @@ -1884,7 +1883,7 @@ static int compileFunction(Compiler* compiler, FuncType fn_type) { } Function* func = newFunction(compiler->vm, name, name_length, - compiler->script, fn_type == FN_NATIVE); + compiler->script, fn_type == FN_NATIVE, NULL); int fn_index = (int)compiler->script->functions.count - 1; if (fn_index == MAX_FUNCTIONS) { parseError(compiler, "A script should contain at most %d functions.", @@ -2561,9 +2560,7 @@ static void compileStatement(Compiler* compiler) { compiler->new_local = false; } - // If running REPL mode, print the expression's evaluated value. Only if - // we're at the top level. Python does print local depth expression too. - // (it's just a design decision). + // If running REPL mode, print the expression's evaluated value. if (compiler->options && compiler->options->repl_mode && compiler->func->ptr == compiler->script->body && is_expression /*&& compiler->scope_depth == DEPTH_GLOBAL*/) { @@ -2591,7 +2588,7 @@ static void compileTopLevelStatement(Compiler* compiler) { } else if (match(compiler, TK_MODULE)) { parseError(compiler, "Module name must be the first statement " - "of the script."); + "of the script."); } else { compileStatement(compiler); diff --git a/src/pk_core.c b/src/pk_core.c index e963a48..c5795fb 100644 --- a/src/pk_core.c +++ b/src/pk_core.c @@ -21,6 +21,16 @@ #define M_PI 3.14159265358979323846 #endif +// Returns the docstring of the function, which is a static const char* defined +// just above the function by the DEF() macro below. +#define DOCSTRING(fn) _pk_doc_##fn + +// A macro to declare a function, with docstring, which is defined as +// _pk_doc_ = docstring; That'll used to generate function help text. +#define DEF(fn, docstring) \ + const char* DOCSTRING(fn) = docstring; \ + static void fn(PKVM* vm) + /*****************************************************************************/ /* CORE PUBLIC API */ /*****************************************************************************/ @@ -36,7 +46,7 @@ static void moduleAddGlobalInternal(PKVM* vm, Script* script, // The internal function to add functions to a module. static void moduleAddFunctionInternal(PKVM* vm, Script* script, const char* name, pkNativeFn fptr, - int arity); + int arity, const char* docstring); // pkNewModule implementation (see pocketlang.h for description). PkHandle* pkNewModule(PKVM* vm, const char* name) { @@ -53,7 +63,6 @@ PK_PUBLIC void pkModuleAddGlobal(PKVM* vm, PkHandle* module, __ASSERT(IS_OBJ_TYPE(scr, OBJ_SCRIPT), "Given handle is not a module"); moduleAddGlobalInternal(vm, (Script*)AS_OBJ(scr), name, value->value); - } // pkModuleAddFunction implementation (see pocketlang.h for description). @@ -62,7 +71,8 @@ void pkModuleAddFunction(PKVM* vm, PkHandle* module, const char* name, __ASSERT(module != NULL, "Argument module was NULL."); Var scr = module->value; __ASSERT(IS_OBJ_TYPE(scr, OBJ_SCRIPT), "Given handle is not a module"); - moduleAddFunctionInternal(vm, (Script*)AS_OBJ(scr), name, fptr, arity); + moduleAddFunctionInternal(vm, (Script*)AS_OBJ(scr), name, fptr, arity, + NULL /*TODO: Public API for function docstring.*/); } PkHandle* pkGetFunction(PKVM* vm, PkHandle* module, @@ -378,44 +388,78 @@ Script* getCoreLib(const PKVM* vm, String* name) { /* CORE BUILTIN FUNCTIONS */ /*****************************************************************************/ -#define FN_IS_PRIMITIVE_TYPE(name, check) \ - static void coreIs##name(PKVM* vm) { \ - RET(VAR_BOOL(check(ARG(1)))); \ - } - -#define FN_IS_OBJ_TYPE(name, _enum) \ - static void coreIs##name(PKVM* vm) { \ - Var arg1 = ARG(1); \ - if (IS_OBJ_TYPE(arg1, _enum)) { \ - RET(VAR_TRUE); \ - } else { \ - RET(VAR_FALSE); \ - } \ - } - -FN_IS_PRIMITIVE_TYPE(Null, IS_NULL) -FN_IS_PRIMITIVE_TYPE(Bool, IS_BOOL) -FN_IS_PRIMITIVE_TYPE(Num, IS_NUM) - -FN_IS_OBJ_TYPE(String, OBJ_STRING) -FN_IS_OBJ_TYPE(List, OBJ_LIST) -FN_IS_OBJ_TYPE(Map, OBJ_MAP) -FN_IS_OBJ_TYPE(Range, OBJ_RANGE) -FN_IS_OBJ_TYPE(Function, OBJ_FUNC) -FN_IS_OBJ_TYPE(Script, OBJ_SCRIPT) -FN_IS_OBJ_TYPE(UserObj, OBJ_USER) - -PK_DOC( +DEF(coreTypeName, "type_name(value:var) -> string\n" - "Returns the type name of the of the value.", -static void coreTypeName(PKVM* vm)) { + "Returns the type name of the of the value.") { + RET(VAR_OBJ(newString(vm, varTypeName(ARG(1))))); } -PK_DOC( +DEF(coreHelp, + "help([fn]) -> null\n" + "This will write an error message to stdout and return null.") { + + int argc = ARGC; + if (argc != 0 && argc != 1) { + RET_ERR(newString(vm, "Invalid argument count.")); + } + + if (argc == 0) { + // If there ins't an io function callback, we're done. + if (vm->config.write_fn == NULL) RET(VAR_NULL); + vm->config.write_fn(vm, "TODO: print help here\n"); + + } else if (argc == 1) { + Function* fn; + if (!validateArgFunction(vm, 1, &fn)) return; + + // If there ins't an io function callback, we're done. + if (vm->config.write_fn == NULL) RET(VAR_NULL); + + if (fn->docstring != NULL) { + vm->config.write_fn(vm, fn->docstring); + vm->config.write_fn(vm, "\n\n"); + } else { + // TODO: A better message. + vm->config.write_fn(vm, "function '"); + vm->config.write_fn(vm, fn->name); + vm->config.write_fn(vm, "()' doesn't have a docstring.\n"); + } + } +} + +DEF(coreAssert, + "assert(condition:bool [, msg:string]) -> void\n" + "If the condition is false it'll terminate the current fiber with the " + "optional error message") { + + int argc = ARGC; + if (argc != 1 && argc != 2) { + RET_ERR(newString(vm, "Invalid argument count.")); + } + + if (!toBool(ARG(1))) { + String* msg = NULL; + + if (argc == 2) { + if (AS_OBJ(ARG(2))->type != OBJ_STRING) { + msg = toString(vm, ARG(2)); + } else { + msg = (String*)AS_OBJ(ARG(2)); + } + vmPushTempRef(vm, &msg->_super); + VM_SET_ERROR(vm, stringFormat(vm, "Assertion failed: '@'.", msg)); + vmPopTempRef(vm); + } else { + VM_SET_ERROR(vm, newString(vm, "Assertion failed.")); + } + } +} + +DEF(coreBin, "bin(value:num) -> string\n" - "Returns as a binary value string with '0x' prefix.", -static void coreBin(PKVM* vm)) { + "Returns as a binary value string with '0x' prefix.") { + int64_t value; if (!validateInteger(vm, ARG(1), &value, "Argument 1")) return; @@ -443,10 +487,10 @@ static void coreBin(PKVM* vm)) { RET(VAR_OBJ(newStringLength(vm, ptr + 1, length))); } -PK_DOC( +DEF(coreHex, "hex(value:num) -> string\n" - "Returns as a hexadecimal value string with '0x' prefix.", -static void coreHex(PKVM* vm)) { + "Returns as a hexadecimal value string with '0x' prefix.") { + int64_t value; if (!validateInteger(vm, ARG(1), &value, "Argument 1")) return; @@ -467,44 +511,15 @@ static void coreHex(PKVM* vm)) { int length = sprintf(ptr, "%x", _x); RET(VAR_OBJ(newStringLength(vm, buff, - (uint32_t) ((ptr + length) - (char*)(buff)) ))); + (uint32_t)((ptr + length) - (char*)(buff))))); } -PK_DOC( - "assert(condition:bool [, msg:string]) -> void\n" - "If the condition is false it'll terminate the current fiber with the " - "optional error message", -static void coreAssert(PKVM* vm)) { - int argc = ARGC; - if (argc != 1 && argc != 2) { - RET_ERR(newString(vm, "Invalid argument count.")); - } - - if (!toBool(ARG(1))) { - String* msg = NULL; - - if (argc == 2) { - if (AS_OBJ(ARG(2))->type != OBJ_STRING) { - msg = toString(vm, ARG(2)); - } else { - msg = (String*)AS_OBJ(ARG(2)); - } - vmPushTempRef(vm, &msg->_super); - VM_SET_ERROR(vm, stringFormat(vm, "Assertion failed: '@'.", msg)); - vmPopTempRef(vm); - } else { - VM_SET_ERROR(vm, newString(vm, "Assertion failed.")); - } - } -} - -PK_DOC( +DEF(coreYield, "yield([value]) -> var\n" "Return the current function with the yield [value] to current running " "fiber. If the fiber is resumed, it'll run from the next statement of the " "yield() call. If the fiber resumed with with a value, the return value of " - "the yield() would be that value otherwise null.", -static void coreYield(PKVM* vm)) { + "the yield() would be that value otherwise null.") { int argc = ARGC; if (argc > 1) { // yield() or yield(val). @@ -514,18 +529,18 @@ static void coreYield(PKVM* vm)) { vmYieldFiber(vm, (argc == 1) ? &ARG(1) : NULL); } -PK_DOC( +DEF(coreToString, "to_string(value:var) -> string\n" - "Returns the string representation of the value.", -static void coreToString(PKVM* vm)) { + "Returns the string representation of the value.") { + RET(VAR_OBJ(toString(vm, ARG(1)))); } -PK_DOC( +DEF(corePrint, "print(...) -> void\n" - "Write each argument as comma seperated to the stdout and ends with a " - "newline.", -static void corePrint(PKVM* vm)) { + "Write each argument as space seperated, to the stdout and ends with a " + "newline.") { + // If the host application doesn't provide any write function, discard the // output. if (vm->config.write_fn == NULL) return; @@ -538,11 +553,11 @@ static void corePrint(PKVM* vm)) { vm->config.write_fn(vm, "\n"); } -PK_DOC( +DEF(coreInput, "input([msg:var]) -> string\n" "Read a line from stdin and returns it without the line ending. Accepting " - "an optional argument [msg] and prints it before reading.", -static void coreInput(PKVM* vm)) { + "an optional argument [msg] and prints it before reading.") { + int argc = ARGC; if (argc != 1 && argc != 2) { RET_ERR(newString(vm, "Invalid argument count.")); @@ -566,10 +581,9 @@ static void coreInput(PKVM* vm)) { // TODO: substring. -PK_DOC( +DEF(coreStrChr, "str_chr(value:number) -> string\n" - "Returns the ASCII string value of the integer argument.", -static void coreStrChr(PKVM* vm)) { + "Returns the ASCII string value of the integer argument.") { int64_t num; if (!validateInteger(vm, ARG(1), &num, "Argument 1")) return; @@ -579,10 +593,10 @@ static void coreStrChr(PKVM* vm)) { RET(VAR_OBJ(newStringLength(vm, &c, 1))); } -PK_DOC( +DEF(coreStrOrd, "str_ord(value:string) -> number\n" - "Returns integer value of the given ASCII character.", -static void coreStrOrd(PKVM* vm)) { + "Returns integer value of the given ASCII character.") { + String* c; if (!validateArgString(vm, 1, &c)) return; if (c->length != 1) { @@ -596,10 +610,10 @@ static void coreStrOrd(PKVM* vm)) { // List functions. // --------------- -PK_DOC( +DEF(coreListAppend, "list_append(self:List, value:var) -> List\n" - "Append the [value] to the list [self] and return the list.", -static void coreListAppend(PKVM* vm)) { + "Append the [value] to the list [self] and return the list.") { + List* list; if (!validateArgList(vm, 1, &list)) return; Var elem = ARG(2); @@ -611,11 +625,11 @@ static void coreListAppend(PKVM* vm)) { // Map functions. // -------------- -PK_DOC( +DEF(coreMapRemove, "map_remove(self:map, key:var) -> var\n" "Remove the [key] from the map [self] and return it's value if the key " - "exists, otherwise it'll return null.", -static void coreMapRemove(PKVM* vm)) { + "exists, otherwise it'll return null.") { + Map* map; if (!validateArgMap(vm, 1, &map)) return; Var key = ARG(2); @@ -626,39 +640,38 @@ static void coreMapRemove(PKVM* vm)) { // Fiber functions. // ---------------- -PK_DOC( +DEF(coreFiberNew, "fiber_new(fn:Function) -> fiber\n" - "Create and return a new fiber from the given function [fn].", -static void coreFiberNew(PKVM* vm)) { + "Create and return a new fiber from the given function [fn].") { + Function* fn; if (!validateArgFunction(vm, 1, &fn)) return; RET(VAR_OBJ(newFiber(vm, fn))); } -PK_DOC( +DEF(coreFiberGetFunc, "fiber_get_func(fb:Fiber) -> function\n" "Retruns the fiber's functions. Which is usefull if you want to re-run the " - "fiber, you can get the function and crate a new fiber.", -static void coreFiberGetFunc(PKVM* vm)) { + "fiber, you can get the function and crate a new fiber.") { + Fiber* fb; if (!validateArgFiber(vm, 1, &fb)) return; RET(VAR_OBJ(fb->func)); } -PK_DOC( +DEF(coreFiberIsDone, "fiber_is_done(fb:Fiber) -> bool\n" - "Returns true if the fiber [fb] is done running and can no more resumed.", -static void coreFiberIsDone(PKVM* vm)) { + "Returns true if the fiber [fb] is done running and can no more resumed.") { + Fiber* fb; if (!validateArgFiber(vm, 1, &fb)) return; RET(VAR_BOOL(fb->state == FIBER_DONE)); } -PK_DOC( +DEF(coreFiberRun, "fiber_run(fb:Fiber, ...) -> var\n" "Runs the fiber's function with the provided arguments and returns it's " - "return value or the yielded value if it's yielded.", -static void coreFiberRun(PKVM* vm)) { + "return value or the yielded value if it's yielded.") { int argc = ARGC; if (argc == 0) // Missing the fiber argument. @@ -682,11 +695,10 @@ static void coreFiberRun(PKVM* vm)) { } } -PK_DOC( +DEF(coreFiberResume, "fiber_resume(fb:Fiber) -> var\n" "Resumes a yielded function from a previous call of fiber_run() function. " - "Return it's return value or the yielded value if it's yielded.", -static void coreFiberResume(PKVM* vm)) { + "Return it's return value or the yielded value if it's yielded.") { int argc = ARGC; if (argc == 0) // Missing the fiber argument. @@ -772,12 +784,13 @@ static void moduleAddGlobalInternal(PKVM* vm, Script* script, // An internal function to add a function to the given [script]. static void moduleAddFunctionInternal(PKVM* vm, Script* script, const char* name, pkNativeFn fptr, - int arity) { + int arity, const char* docstring) { // Ensure the name isn't predefined. assertModuleNameDef(vm, script, name); - Function* fn = newFunction(vm, name, (int)strlen(name), script, true); + Function* fn = newFunction(vm, name, (int)strlen(name), + script, true, docstring); fn->native = fptr; fn->arity = arity; } @@ -787,23 +800,27 @@ static void moduleAddFunctionInternal(PKVM* vm, Script* script, // 'lang' library methods. // ----------------------- -// Returns the number of seconds since the application started. -void stdLangClock(PKVM* vm) { +DEF(stdLangClock, + "clock() -> num\n" + "Returns the number of seconds since the application started") { + RET(VAR_NUM((double)clock() / CLOCKS_PER_SEC)); } -// Trigger garbage collection and return the amount of bytes cleaned. -void stdLangGC(PKVM* vm) { +DEF(stdLangGC, + "gc() -> num\n" + "Trigger garbage collection and return the amount of bytes cleaned.") { + size_t bytes_before = vm->bytes_allocated; vmCollectGarbage(vm); size_t garbage = bytes_before - vm->bytes_allocated; RET(VAR_NUM((double)garbage)); } -PK_DOC( +DEF(stdLangDisas, "disas(fn:Function) -> String\n" - "Returns the disassembled opcode of the function [fn]. ", -static void stdLangDisas(PKVM* vm)) { + "Returns the disassembled opcode of the function [fn].") { + Function* fn; if (!validateArgFunction(vm, 1, &fn)) return; @@ -816,14 +833,18 @@ static void stdLangDisas(PKVM* vm)) { RET(VAR_OBJ(dump)); } -// A debug function for development (will be removed). -void stdLangDebugBreak(PKVM* vm) { +DEF(stdLangDebugBreak, + "debug_break() -> null\n" + "A debug function for development (will be removed).") { + DEBUG_BREAK(); } -// Write function, just like print function but it wont put space between args -// and write a new line at the end. -void stdLangWrite(PKVM* vm) { +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.write_fn == NULL) return; @@ -846,43 +867,51 @@ void stdLangWrite(PKVM* vm) { // 'math' library methods. // ----------------------- -void stdMathFloor(PKVM* vm) { +DEF(stdMathFloor, + "floor(value:num) -> num\n") { + double num; if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; - RET(VAR_NUM(floor(num))); } -void stdMathCeil(PKVM* vm) { +DEF(stdMathCeil, + "ceil(value:num) -> num\n") { + double num; if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; - RET(VAR_NUM(ceil(num))); } -void stdMathPow(PKVM* vm) { +DEF(stdMathPow, + "pow(value:num) -> num\n") { + double num, ex; if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; if (!validateNumeric(vm, ARG(2), &ex, "Argument 2")) return; - RET(VAR_NUM(pow(num, ex))); } -void stdMathSqrt(PKVM* vm) { +DEF(stdMathSqrt, + "sqrt(value:num) -> num\n") { + double num; if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; - RET(VAR_NUM(sqrt(num))); } -void stdMathAbs(PKVM* vm) { +DEF(stdMathAbs, + "abs(value:num) -> num\n") { + double num; if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; if (num < 0) num = -num; RET(VAR_NUM(num)); } -void stdMathSign(PKVM* vm) { +DEF(stdMathSign, + "sign(value:num) -> num\n") { + double num; if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; if (num < 0) num = -1; @@ -891,11 +920,11 @@ void stdMathSign(PKVM* vm) { RET(VAR_NUM(num)); } -PK_DOC( +DEF(stdMathHash, "hash(value:var) -> num\n" "Return the hash value of the variable, if it's not hashable it'll " - "return null.", -static void stdMathHash(PKVM* vm)) { + "return null.") { + if (IS_OBJ(ARG(1))) { if (!isObjectHashable(AS_OBJ(ARG(1))->type)) { RET(VAR_NULL); @@ -904,31 +933,31 @@ static void stdMathHash(PKVM* vm)) { RET(VAR_NUM((double)varHashValue(ARG(1)))); } -PK_DOC( +DEF(stdMathSine, "sin(rad:num) -> num\n" "Return the sine value of the argument [rad] which is an angle expressed " - "in radians.", -static void stdMathSine(PKVM* vm)) { + "in radians.") { + double rad; if (!validateNumeric(vm, ARG(1), &rad, "Argument 1")) return; RET(VAR_NUM(sin(rad))); } -PK_DOC( +DEF(stdMathCosine, "cos(rad:num) -> num\n" "Return the cosine value of the argument [rad] which is an angle expressed " - "in radians.", -static void stdMathCosine(PKVM* vm)) { + "in radians.") { + double rad; if (!validateNumeric(vm, ARG(1), &rad, "Argument 1")) return; RET(VAR_NUM(cos(rad))); } -PK_DOC( +DEF(stdMathTangent, "tan(rad:num) -> num\n" "Return the tangent value of the argument [rad] which is an angle expressed " - "in radians.", -static void stdMathTangent(PKVM* vm)) { + "in radians.") { + double rad; if (!validateNumeric(vm, ARG(1), &rad, "Argument 1")) return; RET(VAR_NUM(tan(rad))); @@ -939,11 +968,12 @@ static void stdMathTangent(PKVM* vm)) { /*****************************************************************************/ static void initializeBuiltinFN(PKVM* vm, BuiltinFn* bfn, const char* name, - int length, int arity, pkNativeFn ptr) { + int length, int arity, pkNativeFn ptr, + const char* docstring) { bfn->name = name; bfn->length = length; - bfn->fn = newFunction(vm, name, length, NULL, true); + bfn->fn = newFunction(vm, name, length, NULL, true, docstring); bfn->fn->arity = arity; bfn->fn->native = ptr; } @@ -952,32 +982,23 @@ void initializeCore(PKVM* vm) { #define INITIALIZE_BUILTIN_FN(name, fn, argc) \ initializeBuiltinFN(vm, &vm->builtins[vm->builtins_count++], name, \ - (int)strlen(name), argc, fn); + (int)strlen(name), argc, fn, DOCSTRING(fn)); + +#define MODULE_ADD_FN(module, name, fn, argc) \ + moduleAddFunctionInternal(vm, module, name, fn, argc, DOCSTRING(fn)) // Initialize builtin functions. INITIALIZE_BUILTIN_FN("type_name", coreTypeName, 1); - // TODO: (maybe remove is_*() functions) suspend by type_name. - // and add is keyword with modules for builtin types + // TODO: Add is keyword with modules for builtin types. // ex: val is Num; val is null; val is List; val is Range // List.append(l, e) # List is implicitly imported core module. // String.lower(s) - INITIALIZE_BUILTIN_FN("is_null", coreIsNull, 1); - INITIALIZE_BUILTIN_FN("is_bool", coreIsBool, 1); - INITIALIZE_BUILTIN_FN("is_num", coreIsNum, 1); - - INITIALIZE_BUILTIN_FN("is_string", coreIsString, 1); - INITIALIZE_BUILTIN_FN("is_list", coreIsList, 1); - INITIALIZE_BUILTIN_FN("is_map", coreIsMap, 1); - INITIALIZE_BUILTIN_FN("is_range", coreIsRange, 1); - INITIALIZE_BUILTIN_FN("is_function", coreIsFunction, 1); - INITIALIZE_BUILTIN_FN("is_script", coreIsScript, 1); - INITIALIZE_BUILTIN_FN("is_userobj", coreIsUserObj, 1); - + INITIALIZE_BUILTIN_FN("help", coreHelp, -1); + INITIALIZE_BUILTIN_FN("assert", coreAssert, -1); INITIALIZE_BUILTIN_FN("bin", coreBin, 1); INITIALIZE_BUILTIN_FN("hex", coreHex, 1); - INITIALIZE_BUILTIN_FN("assert", coreAssert, -1); INITIALIZE_BUILTIN_FN("yield", coreYield, -1); INITIALIZE_BUILTIN_FN("to_string", coreToString, 1); INITIALIZE_BUILTIN_FN("print", corePrint, -1); @@ -1003,25 +1024,25 @@ void initializeCore(PKVM* vm) { // Core Modules ///////////////////////////////////////////////////////////// Script* lang = newModuleInternal(vm, "lang"); - moduleAddFunctionInternal(vm, lang, "clock", stdLangClock, 0); - moduleAddFunctionInternal(vm, lang, "gc", stdLangGC, 0); - moduleAddFunctionInternal(vm, lang, "disas", stdLangDisas, 1); - moduleAddFunctionInternal(vm, lang, "write", stdLangWrite, -1); + MODULE_ADD_FN(lang, "clock", stdLangClock, 0); + MODULE_ADD_FN(lang, "gc", stdLangGC, 0); + MODULE_ADD_FN(lang, "disas", stdLangDisas, 1); + MODULE_ADD_FN(lang, "write", stdLangWrite, -1); #ifdef DEBUG - moduleAddFunctionInternal(vm, lang, "debug_break", stdLangDebugBreak, 0); + MODULE_ADD_FN(lang, "debug_break", stdLangDebugBreak, 0); #endif Script* math = newModuleInternal(vm, "math"); - moduleAddFunctionInternal(vm, math, "floor", stdMathFloor, 1); - moduleAddFunctionInternal(vm, math, "ceil", stdMathCeil, 1); - moduleAddFunctionInternal(vm, math, "pow", stdMathPow, 2); - moduleAddFunctionInternal(vm, math, "sqrt", stdMathSqrt, 1); - moduleAddFunctionInternal(vm, math, "abs", stdMathAbs, 1); - moduleAddFunctionInternal(vm, math, "sign", stdMathSign, 1); - moduleAddFunctionInternal(vm, math, "hash", stdMathHash, 1); - moduleAddFunctionInternal(vm, math, "sin", stdMathSine, 1); - moduleAddFunctionInternal(vm, math, "cos", stdMathCosine, 1); - moduleAddFunctionInternal(vm, math, "tan", stdMathTangent, 1); + MODULE_ADD_FN(math, "floor", stdMathFloor, 1); + MODULE_ADD_FN(math, "ceil", stdMathCeil, 1); + MODULE_ADD_FN(math, "pow", stdMathPow, 2); + MODULE_ADD_FN(math, "sqrt", stdMathSqrt, 1); + MODULE_ADD_FN(math, "abs", stdMathAbs, 1); + MODULE_ADD_FN(math, "sign", stdMathSign, 1); + MODULE_ADD_FN(math, "hash", stdMathHash, 1); + MODULE_ADD_FN(math, "sin", stdMathSine, 1); + MODULE_ADD_FN(math, "cos", stdMathCosine, 1); + MODULE_ADD_FN(math, "tan", stdMathTangent, 1); // TODO: low priority - sinh, cosh, tanh, asin, acos, atan. diff --git a/src/pk_var.c b/src/pk_var.c index dfcad19..ccbe100 100644 --- a/src/pk_var.c +++ b/src/pk_var.c @@ -337,7 +337,8 @@ Script* newScript(PKVM* vm, String* path) { vmPushTempRef(vm, &script->_super); const char* fn_name = PK_IMPLICIT_MAIN_NAME; - script->body = newFunction(vm, fn_name, (int)strlen(fn_name), script, false); + script->body = newFunction(vm, fn_name, (int)strlen(fn_name), + script, false, NULL/*TODO*/); script->body->arity = 0; // TODO: should it be 1 (ARGV)?. vmPopTempRef(vm); @@ -345,7 +346,7 @@ Script* newScript(PKVM* vm, String* path) { } Function* newFunction(PKVM* vm, const char* name, int length, Script* owner, - bool is_native) { + bool is_native, const char* docstring) { Function* func = ALLOCATE(vm, Function); varInitObject(&func->_super, vm, OBJ_FUNC); @@ -380,6 +381,10 @@ Function* newFunction(PKVM* vm, const char* name, int length, Script* owner, fn->stack_size = 0; func->fn = fn; } + + // Both native and script (TODO:) functions support docstring. + func->docstring = docstring; + return func; } diff --git a/src/pk_var.h b/src/pk_var.h index be9fda1..f810c30 100644 --- a/src/pk_var.h +++ b/src/pk_var.h @@ -305,14 +305,19 @@ typedef struct { struct Function { Object _super; - const char* name; //< Name in the script [owner] or C literal. - Script* owner; //< Owner script of the function. - int arity; //< Number of argument the function expects. + const char* name; //< Name in the script [owner] or C literal. + Script* owner; //< Owner script of the function. + int arity; //< Number of argument the function expects. - bool is_native; //< True if Native function. + // Docstring of the function, currently it's just the C string literal + // pointer, refactor this into String* so that we can support public + // native functions to provide a docstring. + const char* docstring; + + bool is_native; //< True if Native function. union { - pkNativeFn native; //< Native function pointer. - Fn* fn; //< Script function pointer. + pkNativeFn native; //< Native function pointer. + Fn* fn; //< Script function pointer. }; }; @@ -406,8 +411,10 @@ Script* newScript(PKVM* vm, String* path); // be the name in the Script's nametable. If the [owner] is NULL the function // would be builtin function. For builtin function arity and the native // function pointer would be initialized after calling this function. +// The argument [docstring] is an optional documentation text (could be NULL) +// That'll printed when running help(fn). Function* newFunction(PKVM* vm, const char* name, int length, Script* owner, - bool is_native); + bool is_native, const char* docstring); // Allocate new Fiber object around the function [fn] and return Fiber*. Fiber* newFiber(PKVM* vm, Function* fn); diff --git a/tests/check.py b/tests/check.py index 927c4ef..37f6f6b 100644 --- a/tests/check.py +++ b/tests/check.py @@ -25,7 +25,7 @@ HASH_CHECK_LIST = [ ] ## A list of directory, contains C source files to perform static checks. -## This will include both '.c' and '.h' files. +## This will include '.c', '.h', '.py' and '.pk' files. C_SOURCE_DIRS = [ "../src/", "../cli/", diff --git a/tests/lang/basics.pk b/tests/lang/basics.pk index f14b3c8..a822247 100644 --- a/tests/lang/basics.pk +++ b/tests/lang/basics.pk @@ -18,9 +18,9 @@ assert(0xa1b2c3 == 10597059 and 0xff == 255) assert(0xffffffffffffffff == 18446744073709551615) ## Lists test. -l1 = [1, false, null, func print('hello') end] -assert(is_null(l1[2])) -l1[1] = true; assert(l1[1]) +l1 = [1, false, null, func print('hello') end, true] +assert(l1[4]) +l1[3] = null; assert(!l1[3]) l1 = [] + [] ; assert(l1.length == 0) l1 = [] + [1]; assert(l1.length == 1); assert(l1[0] == 1) l1 = [1] + []; assert(l1.length == 1); assert(l1[0] == 1) diff --git a/tests/lang/controlflow.pk b/tests/lang/controlflow.pk index 2c4a7b4..90ddb2f 100644 --- a/tests/lang/controlflow.pk +++ b/tests/lang/controlflow.pk @@ -9,13 +9,13 @@ assert(variable == 42, 'If statement failed.') if false then unreachable() elsif true then variable = null else unreachable() end -assert(is_null(variable), 'elsif statement failed.') +assert(variable == null) if false then unreachable() elsif false then unreachable() elsif false then unreachable() else variable = "changed" end -assert(variable == "changed", 'Else statement failed.') +assert(variable == "changed") if false then unreachable() elsif true diff --git a/tests/tests.py b/tests/tests.py index 8dc54fc..87d92d9 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -4,13 +4,13 @@ import os, sys, platform import subprocess, json, re -from os.path import join +from os.path import join, abspath, relpath ## TODO: Re write this in doctest (https://github.com/onqtam/doctest) ## The absolute path of this file, when run as a script. ## This file is not intended to be included in other files at the moment. -THIS_PATH = os.path.abspath(os.path.dirname(__file__)) +THIS_PATH = abspath(os.path.dirname(__file__)) ## All the test files. TEST_SUITE = { @@ -42,9 +42,10 @@ SYSTEM_TO_BINARY_PATH = { ## This global variable will be set to true if any test failed. tests_failed = False + def main(): - ## this will enable ansi codes in windows terminal. + ## This will enable ANSI codes in windows terminal. os.system('') run_all_tests() @@ -58,7 +59,7 @@ def run_all_tests(): for suite in TEST_SUITE: print_title(suite) for test in TEST_SUITE[suite]: - path = os.path.join(THIS_PATH, test) + path = join(THIS_PATH, test) run_test_file(pocket, test, path) def run_test_file(pocket, test, path): @@ -84,7 +85,7 @@ def get_pocket_binary(): if system not in SYSTEM_TO_BINARY_PATH: error_exit("Unsupported platform %s" % system) - pocket = os.path.join(THIS_PATH, SYSTEM_TO_BINARY_PATH[system]) + pocket = abspath(join(THIS_PATH, SYSTEM_TO_BINARY_PATH[system])) if not os.path.exists(pocket): error_exit("Pocket interpreter not found at: '%s'" % pocket) @@ -92,8 +93,8 @@ def get_pocket_binary(): def run_command(command): return subprocess.run(command, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) ## ----------------------------------------------------------------------------