Merge pull request #95 from ThakeeNathees/pk_doc

[WIP] Docstring extracted and `help()` function added
This commit is contained in:
Thakee Nathees 2021-06-18 15:45:19 +05:30 committed by GitHub
commit 733f006b03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 273 additions and 234 deletions

View File

@ -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:

View File

@ -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-to-env/usr/bin/>;%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.

View File

@ -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)"

View File

@ -80,10 +80,13 @@
#include <stdio.h> //< 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).

View File

@ -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);

View File

@ -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_<fn> = 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.

View File

@ -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;
}

View File

@ -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);

View File

@ -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/",

View File

@ -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)

View File

@ -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

View File

@ -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)
## ----------------------------------------------------------------------------